diff options
121 files changed, 1934 insertions, 1335 deletions
diff --git a/.eslintrc.yml b/.eslintrc.yml index 88fbf37780a..d2bae1b21b3 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -75,6 +75,8 @@ rules: - sibling - index pathGroups: + - pattern: '@sentry/browser' + group: external - pattern: ~/** group: internal - pattern: emojis/** diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 3199739a33f..9cbe00d82de 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -108,6 +108,9 @@ Dangerfile @gl-quality/eng-prod /ee/app/models/project_alias.rb @patrickbajao /ee/lib/api/project_aliases.rb @patrickbajao +^[Distribution] +/lib/support/ @gitlab-org/distribution + # Secure & Threat Management ownership delineation # https://about.gitlab.com/handbook/engineering/development/threat-management/delineate-secure-threat-management.html#technical-boundaries ^[Threat Insights] diff --git a/.rubocop.yml b/.rubocop.yml index 143a7db9ae7..2367cef34b8 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -498,8 +498,16 @@ RSpec/FactoryBot/AvoidCreate: Include: - 'spec/presenters/**/*.rb' - 'spec/serializers/**/*.rb' + - 'spec/helpers/**/*.rb' + - 'spec/views/**/*.rb' + - 'spec/components/**/*.rb' + - 'spec/mailers/**/*.rb' - 'ee/spec/presenters/**/*.rb' - 'ee/spec/serializers/**/*.rb' + - 'ee/spec/helpers/**/*.rb' + - 'ee/spec/views/**/*.rb' + - 'ee/spec/components/**/*.rb' + - 'ee/spec/mailers/**/*.rb' RSpec/FactoryBot/StrategyInCallback: Enabled: true diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml index e5881913467..9f9cb409b23 100644 --- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml +++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml @@ -1,6 +1,107 @@ --- RSpec/FactoryBot/AvoidCreate: Exclude: + - 'ee/spec/components/namespaces/free_user_cap/alert_component_spec.rb' + - 'ee/spec/components/namespaces/free_user_cap/non_owner_alert_component_spec.rb' + - 'ee/spec/components/namespaces/free_user_cap/preview_alert_component_spec.rb' + - 'ee/spec/components/namespaces/free_user_cap/usage_quota_alert_component_spec.rb' + - 'ee/spec/components/namespaces/free_user_cap/usage_quota_trial_alert_component_spec.rb' + - 'ee/spec/components/namespaces/storage/limit_alert_component_spec.rb' + - 'ee/spec/components/namespaces/storage/pre_enforcement_alert_component_spec.rb' + - 'ee/spec/components/namespaces/storage/project_pre_enforcement_alert_component_spec.rb' + - 'ee/spec/components/namespaces/storage/subgroup_pre_enforcement_alert_component_spec.rb' + - 'ee/spec/components/namespaces/storage/user_pre_enforcement_alert_component_spec.rb' + - 'ee/spec/helpers/admin/ip_restriction_helper_spec.rb' + - 'ee/spec/helpers/application_helper_spec.rb' + - 'ee/spec/helpers/billing_plans_helper_spec.rb' + - 'ee/spec/helpers/boards_helper_spec.rb' + - 'ee/spec/helpers/compliance_management/compliance_framework/group_settings_helper_spec.rb' + - 'ee/spec/helpers/ee/admin/identities_helper_spec.rb' + - 'ee/spec/helpers/ee/blob_helper_spec.rb' + - 'ee/spec/helpers/ee/branches_helper_spec.rb' + - 'ee/spec/helpers/ee/ci/pipeline_editor_helper_spec.rb' + - 'ee/spec/helpers/ee/ci/runners_helper_spec.rb' + - 'ee/spec/helpers/ee/dashboard_helper_spec.rb' + - 'ee/spec/helpers/ee/environments_helper_spec.rb' + - 'ee/spec/helpers/ee/events_helper_spec.rb' + - 'ee/spec/helpers/ee/feature_flags_helper_spec.rb' + - 'ee/spec/helpers/ee/gitlab_routing_helper_spec.rb' + - 'ee/spec/helpers/ee/graph_helper_spec.rb' + - 'ee/spec/helpers/ee/groups/analytics/cycle_analytics_helper_spec.rb' + - 'ee/spec/helpers/ee/groups/group_members_helper_spec.rb' + - 'ee/spec/helpers/ee/groups_helper_spec.rb' + - 'ee/spec/helpers/ee/hooks_helper_spec.rb' + - 'ee/spec/helpers/ee/integrations_helper_spec.rb' + - 'ee/spec/helpers/ee/invite_members_helper_spec.rb' + - 'ee/spec/helpers/ee/issuables_helper_spec.rb' + - 'ee/spec/helpers/ee/issues_helper_spec.rb' + - 'ee/spec/helpers/ee/labels_helper_spec.rb' + - 'ee/spec/helpers/ee/learn_gitlab_helper_spec.rb' + - 'ee/spec/helpers/ee/lock_helper_spec.rb' + - 'ee/spec/helpers/ee/namespace_user_cap_reached_alert_helper_spec.rb' + - 'ee/spec/helpers/ee/namespaces_helper_spec.rb' + - 'ee/spec/helpers/ee/operations_helper_spec.rb' + - 'ee/spec/helpers/ee/personal_access_tokens_helper_spec.rb' + - 'ee/spec/helpers/ee/projects/pipeline_helper_spec.rb' + - 'ee/spec/helpers/ee/projects/security/api_fuzzing_configuration_helper_spec.rb' + - 'ee/spec/helpers/ee/projects/security/configuration_helper_spec.rb' + - 'ee/spec/helpers/ee/projects/security/dast_configuration_helper_spec.rb' + - 'ee/spec/helpers/ee/projects/security/sast_configuration_helper_spec.rb' + - 'ee/spec/helpers/ee/releases_helper_spec.rb' + - 'ee/spec/helpers/ee/security_orchestration_helper_spec.rb' + - 'ee/spec/helpers/ee/subscribable_banner_helper_spec.rb' + - 'ee/spec/helpers/ee/todos_helper_spec.rb' + - 'ee/spec/helpers/ee/trial_helper_spec.rb' + - 'ee/spec/helpers/ee/users/callouts_helper_spec.rb' + - 'ee/spec/helpers/ee/welcome_helper_spec.rb' + - 'ee/spec/helpers/ee/wiki_helper_spec.rb' + - 'ee/spec/helpers/epics_helper_spec.rb' + - 'ee/spec/helpers/gitlab_subscriptions/upcoming_reconciliation_helper_spec.rb' + - 'ee/spec/helpers/groups/feature_discovery_moments_helper_spec.rb' + - 'ee/spec/helpers/groups/security_features_helper_spec.rb' + - 'ee/spec/helpers/incident_management/escalation_policy_helper_spec.rb' + - 'ee/spec/helpers/incident_management/oncall_schedule_helper_spec.rb' + - 'ee/spec/helpers/license_helper_spec.rb' + - 'ee/spec/helpers/license_monitoring_helper_spec.rb' + - 'ee/spec/helpers/manual_quarterly_co_term_banner_helper_spec.rb' + - 'ee/spec/helpers/markup_helper_spec.rb' + - 'ee/spec/helpers/notes_helper_spec.rb' + - 'ee/spec/helpers/paid_feature_callout_helper_spec.rb' + - 'ee/spec/helpers/path_locks_helper_spec.rb' + - 'ee/spec/helpers/prevent_forking_helper_spec.rb' + - 'ee/spec/helpers/projects/on_demand_scans_helper_spec.rb' + - 'ee/spec/helpers/projects/project_members_helper_spec.rb' + - 'ee/spec/helpers/projects/security/dast_profiles_helper_spec.rb' + - 'ee/spec/helpers/projects/security/discover_helper_spec.rb' + - 'ee/spec/helpers/projects_helper_spec.rb' + - 'ee/spec/helpers/push_rules_helper_spec.rb' + - 'ee/spec/helpers/routing/pseudonymization_helper_spec.rb' + - 'ee/spec/helpers/search_helper_spec.rb' + - 'ee/spec/helpers/seat_count_alert_helper_spec.rb' + - 'ee/spec/helpers/security_helper_spec.rb' + - 'ee/spec/helpers/subscriptions_helper_spec.rb' + - 'ee/spec/helpers/timeboxes_helper_spec.rb' + - 'ee/spec/helpers/trial_status_widget_helper_spec.rb' + - 'ee/spec/helpers/users/identity_verification_helper_spec.rb' + - 'ee/spec/helpers/users_helper_spec.rb' + - 'ee/spec/helpers/vulnerabilities_helper_spec.rb' + - 'ee/spec/mailers/ci_minutes_usage_mailer_spec.rb' + - 'ee/spec/mailers/credentials_inventory_mailer_spec.rb' + - 'ee/spec/mailers/devise_mailer_spec.rb' + - 'ee/spec/mailers/ee/emails/admin_notification_spec.rb' + - 'ee/spec/mailers/ee/emails/issues_spec.rb' + - 'ee/spec/mailers/ee/emails/merge_requests_spec.rb' + - 'ee/spec/mailers/ee/emails/profile_spec.rb' + - 'ee/spec/mailers/ee/emails/projects_spec.rb' + - 'ee/spec/mailers/emails/free_user_cap_spec.rb' + - 'ee/spec/mailers/emails/group_memberships_spec.rb' + - 'ee/spec/mailers/emails/in_product_marketing_spec.rb' + - 'ee/spec/mailers/emails/merge_commits_spec.rb' + - 'ee/spec/mailers/emails/namespace_storage_usage_mailer_spec.rb' + - 'ee/spec/mailers/emails/requirements_spec.rb' + - 'ee/spec/mailers/emails/user_cap_spec.rb' + - 'ee/spec/mailers/license_mailer_spec.rb' + - 'ee/spec/mailers/notify_spec.rb' - 'ee/spec/presenters/approval_rule_presenter_spec.rb' - 'ee/spec/presenters/audit_event_presenter_spec.rb' - 'ee/spec/presenters/ci/build_runner_presenter_spec.rb' @@ -90,6 +191,178 @@ RSpec/FactoryBot/AvoidCreate: - 'ee/spec/serializers/vulnerabilities/scanner_entity_spec.rb' - 'ee/spec/serializers/vulnerability_entity_spec.rb' - 'ee/spec/serializers/vulnerability_note_entity_spec.rb' + - 'ee/spec/views/admin/application_settings/_elasticsearch_form.html.haml_spec.rb' + - 'ee/spec/views/admin/application_settings/_git_abuse_rate_limit.html.haml_spec.rb' + - 'ee/spec/views/admin/application_settings/general.html.haml_spec.rb' + - 'ee/spec/views/admin/dashboard/index.html.haml_spec.rb' + - 'ee/spec/views/admin/groups/_form.html.haml_spec.rb' + - 'ee/spec/views/admin/identities/index.html.haml_spec.rb' + - 'ee/spec/views/admin/users/_credit_card_info.html.haml_spec.rb' + - 'ee/spec/views/admin/users/index.html.haml_spec.rb' + - 'ee/spec/views/admin/users/show.html.haml_spec.rb' + - 'ee/spec/views/clusters/clusters/show.html.haml_spec.rb' + - 'ee/spec/views/compliance_management/compliance_framework/_project_settings.html.haml_spec.rb' + - 'ee/spec/views/groups/billings/index.html.haml_spec.rb' + - 'ee/spec/views/groups/edit.html.haml_spec.rb' + - 'ee/spec/views/groups/feature_discovery_moments/advanced_features_dashboard.html.haml_spec.rb' + - 'ee/spec/views/groups/group_members/index.html.haml_spec.rb' + - 'ee/spec/views/groups/hook_logs/show.html.haml_spec.rb' + - 'ee/spec/views/groups/hooks/edit.html.haml_spec.rb' + - 'ee/spec/views/groups/security/discover/show.html.haml_spec.rb' + - 'ee/spec/views/groups/settings/_remove.html.haml_spec.rb' + - 'ee/spec/views/groups/settings/reporting/show.html.haml_spec.rb' + - 'ee/spec/views/layouts/_search.html.haml_spec.rb' + - 'ee/spec/views/layouts/application.html.haml_spec.rb' + - 'ee/spec/views/layouts/group.html.haml_spec.rb' + - 'ee/spec/views/layouts/header/_current_user_dropdown.html.haml_spec.rb' + - 'ee/spec/views/layouts/header/_new_dropdown.haml_spec.rb' + - 'ee/spec/views/layouts/header/_read_only_banner.html.haml_spec.rb' + - 'ee/spec/views/layouts/header/help_dropdown/_cross_stage_fdm.html.haml_spec.rb' + - 'ee/spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb' + - 'ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' + - 'ee/spec/views/layouts/project.html.haml_spec.rb' + - 'ee/spec/views/projects/edit.html.haml_spec.rb' + - 'ee/spec/views/projects/issues/show.html.haml_spec.rb' + - 'ee/spec/views/projects/on_demand_scans/index.html.haml_spec.rb' + - 'ee/spec/views/projects/pipelines/_tabs_content.html.haml_spec.rb' + - 'ee/spec/views/projects/project_members/index.html.haml_spec.rb' + - 'ee/spec/views/projects/security/corpus_management/show.html.haml_spec.rb' + - 'ee/spec/views/projects/security/dast_profiles/show.html.haml_spec.rb' + - 'ee/spec/views/projects/security/dast_scanner_profiles/edit.html.haml_spec.rb' + - 'ee/spec/views/projects/security/dast_scanner_profiles/new.html.haml_spec.rb' + - 'ee/spec/views/projects/security/dast_site_profiles/edit.html.haml_spec.rb' + - 'ee/spec/views/projects/security/dast_site_profiles/new.html.haml_spec.rb' + - 'ee/spec/views/projects/security/discover/show.html.haml_spec.rb' + - 'ee/spec/views/projects/security/policies/index.html.haml_spec.rb' + - 'ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' + - 'ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' + - 'ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' + - 'ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb' + - 'ee/spec/views/registrations/welcome/show.html.haml_spec.rb' + - 'ee/spec/views/search/_category.html.haml_spec.rb' + - 'ee/spec/views/shared/_clone_panel.html.haml_spec.rb' + - 'ee/spec/views/shared/_kerberos_clone_button.html.haml_spec.rb' + - 'ee/spec/views/shared/_mirror_status.html.haml_spec.rb' + - 'ee/spec/views/shared/_mirror_update_button.html.haml_spec.rb' + - 'ee/spec/views/shared/_namespace_user_cap_reached_alert.html.haml_spec.rb' + - 'ee/spec/views/shared/billings/_eoa_bronze_plan_banner.html.haml_spec.rb' + - 'ee/spec/views/shared/billings/_trial_status.html.haml_spec.rb' + - 'ee/spec/views/shared/credentials_inventory/_expiry_date.html.haml_spec.rb' + - 'ee/spec/views/shared/credentials_inventory/gpg_keys/_gpg_key.html.haml_spec.rb' + - 'ee/spec/views/shared/credentials_inventory/personal_access_tokens/_personal_access_token.html.haml_spec.rb' + - 'ee/spec/views/shared/credentials_inventory/resource_access_tokens/_resource_access_token.html.haml_spec.rb' + - 'ee/spec/views/shared/credentials_inventory/ssh_keys/_ssh_key.html.haml_spec.rb' + - 'ee/spec/views/shared/issuable/_approver_suggestion.html.haml_spec.rb' + - 'ee/spec/views/shared/issuable/_sidebar.html.haml_spec.rb' + - 'ee/spec/views/shared/labels/_create_label_help_text.html.haml_spec.rb' + - 'ee/spec/views/shared/milestones/_milestone.html.haml_spec.rb' + - 'ee/spec/views/shared/promotions/_promotion_link_project.html.haml_spec.rb' + - 'spec/components/diffs/overflow_warning_component_spec.rb' + - 'spec/components/diffs/stats_component_spec.rb' + - 'spec/components/pajamas/avatar_component_spec.rb' + - 'spec/helpers/admin/identities_helper_spec.rb' + - 'spec/helpers/admin/user_actions_helper_spec.rb' + - 'spec/helpers/analytics/cycle_analytics_helper_spec.rb' + - 'spec/helpers/appearances_helper_spec.rb' + - 'spec/helpers/application_helper_spec.rb' + - 'spec/helpers/application_settings_helper_spec.rb' + - 'spec/helpers/auth_helper_spec.rb' + - 'spec/helpers/auto_devops_helper_spec.rb' + - 'spec/helpers/avatars_helper_spec.rb' + - 'spec/helpers/award_emoji_helper_spec.rb' + - 'spec/helpers/blob_helper_spec.rb' + - 'spec/helpers/boards_helper_spec.rb' + - 'spec/helpers/branches_helper_spec.rb' + - 'spec/helpers/broadcast_messages_helper_spec.rb' + - 'spec/helpers/button_helper_spec.rb' + - 'spec/helpers/calendar_helper_spec.rb' + - 'spec/helpers/ci/builds_helper_spec.rb' + - 'spec/helpers/ci/jobs_helper_spec.rb' + - 'spec/helpers/ci/pipeline_editor_helper_spec.rb' + - 'spec/helpers/ci/pipelines_helper_spec.rb' + - 'spec/helpers/ci/runners_helper_spec.rb' + - 'spec/helpers/ci/secure_files_helper_spec.rb' + - 'spec/helpers/clusters_helper_spec.rb' + - 'spec/helpers/commits_helper_spec.rb' + - 'spec/helpers/diff_helper_spec.rb' + - 'spec/helpers/emails_helper_spec.rb' + - 'spec/helpers/environment_helper_spec.rb' + - 'spec/helpers/environments_helper_spec.rb' + - 'spec/helpers/events_helper_spec.rb' + - 'spec/helpers/feature_flags_helper_spec.rb' + - 'spec/helpers/gitlab_routing_helper_spec.rb' + - 'spec/helpers/graph_helper_spec.rb' + - 'spec/helpers/groups/group_members_helper_spec.rb' + - 'spec/helpers/groups/settings_helper_spec.rb' + - 'spec/helpers/groups_helper_spec.rb' + - 'spec/helpers/ide_helper_spec.rb' + - 'spec/helpers/import_helper_spec.rb' + - 'spec/helpers/integrations_helper_spec.rb' + - 'spec/helpers/invite_members_helper_spec.rb' + - 'spec/helpers/issuables_description_templates_helper_spec.rb' + - 'spec/helpers/issuables_helper_spec.rb' + - 'spec/helpers/issues_helper_spec.rb' + - 'spec/helpers/jira_connect_helper_spec.rb' + - 'spec/helpers/keyset_helper_spec.rb' + - 'spec/helpers/labels_helper_spec.rb' + - 'spec/helpers/lazy_image_tag_helper_spec.rb' + - 'spec/helpers/learn_gitlab_helper_spec.rb' + - 'spec/helpers/markup_helper_spec.rb' + - 'spec/helpers/members_helper_spec.rb' + - 'spec/helpers/merge_requests_helper_spec.rb' + - 'spec/helpers/namespaces_helper_spec.rb' + - 'spec/helpers/nav/top_nav_helper_spec.rb' + - 'spec/helpers/nav_helper_spec.rb' + - 'spec/helpers/notes_helper_spec.rb' + - 'spec/helpers/notifications_helper_spec.rb' + - 'spec/helpers/notify_helper_spec.rb' + - 'spec/helpers/operations_helper_spec.rb' + - 'spec/helpers/packages_helper_spec.rb' + - 'spec/helpers/profiles_helper_spec.rb' + - 'spec/helpers/projects/alert_management_helper_spec.rb' + - 'spec/helpers/projects/cluster_agents_helper_spec.rb' + - 'spec/helpers/projects/ml/experiments_helper_spec.rb' + - 'spec/helpers/projects/pages_helper_spec.rb' + - 'spec/helpers/projects/pipeline_helper_spec.rb' + - 'spec/helpers/projects/project_members_helper_spec.rb' + - 'spec/helpers/projects/security/configuration_helper_spec.rb' + - 'spec/helpers/projects/terraform_helper_spec.rb' + - 'spec/helpers/projects_helper_spec.rb' + - 'spec/helpers/releases_helper_spec.rb' + - 'spec/helpers/routing/pseudonymization_helper_spec.rb' + - 'spec/helpers/rss_helper_spec.rb' + - 'spec/helpers/search_helper_spec.rb' + - 'spec/helpers/snippets_helper_spec.rb' + - 'spec/helpers/storage_helper_spec.rb' + - 'spec/helpers/submodule_helper_spec.rb' + - 'spec/helpers/timeboxes_helper_spec.rb' + - 'spec/helpers/todos_helper_spec.rb' + - 'spec/helpers/tree_helper_spec.rb' + - 'spec/helpers/users/callouts_helper_spec.rb' + - 'spec/helpers/users/group_callouts_helper_spec.rb' + - 'spec/helpers/users_helper_spec.rb' + - 'spec/helpers/version_check_helper_spec.rb' + - 'spec/helpers/visibility_level_helper_spec.rb' + - 'spec/helpers/web_hooks/web_hooks_helper_spec.rb' + - 'spec/helpers/whats_new_helper_spec.rb' + - 'spec/helpers/wiki_helper_spec.rb' + - 'spec/helpers/wiki_page_version_helper_spec.rb' + - 'spec/mailers/abuse_report_mailer_spec.rb' + - 'spec/mailers/devise_mailer_spec.rb' + - 'spec/mailers/emails/auto_devops_spec.rb' + - 'spec/mailers/emails/groups_spec.rb' + - 'spec/mailers/emails/in_product_marketing_spec.rb' + - 'spec/mailers/emails/issues_spec.rb' + - 'spec/mailers/emails/merge_requests_spec.rb' + - 'spec/mailers/emails/pages_domains_spec.rb' + - 'spec/mailers/emails/pipelines_spec.rb' + - 'spec/mailers/emails/profile_spec.rb' + - 'spec/mailers/emails/projects_spec.rb' + - 'spec/mailers/emails/releases_spec.rb' + - 'spec/mailers/emails/service_desk_spec.rb' + - 'spec/mailers/notify_spec.rb' + - 'spec/mailers/previews_spec.rb' + - 'spec/mailers/repository_check_mailer_spec.rb' - 'spec/presenters/alert_management/alert_presenter_spec.rb' - 'spec/presenters/blob_presenter_spec.rb' - 'spec/presenters/blobs/notebook_presenter_spec.rb' @@ -280,3 +553,104 @@ RSpec/FactoryBot/AvoidCreate: - 'spec/serializers/user_serializer_spec.rb' - 'spec/serializers/web_ide_terminal_entity_spec.rb' - 'spec/serializers/web_ide_terminal_serializer_spec.rb' + - 'spec/views/admin/application_settings/_ci_cd.html.haml_spec.rb' + - 'spec/views/admin/application_settings/_eks.html.haml_spec.rb' + - 'spec/views/admin/application_settings/_jira_connect.html.haml_spec.rb' + - 'spec/views/admin/application_settings/_package_registry.html.haml_spec.rb' + - 'spec/views/admin/application_settings/_repository_check.html.haml_spec.rb' + - 'spec/views/admin/application_settings/ci_cd.html.haml_spec.rb' + - 'spec/views/admin/application_settings/general.html.haml_spec.rb' + - 'spec/views/admin/application_settings/repository.html.haml_spec.rb' + - 'spec/views/admin/broadcast_messages/index.html.haml_spec.rb' + - 'spec/views/admin/dashboard/index.html.haml_spec.rb' + - 'spec/views/admin/identities/index.html.haml_spec.rb' + - 'spec/views/admin/sessions/new.html.haml_spec.rb' + - 'spec/views/admin/sessions/two_factor.html.haml_spec.rb' + - 'spec/views/ci/status/_badge.html.haml_spec.rb' + - 'spec/views/ci/status/_icon.html.haml_spec.rb' + - 'spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb' + - 'spec/views/dashboard/projects/_blank_state_welcome.html.haml_spec.rb' + - 'spec/views/events/event/_common.html.haml_spec.rb' + - 'spec/views/groups/_home_panel.html.haml_spec.rb' + - 'spec/views/groups/edit.html.haml_spec.rb' + - 'spec/views/groups/group_members/index.html.haml_spec.rb' + - 'spec/views/groups/new.html.haml_spec.rb' + - 'spec/views/help/instance_configuration.html.haml_spec.rb' + - 'spec/views/layouts/_search.html.haml_spec.rb' + - 'spec/views/layouts/application.html.haml_spec.rb' + - 'spec/views/layouts/devise.html.haml_spec.rb' + - 'spec/views/layouts/fullscreen.html.haml_spec.rb' + - 'spec/views/layouts/header/_new_dropdown.haml_spec.rb' + - 'spec/views/layouts/nav/sidebar/_group.html.haml_spec.rb' + - 'spec/views/layouts/nav/sidebar/_profile.html.haml_spec.rb' + - 'spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb' + - 'spec/views/layouts/profile.html.haml_spec.rb' + - 'spec/views/layouts/terms.html.haml_spec.rb' + - 'spec/views/notify/approved_merge_request_email.html.haml_spec.rb' + - 'spec/views/notify/autodevops_disabled_email.text.erb_spec.rb' + - 'spec/views/notify/change_in_merge_request_draft_status_email.html.haml_spec.rb' + - 'spec/views/notify/change_in_merge_request_draft_status_email.text.erb_spec.rb' + - 'spec/views/notify/changed_milestone_email.html.haml_spec.rb' + - 'spec/views/notify/import_issues_csv_email.html.haml_spec.rb' + - 'spec/views/notify/pipeline_failed_email.text.erb_spec.rb' + - 'spec/views/notify/push_to_merge_request_email.text.haml_spec.rb' + - 'spec/views/profiles/audit_log.html.haml_spec.rb' + - 'spec/views/profiles/keys/_key.html.haml_spec.rb' + - 'spec/views/profiles/keys/_key_details.html.haml_spec.rb' + - 'spec/views/profiles/notifications/show.html.haml_spec.rb' + - 'spec/views/profiles/show.html.haml_spec.rb' + - 'spec/views/projects/_files.html.haml_spec.rb' + - 'spec/views/projects/_flash_messages.html.haml_spec.rb' + - 'spec/views/projects/_home_panel.html.haml_spec.rb' + - 'spec/views/projects/branches/index.html.haml_spec.rb' + - 'spec/views/projects/commit/_commit_box.html.haml_spec.rb' + - 'spec/views/projects/commit/branches.html.haml_spec.rb' + - 'spec/views/projects/commit/show.html.haml_spec.rb' + - 'spec/views/projects/commits/_commit.html.haml_spec.rb' + - 'spec/views/projects/commits/show.html.haml_spec.rb' + - 'spec/views/projects/diffs/_viewer.html.haml_spec.rb' + - 'spec/views/projects/edit.html.haml_spec.rb' + - 'spec/views/projects/empty.html.haml_spec.rb' + - 'spec/views/projects/environments/terminal.html.haml_spec.rb' + - 'spec/views/projects/hooks/edit.html.haml_spec.rb' + - 'spec/views/projects/hooks/index.html.haml_spec.rb' + - 'spec/views/projects/imports/new.html.haml_spec.rb' + - 'spec/views/projects/issues/_issue.html.haml_spec.rb' + - 'spec/views/projects/issues/_service_desk_info_content.html.haml_spec.rb' + - 'spec/views/projects/issues/show.html.haml_spec.rb' + - 'spec/views/projects/jobs/_build.html.haml_spec.rb' + - 'spec/views/projects/jobs/_generic_commit_status.html.haml_spec.rb' + - 'spec/views/projects/jobs/show.html.haml_spec.rb' + - 'spec/views/projects/merge_requests/_close_reopen_draft_report_toggle.html.haml_spec.rb' + - 'spec/views/projects/merge_requests/_commits.html.haml_spec.rb' + - 'spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb' + - 'spec/views/projects/merge_requests/edit.html.haml_spec.rb' + - 'spec/views/projects/notes/_more_actions_dropdown.html.haml_spec.rb' + - 'spec/views/projects/pages/new.html.haml_spec.rb' + - 'spec/views/projects/pages/show.html.haml_spec.rb' + - 'spec/views/projects/pages_domains/show.html.haml_spec.rb' + - 'spec/views/projects/pipeline_schedules/_pipeline_schedule.html.haml_spec.rb' + - 'spec/views/projects/pipelines/show.html.haml_spec.rb' + - 'spec/views/projects/project_members/index.html.haml_spec.rb' + - 'spec/views/projects/runners/_specific_runners.html.haml_spec.rb' + - 'spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb' + - 'spec/views/projects/settings/integrations/edit.html.haml_spec.rb' + - 'spec/views/projects/settings/merge_requests/show.html.haml_spec.rb' + - 'spec/views/projects/settings/operations/show.html.haml_spec.rb' + - 'spec/views/projects/tags/index.html.haml_spec.rb' + - 'spec/views/projects/tree/show.html.haml_spec.rb' + - 'spec/views/registrations/welcome/show.html.haml_spec.rb' + - 'spec/views/search/_results.html.haml_spec.rb' + - 'spec/views/shared/_label_row.html.haml_spec.rb' + - 'spec/views/shared/issuable/_sidebar.html.haml_spec.rb' + - 'spec/views/shared/milestones/_issuable.html.haml_spec.rb' + - 'spec/views/shared/milestones/_top.html.haml_spec.rb' + - 'spec/views/shared/nav/_sidebar.html.haml_spec.rb' + - 'spec/views/shared/notes/_form.html.haml_spec.rb' + - 'spec/views/shared/projects/_inactive_project_deletion_alert.html.haml_spec.rb' + - 'spec/views/shared/projects/_list.html.haml_spec.rb' + - 'spec/views/shared/projects/_project.html.haml_spec.rb' + - 'spec/views/shared/runners/_runner_details.html.haml_spec.rb' + - 'spec/views/shared/snippets/_snippet.html.haml_spec.rb' + - 'spec/views/shared/web_hooks/_web_hook_disabled_alert.html.haml_spec.rb' + - 'spec/views/shared/wikis/_sidebar.html.haml_spec.rb' @@ -15,7 +15,7 @@ gem 'bundler-checksum', '~> 0.1.0', path: 'vendor/gems/bundler-checksum', requir # https://gitlab.com/gitlab-org/gitlab/-/issues/375713 gem 'rails', '~> 6.1.6.1' -gem 'bootsnap', '~> 1.14.0', require: false +gem 'bootsnap', '~> 1.15.0', require: false # Pin openssl to match the version bundled with our supported Rubies. # See https://stdgems.org/openssl/#gem-version. @@ -372,6 +372,8 @@ group :development do gem 'better_errors', '~> 2.9.1' gem 'sprite-factory', '~> 1.7' + + gem "listen", "~> 3.7" end group :development, :test do diff --git a/Gemfile.checksum b/Gemfile.checksum index 0aa7313db84..73608acb159 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -57,7 +57,7 @@ {"name":"bindata","version":"2.4.11","platform":"ruby","checksum":"c38e0c99ffcd80c10a0a7ae6c8586d2fe26bf245cbefac90bec8764523220f6a"}, {"name":"binding_ninja","version":"0.2.3","platform":"java","checksum":"bbcf70b211d6e397493bf57c249bbec6aaf28fa7dafeb78e447b1b2f0610484f"}, {"name":"binding_ninja","version":"0.2.3","platform":"ruby","checksum":"4a85550a0066ee4721506b4e150857486808e50c9ddfeed04bdc896bb61eca9d"}, -{"name":"bootsnap","version":"1.14.0","platform":"ruby","checksum":"4c541735f628e6d6bb7284552ce14f63f75a6af238b23f43d2b07789b576de3f"}, +{"name":"bootsnap","version":"1.15.0","platform":"ruby","checksum":"f246bb1152159098f5d5619b92e373c73db77769bf3e0c4b6336feeb934bc8d2"}, {"name":"bootstrap_form","version":"4.2.0","platform":"ruby","checksum":"f578b3c900d2cf15fab641064d357318b29e285bd5fdf090f903727912889710"}, {"name":"browser","version":"5.3.1","platform":"ruby","checksum":"62745301701ff2c6c5d32d077bb12532b20be261929dcb52c6781ed0d5658b3c"}, {"name":"builder","version":"3.2.4","platform":"ruby","checksum":"99caf08af60c8d7f3a6b004029c4c3c0bdaebced6c949165fe98f1db27fbbc10"}, diff --git a/Gemfile.lock b/Gemfile.lock index 65dd5a64f3f..94610975c23 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -229,7 +229,7 @@ GEM rack (>= 0.9.0) bindata (2.4.11) binding_ninja (0.2.3) - bootsnap (1.14.0) + bootsnap (1.15.0) msgpack (~> 1.2) bootstrap_form (4.2.0) actionpack (>= 5.0) @@ -1602,7 +1602,7 @@ DEPENDENCIES benchmark-ips (~> 2.3.0) benchmark-memory (~> 0.1) better_errors (~> 2.9.1) - bootsnap (~> 1.14.0) + bootsnap (~> 1.15.0) bootstrap_form (~> 4.2.0) browser (~> 5.3.1) bullet (~> 7.0.2) @@ -1722,6 +1722,7 @@ DEPENDENCIES letter_opener_web (~> 2.0.0) license_finder (~> 7.0) licensee (~> 9.15) + listen (~> 3.7) lockbox (~> 0.6.2) lograge (~> 0.5) loofah (~> 2.19.0) diff --git a/app/assets/javascripts/blob/openapi/index.js b/app/assets/javascripts/blob/openapi/index.js index 24a54358de5..8cfdc00bb40 100644 --- a/app/assets/javascripts/blob/openapi/index.js +++ b/app/assets/javascripts/blob/openapi/index.js @@ -5,7 +5,7 @@ const createSandbox = () => { const iframeEl = document.createElement('iframe'); setAttributes(iframeEl, { src: '/-/sandbox/swagger', - sandbox: 'allow-scripts allow-popups', + sandbox: 'allow-scripts allow-popups allow-forms', frameBorder: 0, width: '100%', // The height will be adjusted dynamically. diff --git a/app/assets/javascripts/sentry/constants.js b/app/assets/javascripts/sentry/constants.js index fd96da5faf6..5531c4f56db 100644 --- a/app/assets/javascripts/sentry/constants.js +++ b/app/assets/javascripts/sentry/constants.js @@ -1,5 +1,6 @@ import { __ } from '~/locale'; +// TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144 export const IGNORE_ERRORS = [ // Random plugins/extensions 'top.GLOBALS', diff --git a/app/assets/javascripts/sentry/index.js b/app/assets/javascripts/sentry/index.js index 176745b4177..5539a061726 100644 --- a/app/assets/javascripts/sentry/index.js +++ b/app/assets/javascripts/sentry/index.js @@ -1,26 +1,34 @@ import '../webpack'; +import * as Sentry from 'sentrybrowser7'; import SentryConfig from './sentry_config'; const index = function index() { + // Configuration for newer versions of Sentry SDK (v7) SentryConfig.init({ dsn: gon.sentry_dsn, + environment: gon.sentry_environment, currentUserId: gon.current_user_id, - whitelistUrls: + allowUrls: process.env.NODE_ENV === 'production' ? [gon.gitlab_url] : [gon.gitlab_url, 'webpack-internal://'], - environment: gon.sentry_environment, release: gon.revision, tags: { revision: gon.revision, feature_category: gon.feature_category, }, }); - - return SentryConfig; }; index(); +// The _Sentry object is globally exported so it can be used by +// ./sentry_browser_wrapper.js +// This hack allows us to load a single version of `@sentry/browser` +// in the browser, see app/views/layouts/_head.html.haml to find how it is imported. + +// eslint-disable-next-line no-underscore-dangle +window._Sentry = Sentry; + export default index; diff --git a/app/assets/javascripts/sentry/legacy_index.js b/app/assets/javascripts/sentry/legacy_index.js new file mode 100644 index 00000000000..604b982e128 --- /dev/null +++ b/app/assets/javascripts/sentry/legacy_index.js @@ -0,0 +1,34 @@ +import '../webpack'; + +import * as Sentry5 from 'sentrybrowser5'; +import LegacySentryConfig from './legacy_sentry_config'; + +const index = function index() { + // Configuration for legacy versions of Sentry SDK (v5) + LegacySentryConfig.init({ + dsn: gon.sentry_dsn, + currentUserId: gon.current_user_id, + whitelistUrls: + process.env.NODE_ENV === 'production' + ? [gon.gitlab_url] + : [gon.gitlab_url, 'webpack-internal://'], + environment: gon.sentry_environment, + release: gon.revision, + tags: { + revision: gon.revision, + feature_category: gon.feature_category, + }, + }); +}; + +index(); + +// The _Sentry object is globally exported so it can be used by +// ./sentry_browser_wrapper.js +// This hack allows us to load a single version of `@sentry/browser` +// in the browser, see app/views/layouts/_head.html.haml to find how it is imported. + +// eslint-disable-next-line no-underscore-dangle +window._Sentry = Sentry5; + +export default index; diff --git a/app/assets/javascripts/sentry/legacy_sentry_config.js b/app/assets/javascripts/sentry/legacy_sentry_config.js new file mode 100644 index 00000000000..50a943886db --- /dev/null +++ b/app/assets/javascripts/sentry/legacy_sentry_config.js @@ -0,0 +1,64 @@ +import * as Sentry5 from 'sentrybrowser5'; +import $ from 'jquery'; +import { __ } from '~/locale'; +import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants'; + +const SentryConfig = { + IGNORE_ERRORS, + BLACKLIST_URLS: DENY_URLS, + SAMPLE_RATE, + init(options = {}) { + this.options = options; + + this.configure(); + this.bindSentryErrors(); + if (this.options.currentUserId) this.setUser(); + }, + + configure() { + const { dsn, release, tags, whitelistUrls, environment } = this.options; + + Sentry5.init({ + dsn, + release, + whitelistUrls, + environment, + ignoreErrors: this.IGNORE_ERRORS, // TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144 + blacklistUrls: this.BLACKLIST_URLS, + sampleRate: SAMPLE_RATE, + }); + + Sentry5.setTags(tags); + }, + + setUser() { + Sentry5.setUser({ + id: this.options.currentUserId, + }); + }, + + bindSentryErrors() { + $(document).on('ajaxError.sentry', this.handleSentryErrors); + }, + + handleSentryErrors(event, req, config, err) { + const error = err || req.statusText; + const { responseText = __('Unknown response text') } = req; + const { type, url, data } = config; + const { status } = req; + + Sentry5.captureMessage(error, { + extra: { + type, + url, + data, + status, + response: responseText, + error, + event, + }, + }); + }, +}; + +export default SentryConfig; diff --git a/app/assets/javascripts/sentry/sentry_browser_wrapper.js b/app/assets/javascripts/sentry/sentry_browser_wrapper.js new file mode 100644 index 00000000000..0382827f82c --- /dev/null +++ b/app/assets/javascripts/sentry/sentry_browser_wrapper.js @@ -0,0 +1,27 @@ +// The _Sentry object is globally exported so it can be used here +// This hack allows us to load a single version of `@sentry/browser` +// in the browser (or none). See app/views/layouts/_head.html.haml +// to find how it is imported. + +// This module wraps methods used by our production code. +// Each export is names as we cannot export the entire namespace from *. +export const captureException = (...args) => { + // eslint-disable-next-line no-underscore-dangle + const Sentry = window._Sentry; + + Sentry?.captureException(...args); +}; + +export const captureMessage = (...args) => { + // eslint-disable-next-line no-underscore-dangle + const Sentry = window._Sentry; + + Sentry?.captureMessage(...args); +}; + +export const withScope = (...args) => { + // eslint-disable-next-line no-underscore-dangle + const Sentry = window._Sentry; + + Sentry?.withScope(...args); +}; diff --git a/app/assets/javascripts/sentry/sentry_config.js b/app/assets/javascripts/sentry/sentry_config.js index 4c5b8dbad5a..ed8a55b7d44 100644 --- a/app/assets/javascripts/sentry/sentry_config.js +++ b/app/assets/javascripts/sentry/sentry_config.js @@ -1,30 +1,24 @@ -import * as Sentry from '@sentry/browser'; -import $ from 'jquery'; -import { __ } from '~/locale'; +import * as Sentry from 'sentrybrowser7'; import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants'; const SentryConfig = { - IGNORE_ERRORS, - BLACKLIST_URLS: DENY_URLS, - SAMPLE_RATE, init(options = {}) { this.options = options; this.configure(); - this.bindSentryErrors(); if (this.options.currentUserId) this.setUser(); }, configure() { - const { dsn, release, tags, whitelistUrls, environment } = this.options; + const { dsn, release, tags, allowUrls, environment } = this.options; Sentry.init({ dsn, release, - whitelistUrls, + allowUrls, environment, - ignoreErrors: this.IGNORE_ERRORS, // TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144 - blacklistUrls: this.BLACKLIST_URLS, + ignoreErrors: IGNORE_ERRORS, + denyUrls: DENY_URLS, sampleRate: SAMPLE_RATE, }); @@ -36,29 +30,6 @@ const SentryConfig = { id: this.options.currentUserId, }); }, - - bindSentryErrors() { - $(document).on('ajaxError.sentry', this.handleSentryErrors); - }, - - handleSentryErrors(event, req, config, err) { - const error = err || req.statusText; - const { responseText = __('Unknown response text') } = req; - const { type, url, data } = config; - const { status } = req; - - Sentry.captureMessage(error, { - extra: { - type, - url, - data, - status, - response: responseText, - error, - event, - }, - }); - }, }; export default SentryConfig; diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index a60813a36a7..9ad1331b948 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -418,12 +418,12 @@ module Ci end def waiting_for_deployment_approval? - manual? && starts_environment? && deployment&.blocked? + manual? && deployment_job? && deployment&.blocked? end def outdated_deployment? strong_memoize(:outdated_deployment) do - starts_environment? && + deployment_job? && incomplete? && project.ci_forward_deployment_enabled? && deployment&.older_than_last_successful_deployment? @@ -527,7 +527,7 @@ module Ci environment.present? end - def starts_environment? + def deployment_job? has_environment_keyword? && self.environment_action == 'start' end @@ -998,7 +998,7 @@ module Ci # Virtual deployment status depending on the environment status. def deployment_status - return unless starts_environment? + return unless deployment_job? if success? return successful_deployment_status diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index dc7b5e95361..1caa9720c08 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -11,7 +11,7 @@ class BuildDetailsEntity < Ci::JobEntity expose :metadata, using: BuildMetadataEntity expose :pipeline, using: Ci::PipelineEntity - expose :deployment_status, if: -> (*) { build.starts_environment? } do + expose :deployment_status, if: -> (*) { build.deployment_job? } do expose :deployment_status, as: :status expose :persisted_environment, as: :environment do |build, options| options.merge(deployment_details: false).yield_self do |opts| diff --git a/app/services/deployments/create_for_build_service.rb b/app/services/deployments/create_for_build_service.rb index 7bc0ea88910..b58aa50a66f 100644 --- a/app/services/deployments/create_for_build_service.rb +++ b/app/services/deployments/create_for_build_service.rb @@ -28,7 +28,7 @@ module Deployments def to_resource(build, environment) return build.deployment if build.deployment - return unless build.starts_environment? + return unless build.deployment_job? deployment = ::Deployment.new(attributes(build, environment)) diff --git a/app/services/groups/group_links/create_service.rb b/app/services/groups/group_links/create_service.rb index 56ddf3ec0b4..52180c39972 100644 --- a/app/services/groups/group_links/create_service.rb +++ b/app/services/groups/group_links/create_service.rb @@ -2,7 +2,7 @@ module Groups module GroupLinks - class CreateService < Groups::BaseService + class CreateService < ::Groups::BaseService include GroupLinkable def initialize(group, shared_with_group, user, params) diff --git a/app/services/groups/group_links/destroy_service.rb b/app/services/groups/group_links/destroy_service.rb index 4d74b5f32e2..d1f16775ab3 100644 --- a/app/services/groups/group_links/destroy_service.rb +++ b/app/services/groups/group_links/destroy_service.rb @@ -2,7 +2,7 @@ module Groups module GroupLinks - class DestroyService < BaseService + class DestroyService < ::Groups::BaseService def execute(one_or_more_links, skip_authorization: false) unless skip_authorization || group && can?(current_user, :admin_group_member, group) return error('Not Found', 404) diff --git a/app/services/groups/group_links/update_service.rb b/app/services/groups/group_links/update_service.rb index a1411de36d6..244ec2254a8 100644 --- a/app/services/groups/group_links/update_service.rb +++ b/app/services/groups/group_links/update_service.rb @@ -2,7 +2,7 @@ module Groups module GroupLinks - class UpdateService < BaseService + class UpdateService < ::Groups::BaseService def initialize(group_link, user = nil) super(group_link.shared_group, user) diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 61b8dd146e3..aff7c1a6b05 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -22,7 +22,7 @@ = render 'layouts/startup_css', { startup_filename: local_assigns.fetch(:startup_filename, nil) } - else - diffs_colors = user_diffs_colors - = stylesheet_link_tag "themes/#{user_application_theme_css_filename}" + = stylesheet_link_tag "themes/#{user_application_theme_css_filename}" if user_application_theme_css_filename = render 'layouts/diffs_colors_css', diffs_colors if diffs_colors.present? || request.path == profile_preferences_path - if user_application_theme == 'gl-dark' @@ -47,7 +47,10 @@ = Gon::Base.render_data(nonce: content_security_policy_nonce) = render_if_exists 'layouts/header/translations' - = webpack_bundle_tag "sentry" if Gitlab.config.sentry.enabled + - if Feature.enabled?(:enable_new_sentry_clientside_integration, current_user) && Gitlab::CurrentSettings.sentry_enabled + = webpack_bundle_tag 'sentry' + - elsif Gitlab.config.sentry.enabled + = webpack_bundle_tag 'legacy_sentry' = webpack_bundle_tag 'performance_bar' if performance_bar_enabled? = yield :page_specific_javascripts diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index a1d6ef3fec5..c11e7c6ea32 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -73,7 +73,7 @@ .form-group = f.label :layout, class: 'label-bold' do = s_('Preferences|Layout width') - = f.select :layout, layout_choices, {}, class: 'select2' + = f.select :layout, layout_choices, {}, class: 'gl-form-select custom-select' .form-text.text-muted = s_('Preferences|Choose between fixed (max. 1280px) and fluid (%{percentage}) application layout.').html_safe % { percentage: '100%' } .form-group @@ -88,7 +88,7 @@ .form-group = f.label :project_view, class: 'label-bold' do = s_('Preferences|Project overview content') - = f.select :project_view, project_view_choices, {}, class: 'select2' + = f.select :project_view, project_view_choices, {}, class: 'gl-form-select custom-select' .form-text.text-muted = s_('Preferences|Choose what content you want to see on a project’s overview page.') .form-group @@ -144,7 +144,7 @@ .form-group = f.label :first_day_of_week, class: 'label-bold' do = _('First day of the week') - = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'select2' + = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'gl-form-select custom-select' .col-sm-12 %hr .row.js-preferences-form.js-search-settings-section diff --git a/config/environments/development.rb b/config/environments/development.rb index 8f266f2660c..71376b74cfa 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -74,6 +74,10 @@ Rails.application.configure do # Do not log asset requests config.assets.quiet = true + # Use 'listen' gem to watch for file changes and improve performance + # See: https://guides.rubyonrails.org/configuring.html#config-file-watcher + config.file_watcher = ActiveSupport::EventedFileUpdateChecker + # BetterErrors live shell (REPL) on every stack frame BetterErrors::Middleware.allow_ip!("127.0.0.1/0") diff --git a/config/feature_flags/development/gitlab_metrics_error_rate_sli.yml b/config/feature_flags/development/gitlab_metrics_error_rate_sli.yml new file mode 100644 index 00000000000..30b872343ce --- /dev/null +++ b/config/feature_flags/development/gitlab_metrics_error_rate_sli.yml @@ -0,0 +1,8 @@ +--- +name: gitlab_metrics_error_rate_sli +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103976 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383071 +milestone: '15.7' +type: development +group: group::scalability +default_enabled: false diff --git a/config/initializers/diagnostic_reports.rb b/config/initializers/diagnostic_reports.rb index 7e96c266b23..64ddc98c9fb 100644 --- a/config/initializers/diagnostic_reports.rb +++ b/config/initializers/diagnostic_reports.rb @@ -8,6 +8,10 @@ Gitlab::Cluster::LifecycleEvents.on_worker_start do Gitlab::Memory::ReportsDaemon.instance.start end +return unless Gitlab::Utils.to_boolean(ENV['GITLAB_MEMWD_DUMP_HEAP']) + Gitlab::Cluster::LifecycleEvents.on_worker_stop do - Gitlab::Memory::Reports::HeapDump.write_conditionally + Gitlab::Memory::Reporter.new.run_report( + Gitlab::Memory::Reports::HeapDump.new + ) end diff --git a/config/webpack.config.js b/config/webpack.config.js index d79e6e12b39..c8a159605d1 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -160,6 +160,7 @@ function generateEntries() { */ const manualEntries = { default: defaultEntries, + legacy_sentry: './sentry/legacy_index.js', sentry: './sentry/index.js', performance_bar: './performance_bar/index.js', jira_connect_app: './jira_connect/subscriptions/index.js', @@ -174,6 +175,11 @@ function generateEntries() { const alias = { // Map Apollo client to apollo/client/core to prevent react related imports from being loaded '@apollo/client$': '@apollo/client/core', + // Map Sentry calls to use local wrapper + '@sentry/browser$': path.join( + ROOT_PATH, + 'app/assets/javascripts/sentry/sentry_browser_wrapper.js', + ), '~': path.join(ROOT_PATH, 'app/assets/javascripts'), emojis: path.join(ROOT_PATH, 'fixtures/emojis'), empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'), diff --git a/db/post_migrate/20221121155850_change_vulnerabilities_state_transitions_comment_limit.rb b/db/post_migrate/20221121155850_change_vulnerabilities_state_transitions_comment_limit.rb new file mode 100644 index 00000000000..b75216ee413 --- /dev/null +++ b/db/post_migrate/20221121155850_change_vulnerabilities_state_transitions_comment_limit.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +class ChangeVulnerabilitiesStateTransitionsCommentLimit < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + def up + add_text_limit( + :vulnerability_state_transitions, + :comment, + 50_000, + constraint_name: check_constraint_name(:vulnerability_state_transitions, :comment, 'max_length_50000') + ) + remove_text_limit( + :vulnerability_state_transitions, + :comment, + constraint_name: 'check_fca4a7ca39' + ) + end + + def down + # no-op: this can fail if records with length > 255 (previous limit) show up + end +end diff --git a/db/post_migrate/20221122155149_add_index_for_paths_on_non_projects.rb b/db/post_migrate/20221122155149_add_index_for_paths_on_non_projects.rb new file mode 100644 index 00000000000..e9a90844550 --- /dev/null +++ b/db/post_migrate/20221122155149_add_index_for_paths_on_non_projects.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +class AddIndexForPathsOnNonProjects < Gitlab::Database::Migration[2.0] + TABLE_NAME = 'namespaces' + INDEX_NAME = 'index_namespaces_on_path_for_top_level_non_projects' + COLUMN = "(lower(path::text))" + CONDITIONS = "(parent_id IS NULL AND type::text <> 'Project'::text)" + + def up + prepare_async_index TABLE_NAME, COLUMN, name: INDEX_NAME, where: CONDITIONS + end + + def down + unprepare_async_index TABLE_NAME, COLUMN, name: INDEX_NAME + end +end diff --git a/db/post_migrate/20221125222221_add_metrics_index_to_authentication_events.rb b/db/post_migrate/20221125222221_add_metrics_index_to_authentication_events.rb new file mode 100644 index 00000000000..2d3181dea67 --- /dev/null +++ b/db/post_migrate/20221125222221_add_metrics_index_to_authentication_events.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddMetricsIndexToAuthenticationEvents < Gitlab::Database::Migration[2.0] + INDEX_NAME = 'index_successful_authentication_events_for_metrics' + disable_ddl_transaction! + + def up + add_concurrent_index :authentication_events, + %i[user_id provider created_at], + where: "result = 1", + name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :authentication_events, INDEX_NAME + end +end diff --git a/db/post_migrate/20221125222341_remove_result_index_from_authentication_events.rb b/db/post_migrate/20221125222341_remove_result_index_from_authentication_events.rb new file mode 100644 index 00000000000..97fb4b320d1 --- /dev/null +++ b/db/post_migrate/20221125222341_remove_result_index_from_authentication_events.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class RemoveResultIndexFromAuthenticationEvents < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + INDEX_NAME = 'index_authentication_events_on_provider_user_id_created_at' + + def up + remove_concurrent_index_by_name :authentication_events, INDEX_NAME + end + + def down + add_concurrent_index :authentication_events, + [:provider, :user_id, :created_at], + where: 'result = 1', + name: INDEX_NAME + end +end diff --git a/db/schema_migrations/20221121155850 b/db/schema_migrations/20221121155850 new file mode 100644 index 00000000000..33c41d1b35e --- /dev/null +++ b/db/schema_migrations/20221121155850 @@ -0,0 +1 @@ +f73bd76a9ad54932b1f4b880af225a49089fc6ea782d213a9fc608b3029cddab
\ No newline at end of file diff --git a/db/schema_migrations/20221122155149 b/db/schema_migrations/20221122155149 new file mode 100644 index 00000000000..46a4270e5ed --- /dev/null +++ b/db/schema_migrations/20221122155149 @@ -0,0 +1 @@ +3c9b8f6191297e95c47a0ae2e3da7725ce33daa2a702407e0256393774935b0b
\ No newline at end of file diff --git a/db/schema_migrations/20221125222221 b/db/schema_migrations/20221125222221 new file mode 100644 index 00000000000..9235ef557b7 --- /dev/null +++ b/db/schema_migrations/20221125222221 @@ -0,0 +1 @@ +c1974d6763a85469f3d12fe4e51b1bc3b986cc335b7fe79b3875332d34a1b548
\ No newline at end of file diff --git a/db/schema_migrations/20221125222341 b/db/schema_migrations/20221125222341 new file mode 100644 index 00000000000..5f4a29202e1 --- /dev/null +++ b/db/schema_migrations/20221125222341 @@ -0,0 +1 @@ +401b563cf9f92627082bbc9850ab2fbe1d9806ced094fda99783c5d51e00fe1c
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 766a5a738b0..33a261fb753 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -23254,7 +23254,7 @@ CREATE TABLE vulnerability_state_transitions ( comment text, dismissal_reason smallint, CONSTRAINT check_d1ca8ec043 CHECK ((from_state <> to_state)), - CONSTRAINT check_fca4a7ca39 CHECK ((char_length(comment) <= 255)) + CONSTRAINT check_fe2eb6a0f3 CHECK ((char_length(comment) <= 50000)) ); CREATE SEQUENCE vulnerability_state_transitions_id_seq @@ -28382,8 +28382,6 @@ CREATE UNIQUE INDEX index_audit_events_external_audit_on_verification_token ON a CREATE INDEX index_authentication_events_on_provider ON authentication_events USING btree (provider); -CREATE INDEX index_authentication_events_on_provider_user_id_created_at ON authentication_events USING btree (provider, user_id, created_at) WHERE (result = 1); - CREATE INDEX index_authentication_events_on_user_and_ip_address_and_result ON authentication_events USING btree (user_id, ip_address, result); CREATE INDEX index_award_emoji_on_awardable_type_and_awardable_id ON award_emoji USING btree (awardable_type, awardable_id); @@ -30942,6 +30940,8 @@ CREATE INDEX index_subscriptions_on_project_id ON subscriptions USING btree (pro CREATE UNIQUE INDEX index_subscriptions_on_subscribable_and_user_id_and_project_id ON subscriptions USING btree (subscribable_id, subscribable_type, user_id, project_id); +CREATE INDEX index_successful_authentication_events_for_metrics ON authentication_events USING btree (user_id, provider, created_at) WHERE (result = 1); + CREATE INDEX index_successful_deployments_on_cluster_id_and_environment_id ON deployments USING btree (cluster_id, environment_id) WHERE (status = 2); CREATE UNIQUE INDEX index_suggestions_on_note_id_and_relative_order ON suggestions USING btree (note_id, relative_order); diff --git a/doc/administration/reference_architectures/50k_users.md b/doc/administration/reference_architectures/50k_users.md index 854113eebc3..9b425199220 100644 --- a/doc/administration/reference_architectures/50k_users.md +++ b/doc/administration/reference_architectures/50k_users.md @@ -296,8 +296,8 @@ could also be used, those load balancers have not been validated. ### Balancing algorithm -We recommend that a least-connection load balancing algorithm or equivalent -is used wherever possible to ensure equal spread of calls to the nodes and good performance. +You should use a least-connection load balancing algorithm or equivalent +wherever possible to ensure equal spread of calls to the nodes and good performance. We don't recommend the use of round-robin algorithms as they are known to not spread connections equally in practice. diff --git a/doc/administration/troubleshooting/postgresql.md b/doc/administration/troubleshooting/postgresql.md index 6aabe9a5e3d..451e1666bd2 100644 --- a/doc/administration/troubleshooting/postgresql.md +++ b/doc/administration/troubleshooting/postgresql.md @@ -211,7 +211,7 @@ To resolve the error, run `VACUUM` manually: The [database requirements](../../install/requirements.md#database) for GitLab include: -- Support for MySQL was removed in GitLab 12.1; [migrate to PostgreSQL](../../update/mysql_to_postgresql.md). +- Support for MySQL was removed in [GitLab 12.1](../../update/index.md#1210). - Review and install the [required extension list](../../install/postgresql_extensions.md). ### Serialization errors in the `production/sidekiq` log diff --git a/doc/ci/examples/semantic-release.md b/doc/ci/examples/semantic-release.md index 8f0321517ab..1fa526e787a 100644 --- a/doc/ci/examples/semantic-release.md +++ b/doc/ci/examples/semantic-release.md @@ -13,7 +13,7 @@ You can also view or fork the complete [example source](https://gitlab.com/gitla ## Initialize the module 1. Open a terminal and navigate to the project's repository. -1. Run `npm init`. Name the module according to [the Package Registry's naming conventions](../../user/packages/npm_registry/index.md#package-naming-convention). For example, if the project's path is `gitlab-examples/semantic-release-npm`, name the module `@gitlab-examples/semantic-release-npm`. +1. Run `npm init`. Name the module according to [the Package Registry's naming conventions](../../user/packages/npm_registry/index.md#naming-convention). For example, if the project's path is `gitlab-examples/semantic-release-npm`, name the module `@gitlab-examples/semantic-release-npm`. 1. Install the following npm packages: @@ -89,6 +89,7 @@ The default `before_script` generates a temporary `.npmrc` that is used to authe As part of publishing a package, semantic-release increases the version number in `package.json`. For semantic-release to commit this change and push it back to GitLab, the pipeline requires a custom CI/CD variable named `GITLAB_TOKEN`. To create this variable: <!-- markdownlint-disable MD044 --> + 1. On the top bar, on the top right, select your avatar. 1. On the left sidebar, select **Access Tokens**. 1. In the **Token name** box, enter a token name. diff --git a/doc/development/service_ping/metrics_instrumentation.md b/doc/development/service_ping/metrics_instrumentation.md index a9f236819fe..5cc8a7811d7 100644 --- a/doc/development/service_ping/metrics_instrumentation.md +++ b/doc/development/service_ping/metrics_instrumentation.md @@ -464,3 +464,15 @@ This guide describes how to migrate a Service Ping metric from [`lib/gitlab/usag 1. Remove the code from [`lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/usage_data.rb) or [`ee/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/lib/ee/gitlab/usage_data.rb). 1. Remove the tests from [`spec/lib/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/lib/gitlab/usage_data_spec.rb) or [`ee/spec/lib/ee/gitlab/usage_data.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/spec/lib/ee/gitlab/usage_data_spec.rb). + +## Troubleshoot metrics + +Sometimes metrics fail for reasons that are not immediately clear. The failures can be related to performance issues or other problems. +The following pairing session video gives you an example of an investigation in to a real-world failing metric. + +<div class="video-fallback"> + See the video from: <a href="https://www.youtube.com/watch?v=y_6m2POx2ug">Product Intelligence Office Hours Oct 27th</a> to learn more about the metrics troubleshooting process. +</div> +<figure class="video-container"> + <iframe src="https://www.youtube.com/embed/y_6m2POx2ug" frameborder="0" allowfullscreen="true"> </iframe> +</figure> diff --git a/doc/install/requirements.md b/doc/install/requirements.md index f581a1c50f9..7fcb371c178 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -77,8 +77,7 @@ process, such as PostgreSQL, which can have disastrous consequences. PostgreSQL is the only supported database, which is bundled with the Omnibus GitLab package. You can also use an [external PostgreSQL database](https://docs.gitlab.com/omnibus/settings/database.html#using-a-non-packaged-postgresql-database-management-server). -Support for MySQL was removed in GitLab 12.1. Existing users using GitLab with -MySQL/MariaDB are advised to [migrate to PostgreSQL](../update/mysql_to_postgresql.md) before upgrading. +Support for MySQL was removed in [GitLab 12.1](../update/index.md#1210). ### PostgreSQL Requirements diff --git a/doc/tutorials/move_personal_project_to_a_group.md b/doc/tutorials/move_personal_project_to_a_group.md index 08aa8fc5326..c50d94c3b17 100644 --- a/doc/tutorials/move_personal_project_to_a_group.md +++ b/doc/tutorials/move_personal_project_to_a_group.md @@ -60,7 +60,7 @@ Before you move your project to a group: - You must have the Owner role for the project. - Remove any [container images](../user/packages/container_registry/index.md#known-issues) - and [NPM packages](../user/packages/npm_registry/index.md#limitations). +- Remove any npm packages. If you transfer a project to a different root namespace, the project must not contain any npm packages. When you update the path of a user or group, or transfer a subgroup or project, you must remove any npm packages first. You cannot update the root namespace of a project with npm packages. Make sure you update your .npmrc files to follow the naming convention and run npm publish if necessary. Now you're ready to move your project: diff --git a/doc/update/index.md b/doc/update/index.md index 84dfcdeea95..c8a6f9be20d 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -1177,9 +1177,14 @@ any downgrades would result to all sessions being invalidated and users are logg ### 12.1.0 -If you are planning to upgrade from `12.0.Z` to `12.10.Z`, it is necessary to -perform an intermediary upgrade to `12.1.Z` before upgrading to `12.10.Z` to -avoid issues like [#215141](https://gitlab.com/gitlab-org/gitlab/-/issues/215141). +- If you are planning to upgrade from `12.0.Z` to `12.10.Z`, it is necessary to + perform an intermediary upgrade to `12.1.Z` before upgrading to `12.10.Z` to + avoid issues like [#215141](https://gitlab.com/gitlab-org/gitlab/-/issues/215141). + +- Support for MySQL was removed in GitLab 12.1. Existing users using GitLab with + MySQL/MariaDB should + [migrate to PostgreSQL](https://gitlab.com/gitlab-org/gitlab/-/blob/v15.6.0-ee/doc/update/mysql_to_postgresql.md) + before upgrading. ### 12.0.0 @@ -1265,8 +1270,6 @@ This issue is resolved in GitLab 15.3.3, so customers with the following configu ## Miscellaneous -- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating - your database from MySQL to PostgreSQL. - [Restoring from backup after a failed upgrade](restore_after_failure.md) - [Upgrading PostgreSQL Using Slony](upgrading_postgresql_using_slony.md), for upgrading a PostgreSQL database with minimal downtime. diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 14914eee27c..ad36a9ff534 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -2,307 +2,10 @@ stage: Data Stores group: Database info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +remove_date: '2023-02-28' +redirect_to: 'index.md' --- -# Migrating from MySQL to PostgreSQL **(FREE SELF)** +# Migrating from MySQL to PostgreSQL (removed) **(FREE SELF)** -This guide documents how to take a working GitLab instance that uses MySQL and -migrate it to a PostgreSQL database. - -## Requirements - -NOTE: -Support for MySQL was removed in GitLab 12.1. This procedure should be performed -**before** installing GitLab 12.1. - -[pgLoader](https://pgloader.io/) 3.4.1+ is required, confirm with `pgloader -V`. - -You can install it directly from your distribution, for example in -Debian/Ubuntu: - -1. Search for the version: - - ```shell - apt-cache madison pgloader - ``` - -1. If the version is 3.4.1+, install it with: - - ```shell - sudo apt-get install pgloader - ``` - - If your distribution's version is too old, use PostgreSQL's repository: - - ```shell - # Add repository - sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' - - # Add key - sudo apt-get install wget ca-certificates - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - - # Install package - sudo apt-get update - sudo apt-get install pgloader - ``` - -For other distributions, follow the instructions in PostgreSQL's -[download page](https://www.postgresql.org/download/) to add their repository -and then install `pgloader`. - -If you are migrating to a Docker based installation, you must install -pgLoader in the container as it is not included in the container image. - -1. Start a shell session in the context of the running container: - - ```shell - docker exec -it gitlab bash - ``` - -1. Install pgLoader: - - ```shell - apt-get update - apt-get -y install pgloader - ``` - -## Omnibus GitLab installations - -For [Omnibus GitLab packages](https://about.gitlab.com/install/), you first -enable the bundled PostgreSQL: - -1. Stop GitLab: - - ```shell - sudo gitlab-ctl stop - ``` - -1. Edit `/etc/gitlab/gitlab.rb` to enable bundled PostgreSQL: - - ```ruby - postgresql['enable'] = true - ``` - -1. Edit `/etc/gitlab/gitlab.rb` to use the bundled PostgreSQL. Review all of the - settings beginning with `db_` (such as `gitlab_rails['db_adapter']`). To use - the default values, you can comment all of them out. - -1. [Reconfigure GitLab](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure) - for the changes to take effect. - -1. Start Puma and PostgreSQL so that we can prepare the schema: - - ```shell - sudo gitlab-ctl start puma - sudo gitlab-ctl start postgresql - ``` - -1. Run the following commands to prepare the schema: - - ```shell - sudo gitlab-rake db:create db:migrate - ``` - -1. Stop Puma to prevent other database access from interfering with the loading of data: - - ```shell - sudo gitlab-ctl stop puma - ``` - -After these steps, you have a fresh PostgreSQL database with up-to-date schema. - -Next, use `pgloader` to migrate the data from the old MySQL database to the -new PostgreSQL one: - -1. Save the following snippet in a `commands.load` file, and edit with your - MySQL database `username`, `password` and `host`: - - ```sql - LOAD DATABASE - FROM mysql://username:password@host/gitlabhq_production - INTO postgresql://gitlab-psql@unix://var/opt/gitlab/postgresql:/gitlabhq_production - - WITH include no drop, truncate, disable triggers, create no tables, - create no indexes, preserve index names, no foreign keys, - data only - - SET MySQL PARAMETERS - net_read_timeout = '90', - net_write_timeout = '180' - - ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public' - - ; - ``` - -1. Start the migration: - - ```shell - sudo -u gitlab-psql pgloader commands.load - ``` - -1. After the migration finishes, you should see a summary table that looks like - the following: - - ```plaintext - table name read imported errors total time - ----------------------------------------------- --------- --------- --------- -------------- - fetch meta data 119 119 0 0.388s - Truncate 119 119 0 1.134s - ----------------------------------------------- --------- --------- --------- -------------- - public.abuse_reports 0 0 0 0.490s - public.appearances 0 0 0 0.488s - . - . - . - public.web_hook_logs 0 0 0 1.080s - ----------------------------------------------- --------- --------- --------- -------------- - COPY Threads Completion 4 4 0 2.008s - Reset Sequences 113 113 0 0.304s - Install Comments 0 0 0 0.000s - ----------------------------------------------- --------- --------- --------- -------------- - Total import time 1894 1894 0 12.497s - ``` - - If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See - the [troubleshooting guide](#troubleshooting) for more details. - -1. Start GitLab: - - ```shell - sudo gitlab-ctl start - ``` - -You can now verify that everything works as expected by visiting GitLab. - -## Source installations - -For installations from source that use MySQL, you must first -[install PostgreSQL and create a database](../install/installation.md#6-database). - -After the database is created, go on with the following steps: - -1. Stop GitLab: - - ```shell - sudo service gitlab stop - ``` - -1. Switch database from MySQL to PostgreSQL - - ```shell - cd /home/git/gitlab - sudo -u git mv config/database.yml config/database.yml.bak - sudo -u git cp config/database.yml.postgresql config/database.yml - sudo -u git -H chmod o-rwx config/database.yml - ``` - -1. Install Gems related to PostgreSQL - - ```shell - sudo -u git -H rm .bundle/config - sudo -u git -H bundle install --deployment --without development test mysql aws kerberos - ``` - -1. Run the following commands to prepare the schema: - - ```shell - sudo -u git -H bundle exec rake db:create db:migrate RAILS_ENV=production - ``` - -After these steps, you have a fresh PostgreSQL database with up-to-date schema. - -Next, use `pgloader` to migrate the data from the old MySQL database to the -new PostgreSQL one: - -1. Save the following snippet in a `commands.load` file, and edit with your - MySQL `username`, `password` and `host`: - - ```sql - LOAD DATABASE - FROM mysql://username:password@host/gitlabhq_production - INTO postgresql://postgres@unix://var/run/postgresql:/gitlabhq_production - - WITH include no drop, truncate, disable triggers, create no tables, - create no indexes, preserve index names, no foreign keys, - data only - - SET MySQL PARAMETERS - net_read_timeout = '90', - net_write_timeout = '180' - - ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public' - - ; - ``` - -1. Start the migration: - - ```shell - sudo -u postgres pgloader commands.load - ``` - -1. After the migration finishes, you should see a summary table that looks like - the following: - - ```plaintext - table name read imported errors total time - ----------------------------------------------- --------- --------- --------- -------------- - fetch meta data 119 119 0 0.388s - Truncate 119 119 0 1.134s - ----------------------------------------------- --------- --------- --------- -------------- - public.abuse_reports 0 0 0 0.490s - public.appearances 0 0 0 0.488s - . - . - . - public.web_hook_logs 0 0 0 1.080s - ----------------------------------------------- --------- --------- --------- -------------- - COPY Threads Completion 4 4 0 2.008s - Reset Sequences 113 113 0 0.304s - Install Comments 0 0 0 0.000s - ----------------------------------------------- --------- --------- --------- -------------- - Total import time 1894 1894 0 12.497s - ``` - - If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See - the [troubleshooting guide](#troubleshooting) for more details. - -1. Start GitLab: - - ```shell - sudo service gitlab start - ``` - -You can now verify that everything works as expected by visiting GitLab. - -## Troubleshooting - -Sometimes, you might encounter some errors during or after the migration. - -### Database error permission denied - -The PostgreSQL user that you use for the migration **must** have **superuser** privileges. -Otherwise, you may see a similar message to the following: - -```plaintext -debugger invoked on a CL-POSTGRES-ERROR:INSUFFICIENT-PRIVILEGE in thread - #<THREAD "lparallel" RUNNING {10078A3513}>: - Database error 42501: permission denied: "RI_ConstraintTrigger_a_20937" is a system trigger - QUERY: ALTER TABLE ci_builds DISABLE TRIGGER ALL; - 2017-08-23T00:36:56.782000Z ERROR Database error 42501: permission denied: "RI_ConstraintTrigger_c_20864" is a system trigger - QUERY: ALTER TABLE approver_groups DISABLE TRIGGER ALL; -``` - -### 500 errors after the migration - -If you experience 500 errors after the migration, try to clear the cache: - -```shell -# Omnibus GitLab -sudo gitlab-rake cache:clear - -# Installations from source -sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production -``` +Support for MySQL was removed in GitLab 12.1. diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 5d2efc52ba9..f82210c0ef5 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -6,250 +6,97 @@ info: To determine the technical writer assigned to the Stage/Group associated w # npm packages in the Package Registry **(FREE)** -> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3. - -Publish npm packages in your project's Package Registry. Then install the -packages whenever you need to use them as a dependency. - -Only [scoped](https://docs.npmjs.com/misc/scope/) packages are supported. - -For documentation of the specific API endpoints that the npm package manager -client uses, see the [npm API documentation](../../../api/packages/npm.md). - -WARNING: -Never hardcode GitLab tokens (or any tokens) directly in `.npmrc` files or any other files that can -be committed to a repository. +For documentation of the specific API endpoints that the npm package manager client uses, see the [npm API documentation](../../../api/packages/npm.md). Learn how to build an [npm](../workflows/build_packages.md#npm) or [yarn](../workflows/build_packages.md#yarn) package. -## Use the GitLab endpoint for npm packages - -To use the GitLab endpoint for npm packages, choose an option: +Watch a [video demo](https://youtu.be/yvLxtkvsFDA) of how to publish npm packages to the GitLab Package Registry. -- **Project-level**: Use when you have few npm packages and they are not in - the same GitLab group. The [package naming convention](#package-naming-convention) is not enforced at this level. - Instead, you should use a [scope](https://docs.npmjs.com/cli/v6/using-npm/scope/) for your package. - When you use a scope, the registry URL is [updated](#authenticate-to-the-package-registry) only for that scope. -- **Instance-level**: Use when you have many npm packages in different - GitLab groups or in their own namespace. Be sure to comply with the [package naming convention](#package-naming-convention). +## Publish to GitLab Package Registry -Some features such as [publishing](#publish-an-npm-package) a package is only available on the project-level endpoint. +### Authentication to the Package Registry -## Authenticate to the Package Registry +You need an token to publish a package. There are different tokens available depending on what you're trying to achieve. For more information, review the [guidance on tokens](../../../user/packages/package_registry/index.md#authenticate-with-the-registry). -You must authenticate with the Package Registry when the project -is private. Public projects do not require authentication. +- If your organization uses two factor authentication (2FA), you must use a personal access token with the scope set to `api`. +- If you are publishing a package via CI/CD pipelines, you must use a CI job token. -To authenticate, use one of the following: +Create a token and save it to use later in the process. -- A [personal access token](../../../user/profile/personal_access_tokens.md) - (required for two-factor authentication (2FA)), with the scope set to `api`. -- A [deploy token](../../project/deploy_tokens/index.md), with the scope set to `read_package_registry`, `write_package_registry`, or both. -- It's not recommended, but you can use [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow). - Standard OAuth tokens cannot authenticate to the GitLab npm Registry. You must use a personal access token with OAuth headers. -- A [CI job token](#authenticate-with-a-ci-job-token). -- Your npm package name must be in the format of [`@scope/package-name`](#package-naming-convention). - It must match exactly, including the case. +### Naming convention -### Authenticate with a personal access token or deploy token +Depending on how the package will be installed, you may need to adhere to the naming convention. -To authenticate with the Package Registry, you need a [personal access token](../../profile/personal_access_tokens.md) or [deploy token](../../project/deploy_tokens/index.md). +You can use one of two API endpoints to install packages: -#### Project-level npm endpoint +- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace. +- **Project-level**: Use when you have few npm packages and they are not in the same GitLab group. -To use the [project-level](#use-the-gitlab-endpoint-for-npm-packages) npm endpoint, set your npm configuration: +If you plan to install a package through the [project level](#install-from-the-project-level), then you do not have to adhere to the naming convention. -```shell -# Set URL for your scoped packages. -# For example package with name `@foo/bar` will use this URL for download -npm config set @foo:registry https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/ - -# Add the token for the scoped packages URL. Replace <your_project_id> -# with the project where your package is located. -npm config set -- '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>" -``` - -- `<your_project_id>` is your project ID, found on the project's home page. -- `<your_token>` is your personal access token or deploy token. -- Replace `gitlab.example.com` with your domain name. +If you plan to install a package through the [instance level](#install-from-the-instance-level), then you must name your package with a [scope](https://docs.npmjs.com/misc/scope/). Scoped packages begin with a `@` have the format of `@owner/package-name`. You can set up the scope for your package in the `.npmrc` file and by using the `publishConfig` option in the `package.json`. -You should now be able to publish and install npm packages in your project. +- The value used for the `@scope` is the root of the project that will host the packages and not the root + of the project with the source code of the package itself. The scope should be lowercase. +- The package name can be anything you want -If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view -[troubleshooting steps](#troubleshooting). +| Project URL | Package Registry in | Scope | Full package name | +| ------------------------------------------------------- | ------------------- | --------- | ---------------------- | +| `https://gitlab.com/my-org/engineering-group/analytics` | Analytics | `@my-org` | `@my-org/package-name` | -#### Instance-level npm endpoint - -NOTE: -Note: Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project will give you 404 errors. You can use a [personal access token](../../profile/personal_access_tokens.md) as a workaround. [GitLab-#352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962) proposes a fix to this bug. - -To use the [instance-level](#use-the-gitlab-endpoint-for-npm-packages) npm endpoint, set your npm configuration: +Make sure that the name of your package in the `package.json` file matches this convention: ```shell -# Set URL for your scoped packages. -# For example package with name `@foo/bar` will use this URL for download -npm config set @foo:registry https://gitlab.example.com/api/v4/packages/npm/ - -# Add the token for the scoped packages URL. This will allow you to download -# `@foo/` packages from private projects. -npm config set -- '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>" +"name": "@my-org/package-name" ``` -- `<your_token>` is your personal access token or deploy token. -- Replace `gitlab.example.com` with your domain name. - -You should now be able to install npm packages in your project. +## Publishing a package via the command line -If you encounter an error with [Yarn](https://classic.yarnpkg.com/en/), view -[troubleshooting steps](#troubleshooting). +### Authenticating via the `.npmrc` -### Authenticate with a CI job token - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9104) in GitLab 12.5. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3. - -If you're using npm with GitLab CI/CD, a CI job token can be used instead of a personal access token or deploy token. -The token inherits the permissions of the user that generates the pipeline. - -#### Project-level npm endpoint - -To use the [project-level](#use-the-gitlab-endpoint-for-npm-packages) npm endpoint, add a corresponding section to your `.npmrc` file: - -```ini -@foo:registry=https://gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/ -//gitlab.example.com/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN} -``` - -#### Instance-level npm endpoint - -To use the [instance-level](#use-the-gitlab-endpoint-for-npm-packages) npm endpoint, add a corresponding section to your `.npmrc` file: - -```ini -@foo:registry=https://gitlab.example.com/api/v4/packages/npm/ -//gitlab.example.com/api/v4/packages/npm/:_authToken=${CI_JOB_TOKEN} -``` - -#### Use variables to avoid hard-coding auth token values - -To avoid hard-coding the `authToken` value, you may use a variable in its place: +Create or edit the `.npmrc` file in the same directory as your `package.json`. Include the following lines in the `.npmrc` file: ```shell -npm config set -- '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "${NPM_TOKEN}" -npm config set -- '//gitlab.example.com/api/v4/packages/npm/:_authToken' "${NPM_TOKEN}" +@scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/ +//your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken="${NPM_TOKEN}" ``` -Then, you can run `npm publish` either locally or by using GitLab CI/CD. +- Replace `@scope` with the [root level group](#naming-convention) of the project you're publishing to the package to. +- Replace `your_domain_name` with your domain name, for example, `gitlab.com`. +- Replace `your_project_id` is your project ID, found on the project's home page. +- `"${NPM_TOKEN}"` will be associated with the token you created later in the process. -- **Locally:** Export `NPM_TOKEN` before publishing: - - ```shell - NPM_TOKEN=<your_token> npm publish - ``` - -- **GitLab CI/CD:** Set an `NPM_TOKEN` [CI/CD variable](../../../ci/variables/index.md) - under your project's **Settings > CI/CD > Variables**. +WARNING: +Never hardcode GitLab tokens (or any tokens) directly in `.npmrc` files or any other files that can +be committed to a repository. -## Working with private registries +### Publishing a package via the command line -When working with private repositories, you may want to configure additional settings to ensure a secure communication channel: +Associate your [token](#authentication-to-the-package-registry) with the `"${NPM_TOKEN}"` in the `.npmrc`. Replace `your_token` with a deploy token, group access token, project access token, or personal access token. ```shell -# Force npm to always require authentication when accessing the registry, even for GET requests. -npm config set always-auth true -``` - -## Package naming convention - -When you use the [instance-level endpoint](#use-the-gitlab-endpoint-for-npm-packages), only the packages with names in the format of `@scope/package-name` are available. - -- The `@scope` is the root namespace of the GitLab project. To follow npm's convention, it should be - lowercase. However, the GitLab package registry allows for uppercase. Before GitLab 13.10, the - `@scope` had to be a case-sensitive match of the GitLab project's root namespace. This was - problematic because the npm public registry does not allow uppercase letters. GitLab 13.10 relaxes - this requirement and translates uppercase in the GitLab `@scope` to lowercase for npm. For - example, a package `@MyScope/package-name` in GitLab becomes `@myscope/package-name` for npm. -- The `package-name` can be whatever you want. - -NOTE: -The value used for the `@scope` is the root of the project that will end up hosting the packages and not the root -of the project with the source code of the package itself. For example, assume your package source code is located -at `source-code-group/package-code` and deployed to a package registry inside `registries-group/registry-project`. -In this case, the `@scope` needs to be `@registries-group` and not `@source-code-group`. - -For example, if your project is `https://gitlab.example.com/my-org/engineering-group/team-amazing/analytics`, -the root namespace is `my-org`. When you publish a package, it must have `my-org` as the scope. - -| Project | Package | Supported | -| ------------------- | -------------------- | --------- | -| `my-org/bar` | `@my-org/bar` | Yes | -| `my-org/bar/baz` | `@my-org/baz` | Yes | -| `My-Org/Bar/baz` | `@my-org/Baz` | Yes | -| `My-Org/Bar/baz` | `@My-Org/Baz` | Yes | -| `my-org/bar/buz` | `@my-org/anything` | Yes | -| `gitlab-org/gitlab` | `@gitlab-org/gitlab` | Yes | -| `gitlab-org/gitlab` | `@foo/bar` | No | - -In GitLab, this regex validates all package names from all package managers: - -```plaintext -/\A\@?(([\w\-\.\+]*)\/)*([\w\-\.]+)@?(([\w\-\.\+]*)\/)*([\w\-\.]*)\z/ +NPM_TOKEN=your_token npm publish ``` -This regex allows almost all of the characters that npm allows, with a few exceptions (for example, `~` is not allowed). - -The regex also allows for capital letters, while npm does not. - -## Limitations - -When you update the path of a user or group, or transfer a subgroup or project, -you must remove any npm packages first. You cannot update the root namespace -of a project with npm packages. Make sure you update your `.npmrc` files to follow -the naming convention and run `npm publish` if necessary. - -## Publish an npm package +Your package should now publish to the Package Registry. -Prerequisites: +## Publishing a package via a CI/CD pipeline -- [Authenticate](#authenticate-to-the-package-registry) to the Package Registry. -- Set a [project-level npm endpoint](#use-the-gitlab-endpoint-for-npm-packages). +### Authenticating via the `.npmrc` -To upload an npm package to your project, run this command: +Create or edit the `.npmrc` file in the same directory as your `package.json` in a GitLab project. Include the following lines in the `.npmrc` file: ```shell -npm publish +@scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/ +//your_domain_name/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN} ``` -To view the package, go to your project's **Packages and registries**. +- Replace `@scope` with the [root level group](#naming-convention) of the project you're publishing to the package to. +- The `${CI_PROJECT_ID}` and `${CI_JOB_TOKEN}` are [predefined variables](../../../ci/variables/predefined_variables.md) that are available in the pipeline and do not need to be replaced. -You can also define `"publishConfig"` for your project in `package.json`. For example: +### Publishing a package via a CI/CD pipeline -```json -{ - "publishConfig": { - "@foo:registry": " https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/" - } -} -``` - -This forces the package to publish only to the specified registry. - -If you try to publish a package [with a name that already exists](#publishing-packages-with-the-same-name-or-version) within -a given scope, you get a `403 Forbidden!` error. - -## Publish an npm package by using CI/CD - -Prerequisites: - -- [Authenticate](#authenticate-to-the-package-registry) to the Package Registry. -- Set a [project-level npm endpoint](#use-the-gitlab-endpoint-for-npm-packages). -- Your npm package name must be in the format of [`@scope/package-name`](#package-naming-convention). - It must match exactly, including the case. This is different than the - npm naming convention, but it is required to work with the GitLab Package Registry. - -To work with npm commands within [GitLab CI/CD](../../../ci/index.md), you can use -`CI_JOB_TOKEN` in place of the personal access token or deploy token in your commands. - -An example `.gitlab-ci.yml` file for publishing npm packages: +In the GitLab project that houses your `.npmrc` and `package.json`, edit or create a `.gitlab-ci.yml` file. For example: ```yaml image: node:latest @@ -262,115 +109,86 @@ deploy: script: - echo "//${CI_SERVER_HOST}/api/v4/projects/${CI_PROJECT_ID}/packages/npm/:_authToken=${CI_JOB_TOKEN}">.npmrc - npm publish - environment: production ``` -See the -[Publish npm packages to the GitLab Package Registry using semantic-release](../../../ci/examples/semantic-release.md) -step-by-step guide and demo project for a complete example. - -## Configure the GitLab npm registry with Yarn 2 +Your package should now publish to the Package Registry when the pipeline runs. -You can get started with Yarn 2 by following the [Yarn documentation](https://yarnpkg.com/getting-started/install/). - -To publish and install with the project-level npm endpoint, set the following configuration in -`.yarnrc.yml`: +## Install a package -```yaml -npmScopes: - foo: - npmRegistryServer: 'https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/' - npmPublishRegistry: 'https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/' - -npmRegistries: - //gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/: - npmAlwaysAuth: true - npmAuthToken: '<your_token>' -``` +If multiple packages have the same name and version, when you install a package, the most recently-published package is retrieved. -For the instance-level npm endpoint, use this Yarn 2 configuration in `.yarnrc.yml`: +You can install a package from a GitLab project or instance: -```yaml -npmScopes: - foo: - npmRegistryServer: 'https://gitlab.example.com/api/v4/packages/npm/' - -npmRegistries: - //gitlab.example.com/api/v4/packages/npm/: - npmAlwaysAuth: true - npmAuthToken: '<your_token>' -``` +- **Instance-level**: Use when you have many npm packages in different GitLab groups or in their own namespace. +- **Project-level**: Use when you have few npm packages and they are not in the same GitLab group. -In this configuration: +### Install from the instance level -- Replace `<your_token>` with your personal access token or deploy token. -- Replace `<your_project_id>` with your project's ID, which you can find on the project's home page. -- Replace `gitlab.example.com` with your domain name. -- Your scope is `foo`, without `@`. +WARNING: +In order to install a package from the instance level, the package must have been published following the scoped [naming convention](#naming-convention). -## Publishing packages with the same name or version +1. Authenticate to the Package Registry -You cannot publish a package if a package of the same name and version already exists. -You must delete the existing package first. + If you would like to install a package from a private project, you will need to authenticate to the Package Registry. Skip this step if the project is not private. -This rule has a different impact depending on the package name: + ```shell + npm config set -- //your_domain_name/api/v4/packages/npm/:_authToken=your_token + ``` -- For packages following the [naming convention](#package-naming-convention), you can't publish a - package with a duplicate name and version to the root namespace. -- For packages not following the [naming convention](#package-naming-convention), you can't publish - a package with a duplicate name and version to the project you target with the upload. + - Replace `your_domain_name` with your domain name, for example, `gitlab.com`. + - Replace `your_token` with a deploy token, group access token, project access token, or personal access token. -This aligns with npmjs.org's behavior. However, npmjs.org does not ever let you publish -the same version more than once, even if it has been deleted. +1. Set the registry -## `package.json` limitations + ```shell + npm config set @scope:registry https://your_domain_name.com/api/v4/packages/npm/ + ``` -You can't publish a package if its `package.json` file exceeds 20,000 characters. + - Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from. + - Replace `your_domain_name` with your domain name, for example `gitlab.com`. + - Replace `your_token` with a deploy token, group access token, project access token, or personal access token. -## Install a package +1. Install the package -npm packages are commonly-installed by using the `npm` or `yarn` commands -in a JavaScript project. You can install a package from the scope of a project or instance. + ```shell + npm install @scope/my-package + ``` -If multiple packages have the same name and version, when you install a package, the most recently-published package is retrieved. +### Install from the project level -1. Set the URL for scoped packages. +1. Authenticate to the Package Registry - For [instance-level endpoints](#use-the-gitlab-endpoint-for-npm-packages) run: + If you would like to install a package from a private project, you will need to authenticate to the Package Registry. Skip this step if the project is not private. ```shell - npm config set @foo:registry https://gitlab.example.com/api/v4/packages/npm/ + npm config set -- //your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken=your_token ``` - - Replace `@foo` with your scope. - - Replace `gitlab.example.com` with your domain name. + - Replace `your_domain_name` with your domain name, for example, `gitlab.com`. + - Replace `your_project_id` is your project ID, found on the project's home page. + - Replace `your_token` with a deploy token, group access token, project access token, or personal access token. - For [project-level endpoints](#use-the-gitlab-endpoint-for-npm-packages) run: +1. Set the registry ```shell - npm config set @foo:registry https://gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/ + npm config set @scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/ ``` - - Replace `@foo` with your scope. - - Replace `gitlab.example.com` with your domain name. - - Replace `<your_project_id>` with your project ID, found on the project's home page. - -1. Ensure [authentication](#authenticate-to-the-package-registry) is configured. + - Replace `@scope` with the [root level group](#naming-convention) of the project you're installing to the package from. + - Replace `your_domain_name` with your domain name, for example, `gitlab.com`. + - Replace `your_project_id` is your project ID, found on the project's home page. -1. To install a package in your project, run: +1. Install the package ```shell - npm install @my-scope/my-package + npm install @scope/my-package ``` - Or if you're using Yarn: +## Helpful hints - ```shell - yarn add @my-scope/my-package - ``` +### Package forwarding to npmjs.com -In [GitLab 12.9 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/55344), -when an npm package is not found in the Package Registry, the request is forwarded to [npmjs.com](https://www.npmjs.com/). +When an npm package is not found in the Package Registry, the request is forwarded to [npmjs.com](https://www.npmjs.com/). Administrators can disable this behavior in the [Continuous Integration settings](../../admin_area/settings/continuous_integration.md). @@ -378,27 +196,16 @@ Administrators can disable this behavior in the [Continuous Integration settings You can route package requests to organizations and users outside of GitLab. -To do this, add lines to your `.npmrc` file. Replace `my-org` with the namespace or group that owns your project's repository, +To do this, add lines to your `.npmrc` file. Replace `@my-other-org` with the namespace or group that owns your project's repository, and use your organization's URL. The name is case-sensitive and must match the name of your group or namespace exactly. -Use environment variables to set up your tokens: `export MY_TOKEN="<your token>"`. - ```shell -@foo:registry=https://gitlab.example.com/api/v4/packages/npm/ -//gitlab.example.com/api/v4/packages/npm/:_authToken=${MY_TOKEN} -//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=${MY_TOKEN} - -@my-other-org:registry=https://gitlab.example.com/api/v4/packages/npm/ -//gitlab.example.com/api/v4/packages/npm/:_authToken=${MY_TOKEN} -//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=${MY_TOKEN} +@scope:registry=https://my_domain_name.com/api/v4/packages/npm/ +@my-other-org:registry=https://my_domain_name.example.com/api/v4/packages/npm/ ``` ### npm metadata -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/11867) in GitLab 12.6. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3. -> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/330929) in GitLab 14.5. - The GitLab Package Registry exposes the following attributes to the npm client. These are similar to the [abbreviated metadata format](https://github.com/npm/registry/blob/9e368cf6aaca608da5b2c378c0d53f475298b916/docs/responses/package-metadata.md#abbreviated-metadata-format): @@ -417,10 +224,7 @@ These are similar to the [abbreviated metadata format](https://github.com/npm/re - `engines` - `_hasShrinkwrap` -## Add npm distribution tags - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/9425) in GitLab 12.8. -> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3. +### Add npm distribution tags You can add [distribution tags](https://docs.npmjs.com/cli/dist-tag/) to newly-published packages. Tags are optional and can be assigned to only one package at a time. @@ -443,87 +247,46 @@ View [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/258835) for deta Due to a bug in npm 6.9.0, deleting distribution tags fails. Make sure your npm version is 6.9.1 or later. -## Troubleshooting - -When troubleshooting npm issues, first run the same command with the `--verbose` flag to confirm -what registry you are hitting. - -To improve performance, npm caches files related to a package. Note that npm doesn't remove data by -itself. The cache grows as new packages are installed. If you encounter issues, clear the cache with -this command: - -```shell -npm cache clean --force -``` - -### Error running Yarn with the Package Registry for npm registry - -If you are using [Yarn](https://classic.yarnpkg.com/en/) with the npm registry, you may get -an error message like: - -```shell -yarn install v1.15.2 -warning package.json: No license field -info No lockfile found. -warning XXX: No license field -[1/4] 🔍 Resolving packages... -[2/4] 🚚 Fetching packages... -error An unexpected error occurred: "https://gitlab.example.com/api/v4/projects/XXX/packages/npm/XXX/XXX/-/XXX/XXX-X.X.X.tgz: Request failed \"404 Not Found\"". -info If you think this is a bug, please open a bug report with the information provided in "/Users/XXX/gitlab-migration/module-util/yarn-error.log". -info Visit https://classic.yarnpkg.com/en/docs/cli/install for documentation about this command -``` - -In this case, try adding this to your `.npmrc` file (and replace `<your_token>` -with your personal access token or deploy token): +### Supported CLI commands -```plaintext -//gitlab.example.com/api/v4/projects/:_authToken=<your_token> -``` +The GitLab npm repository supports the following commands for the npm CLI (`npm`) and yarn CLI +(`yarn`): -You can also use `yarn config` instead of `npm config` when setting your auth-token dynamically: +- `npm install`: Install npm packages. +- `npm publish`: Publish an npm package to the registry. +- `npm dist-tag add`: Add a dist-tag to an npm package. +- `npm dist-tag ls`: List dist-tags for a package. +- `npm dist-tag rm`: Delete a dist-tag. +- `npm ci`: Install npm packages directly from your `package-lock.json` file. +- `npm view`: Show package metadata. -```shell -yarn config set '//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken' "<your_token>" -yarn config set '//gitlab.example.com/api/v4/packages/npm/:_authToken' "<your_token>" -``` +## Troubleshooting ### `npm publish` targets default npm registry (`registry.npmjs.org`) Ensure that your package scope is set consistently in your `package.json` and `.npmrc` files. -For example, if your project name in GitLab is `foo/my-package`, then your `package.json` file +For example, if your project name in GitLab is `@scope/my-package`, then your `package.json` file should look like: ```json { - "name": "@foo/my-package", - "version": "1.0.0", - "description": "Example package for GitLab npm registry" + "name": "@scope/my-package" } ``` And the `.npmrc` file should look like: -```ini -//gitlab.example.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_token> -//gitlab.example.com/api/v4/packages/npm/:_authToken=<your_token> -@foo:registry=https://gitlab.example.com/api/v4/packages/npm/ -``` - -### `npm install` returns `Error: Failed to replace env in config: ${npm_TOKEN}` - -You do not need a token to run `npm install` unless your project is private. The token is only required to publish. If the `.npmrc` file was checked in with a reference to `$npm_TOKEN`, you can remove it. If you prefer to leave the reference in, you must set a value prior to running `npm install` or set the value by using [GitLab CI/CD variables](../../../ci/variables/index.md): - ```shell -NPM_TOKEN=<your_token> npm install +@scope:registry=https://your_domain_name/api/v4/projects/your_project_id/packages/npm/ +//your_domain_name/api/v4/projects/your_project_id/packages/npm/:_authToken="${NPM_TOKEN}" ``` ### `npm install` returns `npm ERR! 403 Forbidden` If you get this error, ensure that: -- The Package Registry is enabled in your project settings. Although the Package Registry is enabled - by default, it's possible to [disable it](../package_registry/index.md#disable-the-package-registry). +- The Package Registry is enabled in your project settings. Although the Package Registry is enabled by default, it's possible to [disable it](../package_registry/index.md#disable-the-package-registry). - Your token is not expired and has appropriate permissions. - A package with the same name or version doesn't already exist within the given scope. - The scoped packages URL includes a trailing slash: @@ -534,30 +297,25 @@ If you get this error, ensure that: If you get this error, one of the following problems could be causing it. -#### Package name does not meet the naming convention +### Package name does not meet the naming convention -Your package name may not meet the -[`@scope/package-name` package naming convention](#package-naming-convention). +Your package name may not meet the [`@scope/package-name` package naming convention](#naming-convention). -Ensure the name meets the convention exactly, including the case. -Then try to publish again. +Ensure the name meets the convention exactly, including the case. Then try to publish again. -#### Package already exists +### Package already exists -Your package has already been published to another project in the same -root namespace and therefore cannot be published again using the same name. +Your package has already been published to another project in the same root namespace and therefore cannot be published again using the same name. -This is also true even if the prior published package shares the same name, -but not the version. +This is also true even if the prior published package shares the same name, but not the version. -#### Package JSON file is too large +### Package JSON file is too large -Make sure that your `package.json` file does not [exceed `20,000` characters](#packagejson-limitations). +Make sure that your `package.json` file does not exceed `20,000` characters. ### `npm publish` returns `npm ERR! 500 Internal Server Error - PUT` -This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/238950) in GitLab -13.3.x and later. The error in the logs will appear as: +This is a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/238950) in GitLab 13.3.x and later. The error in the logs will appear as: ```plaintext >NoMethodError - undefined method `preferred_language' for #<Rack::Response @@ -576,18 +334,3 @@ This is usually a permissions issue with either: is used. In the latter case, ensure the bucket exists and GitLab has write access to it. - -## Supported CLI commands - -The GitLab npm repository supports the following commands for the npm CLI (`npm`) and yarn CLI -(`yarn`): - -- `npm install`: Install npm packages. -- `npm publish`: Publish an npm package to the registry. -- `npm dist-tag add`: Add a dist-tag to an npm package. -- `npm dist-tag ls`: List dist-tags for a package. -- `npm dist-tag rm`: Delete a dist-tag. -- `npm ci`: Install npm packages directly from your `package-lock.json` file. -- `npm view`: Show package metadata. -- `yarn add`: Install an npm package. -- `yarn update`: Update your dependencies. diff --git a/doc/user/packages/package_registry/index.md b/doc/user/packages/package_registry/index.md index 65ff715a4ee..5e7f3e6ae79 100644 --- a/doc/user/packages/package_registry/index.md +++ b/doc/user/packages/package_registry/index.md @@ -77,7 +77,7 @@ Learn more about using the GitLab Package Registry with CI/CD: - [Conan](../conan_repository/index.md#publish-a-conan-package-by-using-cicd) - [Generic](../generic_packages/index.md#publish-a-generic-package-by-using-cicd) - [Maven](../maven_repository/index.md#create-maven-packages-with-gitlab-cicd) -- [npm](../npm_registry/index.md#publish-an-npm-package-by-using-cicd) +- [npm](../npm_registry/index.md#publishing-a-package-via-a-cicd-pipeline) - [NuGet](../nuget_repository/index.md#publish-a-nuget-package-by-using-cicd) - [PyPI](../pypi_repository/index.md#authenticate-with-a-ci-job-token) - [RubyGems](../rubygems_registry/index.md#authenticate-with-a-ci-job-token) @@ -143,19 +143,19 @@ table's **Status** column. The Package Registry supports the following formats: -| Package type | GitLab version | Status | -| ------------ | -------------- |------- | -| [Maven](../maven_repository/index.md) | 11.3+ | GA | -| [npm](../npm_registry/index.md) | 11.7+ | GA | -| [NuGet](../nuget_repository/index.md) | 12.8+ | GA | -| [PyPI](../pypi_repository/index.md) | 12.10+ | GA | -| [Generic packages](../generic_packages/index.md) | 13.5+ | GA | -| [Composer](../composer_repository/index.md) | 13.2+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6817) | -| [Conan](../conan_repository/index.md) | 12.6+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6816) | -| [Helm](../helm_repository/index.md) | 14.1+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6366) | -| [Debian](../debian_repository/index.md) | 14.2+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/6057) | -| [Go](../go_proxy/index.md) | 13.1+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3043) | -| [Ruby gems](../rubygems_registry/index.md) | 13.10+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3200) | +| Package type | GitLab version | Status | +| ------------------------------------------------ | -------------- | ---------------------------------------------------------- | +| [Maven](../maven_repository/index.md) | 11.3+ | GA | +| [npm](../npm_registry/index.md) | 11.7+ | GA | +| [NuGet](../nuget_repository/index.md) | 12.8+ | GA | +| [PyPI](../pypi_repository/index.md) | 12.10+ | GA | +| [Generic packages](../generic_packages/index.md) | 13.5+ | GA | +| [Composer](../composer_repository/index.md) | 13.2+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6817) | +| [Conan](../conan_repository/index.md) | 12.6+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6816) | +| [Helm](../helm_repository/index.md) | 14.1+ | [Beta](https://gitlab.com/groups/gitlab-org/-/epics/6366) | +| [Debian](../debian_repository/index.md) | 14.2+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/6057) | +| [Go](../go_proxy/index.md) | 13.1+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3043) | +| [Ruby gems](../rubygems_registry/index.md) | 13.10+ | [Alpha](https://gitlab.com/groups/gitlab-org/-/epics/3200) | [Status](../../../policy/alpha-beta-support.md): @@ -173,8 +173,8 @@ guides you through the process. <!-- vale gitlab.Spelling = NO --> -| Format | Status | -| ------ | ------ | +| Format | Status | +| --------- | ------------------------------------------------------------- | | Chef | [#36889](https://gitlab.com/gitlab-org/gitlab/-/issues/36889) | | CocoaPods | [#36890](https://gitlab.com/gitlab-org/gitlab/-/issues/36890) | | Conda | [#36891](https://gitlab.com/gitlab-org/gitlab/-/issues/36891) | diff --git a/doc/user/packages/workflows/build_packages.md b/doc/user/packages/workflows/build_packages.md index ec971195ea9..eab1e4392e3 100644 --- a/doc/user/packages/workflows/build_packages.md +++ b/doc/user/packages/workflows/build_packages.md @@ -302,7 +302,7 @@ The npm version is shown in the output: ``` 1. Enter responses to the questions. Ensure the **package name** follows - the [naming convention](../npm_registry/index.md#package-naming-convention) and is scoped to the project or group where the registry exists. + the [naming convention](../npm_registry/index.md#naming-convention) and is scoped to the project or group where the registry exists. ## Yarn @@ -334,7 +334,7 @@ The Yarn version is shown in the output: ``` 1. Enter responses to the questions. Ensure the **package name** follows - the [naming convention](../npm_registry/index.md#package-naming-convention) and is scoped to the + the [naming convention](../npm_registry/index.md#naming-convention) and is scoped to the project or group where the registry exists. A `package.json` file is created. diff --git a/doc/user/packages/workflows/project_registry.md b/doc/user/packages/workflows/project_registry.md index df4b087f6d5..9e6c2877ca5 100644 --- a/doc/user/packages/workflows/project_registry.md +++ b/doc/user/packages/workflows/project_registry.md @@ -50,9 +50,9 @@ If you're using npm, create an `.npmrc` file. Add the appropriate URL for publis packages to your project. Finally, add a section to your `package.json` file. Follow the instructions in the -[GitLab Package Registry npm documentation](../npm_registry/index.md#authenticate-to-the-package-registry). After +[GitLab Package Registry npm documentation](../npm_registry/index.md#authentication-to-the-package-registry). After you do this, you can publish your npm package to your project using `npm publish`, as described in the -[publishing packages](../npm_registry/index.md#publish-an-npm-package) section. +[publishing packages](../npm_registry/index.md#publish-to-gitlab-package-registry) section. ### Maven diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 13a8e981934..45f84eee4a7 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -1,7 +1,7 @@ --- stage: Manage group: Workspace -info: "To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments" +info: 'To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments' type: reference, index, howto --- @@ -75,41 +75,44 @@ To configure visibility, features, and permissions for a project: Use the toggles to enable or disable features in the project. -| Option | More access limit options | Description | -|:---------------------------------|:--------------------------|:--------------| -| **Issues** | ✓ | Activates the GitLab issues tracker. | -| **Repository** | ✓ | Enables [repository](../repository/index.md) functionality | +| Option | More access limit options | Description | +| :------------------------------- | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------- | +| **Issues** | ✓ | Activates the GitLab issues tracker. | +| **Repository** | ✓ | Enables [repository](../repository/index.md) functionality | | **Merge requests** | ✓ | Enables [merge request](../merge_requests/index.md) functionality; also see [Merge request settings](#configure-merge-request-settings-for-a-project). | -| **Forks** | ✓ | Enables [forking](../repository/forking_workflow.md) functionality. | -| **Git Large File Storage (LFS)** | | Enables the use of [large files](../../../topics/git/lfs/index.md#git-large-file-storage-lfs). | -| **Packages** | | Supports configuration of a [package registry](../../../administration/packages/index.md#gitlab-package-registry-administration) functionality. | -| **CI/CD** | ✓ | Enables [CI/CD](../../../ci/index.md) functionality. | -| **Container Registry** | | Activates a [registry](../../packages/container_registry/index.md) for your Docker images. | -| **Analytics** | ✓ | Enables [analytics](../../analytics/index.md). | -| **Requirements** | ✓ | Control access to [Requirements Management](../requirements/index.md). | -| **Security & Compliance** | ✓ | Control access to [security features](../../application_security/index.md). | -| **Wiki** | ✓ | Enables a separate system for [documentation](../wiki/index.md). | -| **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md). | -| **Pages** | ✓ | Allows you to [publish static websites](../pages/index.md). | -| **Metrics Dashboard** | ✓ | Control access to [metrics dashboard](../integrations/prometheus.md). | -| **Releases** | ✓ | Control access to [Releases](../releases/index.md). | +| **Forks** | ✓ | Enables [forking](../repository/forking_workflow.md) functionality. | +| **Git Large File Storage (LFS)** | | Enables the use of [large files](../../../topics/git/lfs/index.md#git-large-file-storage-lfs). | +| **Packages** | | Supports configuration of a [package registry](../../../administration/packages/index.md#gitlab-package-registry-administration) functionality. | +| **CI/CD** | ✓ | Enables [CI/CD](../../../ci/index.md) functionality. | +| **Container Registry** | | Activates a [registry](../../packages/container_registry/index.md) for your Docker images. | +| **Analytics** | ✓ | Enables [analytics](../../analytics/index.md). | +| **Requirements** | ✓ | Control access to [Requirements Management](../requirements/index.md). | +| **Security & Compliance** | ✓ | Control access to [security features](../../application_security/index.md). | +| **Wiki** | ✓ | Enables a separate system for [documentation](../wiki/index.md). | +| **Snippets** | ✓ | Enables [sharing of code and text](../../snippets.md). | +| **Pages** | ✓ | Allows you to [publish static websites](../pages/index.md). | +| **Metrics Dashboard** | ✓ | Control access to [metrics dashboard](../integrations/prometheus.md). | +| **Releases** | ✓ | Control access to [Releases](../releases/index.md). | | **Environments** | ✓ | Control access to [Environments and Deployments](../../../ci/environments/index.md). | | **Feature flags** | ✓ | Control access to [Feature Flags](../../../operations/feature_flags.md). | -| **Monitor** | ✓ | Control access to [Monitor](../../../operations/index.md) features. | -| **Infrastructure** | ✓ | Control access to [Infrastructure](../../infrastructure/index.md) features. | +| **Monitor** | ✓ | Control access to [Monitor](../../../operations/index.md) features. | +| **Infrastructure** | ✓ | Control access to [Infrastructure](../../infrastructure/index.md) features. | When you disable a feature, the following additional features are also disabled: - If you disable the **Issues** feature, project users cannot use: + - **Issue Boards** - **Service Desk** - Project users can still access **Milestones** from merge requests. - If you disable **Issues** and **Merge Requests**, project users cannot use: + - **Labels** - **Milestones** - If you disable **Repository**, project users cannot access: + - **Merge requests** - **CI/CD** - **Container Registry** @@ -237,9 +240,7 @@ Prerequisites: - You must be the Owner of the project you transfer. - The group must allow creation of new projects. - The project must not contain any [container images](../../packages/container_registry/index.md#known-issues). - - If you transfer a project to a different root namespace, - the project must not contain any - [NPM packages](../../packages/npm_registry/index.md#limitations). +- Remove any npm packages. If you transfer a project to a different root namespace, the project must not contain any npm packages. When you update the path of a user or group, or transfer a subgroup or project, you must remove any npm packages first. You cannot update the root namespace of a project with npm packages. Make sure you update your .npmrc files to follow the naming convention and run npm publish if necessary. To transfer a project: @@ -262,7 +263,7 @@ When you transfer a project from a namespace licensed for GitLab SaaS Premium or - [Project access tokens](../../../user/project/settings/project_access_tokens.md) are revoked - [Pipeline subscriptions](../../../ci/pipelines/index.md#trigger-a-pipeline-when-an-upstream-project-is-rebuilt) -and [test cases](../../../ci/test_cases/index.md) are deleted. + and [test cases](../../../ci/test_cases/index.md) are deleted. ## Delete a project diff --git a/jest.config.base.js b/jest.config.base.js index 30e11122f81..56473d1643f 100644 --- a/jest.config.base.js +++ b/jest.config.base.js @@ -63,6 +63,7 @@ module.exports = (path, options = {}) => { '^jest/(.*)$': '<rootDir>/spec/frontend/$1', '^ee_else_ce_jest/(.*)$': '<rootDir>/spec/frontend/$1', '^jquery$': '<rootDir>/node_modules/jquery/dist/jquery.slim.js', + '^@sentry/browser$': '<rootDir>/app/assets/javascripts/sentry/sentry_browser_wrapper.js', ...extModuleNameMapper, }; diff --git a/lib/api/api.rb b/lib/api/api.rb index 9952941c9c6..8611910406a 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -184,6 +184,7 @@ module API mount ::API::BroadcastMessages mount ::API::BulkImports mount ::API::Ci::JobArtifacts + mount ::API::Groups mount ::API::Ci::Jobs mount ::API::Ci::ResourceGroups mount ::API::Ci::Runner @@ -198,6 +199,7 @@ module API mount ::API::Commits mount ::API::CommitStatuses mount ::API::ContainerRegistryEvent + mount ::API::ContainerRepositories mount ::API::DependencyProxy mount ::API::DeployKeys mount ::API::DeployTokens @@ -230,6 +232,7 @@ module API mount ::API::Keys mount ::API::Lint mount ::API::Markdown + mount ::API::Members mount ::API::MergeRequestApprovals mount ::API::MergeRequests mount ::API::MergeRequestDiffs @@ -294,7 +297,6 @@ module API mount ::API::ComposerPackages mount ::API::ConanInstancePackages mount ::API::ConanProjectPackages - mount ::API::ContainerRepositories mount ::API::DebianGroupPackages mount ::API::DebianProjectPackages mount ::API::Discussions @@ -304,11 +306,9 @@ module API mount ::API::GroupDebianDistributions mount ::API::GroupLabels mount ::API::GroupMilestones - mount ::API::Groups mount ::API::Issues mount ::API::Labels mount ::API::MavenPackages - mount ::API::Members mount ::API::Notes mount ::API::NotificationSettings mount ::API::NpmInstancePackages diff --git a/lib/api/container_repositories.rb b/lib/api/container_repositories.rb index f2dd1fa21fd..b6b5fe10332 100644 --- a/lib/api/container_repositories.rb +++ b/lib/api/container_repositories.rb @@ -14,12 +14,17 @@ module API namespace 'registry' do params do - requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project' + requires :id, types: [String, Integer], desc: 'The ID of the repository' end resource :repositories, requirements: { id: /[0-9]*/ } do desc 'Get a container repository' do detail 'This feature was introduced in GitLab 13.6.' success Entities::ContainerRegistry::Repository + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 404, message: 'Repository Not Found' } + ] + tags %w[container_registry] end params do optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included' diff --git a/lib/api/groups.rb b/lib/api/groups.rb index cb67848247e..f9842c01db2 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -195,6 +195,8 @@ module API desc 'Get a groups list' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -207,6 +209,7 @@ module API desc 'Create a group. Available only for users who can create groups.' do success Entities::Group + tags %w[groups] end params do requires :name, type: String, desc: 'The name of the group' @@ -240,6 +243,7 @@ module API resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Update a group. Available only for users who can administrate groups.' do success Entities::Group + tags %w[groups] end params do optional :name, type: String, desc: 'The name of the group' @@ -265,6 +269,7 @@ module API desc 'Get a single group, with containing projects.' do success Entities::GroupDetail + tags %w[groups] end params do use :with_custom_attributes @@ -278,7 +283,9 @@ module API present_group_details(params, group, with_projects: params[:with_projects]) end - desc 'Remove a group.' + desc 'Remove a group.' do + tags %w[groups] + end delete ":id", feature_category: :subgroups, urgency: :low do group = find_group!(params[:id]) authorize! :admin_group, group @@ -289,6 +296,8 @@ module API desc 'Get a list of projects in this group.' do success Entities::Project + is_array true + tags %w[groups] end params do optional :archived, type: Boolean, desc: 'Limit by archived status' @@ -329,6 +338,8 @@ module API desc 'Get a list of shared projects in this group' do success Entities::Project + is_array true + tags %w[groups] end params do optional :archived, type: Boolean, desc: 'Limit by archived status' @@ -357,6 +368,8 @@ module API desc 'Get a list of subgroups in this group.' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -369,6 +382,8 @@ module API desc 'Get a list of descendant groups of this group.' do success Entities::Group + is_array true + tags %w[groups] end params do use :group_list_params @@ -382,6 +397,7 @@ module API desc 'Transfer a project to the group namespace. Available only for admin.' do success Entities::GroupDetail + tags %w[groups] end params do requires :project_id, type: String, desc: 'The ID or path of the project' @@ -400,7 +416,11 @@ module API end end - desc 'Get the groups to where the current group can be transferred to' + desc 'Get the groups to where the current group can be transferred to' do + success Entities::Group + is_array true + tags %w[groups] + end params do optional :search, type: String, desc: 'Return list of namespaces matching the search criteria' use :pagination @@ -415,7 +435,9 @@ module API present_groups params, groups, serializer: Entities::PublicGroupDetails end - desc 'Transfer a group to a new parent group or promote a subgroup to a root group' + desc 'Transfer a group to a new parent group or promote a subgroup to a root group' do + tags %w[groups] + end params do optional :group_id, type: Integer, @@ -440,6 +462,7 @@ module API desc 'Share a group with a group' do success Entities::GroupDetail + tags %w[groups] end params do requires :group_id, type: Integer, desc: 'The ID of the group to share' diff --git a/lib/api/members.rb b/lib/api/members.rb index f4e38207aca..76f4364106b 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -20,6 +20,8 @@ module API resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do desc 'Gets a list of group or project members viewable by the authenticated user.' do success Entities::Member + is_array true + tags %w[members] end params do optional :query, type: String, desc: 'A query string to search for members' @@ -42,6 +44,8 @@ module API desc 'Gets a list of group or project members viewable by the authenticated user, including those who gained membership through ancestor group.' do success Entities::Member + is_array true + tags %w[members] end params do optional :query, type: String, desc: 'A query string to search for members' @@ -63,6 +67,7 @@ module API desc 'Gets a member of a group or project.' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the member' @@ -82,6 +87,7 @@ module API desc 'Gets a member of a group or project, including those who gained membership through ancestor group' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the member' @@ -101,6 +107,7 @@ module API desc 'Adds a member to a group or project.' do success Entities::Member + tags %w[members] end params do requires :access_level, type: Integer, desc: 'A valid access level (defaults: `30`, developer access level)' @@ -126,6 +133,7 @@ module API desc 'Updates a member of a group or project.' do success Entities::Member + tags %w[members] end params do requires :user_id, type: Integer, desc: 'The user ID of the new member' @@ -153,7 +161,9 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Removes a user from a group or project.' + desc 'Removes a user from a group or project.' do + tags %w[members] + end params do requires :user_id, type: Integer, desc: 'The user ID of the member' optional :skip_subresources, type: Boolean, default: false, diff --git a/lib/gitlab/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb index 29e8e631fb7..8b1298d0561 100644 --- a/lib/gitlab/content_security_policy/config_loader.rb +++ b/lib/gitlab/content_security_policy/config_loader.rb @@ -43,7 +43,10 @@ module Gitlab allow_websocket_connections(directives) allow_cdn(directives, Settings.gitlab.cdn_host) if Settings.gitlab.cdn_host.present? - allow_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn + # Support for Sentry setup via configuration files will be removed in 16.0 + # in favor of Gitlab::CurrentSettings. + allow_legacy_sentry(directives) if Gitlab.config.sentry&.enabled && Gitlab.config.sentry&.clientside_dsn + allow_sentry(directives) if Gitlab::CurrentSettings.try(:sentry_enabled) && Gitlab::CurrentSettings.try(:sentry_clientside_dsn) allow_framed_gitlab_paths(directives) allow_customersdot(directives) if ENV['CUSTOMER_PORTAL_URL'].present? allow_review_apps(directives) if ENV['REVIEW_APPS_ENABLED'] @@ -135,13 +138,22 @@ module Gitlab append_to_directive(directives, 'frame_src', customersdot_host) end - def self.allow_sentry(directives) + def self.allow_legacy_sentry(directives) + # Support for Sentry setup via configuration files will be removed in 16.0 + # in favor of Gitlab::CurrentSettings. sentry_dsn = Gitlab.config.sentry.clientside_dsn sentry_uri = URI(sentry_dsn) append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}") end + def self.allow_sentry(directives) + sentry_dsn = Gitlab::CurrentSettings.sentry_clientside_dsn + sentry_uri = URI(sentry_dsn) + + append_to_directive(directives, 'connect_src', "#{sentry_uri.scheme}://#{sentry_uri.host}") + end + def self.allow_letter_opener(directives) append_to_directive(directives, 'frame_src', Gitlab::Utils.append_path(Gitlab.config.gitlab.url, '/rails/letter_opener/')) end diff --git a/lib/gitlab/memory/jemalloc.rb b/lib/gitlab/memory/jemalloc.rb index 81c4be0f7fc..6025e6ab6f2 100644 --- a/lib/gitlab/memory/jemalloc.rb +++ b/lib/gitlab/memory/jemalloc.rb @@ -14,41 +14,22 @@ module Gitlab STATS_DEFAULT_FORMAT = :json - FILENAME_PREFIX = 'jemalloc_stats' - # Return jemalloc stats as a string. def stats(format: STATS_DEFAULT_FORMAT) - verify_format!(format) - - with_malloc_stats_print do |stats_print| - StringIO.new.tap { |io| write_stats(stats_print, io, STATS_FORMATS[format]) }.string - end + dump_stats(StringIO.new, format: format).string end - # Write jemalloc stats to the given directory - # @param [String] path Directory path the dump will be put into - # @param [String] tmp_dir Directory path the dump will be streaming to. It is moved to `path` when finished. - # @param [String] format `json` or `txt` - # @param [String] filename_label Optional custom string that will be injected into the file name, e.g. `worker_0` - # @return [String] Full path to the resulting dump file - def dump_stats(path:, tmp_dir: Dir.tmpdir, format: STATS_DEFAULT_FORMAT, filename_label: nil) + # Streams jemalloc stats to the given IO object. + def dump_stats(io, format: STATS_DEFAULT_FORMAT) verify_format!(format) format_settings = STATS_FORMATS[format] - tmp_file_path = File.join(tmp_dir, file_name(format_settings[:extension], filename_label)) - file_path = File.join(path, file_name(format_settings[:extension], filename_label)) with_malloc_stats_print do |stats_print| - File.open(tmp_file_path, 'wb') do |io| - write_stats(stats_print, io, format_settings) - end + write_stats(stats_print, io, format_settings) end - # On OSX, `with_malloc_stats_print` is no-op, and, as result, no file will be written - return unless File.exist?(tmp_file_path) - - FileUtils.mv(tmp_file_path, file_path) - file_path + io end private @@ -95,12 +76,6 @@ module Gitlab stats_print.call(callback, nil, format[:options]) end - - def file_name(extension, filename_label) - timestamp = Time.current.strftime('%Y-%m-%d.%H:%M:%S:%L') - - [FILENAME_PREFIX, timestamp, filename_label, extension].reject(&:blank?).join('.') - end end end end diff --git a/lib/gitlab/memory/reporter.rb b/lib/gitlab/memory/reporter.rb index b4f3f950b95..91956cb1ce2 100644 --- a/lib/gitlab/memory/reporter.rb +++ b/lib/gitlab/memory/reporter.rb @@ -3,47 +3,91 @@ module Gitlab module Memory class Reporter - def initialize + attr_reader :reports_path + + def initialize(reports_path: nil, logger: Gitlab::AppLogger) + @reports_path = reports_path || ENV["GITLAB_DIAGNOSTIC_REPORTS_PATH"] || Dir.mktmpdir + @logger = logger + + @worker_id = ::Prometheus::PidProvider.worker_id @worker_uuid = SecureRandom.uuid init_prometheus_metrics end def run_report(report) + @logger.info( + log_labels( + message: 'started', + perf_report: report.name + )) + start_monotonic_time = Gitlab::Metrics::System.monotonic_time start_thread_cpu_time = Gitlab::Metrics::System.thread_cpu_time - file_path = report.run(report_id) + report_file = store_report(report) cpu_s = Gitlab::Metrics::System.thread_cpu_duration(start_thread_cpu_time) duration_s = Gitlab::Metrics::System.monotonic_time - start_monotonic_time - log_report(name: report.name, cpu_s: cpu_s, duration_s: duration_s, size: file_size(file_path)) + @logger.info( + log_labels( + message: 'finished', + perf_report: report.name, + cpu_s: cpu_s.round(2), + duration_s: duration_s.round(2), + perf_report_file: report_file, + perf_report_size_bytes: file_size(report_file) + )) @report_duration_counter.increment({ report: report.name }, duration_s) + + true + rescue StandardError => e + @logger.error( + log_labels( + message: 'failed', + perf_report: report.name, + error: e.inspect + )) + + false end private - def log_report(name:, duration_s:, cpu_s:, size:) - Gitlab::AppLogger.info( - message: 'finished', + def store_report(report) + # Store report in tmp subdir while it is still streaming. + # This will clearly separate finished reports from the files we are still writing to. + tmp_dir = File.join(@reports_path, 'tmp') + FileUtils.mkdir_p(tmp_dir) + + report_file = file_name(report) + tmp_file_path = File.join(tmp_dir, report_file) + + File.open(tmp_file_path, 'wb') do |io| + report.run(io) + end + + File.join(@reports_path, report_file).tap do |report_file_path| + FileUtils.mv(tmp_file_path, report_file_path) + end + end + + def log_labels(**extra_labels) + { pid: $$, - worker_id: worker_id, - perf_report: name, - duration_s: duration_s.round(2), - cpu_s: cpu_s.round(2), - perf_report_size_bytes: size, + worker_id: @worker_id, perf_report_worker_uuid: @worker_uuid - ) + }.merge(extra_labels) end - def report_id - [worker_id, @worker_uuid].join(".") - end + def file_name(report) + timestamp = Time.current.strftime('%Y-%m-%d.%H:%M:%S:%L') + + report_id = [@worker_id, @worker_uuid].join(".") - def worker_id - ::Prometheus::PidProvider.worker_id + [report.name, timestamp, report_id].reject(&:blank?).join('.') end def file_size(file_path) @@ -53,7 +97,7 @@ module Gitlab end def init_prometheus_metrics - default_labels = { pid: worker_id } + default_labels = { pid: @worker_id } @report_duration_counter = Gitlab::Metrics.counter( :gitlab_diag_report_duration_seconds_total, diff --git a/lib/gitlab/memory/reports/heap_dump.rb b/lib/gitlab/memory/reports/heap_dump.rb index b81464ed5cf..07b2b94285b 100644 --- a/lib/gitlab/memory/reports/heap_dump.rb +++ b/lib/gitlab/memory/reports/heap_dump.rb @@ -6,38 +6,24 @@ module Gitlab class HeapDump class << self def enqueue! - log_event('enqueue') @write_heap_dump = true end - # This is a no-op currently and will be implemented at a later time in - # https://gitlab.com/gitlab-org/gitlab/-/issues/370077 - def write_conditionally - return false unless enqueued? - - log_event('write') - - true - end - - private - def enqueued? !!@write_heap_dump end + end - def log_event(message) - Gitlab::AppLogger.info( - message: message, - pid: $$, - worker_id: worker_id, - perf_report: 'heap_dump' - ) - end + def name + 'heap_dump' + end - def worker_id - ::Prometheus::PidProvider.worker_id - end + # This is a no-op currently and will be implemented at a later time in + # https://gitlab.com/gitlab-org/gitlab/-/issues/370077 + def run(writer) + return false unless self.class.enqueued? + + true end end end diff --git a/lib/gitlab/memory/reports/jemalloc_stats.rb b/lib/gitlab/memory/reports/jemalloc_stats.rb index 2e5a053031e..cfda409594f 100644 --- a/lib/gitlab/memory/reports/jemalloc_stats.rb +++ b/lib/gitlab/memory/reports/jemalloc_stats.rb @@ -4,72 +4,19 @@ module Gitlab module Memory module Reports class JemallocStats - # On prod, Jemalloc reports sizes were ~2.5 MB: - # https://gitlab.com/gitlab-com/gl-infra/reliability/-/issues/15993#note_1014767214 - # We configured 1GB emptyDir per pod: - # https://gitlab.com/gitlab-com/gl-infra/k8s-workloads/gitlab-com/-/merge_requests/1949 - # The pod will be evicted when the size limit is exceeded. We never want this to happen, for availability. - # - # With the default, we have a headroom (250*2.5MB=625<1000 MB) to fit into configured emptyDir. - # It would allow us to keep 3+ days worth of reports for 6 workers running every 2 hours: 3*6*12=216<250 - # - # The cleanup logic will be redundant after we'll implement the uploads, which would perform the cleanup. - DEFAULT_MAX_REPORTS_STORED = 250 - - def initialize(reports_path:) - @reports_path = reports_path - - # Store report in tmp subdir while it is still streaming. - # This will clearly separate finished reports from the files we are still writing to. - @tmp_dir = File.join(@reports_path, 'tmp') - FileUtils.mkdir_p(@tmp_dir) - end - def name 'jemalloc_stats' end - def run(report_id) + def run(writer) return unless active? - Gitlab::Memory::Jemalloc.dump_stats(path: reports_path, - tmp_dir: @tmp_dir, - filename_label: report_id).tap do - cleanup - end + Gitlab::Memory::Jemalloc.dump_stats(writer) end def active? Feature.enabled?(:report_jemalloc_stats, type: :ops) end - - private - - attr_reader :reports_path - - def cleanup - reports_files_modified_order[0...-max_reports_stored].each do |f| - File.unlink(f) if File.exist?(f) - rescue Errno::ENOENT - # Path does not exist: Ignore. We already check `File.exist?` - # Rescue to be extra safe, because each worker could perform a cleanup - end - end - - def reports_files_modified_order - pattern = File.join(reports_path, "#{Gitlab::Memory::Jemalloc::FILENAME_PREFIX}*") - - Dir.glob(pattern).sort_by do |f| - test('M', f) - rescue Errno::ENOENT - # Path does not exist: Return any timestamp to proceed with the sort - Time.current - end - end - - def max_reports_stored - ENV["GITLAB_DIAGNOSTIC_REPORTS_JEMALLOC_MAX_REPORTS_STORED"] || DEFAULT_MAX_REPORTS_STORED - end end end end diff --git a/lib/gitlab/memory/reports_daemon.rb b/lib/gitlab/memory/reports_daemon.rb index 9a044dbd06c..9bbfe81116d 100644 --- a/lib/gitlab/memory/reports_daemon.rb +++ b/lib/gitlab/memory/reports_daemon.rb @@ -7,9 +7,7 @@ module Gitlab DEFAULT_SLEEP_MAX_DELTA_S = 600 # 0..10 minutes DEFAULT_SLEEP_BETWEEN_REPORTS_S = 120 # 2 minutes - DEFAULT_REPORTS_PATH = Dir.tmpdir - - def initialize(reporter: Reporter.new, reports: nil, **options) + def initialize(reporter: nil, reports: nil, **options) super @alive = true @@ -21,16 +19,13 @@ module Gitlab @sleep_between_reports_s = ENV['GITLAB_DIAGNOSTIC_REPORTS_SLEEP_BETWEEN_REPORTS_S']&.to_i || DEFAULT_SLEEP_BETWEEN_REPORTS_S - @reports_path = - ENV["GITLAB_DIAGNOSTIC_REPORTS_PATH"] || DEFAULT_REPORTS_PATH - - @reporter = reporter + @reporter = reporter || Reporter.new @reports = reports || [ - Gitlab::Memory::Reports::JemallocStats.new(reports_path: reports_path) + Gitlab::Memory::Reports::JemallocStats.new ] end - attr_reader :sleep_s, :sleep_max_delta_s, :sleep_between_reports_s, :reports_path + attr_reader :sleep_s, :sleep_max_delta_s, :sleep_between_reports_s def run_thread while alive diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 6d7ecb53ec3..e99761a0459 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -20,6 +20,10 @@ module Gitlab status.to_i.between?(200, 499) end + def self.server_error?(status) + status.to_i >= 500 + end + # Tracks an event. # # See `Gitlab::Metrics::Transaction#add_event` for more details. diff --git a/lib/gitlab/metrics/rails_slis.rb b/lib/gitlab/metrics/rails_slis.rb index 71da0085c8c..9fd4eec479e 100644 --- a/lib/gitlab/metrics/rails_slis.rb +++ b/lib/gitlab/metrics/rails_slis.rb @@ -6,6 +6,7 @@ module Gitlab class << self def initialize_request_slis! Gitlab::Metrics::Sli::Apdex.initialize_sli(:rails_request, possible_request_labels) + initialize_rails_request_error_rate Gitlab::Metrics::Sli::Apdex.initialize_sli(:graphql_query, possible_graphql_query_labels) end @@ -13,6 +14,10 @@ module Gitlab Gitlab::Metrics::Sli::Apdex[:rails_request] end + def request_error_rate + Gitlab::Metrics::Sli::ErrorRate[:rails_request] + end + def graphql_query_apdex Gitlab::Metrics::Sli::Apdex[:graphql_query] end @@ -58,6 +63,12 @@ module Gitlab } end end + + def initialize_rails_request_error_rate + return unless Feature.enabled?(:gitlab_metrics_error_rate_sli, type: :development) + + Gitlab::Metrics::Sli::ErrorRate.initialize_sli(:rails_request, possible_request_labels) + end end end end diff --git a/lib/gitlab/metrics/requests_rack_middleware.rb b/lib/gitlab/metrics/requests_rack_middleware.rb index d7fe983c553..d26361ca12f 100644 --- a/lib/gitlab/metrics/requests_rack_middleware.rb +++ b/lib/gitlab/metrics/requests_rack_middleware.rb @@ -75,15 +75,16 @@ module Gitlab begin status, headers, body = @app.call(env) + return [status, headers, body] if health_endpoint - elapsed = ::Gitlab::Metrics::System.monotonic_time - started - - if !health_endpoint && ::Gitlab::Metrics.record_duration_for_status?(status) + if ::Gitlab::Metrics.record_duration_for_status?(status) + elapsed = ::Gitlab::Metrics::System.monotonic_time - started self.class.http_request_duration_seconds.observe({ method: method }, elapsed) - record_apdex(env, elapsed) end + record_error(env, status) + [status, headers, body] rescue StandardError self.class.rack_uncaught_errors_count.increment @@ -124,6 +125,15 @@ module Gitlab ) end + def record_error(env, status) + return unless Feature.enabled?(:gitlab_metrics_error_rate_sli, type: :development) + + Gitlab::Metrics::RailsSlis.request_error_rate.increment( + labels: labels_from_context, + error: ::Gitlab::Metrics.server_error?(status) + ) + end + def labels_from_context { feature_category: feature_category.presence || FEATURE_CATEGORY_DEFAULT, diff --git a/lib/gitlab/slash_commands/deploy.rb b/lib/gitlab/slash_commands/deploy.rb index 9fcefd99f81..16a4875be91 100644 --- a/lib/gitlab/slash_commands/deploy.rb +++ b/lib/gitlab/slash_commands/deploy.rb @@ -54,7 +54,7 @@ module Gitlab return unless environment actions = environment.actions_for(to).select do |action| - action.starts_environment? + action.deployment_job? end if actions.many? diff --git a/package.json b/package.json index a85dd77a0ad..5f007de92b0 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "@gitlab/web-ide": "0.0.1-dev-20221114183058", "@rails/actioncable": "6.1.4-7", "@rails/ujs": "6.1.4-7", - "@sentry/browser": "5.30.0", "@sourcegraph/code-host-integration": "0.0.84", "@tiptap/core": "^2.0.0-beta.182", "@tiptap/extension-blockquote": "^2.0.0-beta.29", @@ -173,6 +172,8 @@ "remark-rehype": "^10.1.0", "scrollparent": "^2.0.1", "select2": "3.5.2-browserify", + "sentrybrowser5": "npm:@sentry/browser@5.30.0", + "sentrybrowser7": "npm:@sentry/browser@^7.21.1", "sortablejs": "^1.10.2", "string-hash": "1.1.3", "style-loader": "^2.0.0", diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index e01382cf31f..03680f681ba 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -1002,7 +1002,7 @@ RSpec.describe 'File blob', :js do end it 'renders sandboxed iframe' do - expected = %(<iframe src="/-/sandbox/swagger" sandbox="allow-scripts allow-popups" frameborder="0" width="100%" height="1000">) + expected = %(<iframe src="/-/sandbox/swagger" sandbox="allow-scripts allow-popups allow-forms" frameborder="0" width="100%" height="1000">) expect(page.html).to include(expected) end end diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb index 8070ae066e7..de81444ed71 100644 --- a/spec/features/security/admin_access_spec.rb +++ b/spec/features/security/admin_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Admin::Projects" do +RSpec.describe "Admin::Projects", feature_category: :permissions do include AccessMatchers describe "GET /admin/projects" do diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 5430329d47d..948a4567624 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Dashboard access" do +RSpec.describe "Dashboard access", feature_category: :permissions do include AccessMatchers describe "GET /dashboard" do diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb index 755f170a93e..904431b4a0f 100644 --- a/spec/features/security/group/internal_access_spec.rb +++ b/spec/features/security/group/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Internal Group access' do +RSpec.describe 'Internal Group access', feature_category: :permissions do include AccessMatchers let(:group) { create(:group, :internal) } diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb index f733145b5e3..3d56468a1c9 100644 --- a/spec/features/security/group/private_access_spec.rb +++ b/spec/features/security/group/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Private Group access' do +RSpec.describe 'Private Group access', feature_category: :permissions do include AccessMatchers let(:group) { create(:group, :private) } diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb index 90de2b58044..ac6b8a8ddd1 100644 --- a/spec/features/security/group/public_access_spec.rb +++ b/spec/features/security/group/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Public Group access' do +RSpec.describe 'Public Group access', feature_category: :permissions do include AccessMatchers let(:group) { create(:group, :public) } diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 301efd2d99b..991ff115d3d 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Profile access" do +RSpec.describe "Profile access", feature_category: :user_management do include AccessMatchers describe "GET /-/profile/keys" do diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index 48cee4b1f19..e35e7ed742b 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Internal Project Access" do +RSpec.describe "Internal Project Access", feature_category: :permissions do include AccessMatchers let_it_be(:project, reload: true) { create(:project, :internal, :repository, :with_namespace_settings) } diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index c06b1e5da54..59ddb18ae8a 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Private Project Access" do +RSpec.describe "Private Project Access", feature_category: :permissions do include AccessMatchers let_it_be(:project, reload: true) do diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index d2112430638..425691001f2 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Public Project Access" do +RSpec.describe "Public Project Access", feature_category: :permissions do include AccessMatchers let_it_be(:project, reload: true) do diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb index ab080f0a460..b7dcc5f31d3 100644 --- a/spec/features/security/project/snippet/internal_access_spec.rb +++ b/spec/features/security/project/snippet/internal_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Internal Project Snippets Access" do +RSpec.describe "Internal Project Snippets Access", feature_category: :permissions do include AccessMatchers let_it_be(:project) { create(:project, :internal) } diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb index 1e0afc09b74..0ae45abb7ec 100644 --- a/spec/features/security/project/snippet/private_access_spec.rb +++ b/spec/features/security/project/snippet/private_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Private Project Snippets Access" do +RSpec.describe "Private Project Snippets Access", feature_category: :permissions do include AccessMatchers let_it_be(:project) { create(:project, :private) } diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb index f734f7ba9e2..b98f665c0dc 100644 --- a/spec/features/security/project/snippet/public_access_spec.rb +++ b/spec/features/security/project/snippet/public_access_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe "Public Project Snippets Access" do +RSpec.describe "Public Project Snippets Access", feature_category: :permissions do include AccessMatchers let_it_be(:project) { create(:project, :public) } diff --git a/spec/features/sentry_js_spec.rb b/spec/features/sentry_js_spec.rb index 1d277ba7b3c..2e46dc459ec 100644 --- a/spec/features/sentry_js_spec.rb +++ b/spec/features/sentry_js_spec.rb @@ -3,26 +3,61 @@ require 'spec_helper' RSpec.describe 'Sentry' do - let(:sentry_regex_path) { '\/sentry.*\.chunk\.js' } + context 'when enable_new_sentry_clientside_integration is disabled' do + before do + stub_feature_flags(enable_new_sentry_clientside_integration: false) + end + + it 'does not load sentry if sentry is disabled' do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) + + visit new_user_session_path + + expect(has_requested_legacy_sentry).to eq(false) + end - it 'does not load sentry if sentry is disabled' do - allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) - visit new_user_session_path + it 'loads legacy sentry if sentry config is enabled', :js do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) - expect(has_requested_sentry).to eq(false) + visit new_user_session_path + + expect(has_requested_legacy_sentry).to eq(true) + expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^5\.}) + end end - it 'loads sentry if sentry is enabled' do - stub_sentry_settings + context 'when enable_new_sentry_clientside_integration is enabled' do + before do + stub_feature_flags(enable_new_sentry_clientside_integration: true) + end + + it 'does not load sentry if sentry settings are disabled' do + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(false) - visit new_user_session_path + visit new_user_session_path - expect(has_requested_sentry).to eq(true) + expect(has_requested_sentry).to eq(false) + end + + it 'loads sentry if sentry settings are enabled', :js do + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true) + + visit new_user_session_path + + expect(has_requested_sentry).to eq(true) + expect(evaluate_script('window._Sentry.SDK_VERSION')).to match(%r{^7\.}) + end + end + + def has_requested_legacy_sentry + page.all('script', visible: false).one? do |elm| + elm[:src] =~ %r{/legacy_sentry.*\.chunk\.js\z} + end end def has_requested_sentry page.all('script', visible: false).one? do |elm| - elm[:src] =~ /#{sentry_regex_path}$/ + elm[:src] =~ %r{/sentry.*\.chunk\.js\z} end end end diff --git a/spec/features/snippets/embedded_snippet_spec.rb b/spec/features/snippets/embedded_snippet_spec.rb index 90d877d29b7..7bd61824494 100644 --- a/spec/features/snippets/embedded_snippet_spec.rb +++ b/spec/features/snippets/embedded_snippet_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Embedded Snippets' do +RSpec.describe 'Embedded Snippets', feature_category: :snippets do let_it_be(:snippet) { create(:personal_snippet, :public, :repository) } let(:blobs) { snippet.blobs.first(3) } diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb index b62c35bf96e..d0f4d888d94 100644 --- a/spec/features/snippets/explore_spec.rb +++ b/spec/features/snippets/explore_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Explore Snippets' do +RSpec.describe 'Explore Snippets', feature_category: :snippets do let!(:public_snippet) { create(:personal_snippet, :public) } let!(:internal_snippet) { create(:personal_snippet, :internal) } let!(:private_snippet) { create(:personal_snippet, :private) } diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb index 2fcd11c2a47..5169cc2a0f8 100644 --- a/spec/features/snippets/internal_snippet_spec.rb +++ b/spec/features/snippets/internal_snippet_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Internal Snippets', :js do +RSpec.describe 'Internal Snippets', :js, feature_category: :snippets do let(:internal_snippet) { create(:personal_snippet, :internal, :repository) } let(:content) { internal_snippet.blobs.first.data.strip! } diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb index 8d55a7a64f4..b9d5e56d432 100644 --- a/spec/features/snippets/notes_on_personal_snippets_spec.rb +++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Comments on personal snippets', :js do +RSpec.describe 'Comments on personal snippets', :js, feature_category: :snippets do include NoteInteractionHelpers include Spec::Support::Helpers::ModalHelpers diff --git a/spec/features/snippets/private_snippets_spec.rb b/spec/features/snippets/private_snippets_spec.rb index 7ff27419cf7..198ea11972d 100644 --- a/spec/features/snippets/private_snippets_spec.rb +++ b/spec/features/snippets/private_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Private Snippets', :js do +RSpec.describe 'Private Snippets', :js, feature_category: :snippets do let(:user) { create(:user) } let(:private_snippet) { create(:personal_snippet, :repository, :private, author: user) } let(:content) { private_snippet.blobs.first.data.strip! } diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb index 0f27d96d8e9..627a12aa765 100644 --- a/spec/features/snippets/public_snippets_spec.rb +++ b/spec/features/snippets/public_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Public Snippets', :js do +RSpec.describe 'Public Snippets', :js, feature_category: :snippets do let(:public_snippet) { create(:personal_snippet, :public, :repository) } let(:content) { public_snippet.blobs.first.data.strip! } diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb index d18729d080a..002fe05fcb0 100644 --- a/spec/features/snippets/search_snippets_spec.rb +++ b/spec/features/snippets/search_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Search Snippets', :js do +RSpec.describe 'Search Snippets', :js, feature_category: :snippets 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/show_spec.rb b/spec/features/snippets/show_spec.rb index 2103d362f94..6cce60fbef3 100644 --- a/spec/features/snippets/show_spec.rb +++ b/spec/features/snippets/show_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Snippet', :js do +RSpec.describe 'Snippet', :js, feature_category: :snippets do let_it_be(:user) { create(:user) } let_it_be(:snippet) { create(:personal_snippet, :public, :repository, author: user) } diff --git a/spec/features/snippets/spam_snippets_spec.rb b/spec/features/snippets/spam_snippets_spec.rb index 3748a916780..9e74df3ac05 100644 --- a/spec/features/snippets/spam_snippets_spec.rb +++ b/spec/features/snippets/spam_snippets_spec.rb @@ -2,7 +2,8 @@ require 'spec_helper' -RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/217722" do +RSpec.describe 'snippet editor with spam', skip: "Will be handled in https://gitlab.com/gitlab-org/gitlab/-/issues/217722", + feature_category: :snippets do include_context 'includes Spam constants' let_it_be(:user) { create(:user) } diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb index fd95516090a..4aa099e5737 100644 --- a/spec/features/snippets/user_creates_snippet_spec.rb +++ b/spec/features/snippets/user_creates_snippet_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User creates snippet', :js do +RSpec.describe 'User creates snippet', :js, feature_category: :snippets do include DropzoneHelper include Spec::Support::Helpers::Features::SnippetSpecHelpers diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb index e896f7eb25b..23436a8933c 100644 --- a/spec/features/snippets/user_deletes_snippet_spec.rb +++ b/spec/features/snippets/user_deletes_snippet_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User deletes snippet', :js do +RSpec.describe 'User deletes snippet', :js, feature_category: :snippets do let(:user) { create(:user) } let(:content) { 'puts "test"' } let(:snippet) { create(:personal_snippet, :repository, :public, content: content, author: user) } diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb index a04c59b53d2..561f1cce26b 100644 --- a/spec/features/snippets/user_edits_snippet_spec.rb +++ b/spec/features/snippets/user_edits_snippet_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User edits snippet', :js do +RSpec.describe 'User edits snippet', :js, feature_category: :snippets do include DropzoneHelper include Spec::Support::Helpers::Features::SnippetSpecHelpers diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb index bb733431b22..9839f8a472a 100644 --- a/spec/features/snippets/user_snippets_spec.rb +++ b/spec/features/snippets/user_snippets_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User Snippets' do +RSpec.describe 'User Snippets', feature_category: :snippets do let(:author) { create(:user) } let!(:public_snippet) { create(:personal_snippet, :public, author: author, title: "This is a public snippet") } let!(:internal_snippet) { create(:personal_snippet, :internal, author: author, title: "This is an internal snippet") } diff --git a/spec/features/tags/developer_creates_tag_spec.rb b/spec/features/tags/developer_creates_tag_spec.rb index 5657115fb3c..39d34a5ae64 100644 --- a/spec/features/tags/developer_creates_tag_spec.rb +++ b/spec/features/tags/developer_creates_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Developer creates tag' do +RSpec.describe 'Developer creates tag', feature_category: :source_code_management do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb index efd4b42c136..76cf3aa691d 100644 --- a/spec/features/tags/developer_deletes_tag_spec.rb +++ b/spec/features/tags/developer_deletes_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Developer deletes tag', :js do +RSpec.describe 'Developer deletes tag', :js, feature_category: :source_code_management do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb index 57e1f7da04e..0057ff1a5f3 100644 --- a/spec/features/tags/developer_views_tags_spec.rb +++ b/spec/features/tags/developer_views_tags_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Developer views tags' do +RSpec.describe 'Developer views tags', feature_category: :source_code_management do include RepoHelpers let(:user) { create(:user) } diff --git a/spec/features/tags/maintainer_deletes_protected_tag_spec.rb b/spec/features/tags/maintainer_deletes_protected_tag_spec.rb index 0bf9645c2fb..ce518b962cd 100644 --- a/spec/features/tags/maintainer_deletes_protected_tag_spec.rb +++ b/spec/features/tags/maintainer_deletes_protected_tag_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'Maintainer deletes protected tag', :js do +RSpec.describe 'Maintainer deletes protected tag', :js, feature_category: :source_code_management do let(:user) { create(:user) } let(:group) { create(:group) } let(:project) { create(:project, :repository, namespace: group) } diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb index 8daa869a6e3..78cede77fea 100644 --- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uploads avatar to group' do +RSpec.describe 'User uploads avatar to group', feature_category: :users do it 'they see the new avatar' do user = create(:user) group = create(:group) diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb index 02f9d57fcfe..fb62b5eadc5 100644 --- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb +++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uploads avatar to profile' do +RSpec.describe 'User uploads avatar to profile', feature_category: :users do let!(:user) { create(:user) } let(:avatar_file_path) { Rails.root.join('spec', 'fixtures', 'dk.png') } diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb index 2547e2d274c..e5ad62592ae 100644 --- a/spec/features/uploads/user_uploads_file_to_note_spec.rb +++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User uploads file to note' do +RSpec.describe 'User uploads file to note', feature_category: :team_planning do include DropzoneHelper let(:user) { create(:user) } diff --git a/spec/frontend/blob/openapi/index_spec.js b/spec/frontend/blob/openapi/index_spec.js index 17e718df495..d9d65258516 100644 --- a/spec/frontend/blob/openapi/index_spec.js +++ b/spec/frontend/blob/openapi/index_spec.js @@ -21,7 +21,7 @@ describe('OpenAPI blob viewer', () => { it('initializes SwaggerUI with the correct configuration', () => { expect(document.body.innerHTML).toContain( - '<iframe src="/-/sandbox/swagger" sandbox="allow-scripts allow-popups" frameborder="0" width="100%" height="1000"></iframe>', + '<iframe src="/-/sandbox/swagger" sandbox="allow-scripts allow-popups allow-forms" frameborder="0" width="100%" height="1000"></iframe>', ); }); }); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index a3f42c1f161..e8e705a6384 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -61,6 +61,10 @@ describe('Clusters', () => { let captureException; beforeEach(() => { + jest.spyOn(Sentry, 'withScope').mockImplementation((fn) => { + const mockScope = { setTag: () => {} }; + fn(mockScope); + }); captureException = jest.spyOn(Sentry, 'captureException'); mock = new MockAdapter(axios); diff --git a/spec/frontend/clusters_list/store/actions_spec.js b/spec/frontend/clusters_list/store/actions_spec.js index 09b1f80ff9b..1deebf8b75a 100644 --- a/spec/frontend/clusters_list/store/actions_spec.js +++ b/spec/frontend/clusters_list/store/actions_spec.js @@ -17,6 +17,10 @@ describe('Clusters store actions', () => { describe('reportSentryError', () => { beforeEach(() => { + jest.spyOn(Sentry, 'withScope').mockImplementation((fn) => { + const mockScope = { setTag: () => {} }; + fn(mockScope); + }); captureException = jest.spyOn(Sentry, 'captureException'); }); diff --git a/spec/frontend/sentry/index_spec.js b/spec/frontend/sentry/index_spec.js index d1f098112e8..2dd528a8a1c 100644 --- a/spec/frontend/sentry/index_spec.js +++ b/spec/frontend/sentry/index_spec.js @@ -1,17 +1,20 @@ import index from '~/sentry/index'; + +import LegacySentryConfig from '~/sentry/legacy_sentry_config'; import SentryConfig from '~/sentry/sentry_config'; -describe('SentryConfig options', () => { +describe('Sentry init', () => { + let originalGon; + const dsn = 'https://123@sentry.gitlab.test/123'; - const currentUserId = 'currentUserId'; - const gitlabUrl = 'gitlabUrl'; const environment = 'test'; + const currentUserId = '1'; + const gitlabUrl = 'gitlabUrl'; const revision = 'revision'; const featureCategory = 'my_feature_category'; - let indexReturnValue; - beforeEach(() => { + originalGon = window.gon; window.gon = { sentry_dsn: dsn, sentry_environment: environment, @@ -21,28 +24,41 @@ describe('SentryConfig options', () => { feature_category: featureCategory, }; - process.env.HEAD_COMMIT_SHA = revision; - + jest.spyOn(LegacySentryConfig, 'init').mockImplementation(); jest.spyOn(SentryConfig, 'init').mockImplementation(); + }); - indexReturnValue = index(); + afterEach(() => { + window.gon = originalGon; }); - it('should init with .sentryDsn, .currentUserId, .whitelistUrls and environment', () => { - expect(SentryConfig.init).toHaveBeenCalledWith({ - dsn, - currentUserId, - whitelistUrls: [gitlabUrl, 'webpack-internal://'], - environment, - release: revision, - tags: { - revision, - feature_category: featureCategory, - }, - }); + it('exports new version of Sentry in the global object', () => { + // eslint-disable-next-line no-underscore-dangle + expect(window._Sentry.SDK_VERSION).not.toMatch(/^5\./); }); - it('should return SentryConfig', () => { - expect(indexReturnValue).toBe(SentryConfig); + describe('when called', () => { + beforeEach(() => { + index(); + }); + + it('configures sentry', () => { + expect(SentryConfig.init).toHaveBeenCalledTimes(1); + expect(SentryConfig.init).toHaveBeenCalledWith({ + dsn, + currentUserId, + allowUrls: [gitlabUrl, 'webpack-internal://'], + environment, + release: revision, + tags: { + revision, + feature_category: featureCategory, + }, + }); + }); + + it('does not configure legacy sentry', () => { + expect(LegacySentryConfig.init).not.toHaveBeenCalled(); + }); }); }); diff --git a/spec/frontend/sentry/legacy_index_spec.js b/spec/frontend/sentry/legacy_index_spec.js new file mode 100644 index 00000000000..5c336f8392e --- /dev/null +++ b/spec/frontend/sentry/legacy_index_spec.js @@ -0,0 +1,64 @@ +import index from '~/sentry/legacy_index'; + +import LegacySentryConfig from '~/sentry/legacy_sentry_config'; +import SentryConfig from '~/sentry/sentry_config'; + +describe('Sentry init', () => { + let originalGon; + + const dsn = 'https://123@sentry.gitlab.test/123'; + const environment = 'test'; + const currentUserId = '1'; + const gitlabUrl = 'gitlabUrl'; + const revision = 'revision'; + const featureCategory = 'my_feature_category'; + + beforeEach(() => { + originalGon = window.gon; + window.gon = { + sentry_dsn: dsn, + sentry_environment: environment, + current_user_id: currentUserId, + gitlab_url: gitlabUrl, + revision, + feature_category: featureCategory, + }; + + jest.spyOn(LegacySentryConfig, 'init').mockImplementation(); + jest.spyOn(SentryConfig, 'init').mockImplementation(); + }); + + afterEach(() => { + window.gon = originalGon; + }); + + it('exports legacy version of Sentry in the global object', () => { + // eslint-disable-next-line no-underscore-dangle + expect(window._Sentry.SDK_VERSION).toMatch(/^5\./); + }); + + describe('when called', () => { + beforeEach(() => { + index(); + }); + + it('configures legacy sentry', () => { + expect(LegacySentryConfig.init).toHaveBeenCalledTimes(1); + expect(LegacySentryConfig.init).toHaveBeenCalledWith({ + dsn, + currentUserId, + whitelistUrls: [gitlabUrl, 'webpack-internal://'], + environment, + release: revision, + tags: { + revision, + feature_category: featureCategory, + }, + }); + }); + + it('does not configure new sentry', () => { + expect(SentryConfig.init).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/sentry/legacy_sentry_config_spec.js b/spec/frontend/sentry/legacy_sentry_config_spec.js new file mode 100644 index 00000000000..fe90cb49074 --- /dev/null +++ b/spec/frontend/sentry/legacy_sentry_config_spec.js @@ -0,0 +1,215 @@ +import * as Sentry5 from 'sentrybrowser5'; +import LegacySentryConfig from '~/sentry/legacy_sentry_config'; + +describe('LegacySentryConfig', () => { + describe('IGNORE_ERRORS', () => { + it('should be an array of strings', () => { + const areStrings = LegacySentryConfig.IGNORE_ERRORS.every( + (error) => typeof error === 'string', + ); + + expect(areStrings).toBe(true); + }); + }); + + describe('BLACKLIST_URLS', () => { + it('should be an array of regexps', () => { + const areRegExps = LegacySentryConfig.BLACKLIST_URLS.every((url) => url instanceof RegExp); + + expect(areRegExps).toBe(true); + }); + }); + + describe('SAMPLE_RATE', () => { + it('should be a finite number', () => { + expect(typeof LegacySentryConfig.SAMPLE_RATE).toEqual('number'); + }); + }); + + describe('init', () => { + const options = { + currentUserId: 1, + }; + + beforeEach(() => { + jest.spyOn(LegacySentryConfig, 'configure'); + jest.spyOn(LegacySentryConfig, 'bindSentryErrors'); + jest.spyOn(LegacySentryConfig, 'setUser'); + + LegacySentryConfig.init(options); + }); + + it('should set the options property', () => { + expect(LegacySentryConfig.options).toEqual(options); + }); + + it('should call the configure method', () => { + expect(LegacySentryConfig.configure).toHaveBeenCalled(); + }); + + it('should call the error bindings method', () => { + expect(LegacySentryConfig.bindSentryErrors).toHaveBeenCalled(); + }); + + it('should call setUser', () => { + expect(LegacySentryConfig.setUser).toHaveBeenCalled(); + }); + + it('should not call setUser if there is no current user ID', () => { + LegacySentryConfig.setUser.mockClear(); + options.currentUserId = undefined; + + LegacySentryConfig.init(options); + + expect(LegacySentryConfig.setUser).not.toHaveBeenCalled(); + }); + }); + + describe('configure', () => { + const sentryConfig = {}; + const options = { + dsn: 'https://123@sentry.gitlab.test/123', + whitelistUrls: ['//gitlabUrl', 'webpack-internal://'], + environment: 'test', + release: 'revision', + tags: { + revision: 'revision', + feature_category: 'my_feature_category', + }, + }; + + beforeEach(() => { + jest.spyOn(Sentry5, 'init').mockImplementation(); + jest.spyOn(Sentry5, 'setTags').mockImplementation(); + + sentryConfig.options = options; + sentryConfig.IGNORE_ERRORS = 'ignore_errors'; + sentryConfig.BLACKLIST_URLS = 'blacklist_urls'; + + LegacySentryConfig.configure.call(sentryConfig); + }); + + it('should call Sentry5.init', () => { + expect(Sentry5.init).toHaveBeenCalledWith({ + dsn: options.dsn, + release: options.release, + sampleRate: 0.95, + whitelistUrls: options.whitelistUrls, + environment: 'test', + ignoreErrors: sentryConfig.IGNORE_ERRORS, + blacklistUrls: sentryConfig.BLACKLIST_URLS, + }); + }); + + it('should call Sentry5.setTags', () => { + expect(Sentry5.setTags).toHaveBeenCalledWith(options.tags); + }); + + it('should set environment from options', () => { + sentryConfig.options.environment = 'development'; + + LegacySentryConfig.configure.call(sentryConfig); + + expect(Sentry5.init).toHaveBeenCalledWith({ + dsn: options.dsn, + release: options.release, + sampleRate: 0.95, + whitelistUrls: options.whitelistUrls, + environment: 'development', + ignoreErrors: sentryConfig.IGNORE_ERRORS, + blacklistUrls: sentryConfig.BLACKLIST_URLS, + }); + }); + }); + + describe('setUser', () => { + let sentryConfig; + + beforeEach(() => { + sentryConfig = { options: { currentUserId: 1 } }; + jest.spyOn(Sentry5, 'setUser'); + + LegacySentryConfig.setUser.call(sentryConfig); + }); + + it('should call .setUser', () => { + expect(Sentry5.setUser).toHaveBeenCalledWith({ + id: sentryConfig.options.currentUserId, + }); + }); + }); + + describe('handleSentryErrors', () => { + let event; + let req; + let config; + let err; + + beforeEach(() => { + event = {}; + req = { status: 'status', responseText: 'Unknown response text', statusText: 'statusText' }; + config = { type: 'type', url: 'url', data: 'data' }; + err = {}; + + jest.spyOn(Sentry5, 'captureMessage'); + + LegacySentryConfig.handleSentryErrors(event, req, config, err); + }); + + it('should call Sentry5.captureMessage', () => { + expect(Sentry5.captureMessage).toHaveBeenCalledWith(err, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: req.responseText, + error: err, + event, + }, + }); + }); + + describe('if no err is provided', () => { + beforeEach(() => { + LegacySentryConfig.handleSentryErrors(event, req, config); + }); + + it('should use req.statusText as the error value', () => { + expect(Sentry5.captureMessage).toHaveBeenCalledWith(req.statusText, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: req.responseText, + error: req.statusText, + event, + }, + }); + }); + }); + + describe('if no req.responseText is provided', () => { + beforeEach(() => { + req.responseText = undefined; + + LegacySentryConfig.handleSentryErrors(event, req, config, err); + }); + + it('should use `Unknown response text` as the response', () => { + expect(Sentry5.captureMessage).toHaveBeenCalledWith(err, { + extra: { + type: config.type, + url: config.url, + data: config.data, + status: req.status, + response: 'Unknown response text', + error: err, + event, + }, + }); + }); + }); + }); +}); diff --git a/spec/frontend/sentry/sentry_browser_wrapper_spec.js b/spec/frontend/sentry/sentry_browser_wrapper_spec.js new file mode 100644 index 00000000000..f4d646bab78 --- /dev/null +++ b/spec/frontend/sentry/sentry_browser_wrapper_spec.js @@ -0,0 +1,59 @@ +import * as Sentry from '~/sentry/sentry_browser_wrapper'; + +const mockError = new Error('error!'); +const mockMsg = 'msg!'; +const mockFn = () => {}; + +describe('SentryBrowserWrapper', () => { + afterEach(() => { + // eslint-disable-next-line no-underscore-dangle + delete window._Sentry; + }); + + describe('when _Sentry is not defined', () => { + it('methods fail silently', () => { + expect(() => { + Sentry.captureException(mockError); + Sentry.captureMessage(mockMsg); + Sentry.withScope(mockFn); + }).not.toThrow(); + }); + }); + + describe('when _Sentry is defined', () => { + let mockCaptureException; + let mockCaptureMessage; + let mockWithScope; + + beforeEach(async () => { + mockCaptureException = jest.fn(); + mockCaptureMessage = jest.fn(); + mockWithScope = jest.fn(); + + // eslint-disable-next-line no-underscore-dangle + window._Sentry = { + captureException: mockCaptureException, + captureMessage: mockCaptureMessage, + withScope: mockWithScope, + }; + }); + + it('captureException is called', () => { + Sentry.captureException(mockError); + + expect(mockCaptureException).toHaveBeenCalledWith(mockError); + }); + + it('captureMessage is called', () => { + Sentry.captureMessage(mockMsg); + + expect(mockCaptureMessage).toHaveBeenCalledWith(mockMsg); + }); + + it('withScope is called', () => { + Sentry.withScope(mockFn); + + expect(mockWithScope).toHaveBeenCalledWith(mockFn); + }); + }); +}); diff --git a/spec/frontend/sentry/sentry_config_spec.js b/spec/frontend/sentry/sentry_config_spec.js index 9f67b681b8d..44acbee9b38 100644 --- a/spec/frontend/sentry/sentry_config_spec.js +++ b/spec/frontend/sentry/sentry_config_spec.js @@ -1,29 +1,9 @@ -import * as Sentry from '@sentry/browser'; +import * as Sentry from 'sentrybrowser7'; +import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from '~/sentry/constants'; + import SentryConfig from '~/sentry/sentry_config'; describe('SentryConfig', () => { - describe('IGNORE_ERRORS', () => { - it('should be an array of strings', () => { - const areStrings = SentryConfig.IGNORE_ERRORS.every((error) => typeof error === 'string'); - - expect(areStrings).toBe(true); - }); - }); - - describe('BLACKLIST_URLS', () => { - it('should be an array of regexps', () => { - const areRegExps = SentryConfig.BLACKLIST_URLS.every((url) => url instanceof RegExp); - - expect(areRegExps).toBe(true); - }); - }); - - describe('SAMPLE_RATE', () => { - it('should be a finite number', () => { - expect(typeof SentryConfig.SAMPLE_RATE).toEqual('number'); - }); - }); - describe('init', () => { const options = { currentUserId: 1, @@ -31,7 +11,6 @@ describe('SentryConfig', () => { beforeEach(() => { jest.spyOn(SentryConfig, 'configure'); - jest.spyOn(SentryConfig, 'bindSentryErrors'); jest.spyOn(SentryConfig, 'setUser'); SentryConfig.init(options); @@ -45,19 +24,13 @@ describe('SentryConfig', () => { expect(SentryConfig.configure).toHaveBeenCalled(); }); - it('should call the error bindings method', () => { - expect(SentryConfig.bindSentryErrors).toHaveBeenCalled(); - }); - it('should call setUser', () => { expect(SentryConfig.setUser).toHaveBeenCalled(); }); it('should not call setUser if there is no current user ID', () => { SentryConfig.setUser.mockClear(); - options.currentUserId = undefined; - - SentryConfig.init(options); + SentryConfig.init({ currentUserId: undefined }); expect(SentryConfig.setUser).not.toHaveBeenCalled(); }); @@ -67,7 +40,7 @@ describe('SentryConfig', () => { const sentryConfig = {}; const options = { dsn: 'https://123@sentry.gitlab.test/123', - whitelistUrls: ['//gitlabUrl', 'webpack-internal://'], + allowUrls: ['//gitlabUrl', 'webpack-internal://'], environment: 'test', release: 'revision', tags: { @@ -81,8 +54,6 @@ describe('SentryConfig', () => { jest.spyOn(Sentry, 'setTags').mockImplementation(); sentryConfig.options = options; - sentryConfig.IGNORE_ERRORS = 'ignore_errors'; - sentryConfig.BLACKLIST_URLS = 'blacklist_urls'; SentryConfig.configure.call(sentryConfig); }); @@ -91,11 +62,11 @@ describe('SentryConfig', () => { expect(Sentry.init).toHaveBeenCalledWith({ dsn: options.dsn, release: options.release, - sampleRate: 0.95, - whitelistUrls: options.whitelistUrls, - environment: 'test', - ignoreErrors: sentryConfig.IGNORE_ERRORS, - blacklistUrls: sentryConfig.BLACKLIST_URLS, + sampleRate: SAMPLE_RATE, + allowUrls: options.allowUrls, + environment: options.environment, + ignoreErrors: IGNORE_ERRORS, + denyUrls: DENY_URLS, }); }); @@ -111,11 +82,11 @@ describe('SentryConfig', () => { expect(Sentry.init).toHaveBeenCalledWith({ dsn: options.dsn, release: options.release, - sampleRate: 0.95, - whitelistUrls: options.whitelistUrls, + sampleRate: SAMPLE_RATE, + allowUrls: options.allowUrls, environment: 'development', - ignoreErrors: sentryConfig.IGNORE_ERRORS, - blacklistUrls: sentryConfig.BLACKLIST_URLS, + ignoreErrors: IGNORE_ERRORS, + denyUrls: DENY_URLS, }); }); }); @@ -136,78 +107,4 @@ describe('SentryConfig', () => { }); }); }); - - describe('handleSentryErrors', () => { - let event; - let req; - let config; - let err; - - beforeEach(() => { - event = {}; - req = { status: 'status', responseText: 'Unknown response text', statusText: 'statusText' }; - config = { type: 'type', url: 'url', data: 'data' }; - err = {}; - - jest.spyOn(Sentry, 'captureMessage'); - - SentryConfig.handleSentryErrors(event, req, config, err); - }); - - it('should call Sentry.captureMessage', () => { - expect(Sentry.captureMessage).toHaveBeenCalledWith(err, { - extra: { - type: config.type, - url: config.url, - data: config.data, - status: req.status, - response: req.responseText, - error: err, - event, - }, - }); - }); - - describe('if no err is provided', () => { - beforeEach(() => { - SentryConfig.handleSentryErrors(event, req, config); - }); - - it('should use req.statusText as the error value', () => { - expect(Sentry.captureMessage).toHaveBeenCalledWith(req.statusText, { - extra: { - type: config.type, - url: config.url, - data: config.data, - status: req.status, - response: req.responseText, - error: req.statusText, - event, - }, - }); - }); - }); - - describe('if no req.responseText is provided', () => { - beforeEach(() => { - req.responseText = undefined; - - SentryConfig.handleSentryErrors(event, req, config, err); - }); - - it('should use `Unknown response text` as the response', () => { - expect(Sentry.captureMessage).toHaveBeenCalledWith(err, { - extra: { - type: config.type, - url: config.url, - data: config.data, - status: req.status, - response: 'Unknown response text', - error: err, - event, - }, - }); - }); - }); - }); }); diff --git a/spec/initializers/diagnostic_reports_spec.rb b/spec/initializers/diagnostic_reports_spec.rb index 2022076072b..20d0b2714f0 100644 --- a/spec/initializers/diagnostic_reports_spec.rb +++ b/spec/initializers/diagnostic_reports_spec.rb @@ -28,10 +28,10 @@ RSpec.describe 'diagnostic reports' do end let(:report_daemon) { instance_double(Gitlab::Memory::ReportsDaemon) } + let(:reporter) { instance_double(Gitlab::Memory::Reporter) } it 'modifies worker startup hooks, starts Gitlab::Memory::ReportsDaemon' do expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_call_original - expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_stop) expect_next_instance_of(Gitlab::Memory::ReportsDaemon) do |daemon| expect(daemon).to receive(:start) end @@ -39,14 +39,30 @@ RSpec.describe 'diagnostic reports' do load_initializer end - it 'writes scheduled heap dumps in on_worker_stop' do - expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start) - expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_stop).and_call_original - expect(Gitlab::Memory::Reports::HeapDump).to receive(:write_conditionally) + context 'when GITLAB_MEMWD_DUMP_HEAP is set' do + before do + stub_env('GITLAB_MEMWD_DUMP_HEAP', '1') + end - load_initializer - # This is necessary because this hook normally fires during worker shutdown. - Gitlab::Cluster::LifecycleEvents.do_worker_stop + it 'writes scheduled heap dumps in on_worker_stop' do + expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start) + expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_stop).and_call_original + expect(Gitlab::Memory::Reporter).to receive(:new).and_return(reporter) + expect(reporter).to receive(:run_report).with(an_instance_of(Gitlab::Memory::Reports::HeapDump)) + + load_initializer + # This is necessary because this hook normally fires during worker shutdown. + Gitlab::Cluster::LifecycleEvents.do_worker_stop + end + end + + context 'when GITLAB_MEMWD_DUMP_HEAP is not set' do + it 'does not write heap dumps' do + expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start) + expect(Gitlab::Cluster::LifecycleEvents).not_to receive(:on_worker_stop) + + load_initializer + end end end diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb index aadfb41a46e..88bffd41947 100644 --- a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb +++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb @@ -102,13 +102,65 @@ RSpec.describe Gitlab::ContentSecurityPolicy::ConfigLoader do end context 'when sentry is configured' do + let(:legacy_dsn) { 'dummy://abc@legacy-sentry.example.com/1' } + let(:dsn) { 'dummy://def@sentry.example.com/2' } + before do - stub_sentry_settings stub_config_setting(host: 'gitlab.example.com') end - it 'adds sentry path to CSP without user' do - expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://example.com") + context 'when legacy sentry is configured' do + before do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(legacy_dsn) + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(false) + end + + it 'adds legacy sentry path to CSP' do + expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com") + end + end + + context 'when sentry is configured' do + before do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true) + allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_return(dsn) + end + + it 'adds new sentry path to CSP' do + expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://sentry.example.com") + end + end + + context 'when sentry settings are from older schemas and sentry setting are missing' do + before do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(false) + + allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_enabled).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_raise(NoMethodError) + + allow(Gitlab::CurrentSettings).to receive(:respond_to?).with(:sentry_clientside_dsn).and_return(false) + allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_raise(NoMethodError) + end + + it 'config is backwards compatible, does not add sentry path to CSP' do + expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com") + end + end + + context 'when legacy sentry and sentry are both configured' do + before do + allow(Gitlab.config.sentry).to receive(:enabled).and_return(true) + allow(Gitlab.config.sentry).to receive(:clientside_dsn).and_return(legacy_dsn) + + allow(Gitlab::CurrentSettings).to receive(:sentry_enabled).and_return(true) + allow(Gitlab::CurrentSettings).to receive(:sentry_clientside_dsn).and_return(dsn) + end + + it 'adds both sentry paths to CSP' do + expect(directives['connect_src']).to eq("'self' ws://gitlab.example.com dummy://legacy-sentry.example.com dummy://sentry.example.com") + end end end diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb index 5a1fcc5e2dc..f8fb998b84d 100644 --- a/spec/lib/gitlab/gon_helper_spec.rb +++ b/spec/lib/gitlab/gon_helper_spec.rb @@ -44,7 +44,7 @@ RSpec.describe Gitlab::GonHelper do let(:clientside_dsn) { 'https://xxx@sentry.example.com/1' } let(:environment) { 'staging' } - describe 'sentry integration' do + describe 'legacy sentry integration' do before do stub_config(sentry: { enabled: true, clientside_dsn: clientside_dsn, environment: environment }) end @@ -57,7 +57,7 @@ RSpec.describe Gitlab::GonHelper do end end - describe 'new sentry integration' do + describe 'sentry integration' do before do stub_application_setting(sentry_enabled: true) stub_application_setting(sentry_clientside_dsn: clientside_dsn) diff --git a/spec/lib/gitlab/memory/jemalloc_spec.rb b/spec/lib/gitlab/memory/jemalloc_spec.rb index 9986af4bc13..8cce2278f8e 100644 --- a/spec/lib/gitlab/memory/jemalloc_spec.rb +++ b/spec/lib/gitlab/memory/jemalloc_spec.rb @@ -1,15 +1,14 @@ # frozen_string_literal: true require 'fast_spec_helper' -require 'tmpdir' +require 'tempfile' RSpec.describe Gitlab::Memory::Jemalloc do - let(:outdir) { Dir.mktmpdir } - let(:tmp_outdir) { Dir.mktmpdir } + let(:outfile) { Tempfile.new } after do - FileUtils.rm_f(outdir) - FileUtils.rm_f(tmp_outdir) + outfile.close + outfile.unlink end context 'when jemalloc is loaded' do @@ -31,12 +30,11 @@ RSpec.describe Gitlab::Memory::Jemalloc do describe '.dump_stats' do it 'writes stats JSON file' do - file_path = described_class.dump_stats(path: outdir, tmp_dir: tmp_outdir, format: format) + described_class.dump_stats(outfile, format: format) - file = Dir.entries(outdir).find { |e| e.match(/jemalloc_stats\..*\.json$/) } - expect(file).not_to be_nil - expect(file_path).to eq(File.join(outdir, file)) - expect(File.read(file_path)).to eq(output) + outfile.rewind + + expect(outfile.read).to eq(output) end end end @@ -56,25 +54,12 @@ RSpec.describe Gitlab::Memory::Jemalloc do end describe '.dump_stats' do - shared_examples 'writes stats text file' do |filename_label, filename_pattern| - it do - described_class.dump_stats( - path: outdir, tmp_dir: tmp_outdir, format: format, filename_label: filename_label) - - file = Dir.entries(outdir).find { |e| e.match(filename_pattern) } - expect(file).not_to be_nil - expect(File.read(File.join(outdir, file))).to eq(output) - end - end + it 'writes stats text file' do + described_class.dump_stats(outfile, format: format) - context 'when custom filename label is passed' do - include_examples 'writes stats text file', - 'puma_0.some-uuid', - /jemalloc_stats\..*\.puma_0\.some-uuid\.txt$/ - end + outfile.rewind - context 'when custom filename label is not passed' do - include_examples 'writes stats text file', nil, /jemalloc_stats\..*\.txt$/ + expect(outfile.read).to eq(output) end end end @@ -93,7 +78,7 @@ RSpec.describe Gitlab::Memory::Jemalloc do describe '.dump_stats' do it 'raises an error' do expect do - described_class.dump_stats(path: outdir, tmp_dir: tmp_outdir, format: format) + described_class.dump_stats(outfile, format: format) end.to raise_error(/format must be one of/) end end @@ -106,18 +91,18 @@ RSpec.describe Gitlab::Memory::Jemalloc do end describe '.stats' do - it 'returns nil' do - expect(described_class.stats).to be_nil + it 'returns empty string' do + expect(described_class.stats).to be_empty end end describe '.dump_stats' do it 'does nothing' do - stub_env('LD_PRELOAD', nil) + described_class.dump_stats(outfile) - described_class.dump_stats(path: outdir, tmp_dir: tmp_outdir) + outfile.rewind - expect(Dir.empty?(outdir)).to be(true) + expect(outfile.read).to be_empty end end end diff --git a/spec/lib/gitlab/memory/reporter_spec.rb b/spec/lib/gitlab/memory/reporter_spec.rb index 7924b37cb4b..ad6e556b3dd 100644 --- a/spec/lib/gitlab/memory/reporter_spec.rb +++ b/spec/lib/gitlab/memory/reporter_spec.rb @@ -3,75 +3,144 @@ require 'spec_helper' RSpec.describe Gitlab::Memory::Reporter, :aggregate_failures do - subject(:reporter) { described_class.new } - let(:fake_report) do Class.new do - attr_reader :did_run - def name 'fake_report' end - def run(report_id) - @did_run = true - '/path/to/report' + def run(writer) + writer << 'I ran' end end end + let(:logger) { instance_double(::Logger) } let(:report) { fake_report.new } - describe '#run_report' do + after do + FileUtils.rm_rf(reports_path) + end + + describe '#run_report', time_travel_to: '2020-02-02 10:30:45 0000' do let(:report_duration_counter) { instance_double(::Prometheus::Client::Counter) } let(:file_size) { 1_000_000 } + let(:report_file) { "#{reports_path}/fake_report.2020-02-02.10:30:45:000.worker_1.abc123" } before do + allow(SecureRandom).to receive(:uuid).and_return('abc123') + allow(Gitlab::Metrics).to receive(:counter).and_return(report_duration_counter) allow(report_duration_counter).to receive(:increment) allow(::Prometheus::PidProvider).to receive(:worker_id).and_return('worker_1') - allow(File).to receive(:size).with('/path/to/report').and_return(file_size) + allow(File).to receive(:size).with(report_file).and_return(file_size) - allow(SecureRandom).to receive(:uuid).and_return('abc123') + allow(logger).to receive(:info) end - it 'runs the given report' do - expect { reporter.run_report(report) }.to change { report.did_run }.from(nil).to(true) - end + shared_examples 'runs and stores reports' do + it 'runs the given report and returns true' do + expect(reporter.run_report(report)).to be(true) - it 'logs duration and other metrics' do - expect(Gitlab::AppLogger).to receive(:info).with( - hash_including( - :duration_s, - :cpu_s, - perf_report_size_bytes: file_size, - message: 'finished', - pid: Process.pid, - worker_id: 'worker_1', - perf_report_worker_uuid: 'abc123', - perf_report: 'fake_report' - )) - - reporter.run_report(report) + expect(File.read(report_file)).to eq('I ran') + end + + it 'logs start and finish event' do + expect(logger).to receive(:info).ordered.with( + hash_including( + message: 'started', + pid: Process.pid, + worker_id: 'worker_1', + perf_report_worker_uuid: 'abc123', + perf_report: 'fake_report' + )) + expect(logger).to receive(:info).ordered.with( + hash_including( + :duration_s, + :cpu_s, + perf_report_file: report_file, + perf_report_size_bytes: file_size, + message: 'finished', + pid: Process.pid, + worker_id: 'worker_1', + perf_report_worker_uuid: 'abc123', + perf_report: 'fake_report' + )) + + reporter.run_report(report) + end + + it 'increments Prometheus duration counter' do + expect(report_duration_counter).to receive(:increment).with({ report: 'fake_report' }, an_instance_of(Float)) + + reporter.run_report(report) + end + + context 'when the report returns invalid file path' do + before do + allow(File).to receive(:size).with(report_file).and_raise(Errno::ENOENT) + end + + it 'logs `0` as `perf_report_size_bytes`' do + expect(logger).to receive(:info).ordered.with( + hash_including(message: 'started') + ) + expect(logger).to receive(:info).ordered.with( + hash_including(message: 'finished', perf_report_size_bytes: 0) + ) + + reporter.run_report(report) + end + end + + context 'when an error occurs' do + before do + allow(report).to receive(:run).and_raise(RuntimeError.new('report failed')) + end + + it 'logs the error and returns false' do + expect(logger).to receive(:info).ordered.with(hash_including(message: 'started')) + expect(logger).to receive(:error).ordered.with( + hash_including( + message: 'failed', error: '#<RuntimeError: report failed>' + )) + + expect(reporter.run_report(report)).to be(false) + end + end end - it 'increments Prometheus duration counter' do - expect(report_duration_counter).to receive(:increment).with({ report: 'fake_report' }, an_instance_of(Float)) + context 'when reports path is specified directly' do + let(:reports_path) { Dir.mktmpdir } + + subject(:reporter) { described_class.new(reports_path: reports_path, logger: logger) } - reporter.run_report(report) + it_behaves_like 'runs and stores reports' end - context 'when the report returns invalid file path' do + context 'when reports path is specified via environment' do + let(:reports_path) { Dir.mktmpdir } + + subject(:reporter) { described_class.new(logger: logger) } + before do - allow(File).to receive(:size).with('/path/to/report').and_raise(Errno::ENOENT) + stub_env('GITLAB_DIAGNOSTIC_REPORTS_PATH', reports_path) end - it 'logs `0` as `perf_report_size_bytes`' do - expect(Gitlab::AppLogger).to receive(:info).with(hash_including(perf_report_size_bytes: 0)) + it_behaves_like 'runs and stores reports' + end - reporter.run_report(report) + context 'when reports path is not specified' do + let(:reports_path) { reporter.reports_path } + + subject(:reporter) { described_class.new(logger: logger) } + + it 'defaults to a temporary location' do + expect(reports_path).not_to be_empty end + + it_behaves_like 'runs and stores reports' end end end diff --git a/spec/lib/gitlab/memory/reports/heap_dump_spec.rb b/spec/lib/gitlab/memory/reports/heap_dump_spec.rb index 2532b8c5295..9cf455b3202 100644 --- a/spec/lib/gitlab/memory/reports/heap_dump_spec.rb +++ b/spec/lib/gitlab/memory/reports/heap_dump_spec.rb @@ -3,20 +3,34 @@ require 'spec_helper' RSpec.describe Gitlab::Memory::Reports::HeapDump do - describe '.write_conditionally' do - subject(:call) { described_class.write_conditionally } + # Copy this class so we do not mess with its state. + let(:klass) { described_class.dup } + + subject(:report) { klass.new } + + describe '#name' do + # This is a bit silly, but it caused code coverage failures. + it 'is set' do + expect(report.name).to eq('heap_dump') + end + end + + describe '#run' do + subject(:run) { report.run(writer) } + + let(:writer) { StringIO.new } context 'when no heap dump is enqueued' do it 'does nothing and returns false' do - expect(call).to be(false) + expect(run).to be(false) end end context 'when a heap dump is enqueued' do it 'does nothing and returns true' do - described_class.enqueue! + klass.enqueue! - expect(call).to be(true) + expect(run).to be(true) end end end diff --git a/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb b/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb index ee4c6004c52..ce06c270a05 100644 --- a/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb +++ b/spec/lib/gitlab/memory/reports/jemalloc_stats_spec.rb @@ -3,96 +3,16 @@ require 'spec_helper' RSpec.describe Gitlab::Memory::Reports::JemallocStats do - let_it_be(:outdir) { Dir.mktmpdir } + subject(:jemalloc_stats) { described_class.new } - let(:jemalloc_stats) { described_class.new(reports_path: outdir) } - - after do - FileUtils.rm_f(outdir) - end + let(:writer) { StringIO.new } describe '.run' do context 'when :report_jemalloc_stats ops FF is enabled' do - let(:worker_id) { 'puma_1' } - let(:report_name) { 'report.json' } - let(:report_path) { File.join(outdir, report_name) } - - before do - allow(Prometheus::PidProvider).to receive(:worker_id).and_return(worker_id) - end - - it 'invokes Jemalloc.dump_stats and returns file path' do - expect(Gitlab::Memory::Jemalloc) - .to receive(:dump_stats) - .with(path: outdir, - tmp_dir: File.join(outdir, '/tmp'), - filename_label: 'test_report') - .and_return(report_path) - - expect(jemalloc_stats.run('test_report')).to eq(report_path) - end - - describe 'reports cleanup' do - let(:jemalloc_stats) { described_class.new(reports_path: outdir) } - - before do - stub_env('GITLAB_DIAGNOSTIC_REPORTS_JEMALLOC_MAX_REPORTS_STORED', 3) - allow(Gitlab::Memory::Jemalloc).to receive(:dump_stats) - end - - context 'when number of reports exceeds `max_reports_stored`' do - let_it_be(:reports) do - now = Time.current - - (1..5).map do |i| - Tempfile.new("jemalloc_stats.#{i}.worker_#{i}.#{Time.current.to_i}.json", outdir).tap do |f| - FileUtils.touch(f, mtime: (now + i.second).to_i) - end - end - end - - after do - reports.each do |f| - f.close - f.unlink - rescue Errno::ENOENT - # Some of the files are already unlinked by the code we test; Ignore - end - end - - it 'keeps only `max_reports_stored` total newest files' do - expect { jemalloc_stats.run('test_report') } - .to change { Dir.entries(outdir).count { |e| e.match(/jemalloc_stats.*/) } } - .from(5).to(3) - - # Keeps only the newest reports - expect(reports.last(3).all? { |r| File.exist?(r) }).to be true - end - end - - context 'when number of reports does not exceed `max_reports_stored`' do - let_it_be(:reports) do - now = Time.current - - (1..3).map do |i| - Tempfile.new("jemalloc_stats.#{i}.worker_#{i}.#{Time.current.to_i}.json", outdir).tap do |f| - FileUtils.touch(f, mtime: (now + i.second).to_i) - end - end - end - - after do - reports.each do |f| - f.close - f.unlink - end - end + it 'dumps jemalloc stats to the given writer' do + expect(Gitlab::Memory::Jemalloc).to receive(:dump_stats).with(writer) - it 'does not remove any reports' do - expect { jemalloc_stats.run('test_report') } - .not_to change { Dir.entries(outdir).count { |e| e.match(/jemalloc_stats.*/) } } - end - end + jemalloc_stats.run(writer) end end @@ -101,10 +21,10 @@ RSpec.describe Gitlab::Memory::Reports::JemallocStats do stub_feature_flags(report_jemalloc_stats: false) end - it 'does not run the report and returns nil' do + it 'does not run the report' do expect(Gitlab::Memory::Jemalloc).not_to receive(:dump_stats) - expect(jemalloc_stats.run('test_report')).to be_nil + jemalloc_stats.run(writer) end end end diff --git a/spec/lib/gitlab/memory/reports_daemon_spec.rb b/spec/lib/gitlab/memory/reports_daemon_spec.rb index b6be4eac919..91c36c87253 100644 --- a/spec/lib/gitlab/memory/reports_daemon_spec.rb +++ b/spec/lib/gitlab/memory/reports_daemon_spec.rb @@ -6,14 +6,8 @@ RSpec.describe Gitlab::Memory::ReportsDaemon, :aggregate_failures do let(:reporter) { instance_double(Gitlab::Memory::Reporter) } let(:reports) { nil } - let_it_be(:tmp_dir) { Dir.mktmpdir } - subject(:daemon) { described_class.new(reporter: reporter, reports: reports) } - after(:all) do - FileUtils.remove_entry(tmp_dir) - end - describe '#run_thread' do before do # make sleep no-op @@ -58,8 +52,9 @@ RSpec.describe Gitlab::Memory::ReportsDaemon, :aggregate_failures do context 'sleep timers logic' do it 'wakes up every (fixed interval + defined delta), sleeps between reports each cycle' do stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_MAX_DELTA_S', 1) # rand(1) == 0, so we will have fixed sleep interval - daemon = described_class.new + daemon = described_class.new(reporter: reporter, reports: reports) allow(daemon).to receive(:alive).and_return(true, true, false) + allow(reporter).to receive(:run_report) expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_S).ordered expect(daemon).to receive(:sleep).with(described_class::DEFAULT_SLEEP_BETWEEN_REPORTS_S).ordered @@ -85,7 +80,6 @@ RSpec.describe Gitlab::Memory::ReportsDaemon, :aggregate_failures do expect(daemon.sleep_s).to eq(described_class::DEFAULT_SLEEP_S) expect(daemon.sleep_max_delta_s).to eq(described_class::DEFAULT_SLEEP_MAX_DELTA_S) expect(daemon.sleep_between_reports_s).to eq(described_class::DEFAULT_SLEEP_BETWEEN_REPORTS_S) - expect(daemon.reports_path).to eq(described_class::DEFAULT_REPORTS_PATH) end end @@ -94,7 +88,6 @@ RSpec.describe Gitlab::Memory::ReportsDaemon, :aggregate_failures do stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_S', 100) stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_MAX_DELTA_S', 50) stub_env('GITLAB_DIAGNOSTIC_REPORTS_SLEEP_BETWEEN_REPORTS_S', 2) - stub_env('GITLAB_DIAGNOSTIC_REPORTS_PATH', tmp_dir) end it 'uses provided values' do @@ -103,7 +96,6 @@ RSpec.describe Gitlab::Memory::ReportsDaemon, :aggregate_failures do expect(daemon.sleep_s).to eq(100) expect(daemon.sleep_max_delta_s).to eq(50) expect(daemon.sleep_between_reports_s).to eq(2) - expect(daemon.reports_path).to eq(tmp_dir) end end end diff --git a/spec/lib/gitlab/metrics/rails_slis_spec.rb b/spec/lib/gitlab/metrics/rails_slis_spec.rb index b30eb57101f..9da102fb8b8 100644 --- a/spec/lib/gitlab/metrics/rails_slis_spec.rb +++ b/spec/lib/gitlab/metrics/rails_slis_spec.rb @@ -14,8 +14,8 @@ RSpec.describe Gitlab::Metrics::RailsSlis do end describe '.initialize_request_slis!' do - it "initializes the SLI for all possible endpoints if they weren't", :aggregate_failures do - possible_labels = [ + let(:possible_labels) do + [ { endpoint_id: "GET /api/:version/version", feature_category: :not_owned, @@ -27,17 +27,32 @@ RSpec.describe Gitlab::Metrics::RailsSlis do request_urgency: :default } ] + end - possible_graphql_labels = ['graphql:foo', 'graphql:bar', 'graphql:unknown'].map do |endpoint_id| + let(:possible_graphql_labels) do + ['graphql:foo', 'graphql:bar', 'graphql:unknown'].map do |endpoint_id| { endpoint_id: endpoint_id, feature_category: nil, query_urgency: ::Gitlab::EndpointAttributes::DEFAULT_URGENCY.name } end + end + + it "initializes the SLI for all possible endpoints if they weren't", :aggregate_failures do + expect(Gitlab::Metrics::Sli::Apdex).to receive(:initialize_sli).with(:rails_request, array_including(*possible_labels)).and_call_original + expect(Gitlab::Metrics::Sli::Apdex).to receive(:initialize_sli).with(:graphql_query, array_including(*possible_graphql_labels)).and_call_original + expect(Gitlab::Metrics::Sli::ErrorRate).to receive(:initialize_sli).with(:rails_request, array_including(*possible_labels)).and_call_original + + described_class.initialize_request_slis! + end + + it "initializes the SLI for all possible endpoints if they weren't given error rate feature flag is disabled", :aggregate_failures do + stub_feature_flags(gitlab_metrics_error_rate_sli: false) expect(Gitlab::Metrics::Sli::Apdex).to receive(:initialize_sli).with(:rails_request, array_including(*possible_labels)).and_call_original expect(Gitlab::Metrics::Sli::Apdex).to receive(:initialize_sli).with(:graphql_query, array_including(*possible_graphql_labels)).and_call_original + expect(Gitlab::Metrics::Sli::ErrorRate).not_to receive(:initialize_sli) described_class.initialize_request_slis! end @@ -51,6 +66,14 @@ RSpec.describe Gitlab::Metrics::RailsSlis do end end + describe '.request_error' do + it 'returns the initialized request error rate SLI object' do + described_class.initialize_request_slis! + + expect(described_class.request_error_rate).to be_initialized + end + end + describe '.graphql_query_apdex' do it 'returns the initialized request apdex SLI object' do described_class.initialize_request_slis! diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb index ed78548ef62..56ba880c906 100644 --- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb +++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb @@ -38,6 +38,20 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time) expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment) .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment) + .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, error: false) + + subject.call(env) + end + + it 'does not track error rate when feature flag is disabled' do + stub_feature_flags(gitlab_metrics_error_rate_sli: false) + + expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '200', feature_category: 'unknown') + expect(described_class).to receive_message_chain(:http_request_duration_seconds, :observe).with({ method: 'get' }, a_positive_execution_time) + expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment) + .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).not_to receive(:increment) subject.call(env) end @@ -84,10 +98,23 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do context '@app.call returns an error code' do let(:status) { '500' } - it 'tracks count but not duration or apdex' do + it 'tracks count and error rate but not duration and apdex' do expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '500', feature_category: 'unknown') expect(described_class).not_to receive(:http_request_duration_seconds) expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment) + .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, error: true) + + subject.call(env) + end + + it 'does not track error rate when feature flag is disabled' do + stub_feature_flags(gitlab_metrics_error_rate_sli: false) + + expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: '500', feature_category: 'unknown') + expect(described_class).not_to receive(:http_request_duration_seconds) + expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).not_to receive(:increment) subject.call(env) end @@ -108,6 +135,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'unknown') expect(described_class.http_request_duration_seconds).not_to receive(:observe) expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).not_to receive(:increment) expect { subject.call(env) }.to raise_error(StandardError) end @@ -124,6 +152,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do expect(described_class).not_to receive(:http_health_requests_total) expect(Gitlab::Metrics::RailsSlis.request_apdex) .to receive(:increment).with(labels: { feature_category: 'team_planning', endpoint_id: 'IssuesController#show', request_urgency: :default }, success: true) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment) + .with(labels: { feature_category: 'team_planning', endpoint_id: 'IssuesController#show' }, error: false) subject.call(env) end @@ -134,6 +164,7 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do expect(described_class).to receive_message_chain(:http_health_requests_total, :increment).with(method: 'get', status: '200') expect(described_class).not_to receive(:http_requests_total) expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_error_rate) subject.call(env) end @@ -147,8 +178,9 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do it 'adds the feature category to the labels for http_requests_total' do expect(described_class).to receive_message_chain(:http_requests_total, :increment).with(method: 'get', status: 'undefined', feature_category: 'team_planning') - expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_apdex) + expect(Gitlab::Metrics::RailsSlis).not_to receive(:request_error_rate) expect { subject.call(env) }.to raise_error(StandardError) end end @@ -159,6 +191,8 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do expect(described_class).not_to receive(:http_health_requests_total) expect(Gitlab::Metrics::RailsSlis.request_apdex).to receive(:increment) .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown', request_urgency: :default }, success: true) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment) + .with(labels: { feature_category: 'unknown', endpoint_id: 'unknown' }, error: false) subject.call(env) end @@ -214,6 +248,14 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: success ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'hello_world', + endpoint_id: 'GET /projects/:id/archive' + }, + error: false + ) + subject.call(env) end end @@ -247,6 +289,14 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: success ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'hello_world', + endpoint_id: 'AnonymousController#index' + }, + error: false + ) + subject.call(env) end end @@ -273,6 +323,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: true ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101) @@ -284,6 +341,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: false ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) end end @@ -307,6 +371,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: true ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101) @@ -318,6 +389,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: false ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) end end @@ -337,6 +415,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: true ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) allow(Gitlab::Metrics::System).to receive(:monotonic_time).and_return(100, 101) @@ -348,6 +433,13 @@ RSpec.describe Gitlab::Metrics::RequestsRackMiddleware, :aggregate_failures do }, success: false ) + expect(Gitlab::Metrics::RailsSlis.request_error_rate).to receive(:increment).with( + labels: { + feature_category: 'unknown', + endpoint_id: 'unknown' + }, + error: false + ) subject.call(env) end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 366843a4c03..dbd6c07ef75 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -101,14 +101,32 @@ RSpec.describe Gitlab::Metrics do 401 | true nil | false 500 | false - 503 | false - '100' | false - '201' | true + 503 | false 'nothing' | false end with_them do specify { expect(described_class.record_duration_for_status?(status)).to be(should_record) } + specify { expect(described_class.record_duration_for_status?(status.to_s)).to be(should_record) } + end + end + + describe '.server_error?' do + using RSpec::Parameterized::TableSyntax + + where(:status, :should_record) do + 100 | false + 200 | false + 401 | false + 500 | true + 503 | true + nil | false + 'nothing' | false + end + + with_them do + specify { expect(described_class.server_error?(status)).to be(should_record) } + specify { expect(described_class.server_error?(status.to_s)).to be(should_record) } end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index c03bb61f1f8..52d35f6d79d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1753,8 +1753,8 @@ RSpec.describe Ci::Build do end end - describe '#starts_environment?' do - subject { build.starts_environment? } + describe '#deployment_job?' do + subject { build.deployment_job? } context 'when environment is defined' do before do diff --git a/spec/requests/api/container_repositories_spec.rb b/spec/requests/api/container_repositories_spec.rb index 90f0243dbfc..0f0c88bef74 100644 --- a/spec/requests/api/container_repositories_spec.rb +++ b/spec/requests/api/container_repositories_spec.rb @@ -75,6 +75,13 @@ RSpec.describe API::ContainerRepositories do expect(json_response['id']).to eq(repository.id) expect(response.body).to include('tags') + expect(json_response['tags']).to eq(repository.tags.map do |tag| + { + "location" => tag.location, + "name" => tag.name, + "path" => tag.path + } + end) end context 'with a network error' do diff --git a/spec/requests/jira_connect/subscriptions_controller_spec.rb b/spec/requests/jira_connect/subscriptions_controller_spec.rb index 73a825d471a..1a47c9d8139 100644 --- a/spec/requests/jira_connect/subscriptions_controller_spec.rb +++ b/spec/requests/jira_connect/subscriptions_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe JiraConnect::SubscriptionsController do +RSpec.describe JiraConnect::SubscriptionsController, feature_category: :integrations do describe 'GET /-/jira_connect/subscriptions' do let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'http://self-managed-gitlab.com') } let(:qsh) do diff --git a/yarn.lock b/yarn.lock index 82f0d01c399..27a6deff96a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1651,16 +1651,6 @@ resolved "https://registry.yarnpkg.com/@rails/ujs/-/ujs-6.1.4-7.tgz#ef0b83ef40f64bc6704e13ae6624236a4a91fa6f" integrity sha512-842WcLh0BErNgGE8rdqNh31VnqGQcklPQ7RXzQfA0ilQNZcU7AO+t576g1m//18Lk8m7cXZ8fIKA1YB41LKWAQ== -"@sentry/browser@5.30.0": - version "5.30.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" - integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== - dependencies: - "@sentry/core" "5.30.0" - "@sentry/types" "5.30.0" - "@sentry/utils" "5.30.0" - tslib "^1.9.3" - "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -1672,6 +1662,15 @@ "@sentry/utils" "5.30.0" tslib "^1.9.3" +"@sentry/core@7.21.1": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.21.1.tgz#d0423282d90875625802dfe380f9657e9242b72b" + integrity sha512-Og5wEEsy24fNvT/T7IKjcV4EvVK5ryY2kxbJzKY6GU2eX+i+aBl+n/vp7U0Es351C/AlTkS+0NOUsp2TQQFxZA== + dependencies: + "@sentry/types" "7.21.1" + "@sentry/utils" "7.21.1" + tslib "^1.9.3" + "@sentry/hub@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-5.30.0.tgz#2453be9b9cb903404366e198bd30c7ca74cdc100" @@ -1695,6 +1694,11 @@ resolved "https://registry.yarnpkg.com/@sentry/types/-/types-5.30.0.tgz#19709bbe12a1a0115bc790b8942917da5636f402" integrity sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw== +"@sentry/types@7.21.1": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.21.1.tgz#408a7b95a66ddc30c4359979594e03bee8f9fbdc" + integrity sha512-3/IKnd52Ol21amQvI+kz+WB76s8/LR5YvFJzMgIoI2S8d82smIr253zGijRXxHPEif8kMLX4Yt+36VzrLxg6+A== + "@sentry/utils@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-5.30.0.tgz#9a5bd7ccff85ccfe7856d493bffa64cabc41e980" @@ -1703,6 +1707,14 @@ "@sentry/types" "5.30.0" tslib "^1.9.3" +"@sentry/utils@7.21.1": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.21.1.tgz#96582345178015fd32fe9159c25c44ccf2f99d2a" + integrity sha512-F0W0AAi8tgtTx6ApZRI2S9HbXEA9ENX1phTZgdNNWcMFm1BNbc21XEwLqwXBNjub5nlA6CE8xnjXRgdZKx4kzQ== + dependencies: + "@sentry/types" "7.21.1" + tslib "^1.9.3" + "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -10882,6 +10894,26 @@ send@0.17.2: range-parser "~1.2.1" statuses "~1.5.0" +"sentrybrowser5@npm:@sentry/browser@5.30.0": + version "5.30.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-5.30.0.tgz#c28f49d551db3172080caef9f18791a7fd39e3b3" + integrity sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw== + dependencies: + "@sentry/core" "5.30.0" + "@sentry/types" "5.30.0" + "@sentry/utils" "5.30.0" + tslib "^1.9.3" + +"sentrybrowser7@npm:@sentry/browser@^7.21.1": + version "7.21.1" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-7.21.1.tgz#bffa3ea19050c06400107d2297b9802f9719f98b" + integrity sha512-cS2Jz2+fs9+4pJqLJPtYqGyY97ywJDWAWIR1Yla3hs1QQuH6m0Nz3ojZD1gE2eKH9mHwkGbnNAh+hHcrYrfGzw== + dependencies: + "@sentry/core" "7.21.1" + "@sentry/types" "7.21.1" + "@sentry/utils" "7.21.1" + tslib "^1.9.3" + serialize-javascript@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" |