summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/api.js6
-rw-r--r--app/assets/javascripts/boards/config_toggle.js1
-rw-r--r--app/assets/javascripts/boards/index.js33
-rw-r--r--app/assets/javascripts/boards/toggle_focus.js1
-rw-r--r--app/assets/javascripts/commons/index.js3
-rw-r--r--app/assets/javascripts/commons/nav/user_merge_requests.js67
-rw-r--r--app/assets/javascripts/confidential_merge_request/components/project_form_group.vue12
-rw-r--r--app/assets/javascripts/issuable_bulk_update_actions.js4
-rw-r--r--app/assets/javascripts/issuable_index.js17
-rw-r--r--app/assets/javascripts/issuable_init_bulk_update_sidebar.js19
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue15
-rw-r--r--app/assets/javascripts/monitoring/components/charts/column.vue23
-rw-r--r--app/assets/javascripts/monitoring/components/charts/single_stat.vue29
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue53
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue62
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js4
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js2
-rw-r--r--app/assets/javascripts/monitoring/stores/utils.js27
-rw-r--r--app/assets/javascripts/monitoring/utils.js24
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue11
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js6
-rw-r--r--app/assets/javascripts/notes/stores/actions.js11
-rw-r--r--app/assets/javascripts/pages/groups/issues/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/issues/form.js4
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue3
-rw-r--r--app/assets/javascripts/vue_shared/directives/tooltip.js4
-rw-r--r--app/assets/stylesheets/components/toast.scss13
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss7
-rw-r--r--app/assets/stylesheets/framework/modal.scss25
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/pages/wiki.scss4
-rw-r--r--app/controllers/concerns/issuable_actions.rb4
-rw-r--r--app/controllers/graphql_controller.rb5
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb2
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--app/graphql/gitlab_schema.rb2
-rw-r--r--app/graphql/mutations/notes/base.rb34
-rw-r--r--app/graphql/mutations/notes/create/base.rb49
-rw-r--r--app/graphql/mutations/notes/create/diff_note.rb33
-rw-r--r--app/graphql/mutations/notes/create/image_diff_note.rb33
-rw-r--r--app/graphql/mutations/notes/create/note.rb40
-rw-r--r--app/graphql/mutations/notes/destroy.rb28
-rw-r--r--app/graphql/mutations/notes/update.rb38
-rw-r--r--app/graphql/types/base_input_object.rb1
-rw-r--r--app/graphql/types/diff_paths_input_type.rb12
-rw-r--r--app/graphql/types/diff_refs_type.rb14
-rw-r--r--app/graphql/types/merge_request_type.rb1
-rw-r--r--app/graphql/types/mutation_type.rb5
-rw-r--r--app/graphql/types/notes/diff_image_position_input_type.rb20
-rw-r--r--app/graphql/types/notes/diff_position_base_input_type.rb22
-rw-r--r--app/graphql/types/notes/diff_position_input_type.rb16
-rw-r--r--app/graphql/types/notes/diff_position_type.rb7
-rw-r--r--app/graphql/types/notes/discussion_type.rb8
-rw-r--r--app/helpers/markup_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/storage_helper.rb2
-rw-r--r--app/models/chat_team.rb2
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/concerns/cacheable_attributes.rb2
-rw-r--r--app/models/concerns/project_api_compatibility.rb20
-rw-r--r--app/models/concerns/project_features_compatibility.rb54
-rw-r--r--app/models/concerns/storage/legacy_namespace.rb2
-rw-r--r--app/models/cycle_analytics.rb46
-rw-r--r--app/models/cycle_analytics/base.rb27
-rw-r--r--app/models/cycle_analytics/project_level.rb22
-rw-r--r--app/models/discussion.rb11
-rw-r--r--app/models/environment_status.rb6
-rw-r--r--app/models/label_note.rb18
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb12
-rw-r--r--app/models/project_feature.rb18
-rw-r--r--app/models/project_import_state.rb2
-rw-r--r--app/models/prometheus_metric.rb65
-rw-r--r--app/models/prometheus_metric_enums.rb70
-rw-r--r--app/models/repository.rb4
-rw-r--r--app/models/resource_label_event.rb7
-rw-r--r--app/models/ssh_host_key.rb2
-rw-r--r--app/models/storage/legacy_project.rb2
-rw-r--r--app/models/uploads/base.rb2
-rw-r--r--app/services/akismet_service.rb4
-rw-r--r--app/services/ci/archive_trace_service.rb4
-rw-r--r--app/services/ci/register_job_service.rb2
-rw-r--r--app/services/concerns/exclusive_lease_guard.rb2
-rw-r--r--app/services/groups/destroy_service.rb2
-rw-r--r--app/services/issuable/bulk_update_service.rb10
-rw-r--r--app/services/issuable_base_service.rb4
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/labels/create_service.rb2
-rw-r--r--app/services/merge_requests/merge_base_service.rb11
-rw-r--r--app/services/merge_requests/merge_service.rb8
-rw-r--r--app/services/merge_requests/merge_to_ref_service.rb18
-rw-r--r--app/services/projects/after_import_service.rb2
-rw-r--r--app/services/projects/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb2
-rw-r--r--app/services/projects/hashed_storage/migrate_attachments_service.rb2
-rw-r--r--app/services/projects/hashed_storage/rollback_attachments_service.rb2
-rw-r--r--app/services/projects/hashed_storage/rollback_service.rb2
-rw-r--r--app/services/projects/import_export/export_service.rb4
-rw-r--r--app/services/projects/propagate_service_template.rb2
-rw-r--r--app/services/projects/update_statistics_service.rb2
-rw-r--r--app/services/submit_usage_ping_service.rb2
-rw-r--r--app/services/web_hook_service.rb2
-rw-r--r--app/uploaders/file_mover.rb2
-rw-r--r--app/views/admin/groups/show.html.haml6
-rw-r--r--app/views/admin/projects/show.html.haml6
-rw-r--r--app/views/dashboard/milestones/index.html.haml2
-rw-r--r--app/views/groups/issues.html.haml9
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/projects/_new_project_fields.html.haml20
-rw-r--r--app/views/projects/new.html.haml25
-rw-r--r--app/views/shared/_storage_counter_statistics.html.haml4
-rw-r--r--app/views/shared/issuable/_form.html.haml3
-rw-r--r--app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml2
-rw-r--r--app/workers/concerns/new_issuable.rb2
-rw-r--r--app/workers/create_gpg_signature_worker.rb2
-rw-r--r--app/workers/delete_user_worker.rb2
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb2
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb2
-rw-r--r--app/workers/new_note_worker.rb2
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb4
-rw-r--r--app/workers/repository_fork_worker.rb2
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--app/workers/repository_update_remote_mirror_worker.rb2
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb4
-rw-r--r--app/workers/stuck_import_jobs_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb2
-rw-r--r--app/workers/trending_projects_worker.rb2
-rw-r--r--app/workers/update_merge_requests_worker.rb2
-rw-r--r--app/workers/upload_checksum_worker.rb2
-rw-r--r--changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml5
-rw-r--r--changelogs/unreleased/62826-graphql-note-mutations.yml5
-rw-r--r--changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml5
-rw-r--r--changelogs/unreleased/64066-fix-uneven-click-areas.yml5
-rw-r--r--changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml5
-rw-r--r--changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml5
-rw-r--r--changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml5
-rw-r--r--changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml5
-rw-r--r--changelogs/unreleased/caneldem-master-patch-77839.yml5
-rw-r--r--changelogs/unreleased/embedded-metrics-be-2.yml5
-rw-r--r--changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/fix-unicorn-sampler-workers-count.yml5
-rw-r--r--changelogs/unreleased/fj-fix-subgroup-search-url.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.52.0.yml5
-rw-r--r--changelogs/unreleased/issue_64021.yml5
-rw-r--r--changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml5
-rw-r--r--changelogs/unreleased/patch-29.yml5
-rw-r--r--changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml5
-rw-r--r--changelogs/unreleased/project_api.yml5
-rw-r--r--changelogs/unreleased/tz-update-mr-count-over-tabs.yml6
-rw-r--r--changelogs/unreleased/update-clair-version.yml6
-rw-r--r--changelogs/unreleased/winh-notes-service-deleteNote.yml5
-rw-r--r--changelogs/unreleased/winh-notes-service-toggleAward.yml5
-rw-r--r--config/application.rb1
-rw-r--r--config/brakeman.ignore24
-rw-r--r--config/database_geo.yml.postgresql51
-rw-r--r--config/gitlab.yml.example3
-rw-r--r--config/initializers/0_license.rb19
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/initializers/7_prometheus_metrics.rb2
-rw-r--r--config/initializers/active_record_lifecycle.rb4
-rw-r--r--config/initializers/ar_speed_up_migration_checking.rb3
-rw-r--r--config/initializers/console_message.rb13
-rw-r--r--config/initializers/deprecations.rb2
-rw-r--r--config/initializers/elastic_client_setup.rb50
-rw-r--r--config/initializers/geo.rb17
-rw-r--r--config/initializers/health_check.rb6
-rw-r--r--config/initializers/load_balancing.rb25
-rw-r--r--config/initializers/sidekiq.rb17
-rw-r--r--config/initializers/sidekiq_cluster.rb20
-rw-r--r--config/prometheus/cluster_metrics.yml63
-rw-r--r--config/pseudonymizer.yml475
-rw-r--r--config/routes/api.rb8
-rw-r--r--config/settings.rb25
-rw-r--r--danger/only_documentation/Dangerfile2
-rw-r--r--db/fixtures/development/99_common_metrics.rb4
-rw-r--r--db/fixtures/production/999_common_metrics.rb4
-rw-r--r--db/importers/common_metrics_importer.rb105
-rw-r--r--db/migrate/20180831164910_import_common_metrics.rb4
-rw-r--r--db/migrate/20181006004100_import_common_metrics_nginx_vts.rb4
-rw-r--r--db/migrate/20190326164045_import_common_metrics_knative.rb4
-rw-r--r--db/migrate/20190408163745_prometheus_knative05_fix.rb4
-rw-r--r--db/post_migrate/20180223124427_build_user_interacted_projects_table.rb4
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/administration/auditor_users.md2
-rw-r--r--doc/administration/container_registry.md300
-rw-r--r--doc/administration/database_load_balancing.md26
-rw-r--r--doc/administration/high_availability/database.md83
-rw-r--r--doc/administration/high_availability/gitlab.md2
-rw-r--r--doc/administration/high_availability/pgbouncer.md83
-rw-r--r--doc/administration/img/audit_log.png (renamed from doc/administration/audit_log.png)bin25767 -> 25767 bytes
-rw-r--r--doc/administration/img/auditor_access_form.png (renamed from doc/administration/auditor_access_form.png)bin11910 -> 11910 bytes
-rw-r--r--doc/administration/incoming_email.md40
-rw-r--r--doc/administration/integration/plantuml.md55
-rw-r--r--doc/administration/issue_closing_pattern.md13
-rw-r--r--doc/administration/job_artifacts.md154
-rw-r--r--doc/administration/job_traces.md54
-rw-r--r--doc/administration/merge_request_diffs.md116
-rw-r--r--doc/administration/monitoring/ip_whitelist.md20
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/index.md96
-rw-r--r--doc/administration/monitoring/prometheus/node_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/pgbouncer_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/postgres_exporter.md6
-rw-r--r--doc/administration/monitoring/prometheus/redis_exporter.md6
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md104
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md27
-rw-r--r--doc/administration/packages.md140
-rw-r--r--doc/administration/pages/index.md176
-rw-r--r--doc/administration/pages/source.md248
-rw-r--r--doc/administration/pseudonymizer.md64
-rw-r--r--doc/administration/raketasks/uploads/sanitize.md16
-rw-r--r--doc/administration/reply_by_email_postfix_setup.md384
-rw-r--r--doc/administration/repository_storage_paths.md42
-rw-r--r--doc/administration/repository_storage_types.md19
-rw-r--r--doc/administration/troubleshooting/debug.md108
-rw-r--r--doc/administration/uploads.md82
-rw-r--r--doc/api/README.md21
-rw-r--r--doc/api/graphql/index.md20
-rw-r--r--doc/api/lint.md38
-rw-r--r--doc/api/projects.md123
-rw-r--r--doc/api/repositories.md6
-rw-r--r--doc/api/runners.md8
-rw-r--r--doc/api/users.md25
-rw-r--r--doc/api/vulnerabilities.md2
-rw-r--r--doc/ci/git_submodules.md16
-rw-r--r--doc/development/architecture.md6
-rw-r--r--doc/development/documentation/index.md9
-rw-r--r--doc/development/ee_features.md70
-rw-r--r--doc/development/emails.md94
-rw-r--r--doc/development/gotchas.md8
-rw-r--r--doc/development/logging.md82
-rw-r--r--doc/development/profiling.md8
-rw-r--r--doc/development/prometheus_metrics.md4
-rw-r--r--doc/development/routing.md34
-rw-r--r--doc/development/sql.md4
-rw-r--r--doc/topics/autodevops/index.md11
-rw-r--r--doc/user/discussions/index.md2
-rw-r--r--doc/user/project/integrations/prometheus.md65
-rw-r--r--doc/user/project/packages/npm_registry.md15
-rw-r--r--doc/user/project/settings/import_export.md7
-rw-r--r--doc/workflow/timezone.md8
-rw-r--r--jest.config.js11
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb28
-rw-r--r--lib/api/helpers/graphql_helpers.rb2
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/api/helpers/projects_helpers.rb45
-rw-r--r--lib/api/import_github.rb2
-rw-r--r--lib/api/user_counts.rb18
-rw-r--r--lib/banzai/filter/inline_embeds_filter.rb67
-rw-r--r--lib/banzai/filter/inline_metrics_filter.rb43
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb98
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb1
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/auth/ldap/adapter.rb4
-rw-r--r--lib/gitlab/auth/ldap/config.rb4
-rw-r--r--lib/gitlab/auth/ldap/person.rb6
-rw-r--r--lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb2
-rw-r--r--lib/gitlab/background_migration/archive_legacy_traces.rb2
-rw-r--r--lib/gitlab/background_migration/calculate_wiki_sizes.rb2
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb2
-rw-r--r--lib/gitlab/background_migration/fix_cross_project_label_links.rb2
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads.rb2
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/PHP.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml1
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files.rb2
-rw-r--r--lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb2
-rw-r--r--lib/gitlab/cleanup/project_upload_file_finder.rb2
-rw-r--r--lib/gitlab/cleanup/project_uploads.rb2
-rw-r--r--lib/gitlab/cleanup/remote_uploads.rb2
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb18
-rw-r--r--lib/gitlab/cycle_analytics/base_query.rb4
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb49
-rw-r--r--lib/gitlab/cycle_analytics/code_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/issue_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/issue_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/permissions.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/production_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/production_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/review_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/test_helper.rb2
-rw-r--r--lib/gitlab/cycle_analytics/usage_data.rb2
-rw-r--r--lib/gitlab/database.rb2
-rw-r--r--lib/gitlab/database/median.rb2
-rw-r--r--lib/gitlab/database/migration_helpers.rb9
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb2
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb2
-rw-r--r--lib/gitlab/database_importers/common_metrics.rb8
-rw-r--r--lib/gitlab/database_importers/common_metrics/importer.rb78
-rw-r--r--lib/gitlab/database_importers/common_metrics/prometheus_metric.rb12
-rw-r--r--lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb40
-rw-r--r--lib/gitlab/email/hook/disable_email_interceptor.rb2
-rw-r--r--lib/gitlab/encoding_helper.rb2
-rw-r--r--lib/gitlab/git/repository.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb25
-rw-r--r--lib/gitlab/github_import/importer/lfs_objects_importer.rb2
-rw-r--r--lib/gitlab/github_import/importer/pull_requests_importer.rb2
-rw-r--r--lib/gitlab/global_id.rb14
-rw-r--r--lib/gitlab/graphql.rb4
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb4
-rw-r--r--lib/gitlab/health_checks/simple_abstract_check.rb2
-rw-r--r--lib/gitlab/import_export/merge_request_parser.rb2
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb2
-rw-r--r--lib/gitlab/import_export/saver.rb2
-rw-r--r--lib/gitlab/import_export/version_checker.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/url.rb40
-rw-r--r--lib/gitlab/metrics/samplers/base_sampler.rb2
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb2
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb11
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb2
-rw-r--r--lib/gitlab/pages_client.rb2
-rw-r--r--lib/gitlab/reference_counter.rb4
-rw-r--r--lib/gitlab/repository_cache_adapter.rb2
-rw-r--r--lib/gitlab/sanitizers/exif.rb2
-rw-r--r--lib/gitlab/shell.rb12
-rw-r--r--lib/mattermost/session.rb2
-rw-r--r--lib/microsoft_teams/notifier.rb2
-rw-r--r--lib/peek/views/redis.rb8
-rw-r--r--lib/rspec_flaky/listener.rb2
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
-rw-r--r--locale/gitlab.pot64
-rw-r--r--package.json2
-rw-r--r--qa/.rspec_parallel5
-rw-r--r--qa/Gemfile2
-rw-r--r--qa/Gemfile.lock6
-rw-r--r--qa/qa.rb1
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/runtime/browser.rb6
-rw-r--r--qa/qa/runtime/env.rb12
-rw-r--r--qa/qa/runtime/scenario.rb6
-rw-r--r--qa/qa/scenario/shared_attributes.rb1
-rw-r--r--qa/qa/service/kubernetes_cluster.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb3
-rw-r--r--qa/qa/specs/parallel_runner.rb33
-rw-r--r--qa/qa/specs/runner.rb58
-rw-r--r--qa/qa/tools/generate_perf_testdata.rb65
-rw-r--r--qa/spec/page/logging_spec.rb8
-rw-r--r--qa/spec/spec_helper.rb4
-rw-r--r--qa/spec/specs/parallel_runner_spec.rb58
-rw-r--r--qa/spec/specs/runner_spec.rb8
-rw-r--r--rubocop/cop/gitlab/rails_logger.rb51
-rw-r--r--rubocop/rubocop.rb1
-rwxr-xr-xscripts/trigger-build-docs4
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb65
-rw-r--r--spec/features/admin/admin_sees_project_statistics_spec.rb2
-rw-r--r--spec/features/dashboard/milestones_spec.rb14
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb124
-rw-r--r--spec/frontend/api_spec.js16
-rw-r--r--spec/frontend/commons/nav/user_merge_requests_spec.js113
-rw-r--r--spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap12
-rw-r--r--spec/frontend/error_tracking_settings/mock.js2
-rw-r--r--spec/graphql/gitlab_schema_spec.rb20
-rw-r--r--spec/graphql/types/diff_refs_type_spec.rb9
-rw-r--r--spec/graphql/types/merge_request_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/diff_position_type_spec.rb2
-rw-r--r--spec/graphql/types/notes/discussion_type_spec.rb2
-rw-r--r--spec/helpers/markup_helper_spec.rb73
-rw-r--r--spec/helpers/storage_helper_spec.rb2
-rw-r--r--spec/javascripts/issuable_spec.js12
-rw-r--r--spec/javascripts/monitoring/charts/single_stat_spec.js9
-rw-r--r--spec/javascripts/monitoring/mock_data.js72
-rw-r--r--spec/javascripts/monitoring/store/mutations_spec.js2
-rw-r--r--spec/javascripts/monitoring/utils_spec.js27
-rw-r--r--spec/javascripts/notes/components/comment_form_spec.js15
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js20
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js3
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb55
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb58
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb34
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb2
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb (renamed from spec/db/importers/common_metrics_importer_spec.rb)21
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb16
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb2
-rw-r--r--spec/lib/gitlab/global_id_spec.rb37
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb56
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb33
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb36
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb38
-rw-r--r--spec/models/concerns/project_features_compatibility_spec.rb18
-rw-r--r--spec/models/cycle_analytics/code_spec.rb3
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb3
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb3
-rw-r--r--spec/models/cycle_analytics/production_spec.rb3
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb (renamed from spec/models/cycle_analytics_spec.rb)8
-rw-r--r--spec/models/cycle_analytics/review_spec.rb3
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb2
-rw-r--r--spec/models/cycle_analytics/test_spec.rb3
-rw-r--r--spec/models/discussion_spec.rb14
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb70
-rw-r--r--spec/requests/api/graphql/mutations/notes/create/note_spec.rb64
-rw-r--r--spec/requests/api/graphql/mutations/notes/destroy_spec.rb52
-rw-r--r--spec/requests/api/graphql/mutations/notes/update_spec.rb72
-rw-r--r--spec/requests/api/graphql_spec.rb10
-rw-r--r--spec/requests/api/projects_spec.rb54
-rw-r--r--spec/requests/api/user_counts_spec.rb40
-rw-r--r--spec/routing/api_routing_spec.rb23
-rw-r--r--spec/routing/project_routing_spec.rb6
-rw-r--r--spec/rubocop/cop/gitlab/rails_logger_spec.rb42
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb496
-rw-r--r--spec/services/issues/update_service_spec.rb16
-rw-r--r--spec/services/merge_requests/merge_service_spec.rb13
-rw-r--r--spec/services/merge_requests/merge_to_ref_service_spec.rb13
-rw-r--r--spec/support/helpers/git_http_helpers.rb4
-rw-r--r--spec/support/helpers/graphql_helpers.rb15
-rw-r--r--spec/support/shared_examples/graphql/notes_creation_shared_examples.rb61
-rw-r--r--yarn.lock14
428 files changed, 6501 insertions, 2679 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index ba0a719118c..a63cb35e6f0 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.51.0
+1.52.0
diff --git a/Gemfile b/Gemfile
index 33f9c4650ef..f32e899342b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -420,7 +420,7 @@ gem 'vmstat', '~> 2.3.0'
gem 'sys-filesystem', '~> 1.1.6'
# SSH host key support
-gem 'net-ssh', '~> 5.0'
+gem 'net-ssh', '~> 5.2'
gem 'sshkey', '~> 2.0'
# Required for ED25519 SSH host key support
diff --git a/Gemfile.lock b/Gemfile.lock
index c8b8f0e4f90..85b4c32f168 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -523,7 +523,7 @@ GEM
mysql2 (0.4.10)
nakayoshi_fork (0.0.4)
net-ldap (0.16.0)
- net-ssh (5.0.1)
+ net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
nokogiri (1.10.3)
@@ -1152,7 +1152,7 @@ DEPENDENCIES
mysql2 (~> 0.4.10)
nakayoshi_fork (~> 0.0.4)
net-ldap
- net-ssh (~> 5.0)
+ net-ssh (~> 5.2)
nokogiri (~> 1.10.3)
oauth2 (~> 1.4)
octokit (~> 4.9)
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index 4f66a5d080c..a649c521405 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -24,6 +24,7 @@ const Api = {
issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key',
projectTemplatePath: '/api/:version/projects/:id/templates/:type/:key',
projectTemplatesPath: '/api/:version/projects/:id/templates/:type',
+ userCountsPath: '/api/:version/user_counts',
usersPath: '/api/:version/users.json',
userPath: '/api/:version/users/:id',
userStatusPath: '/api/:version/users/:id/status',
@@ -312,6 +313,11 @@ const Api = {
});
},
+ userCounts() {
+ const url = Api.buildUrl(this.userCountsPath);
+ return axios.get(url);
+ },
+
userStatus(id, options) {
const url = Api.buildUrl(this.userStatusPath).replace(':id', encodeURIComponent(id));
return axios.get(url, {
diff --git a/app/assets/javascripts/boards/config_toggle.js b/app/assets/javascripts/boards/config_toggle.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/boards/config_toggle.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index 23b107abefa..d6a5372b22d 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -7,28 +7,30 @@ import { __ } from '~/locale';
import './models/label';
import './models/assignee';
-import FilteredSearchBoards from './filtered_search_boards';
-import eventHub from './eventhub';
+import FilteredSearchBoards from '~/boards/filtered_search_boards';
+import eventHub from '~/boards/eventhub';
import sidebarEventHub from '~/sidebar/event_hub';
-import './models/issue';
-import './models/list';
-import './models/milestone';
-import './models/project';
-import boardsStore from './stores/boards_store';
-import ModalStore from './stores/modal_store';
-import BoardService from './services/board_service';
-import modalMixin from './mixins/modal_mixins';
-import './filters/due_date_filters';
-import Board from './components/board';
-import BoardSidebar from './components/board_sidebar';
+import 'ee_else_ce/boards/models/issue';
+import 'ee_else_ce/boards/models/list';
+import '~/boards/models/milestone';
+import '~/boards/models/project';
+import boardsStore from '~/boards/stores/boards_store';
+import ModalStore from '~/boards/stores/modal_store';
+import BoardService from 'ee_else_ce/boards/services/board_service';
+import modalMixin from '~/boards/mixins/modal_mixins';
+import '~/boards/filters/due_date_filters';
+import Board from 'ee_else_ce/boards/components/board';
+import BoardSidebar from 'ee_else_ce/boards/components/board_sidebar';
import initNewListDropdown from 'ee_else_ce/boards/components/new_list_dropdown';
-import BoardAddIssuesModal from './components/modal/index.vue';
+import BoardAddIssuesModal from '~/boards/components/modal/index.vue';
import '~/vue_shared/vue_resource_interceptor';
import {
NavigationType,
convertObjectPropsToCamelCase,
parseBoolean,
} from '~/lib/utils/common_utils';
+import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
+import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
let issueBoardsApp;
@@ -207,6 +209,8 @@ export default () => {
},
});
+ boardConfigToggle(boardsStore);
+
const issueBoardsModal = document.getElementById('js-add-issues-btn');
if (issueBoardsModal) {
@@ -281,5 +285,6 @@ export default () => {
});
}
+ toggleFocusMode(ModalStore, boardsStore);
mountMultipleBoardsSwitcher();
};
diff --git a/app/assets/javascripts/boards/toggle_focus.js b/app/assets/javascripts/boards/toggle_focus.js
new file mode 100644
index 00000000000..2d1ec238274
--- /dev/null
+++ b/app/assets/javascripts/boards/toggle_focus.js
@@ -0,0 +1 @@
+export default () => {};
diff --git a/app/assets/javascripts/commons/index.js b/app/assets/javascripts/commons/index.js
index 0d2fe2925d8..ad0f6cc1496 100644
--- a/app/assets/javascripts/commons/index.js
+++ b/app/assets/javascripts/commons/index.js
@@ -4,3 +4,6 @@ import './jquery';
import './bootstrap';
import './vue';
import '../lib/utils/axios_utils';
+import { openUserCountsBroadcast } from './nav/user_merge_requests';
+
+openUserCountsBroadcast();
diff --git a/app/assets/javascripts/commons/nav/user_merge_requests.js b/app/assets/javascripts/commons/nav/user_merge_requests.js
new file mode 100644
index 00000000000..8e694cca6a1
--- /dev/null
+++ b/app/assets/javascripts/commons/nav/user_merge_requests.js
@@ -0,0 +1,67 @@
+import Api from '~/api';
+
+let channel;
+
+function broadcastCount(newCount) {
+ if (!channel) {
+ return;
+ }
+
+ channel.postMessage(newCount);
+}
+
+function updateUserMergeRequestCounts(newCount) {
+ const mergeRequestsCountEl = document.querySelector('.merge-requests-count');
+ mergeRequestsCountEl.textContent = newCount.toLocaleString();
+ mergeRequestsCountEl.classList.toggle('hidden', Number(newCount) === 0);
+}
+
+/**
+ * Refresh user counts (and broadcast if open)
+ */
+export function refreshUserMergeRequestCounts() {
+ return Api.userCounts()
+ .then(({ data }) => {
+ const count = data.merge_requests;
+
+ updateUserMergeRequestCounts(count);
+ broadcastCount(count);
+ })
+ .catch(ex => {
+ console.error(ex); // eslint-disable-line no-console
+ });
+}
+
+/**
+ * Close the broadcast channel for user counts
+ */
+export function closeUserCountsBroadcast() {
+ if (!channel) {
+ return;
+ }
+
+ channel.close();
+ channel = null;
+}
+
+/**
+ * Open the broadcast channel for user counts, adds user id so we only update
+ *
+ * **Please note:**
+ * Not supported in all browsers, but not polyfilling for now
+ * to keep bundle size small and
+ * no special functionality lost except cross tab notifications
+ */
+export function openUserCountsBroadcast() {
+ closeUserCountsBroadcast();
+
+ if (window.BroadcastChannel) {
+ const currentUserId = typeof gon !== 'undefined' && gon && gon.current_user_id;
+ if (currentUserId) {
+ channel = new BroadcastChannel(`mr_count_channel_${currentUserId}`);
+ channel.onmessage = ev => {
+ updateUserMergeRequestCounts(ev.data);
+ };
+ }
+ }
+}
diff --git a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
index b89729375be..197a0706062 100644
--- a/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
+++ b/app/assets/javascripts/confidential_merge_request/components/project_form_group.vue
@@ -41,7 +41,7 @@ export default {
noForkText() {
return sprintf(
__(
- 'To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.',
+ "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private.",
),
{ link_start: `<a href="${this.newForkPath}" class="help-link">`, link_end: '</a>' },
false,
@@ -105,7 +105,7 @@ export default {
</script>
<template>
- <div class="form-group">
+ <div class="confidential-merge-request-fork-group form-group">
<label>{{ __('Project') }}</label>
<div>
<dropdown
@@ -118,7 +118,7 @@ export default {
<template v-if="projects.length">
{{
__(
- 'To protect this issues confidentiality, a private fork of this project was selected.',
+ "To protect this issue's confidentiality, a private fork of this project was selected.",
)
}}
</template>
@@ -126,7 +126,11 @@ export default {
{{ __('No forks available to you.') }}<br />
<span v-html="noForkText"></span>
</template>
- <gl-link :href="helpPagePath" class="help-link" target="_blank">
+ <gl-link
+ :href="helpPagePath"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
+ target="_blank"
+ >
<span class="sr-only">{{ __('Read more') }}</span>
<i class="fa fa-question-circle" aria-hidden="true"></i>
</gl-link>
diff --git a/app/assets/javascripts/issuable_bulk_update_actions.js b/app/assets/javascripts/issuable_bulk_update_actions.js
index bc9d7fcf30d..c855f3973b0 100644
--- a/app/assets/javascripts/issuable_bulk_update_actions.js
+++ b/app/assets/javascripts/issuable_bulk_update_actions.js
@@ -1,4 +1,4 @@
-/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback, no-unused-vars */
+/* eslint-disable consistent-return, func-names, array-callback-return, prefer-arrow-callback */
import $ from 'jquery';
import _ from 'underscore';
@@ -7,7 +7,7 @@ import Flash from './flash';
import { __ } from './locale';
export default {
- init({ container, form, issues, prefixId } = {}) {
+ init({ form, issues, prefixId } = {}) {
this.prefixId = prefixId || 'issue_';
this.form = form || this.getElement('.bulk-update');
this.$labelDropdown = this.form.find('.js-label-select');
diff --git a/app/assets/javascripts/issuable_index.js b/app/assets/javascripts/issuable_index.js
index 16f88cddce3..f3f8b6ec715 100644
--- a/app/assets/javascripts/issuable_index.js
+++ b/app/assets/javascripts/issuable_index.js
@@ -2,26 +2,13 @@ import $ from 'jquery';
import axios from './lib/utils/axios_utils';
import flash from './flash';
import { s__, __ } from './locale';
-import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
-import IssuableBulkUpdateActions from './issuable_bulk_update_actions';
+import issuableInitBulkUpdateSidebar from './issuable_init_bulk_update_sidebar';
export default class IssuableIndex {
constructor(pagePrefix) {
- this.initBulkUpdate(pagePrefix);
+ issuableInitBulkUpdateSidebar.init(pagePrefix);
IssuableIndex.resetIncomingEmailToken();
}
- initBulkUpdate(pagePrefix) {
- const userCanBulkUpdate = $('.issues-bulk-update').length > 0;
- const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
-
- if (userCanBulkUpdate && !alreadyInitialized) {
- IssuableBulkUpdateActions.init({
- prefixId: pagePrefix,
- });
-
- this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
- }
- }
static resetIncomingEmailToken() {
const $resetToken = $('.incoming-email-token-reset');
diff --git a/app/assets/javascripts/issuable_init_bulk_update_sidebar.js b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
new file mode 100644
index 00000000000..da8969c80f3
--- /dev/null
+++ b/app/assets/javascripts/issuable_init_bulk_update_sidebar.js
@@ -0,0 +1,19 @@
+import IssuableBulkUpdateSidebar from './issuable_bulk_update_sidebar';
+import issuableBulkUpdateActions from './issuable_bulk_update_actions';
+
+export default {
+ bulkUpdateSidebar: null,
+
+ init(prefixId) {
+ const bulkUpdateEl = document.querySelector('.issues-bulk-update');
+ const alreadyInitialized = Boolean(this.bulkUpdateSidebar);
+
+ if (bulkUpdateEl && !alreadyInitialized) {
+ issuableBulkUpdateActions.init({ prefixId });
+
+ this.bulkUpdateSidebar = new IssuableBulkUpdateSidebar();
+ }
+
+ return this.bulkUpdateSidebar;
+ },
+};
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 81773bd140e..454ff4f284e 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -8,6 +8,7 @@ import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import Icon from '~/vue_shared/components/icon.vue';
import { chartHeight, graphTypes, lineTypes } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
let debouncedResize;
@@ -23,19 +24,7 @@ export default {
graphData: {
type: Object,
required: true,
- validator(data) {
- return (
- Array.isArray(data.queries) &&
- data.queries.filter(query => {
- if (Array.isArray(query.result)) {
- return (
- query.result.filter(res => Array.isArray(res.values)).length === query.result.length
- );
- }
- return false;
- }).length === data.queries.length
- );
- },
+ validator: graphDataValidatorForValues.bind(null, false),
},
containerWidth: {
type: Number,
diff --git a/app/assets/javascripts/monitoring/components/charts/column.vue b/app/assets/javascripts/monitoring/components/charts/column.vue
index 05a2036f4c3..83136d43479 100644
--- a/app/assets/javascripts/monitoring/components/charts/column.vue
+++ b/app/assets/javascripts/monitoring/components/charts/column.vue
@@ -4,6 +4,7 @@ import { debounceByAnimationFrame } from '~/lib/utils/common_utils';
import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
import { chartHeight } from '../../constants';
import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
@@ -14,23 +15,11 @@ export default {
graphData: {
type: Object,
required: true,
- validator(data) {
- return (
- Array.isArray(data.queries) &&
- data.queries.filter(query => {
- if (Array.isArray(query.result)) {
- return (
- query.result.filter(res => Array.isArray(res.values)).length === query.result.length
- );
- }
- return false;
- }).length === data.queries.length
- );
- },
- containerWidth: {
- type: Number,
- required: true,
- },
+ validator: graphDataValidatorForValues.bind(null, false),
+ },
+ containerWidth: {
+ type: Number,
+ required: true,
},
},
data() {
diff --git a/app/assets/javascripts/monitoring/components/charts/single_stat.vue b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
index b03a6ca1806..7428b27a9c3 100644
--- a/app/assets/javascripts/monitoring/components/charts/single_stat.vue
+++ b/app/assets/javascripts/monitoring/components/charts/single_stat.vue
@@ -1,5 +1,7 @@
<script>
import { GlSingleStat } from '@gitlab/ui/dist/charts';
+import { roundOffFloat } from '~/lib/utils/common_utils';
+import { graphDataValidatorForValues } from '../../utils';
export default {
components: {
@@ -7,22 +9,21 @@ export default {
},
inheritAttrs: false,
props: {
- title: {
- type: String,
- required: true,
- },
- value: {
- type: Number,
- required: true,
- },
- unit: {
- type: String,
+ graphData: {
+ type: Object,
required: true,
+ validator: graphDataValidatorForValues.bind(null, true),
},
},
computed: {
- valueWithUnit() {
- return `${this.value}${this.unit}`;
+ queryInfo() {
+ return this.graphData.queries[0];
+ },
+ engineeringNotation() {
+ return `${roundOffFloat(this.queryInfo.result[0].value[1], 1)}${this.queryInfo.unit}`;
+ },
+ graphTitle() {
+ return this.queryInfo.label;
},
},
};
@@ -30,8 +31,8 @@ export default {
<template>
<div class="prometheus-graph col-12 col-lg-6">
<div class="prometheus-graph-header">
- <h5 ref="graphTitle" class="prometheus-graph-title">{{ title }}</h5>
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
</div>
- <gl-single-stat :value="valueWithUnit" :title="title" variant="success" />
+ <gl-single-stat :value="engineeringNotation" :title="graphTitle" variant="success" />
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index ba79a697df2..6eaced0c108 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -7,6 +7,8 @@ import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import MonitorAreaChart from './charts/area.vue';
+import MonitorSingleStatChart from './charts/single_stat.vue';
+import PanelType from './panel_type.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
import { timeWindows, timeWindowsKeyNames } from '../constants';
@@ -18,6 +20,8 @@ let sidebarMutationObserver;
export default {
components: {
MonitorAreaChart,
+ MonitorSingleStatChart,
+ PanelType,
GraphGroup,
EmptyState,
Icon,
@@ -152,6 +156,7 @@ export default {
'useDashboardEndpoint',
'allDashboards',
'multipleDashboardsEnabled',
+ 'additionalPanelTypesEnabled',
]),
groupsWithData() {
return this.groups.filter(group => this.chartsWithData(group.metrics).length > 0);
@@ -173,6 +178,7 @@ export default {
deploymentsEndpoint: this.deploymentsEndpoint,
dashboardEndpoint: this.dashboardEndpoint,
currentDashboard: this.currentDashboard,
+ projectPath: this.projectPath,
});
this.timeWindows = timeWindows;
@@ -220,6 +226,8 @@ export default {
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
+ // TODO: BEGIN, Duplicated code with panel_type until feature flag is removed
+ // Issue number: https://gitlab.com/gitlab-org/gitlab-ce/issues/63845
getGraphAlerts(queries) {
if (!this.allAlerts) return {};
const metricIdsForChart = queries.map(q => q.metricId);
@@ -228,6 +236,7 @@ export default {
getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries));
},
+ // TODO: END
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -366,24 +375,34 @@ export default {
:name="groupData.group"
:show-panels="showPanels"
>
- <monitor-area-chart
- v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
- :key="graphIndex"
- :project-path="projectPath"
- :graph-data="graphData"
- :deployment-data="deploymentData"
- :thresholds="getGraphAlertValues(graphData.queries)"
- :container-width="elWidth"
- group-id="monitor-area-chart"
- >
- <alert-widget
- v-if="alertWidgetAvailable && graphData"
- :alerts-endpoint="alertsEndpoint"
- :relevant-queries="graphData.queries"
- :alerts-to-manage="getGraphAlerts(graphData.queries)"
- @setAlerts="setAlerts"
+ <template v-if="additionalPanelTypesEnabled">
+ <panel-type
+ v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
+ :key="`panel-type-${graphIndex}`"
+ :graph-data="graphData"
+ :dashboard-width="elWidth"
/>
- </monitor-area-chart>
+ </template>
+ <template v-else>
+ <monitor-area-chart
+ v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
+ :key="graphIndex"
+ :graph-data="graphData"
+ :deployment-data="deploymentData"
+ :thresholds="getGraphAlertValues(graphData.queries)"
+ :container-width="elWidth"
+ :project-path="projectPath"
+ group-id="monitor-area-chart"
+ >
+ <alert-widget
+ v-if="alertWidgetAvailable && graphData"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
+ </template>
</graph-group>
</div>
<empty-state
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
new file mode 100644
index 00000000000..45ee067de83
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -0,0 +1,62 @@
+<script>
+import { mapState } from 'vuex';
+import _ from 'underscore';
+import MonitorAreaChart from './charts/area.vue';
+import MonitorSingleStatChart from './charts/single_stat.vue';
+
+export default {
+ components: {
+ MonitorAreaChart,
+ MonitorSingleStatChart,
+ },
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ },
+ dashboardWidth: {
+ type: Number,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
+ alertWidgetAvailable() {
+ return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
+ },
+ },
+ methods: {
+ getGraphAlerts(queries) {
+ if (!this.allAlerts) return {};
+ const metricIdsForChart = queries.map(q => q.metricId);
+ return _.pick(this.allAlerts, alert => metricIdsForChart.includes(alert.metricId));
+ },
+ getGraphAlertValues(queries) {
+ return Object.values(this.getGraphAlerts(queries));
+ },
+ isPanelType(type) {
+ return this.graphData.type && this.graphData.type === type;
+ },
+ },
+};
+</script>
+<template>
+ <monitor-single-stat-chart v-if="isPanelType('single-stat')" :graph-data="graphData" />
+ <monitor-area-chart
+ v-else
+ :graph-data="graphData"
+ :deployment-data="deploymentData"
+ :project-path="projectPath"
+ :thresholds="getGraphAlertValues(graphData.queries)"
+ :container-width="dashboardWidth"
+ group-id="monitor-area-chart"
+ >
+ <alert-widget
+ v-if="alertWidgetAvailable"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ </monitor-area-chart>
+</template>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 97d149e9ad5..c0fee1ebb99 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -12,6 +12,7 @@ export default (props = {}) => {
store.dispatch('monitoringDashboard/setFeatureFlags', {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
+ additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
});
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 0fa2a5d6370..5b3da51e9a6 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -37,10 +37,11 @@ export const setEndpoints = ({ commit }, endpoints) => {
export const setFeatureFlags = (
{ commit },
- { prometheusEndpointEnabled, multipleDashboardsEnabled },
+ { prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
+ commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
};
export const requestMetricsDashboard = ({ commit }) => {
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 2c78a0b9315..c89daba3df7 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -11,6 +11,7 @@ export const SET_QUERY_RESULT = 'SET_QUERY_RESULT';
export const SET_TIME_WINDOW = 'SET_TIME_WINDOW';
export const SET_DASHBOARD_ENABLED = 'SET_DASHBOARD_ENABLED';
export const SET_MULTIPLE_DASHBOARDS_ENABLED = 'SET_MULTIPLE_DASHBOARDS_ENABLED';
+export const SET_ADDITIONAL_PANEL_TYPES_ENABLED = 'SET_ADDITIONAL_PANEL_TYPES_ENABLED';
export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index a85a7723c1f..0104dcb867d 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -75,6 +75,7 @@ export default {
state.deploymentsEndpoint = endpoints.deploymentsEndpoint;
state.dashboardEndpoint = endpoints.dashboardEndpoint;
state.currentDashboard = endpoints.currentDashboard;
+ state.projectPath = endpoints.projectPath;
},
[types.SET_DASHBOARD_ENABLED](state, enabled) {
state.useDashboardEndpoint = enabled;
@@ -92,4 +93,7 @@ export default {
[types.SET_ALL_DASHBOARDS](state, dashboards) {
state.allDashboards = dashboards;
},
+ [types.SET_ADDITIONAL_PANEL_TYPES_ENABLED](state, enabled) {
+ state.additionalPanelTypesEnabled = enabled;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index de711d6ccae..e54bb712695 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -9,6 +9,7 @@ export default () => ({
dashboardEndpoint: invalidUrl,
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
+ additionalPanelTypesEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
groups: [],
@@ -17,4 +18,5 @@ export default () => ({
metricsWithData: [],
allDashboards: [],
currentDashboard: null,
+ projectPath: null,
});
diff --git a/app/assets/javascripts/monitoring/stores/utils.js b/app/assets/javascripts/monitoring/stores/utils.js
index 721942f9d3b..938ee2f0a9a 100644
--- a/app/assets/javascripts/monitoring/stores/utils.js
+++ b/app/assets/javascripts/monitoring/stores/utils.js
@@ -69,13 +69,26 @@ export const sortMetrics = metrics =>
.sortBy('weight')
.value();
-export const normalizeQueryResult = timeSeries => ({
- ...timeSeries,
- values: timeSeries.values.map(([timestamp, value]) => [
- new Date(timestamp * 1000).toISOString(),
- Number(value),
- ]),
-});
+export const normalizeQueryResult = timeSeries => {
+ let normalizedResult = {};
+
+ if (timeSeries.values) {
+ normalizedResult = {
+ ...timeSeries,
+ values: timeSeries.values.map(([timestamp, value]) => [
+ new Date(timestamp * 1000).toISOString(),
+ Number(value),
+ ]),
+ };
+ } else if (timeSeries.value) {
+ normalizedResult = {
+ ...timeSeries,
+ value: [new Date(timeSeries.value[0] * 1000).toISOString(), Number(timeSeries.value[1])],
+ };
+ }
+
+ return normalizedResult;
+};
export const normalizeMetrics = metrics => {
const groupedMetrics = groupQueriesByChartInfo(metrics);
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index ef309c8a398..478e2b3d06c 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -30,4 +30,28 @@ export const getTimeDiff = selectedTimeWindow => {
return { start, end };
};
+/**
+ * This method is used to validate if the graph data format for a chart component
+ * that needs a time series as a response from a prometheus query (query_range) is
+ * of a valid format or not.
+ * @param {Object} graphData the graph data response from a prometheus request
+ * @returns {boolean} whether the graphData format is correct
+ */
+export const graphDataValidatorForValues = (isValues, graphData) => {
+ const responseValueKeyName = isValues ? 'value' : 'values';
+
+ return (
+ Array.isArray(graphData.queries) &&
+ graphData.queries.filter(query => {
+ if (Array.isArray(query.result)) {
+ return (
+ query.result.filter(res => Array.isArray(res[responseValueKeyName])).length ===
+ query.result.length
+ );
+ }
+ return false;
+ }).length === graphData.queries.length
+ );
+};
+
export default {};
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 6c1738f0f1b..fda494fec07 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -13,6 +13,7 @@ import {
splitCamelCase,
slugifyWithUnderscore,
} from '../../lib/utils/text_utility';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import * as constants from '../constants';
import eventHub from '../event_hub';
import issueWarning from '../../vue_shared/components/issue/issue_warning.vue';
@@ -234,7 +235,10 @@ export default {
toggleIssueState() {
if (this.isOpen) {
this.closeIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
this.enableButton();
this.toggleStateButtonLoading(false);
@@ -247,7 +251,10 @@ export default {
});
} else {
this.reopenIssue()
- .then(() => this.enableButton())
+ .then(() => {
+ this.enableButton();
+ refreshUserMergeRequestCounts();
+ })
.catch(({ data }) => {
this.enableButton();
this.toggleStateButtonLoading(false);
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 47a6f07cce2..9e0392110b6 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -9,9 +9,6 @@ export default {
const config = filter !== undefined ? { params: { notes_filter: filter } } : null;
return Vue.http.get(endpoint, config);
},
- deleteNote(endpoint) {
- return Vue.http.delete(endpoint);
- },
replyToDiscussion(endpoint, data) {
return Vue.http.post(endpoint, data, { emulateJSON: true });
},
@@ -38,9 +35,6 @@ export default {
return Vue.http.get(endpoint, options);
},
- toggleAward(endpoint, data) {
- return Vue.http.post(endpoint, data, { emulateJSON: true });
- },
toggleIssueState(endpoint, data) {
return Vue.http.put(endpoint, data);
},
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index fef962f008e..30eab272aa9 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -62,7 +62,7 @@ export const updateDiscussion = ({ commit, state }, discussion) => {
};
export const deleteNote = ({ commit, dispatch, state }, note) =>
- service.deleteNote(note.path).then(() => {
+ axios.delete(note.path).then(() => {
const discussion = state.discussions.find(({ id }) => id === note.discussion_id);
commit(types.DELETE_NOTE, note);
@@ -384,12 +384,9 @@ export const toggleAward = ({ commit, getters }, { awardName, noteId }) => {
export const toggleAwardRequest = ({ dispatch }, data) => {
const { endpoint, awardName } = data;
- return service
- .toggleAward(endpoint, { name: awardName })
- .then(res => res.json())
- .then(() => {
- dispatch('toggleAward', data);
- });
+ return axios.post(endpoint, { name: awardName }).then(() => {
+ dispatch('toggleAward', data);
+ });
};
export const scrollToNoteIfNeeded = (context, el) => {
diff --git a/app/assets/javascripts/pages/groups/issues/index.js b/app/assets/javascripts/pages/groups/issues/index.js
index 23fb5656008..dcdee77a8ab 100644
--- a/app/assets/javascripts/pages/groups/issues/index.js
+++ b/app/assets/javascripts/pages/groups/issues/index.js
@@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import { FILTERED_SEARCH } from '~/pages/constants';
import IssuableFilteredSearchTokenKeys from 'ee_else_ce/filtered_search/issuable_filtered_search_token_keys';
import initManualOrdering from '~/manual_ordering';
+const ISSUE_BULK_UPDATE_PREFIX = 'issue_';
+
document.addEventListener('DOMContentLoaded', () => {
IssuableFilteredSearchTokenKeys.addExtraTokensForIssues();
+ issuableInitBulkUpdateSidebar.init(ISSUE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.ISSUES,
diff --git a/app/assets/javascripts/pages/projects/issues/form.js b/app/assets/javascripts/pages/projects/issues/form.js
index 941c4552579..2205a7bafe3 100644
--- a/app/assets/javascripts/pages/projects/issues/form.js
+++ b/app/assets/javascripts/pages/projects/issues/form.js
@@ -17,7 +17,5 @@ export default () => {
new MilestoneSelect();
new IssuableTemplateSelectors();
- if (gon.features.graphql) {
- initSuggestions();
- }
+ initSuggestions();
};
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 1e66ccbfa29..0d9e992e596 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -76,7 +76,7 @@ export default {
variables: {
projectPath: this.projectPath,
ref: this.ref,
- path: this.path,
+ path: this.path || '/',
nextPageCursor: this.nextPageCursor,
pageSize: PAGE_SIZE,
},
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index 70dc3d2cdfa..be1e4811856 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -2,6 +2,7 @@
import Flash from '~/flash';
import eventHub from '~/sidebar/event_hub';
import Store from '~/sidebar/stores/sidebar_store';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import AssigneeTitle from './assignee_title.vue';
import Assignees from './assignees.vue';
import { __ } from '~/locale';
@@ -73,6 +74,9 @@ export default {
this.mediator
.saveAssignees(this.field)
.then(setLoadingFalse.bind(this))
+ .then(() => {
+ refreshUserMergeRequestCounts();
+ })
.catch(() => {
setLoadingFalse();
return new Flash(__('Error occurred when saving assignees'));
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index d1f75593d14..d4514767912 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -6,6 +6,7 @@ import simplePoll from '~/lib/utils/simple_poll';
import { __ } from '~/locale';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
import MergeRequest from '../../../merge_request';
+import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
@@ -174,6 +175,8 @@ export default {
MergeRequest.decreaseCounter();
stopPolling();
+ refreshUserMergeRequestCounts();
+
// If user checked remove source branch and we didn't remove the branch yet
// we should start another polling for source branch remove process
if (this.removeSourceBranch && data.source_branch_exists) {
diff --git a/app/assets/javascripts/vue_shared/directives/tooltip.js b/app/assets/javascripts/vue_shared/directives/tooltip.js
index 2d1f7a1cfd0..73e92728cb9 100644
--- a/app/assets/javascripts/vue_shared/directives/tooltip.js
+++ b/app/assets/javascripts/vue_shared/directives/tooltip.js
@@ -3,8 +3,12 @@ import '~/commons/bootstrap';
export default {
bind(el) {
+ const glTooltipDelay = localStorage.getItem('gl-tooltip-delay');
+ const delay = glTooltipDelay ? JSON.parse(glTooltipDelay) : 0;
+
$(el).tooltip({
trigger: 'hover',
+ delay,
});
},
diff --git a/app/assets/stylesheets/components/toast.scss b/app/assets/stylesheets/components/toast.scss
index acbd909d595..e27bf282247 100644
--- a/app/assets/stylesheets/components/toast.scss
+++ b/app/assets/stylesheets/components/toast.scss
@@ -15,11 +15,15 @@
.toasted.gl-toast {
border-radius: $border-radius-default;
font-size: $gl-font-size;
- padding: $gl-padding-8 $gl-padding-24;
+ padding: $gl-padding-8 $gl-padding $gl-padding-8 $gl-padding-24;
margin-top: $toast-default-margin;
line-height: $gl-line-height;
background-color: rgba($gray-900, $toast-background-opacity);
+ span {
+ padding-right: $gl-padding-8;
+ }
+
@include media-breakpoint-down(xs) {
.action:first-of-type {
// Ensures actions buttons are right aligned on mobile
@@ -29,19 +33,14 @@
.action {
color: $blue-300;
- margin: 0 0 0 $toast-action-margin-left;
+ margin: 0 0 0 $toast-default-margin;
text-transform: none;
font-size: $gl-font-size;
-
- &:first-of-type {
- padding-right: 0;
- }
}
.toast-close {
font-size: $default-icon-size;
margin-left: $toast-default-margin;
- padding-left: $gl-padding;
}
}
}
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index a12029d2419..29f63e9578d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -63,7 +63,8 @@
margin-top: 11px;
}
-.dropdown-toggle {
+.dropdown-toggle,
+.confidential-merge-request-fork-group .dropdown-toggle {
padding: 6px 8px 6px 10px;
background-color: $white-light;
color: $gl-text-color;
@@ -287,8 +288,8 @@
list-style: none;
padding: 0 1px;
- a:not(.help-link),
- button:not(.btn),
+ a,
+ button,
.menu-item {
@include dropdown-link;
}
diff --git a/app/assets/stylesheets/framework/modal.scss b/app/assets/stylesheets/framework/modal.scss
index f75e5b55506..b721b90fbb3 100644
--- a/app/assets/stylesheets/framework/modal.scss
+++ b/app/assets/stylesheets/framework/modal.scss
@@ -35,7 +35,8 @@
background-color: $modal-body-bg;
line-height: $line-height-base;
position: relative;
- padding: #{3 * $grid-size} #{2 * $grid-size};
+ min-height: $modal-body-height;
+ padding: #{2 * $grid-size} #{6 * $grid-size} #{2 * $grid-size} #{2 * $grid-size};
text-align: left;
white-space: normal;
@@ -52,6 +53,10 @@
display: flex;
flex-direction: row;
+ .btn {
+ margin: 0;
+ }
+
.btn + .btn:not(.dropdown-toggle-split),
.btn + .btn-group,
.btn-group + .btn {
@@ -85,9 +90,23 @@ body.modal-open {
.modal {
background-color: $black-transparent;
- @include media-breakpoint-up(md) {
+ .modal-content {
+ border-radius: $modal-border-radius;
+
+ *:first-child {
+ border-top-left-radius: $modal-border-radius;
+ border-top-right-radius: $modal-border-radius;
+ }
+
+ *:last-child {
+ border-bottom-left-radius: $modal-border-radius;
+ border-bottom-right-radius: $modal-border-radius;
+ }
+ }
+
+ @include media-breakpoint-up(sm) {
.modal-dialog {
- margin: 30px auto;
+ margin: 64px auto;
}
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index b6a24247d40..c108f45622f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -507,7 +507,6 @@ $toast-height: 48px;
$toast-max-width: 586px;
$toast-padding-right: 42px;
$toast-default-margin: 8px;
-$toast-action-margin-left: 16px;
$toast-background-opacity: 0.95;
/*
@@ -805,8 +804,9 @@ $border-color-settings: #e1e1e1;
/*
Modals
*/
-$modal-body-height: 134px;
+$modal-body-height: 80px;
$modal-border-color: #e9ecef;
+$modal-border-radius: 0.25rem;
$priority-label-empty-state-width: 114px;
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 10120a472d3..60400f10ca5 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -168,6 +168,10 @@
}
ul.wiki-pages-list.content-list {
+ a {
+ color: $blue-600;
+ }
+
ul {
list-style: none;
margin-left: 0;
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 065d2d3a4ec..6fa2f75be33 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -92,7 +92,7 @@ module IssuableActions
end
def bulk_update
- result = Issuable::BulkUpdateService.new(project, current_user, bulk_update_params).execute(resource_name)
+ result = Issuable::BulkUpdateService.new(current_user, bulk_update_params).execute(resource_name)
quantity = result[:count]
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
@@ -181,7 +181,7 @@ module IssuableActions
end
def authorize_admin_issuable!
- unless can?(current_user, :"admin_#{resource_name}", @project) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ unless can?(current_user, :"admin_#{resource_name}", parent)
return access_denied!
end
end
diff --git a/app/controllers/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 1ce0afac83b..9fbbe373b0d 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -11,7 +11,6 @@ class GraphqlController < ApplicationController
# around in GraphiQL.
protect_from_forgery with: :null_session, only: :execute
- before_action :check_graphql_feature_flag!
before_action :authorize_access_api!
before_action(only: [:execute]) { authenticate_sessionless_user!(:api) }
@@ -86,8 +85,4 @@ class GraphqlController < ApplicationController
render json: error, status: status
end
-
- def check_graphql_feature_flag!
- render_404 unless Gitlab::Graphql.enabled?
- end
end
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index fb43356ff10..6314d9f2a9f 100644
--- a/app/controllers/projects/cycle_analytics/events_controller.rb
+++ b/app/controllers/projects/cycle_analytics/events_controller.rb
@@ -50,7 +50,7 @@ module Projects
end
def cycle_analytics
- @cycle_analytics ||= ::CycleAnalytics.new(project, options(events_params))
+ @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(events_params))
end
def events_params
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 8c071496ba9..2d46a71bf99 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -9,7 +9,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
before_action :authorize_read_cycle_analytics!
def show
- @cycle_analytics = ::CycleAnalytics.new(@project, options(cycle_analytics_params))
+ @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_params))
@cycle_analytics_no_data = @cycle_analytics.no_stats?
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index ecf05e6ea64..1bca52106fa 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -13,6 +13,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
+ push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b866f574f67..228de8bc6f3 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -45,8 +45,6 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_import_issues!, only: [:import_csv]
before_action :authorize_download_code!, only: [:related_branches]
- before_action :set_suggested_issues_feature_flags, only: [:new]
-
respond_to :html
def index
@@ -285,8 +283,4 @@ class Projects::IssuesController < Projects::ApplicationController
# 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
end
-
- def set_suggested_issues_feature_flags
- push_frontend_feature_flag(:graphql, default_enabled: true)
- end
end
diff --git a/app/graphql/gitlab_schema.rb b/app/graphql/gitlab_schema.rb
index 152ebb930e2..7edd14e48f7 100644
--- a/app/graphql/gitlab_schema.rb
+++ b/app/graphql/gitlab_schema.rb
@@ -66,6 +66,8 @@ class GitlabSchema < GraphQL::Schema
if gid.model_class < ApplicationRecord
Gitlab::Graphql::Loaders::BatchModelLoader.new(gid.model_class, gid.model_id).find
+ elsif gid.model_class.respond_to?(:lazy_find)
+ gid.model_class.lazy_find(gid.model_id)
else
gid.find
end
diff --git a/app/graphql/mutations/notes/base.rb b/app/graphql/mutations/notes/base.rb
new file mode 100644
index 00000000000..a7198f5fba6
--- /dev/null
+++ b/app/graphql/mutations/notes/base.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Base < BaseMutation
+ include Gitlab::Graphql::Authorize::AuthorizeResource
+
+ field :note,
+ Types::Notes::NoteType,
+ null: true,
+ description: 'The note after mutation'
+
+ private
+
+ def find_object(id:)
+ GitlabSchema.object_from_id(id)
+ end
+
+ def check_object_is_noteable!(object)
+ unless object.is_a?(Noteable)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Cannot add notes to this resource'
+ end
+ end
+
+ def check_object_is_note!(object)
+ unless object.is_a?(Note)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ 'Resource is not a note'
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/base.rb b/app/graphql/mutations/notes/create/base.rb
new file mode 100644
index 00000000000..d3a5dae2188
--- /dev/null
+++ b/app/graphql/mutations/notes/create/base.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ # This is a Base class for the Note creation Mutations and is not
+ # mounted as a GraphQL mutation itself.
+ class Base < Mutations::Notes::Base
+ authorize :create_note
+
+ argument :noteable_id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the resource to add a note to'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ private
+
+ def resolve(args)
+ noteable = authorized_find!(id: args[:noteable_id])
+
+ check_object_is_noteable!(noteable)
+
+ note = ::Notes::CreateService.new(
+ noteable.project,
+ current_user,
+ create_note_params(noteable, args)
+ ).execute
+
+ {
+ note: (note if note.persisted?),
+ errors: errors_on_object(note)
+ }
+ end
+
+ def create_note_params(noteable, args)
+ {
+ noteable: noteable,
+ note: args[:body]
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/diff_note.rb b/app/graphql/mutations/notes/create/diff_note.rb
new file mode 100644
index 00000000000..9b5f3092006
--- /dev/null
+++ b/app/graphql/mutations/notes/create/diff_note.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class DiffNote < Base
+ graphql_name 'CreateDiffNote'
+
+ argument :position,
+ Types::Notes::DiffPositionInputType,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ private
+
+ def create_note_params(noteable, args)
+ super(noteable, args).merge({
+ type: 'DiffNote',
+ position: position(noteable, args)
+ })
+ end
+
+ def position(noteable, args)
+ position = args[:position].to_h
+ position[:position_type] = 'text'
+ position.merge!(position[:paths].to_h)
+
+ Gitlab::Diff::Position.new(position)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/image_diff_note.rb b/app/graphql/mutations/notes/create/image_diff_note.rb
new file mode 100644
index 00000000000..d94fd4d6ff8
--- /dev/null
+++ b/app/graphql/mutations/notes/create/image_diff_note.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class ImageDiffNote < Base
+ graphql_name 'CreateImageDiffNote'
+
+ argument :position,
+ Types::Notes::DiffImagePositionInputType,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :position)
+
+ private
+
+ def create_note_params(noteable, args)
+ super(noteable, args).merge({
+ type: 'DiffNote',
+ position: position(noteable, args)
+ })
+ end
+
+ def position(noteable, args)
+ position = args[:position].to_h
+ position[:position_type] = 'image'
+ position.merge!(position[:paths].to_h)
+
+ Gitlab::Diff::Position.new(position)
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/create/note.rb b/app/graphql/mutations/notes/create/note.rb
new file mode 100644
index 00000000000..5236e48026e
--- /dev/null
+++ b/app/graphql/mutations/notes/create/note.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ module Create
+ class Note < Base
+ graphql_name 'CreateNote'
+
+ argument :discussion_id,
+ GraphQL::ID_TYPE,
+ required: false,
+ description: 'The global id of the discussion this note is in reply to'
+
+ private
+
+ def create_note_params(noteable, args)
+ discussion_id = nil
+
+ if args[:discussion_id]
+ discussion = GitlabSchema.object_from_id(args[:discussion_id])
+ authorize_discussion!(discussion)
+
+ discussion_id = discussion.id
+ end
+
+ super(noteable, args).merge({
+ in_reply_to_discussion_id: discussion_id
+ })
+ end
+
+ def authorize_discussion!(discussion)
+ unless Ability.allowed?(current_user, :read_note, discussion, scope: :user)
+ raise Gitlab::Graphql::Errors::ResourceNotAvailable,
+ "The discussion does not exist or you don't have permission to perform this action"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/destroy.rb b/app/graphql/mutations/notes/destroy.rb
new file mode 100644
index 00000000000..a81322bc9b7
--- /dev/null
+++ b/app/graphql/mutations/notes/destroy.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Destroy < Base
+ graphql_name 'DestroyNote'
+
+ authorize :admin_note
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the note to destroy'
+
+ def resolve(id:)
+ note = authorized_find!(id: id)
+
+ check_object_is_note!(note)
+
+ ::Notes::DestroyService.new(note.project, current_user).execute(note)
+
+ {
+ errors: []
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/notes/update.rb b/app/graphql/mutations/notes/update.rb
new file mode 100644
index 00000000000..ebf57b800c0
--- /dev/null
+++ b/app/graphql/mutations/notes/update.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Notes
+ class Update < Base
+ graphql_name 'UpdateNote'
+
+ authorize :admin_note
+
+ argument :id,
+ GraphQL::ID_TYPE,
+ required: true,
+ description: 'The global id of the note to update'
+
+ argument :body,
+ GraphQL::STRING_TYPE,
+ required: true,
+ description: copy_field_description(Types::Notes::NoteType, :body)
+
+ def resolve(args)
+ note = authorized_find!(id: args[:id])
+
+ check_object_is_note!(note)
+
+ note = ::Notes::UpdateService.new(
+ note.project,
+ current_user,
+ { note: args[:body] }
+ ).execute(note)
+
+ {
+ note: note.reset,
+ errors: errors_on_object(note)
+ }
+ end
+ end
+ end
+end
diff --git a/app/graphql/types/base_input_object.rb b/app/graphql/types/base_input_object.rb
index aebed035d3b..90a29b0cfb8 100644
--- a/app/graphql/types/base_input_object.rb
+++ b/app/graphql/types/base_input_object.rb
@@ -2,5 +2,6 @@
module Types
class BaseInputObject < GraphQL::Schema::InputObject
+ prepend Gitlab::Graphql::CopyFieldDescription
end
end
diff --git a/app/graphql/types/diff_paths_input_type.rb b/app/graphql/types/diff_paths_input_type.rb
new file mode 100644
index 00000000000..43feddd9827
--- /dev/null
+++ b/app/graphql/types/diff_paths_input_type.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPathsInputType < BaseInputObject
+ argument :old_path, GraphQL::STRING_TYPE, required: false,
+ description: 'The path of the file on the start sha'
+ argument :new_path, GraphQL::STRING_TYPE, required: false,
+ description: 'The path of the file on the head sha'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/diff_refs_type.rb b/app/graphql/types/diff_refs_type.rb
new file mode 100644
index 00000000000..33a5780cd68
--- /dev/null
+++ b/app/graphql/types/diff_refs_type.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Types
+ # rubocop: disable Graphql/AuthorizeTypes
+ # Types that use DiffRefsType should have their own authorization
+ class DiffRefsType < BaseObject
+ graphql_name 'DiffRefs'
+
+ field :head_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the head at the time the comment was made'
+ field :base_sha, GraphQL::STRING_TYPE, null: false, description: 'The merge base of the branch the comment was made on'
+ field :start_sha, GraphQL::STRING_TYPE, null: false, description: 'The sha of the branch being compared against'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+end
diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb
index 6734d4761c2..b8f63a750c5 100644
--- a/app/graphql/types/merge_request_type.rb
+++ b/app/graphql/types/merge_request_type.rb
@@ -23,6 +23,7 @@ module Types
field :updated_at, Types::TimeType, null: false
field :source_project, Types::ProjectType, null: true
field :target_project, Types::ProjectType, null: false
+ field :diff_refs, Types::DiffRefsType, null: true
# Alias for target_project
field :project, Types::ProjectType, null: false
field :project_id, GraphQL::INT_TYPE, null: false, method: :target_project_id
diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb
index bc5fb709522..f843d6ad86f 100644
--- a/app/graphql/types/mutation_type.rb
+++ b/app/graphql/types/mutation_type.rb
@@ -10,5 +10,10 @@ module Types
mount_mutation Mutations::AwardEmojis::Remove
mount_mutation Mutations::AwardEmojis::Toggle
mount_mutation Mutations::MergeRequests::SetWip, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::Note, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::DiffNote, calls_gitaly: true
+ mount_mutation Mutations::Notes::Create::ImageDiffNote, calls_gitaly: true
+ mount_mutation Mutations::Notes::Update
+ mount_mutation Mutations::Notes::Destroy
end
end
diff --git a/app/graphql/types/notes/diff_image_position_input_type.rb b/app/graphql/types/notes/diff_image_position_input_type.rb
new file mode 100644
index 00000000000..23b53b20815
--- /dev/null
+++ b/app/graphql/types/notes/diff_image_position_input_type.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffImagePositionInputType < DiffPositionBaseInputType
+ graphql_name 'DiffImagePositionInput'
+
+ argument :x, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :x)
+ argument :y, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :y)
+ argument :width, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :width)
+ argument :height, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :height)
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_base_input_type.rb b/app/graphql/types/notes/diff_position_base_input_type.rb
new file mode 100644
index 00000000000..a9b4e1a8948
--- /dev/null
+++ b/app/graphql/types/notes/diff_position_base_input_type.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPositionBaseInputType < BaseInputObject
+ argument :head_sha, GraphQL::STRING_TYPE, required: true,
+ description: copy_field_description(Types::DiffRefsType, :head_sha)
+ argument :base_sha, GraphQL::STRING_TYPE, required: false,
+ description: copy_field_description(Types::DiffRefsType, :base_sha)
+ argument :start_sha, GraphQL::STRING_TYPE, required: true,
+ description: copy_field_description(Types::DiffRefsType, :start_sha)
+
+ argument :paths,
+ Types::DiffPathsInputType,
+ required: true,
+ description: 'The paths of the file that was changed. ' \
+ 'Both of the properties of this input are optional, but at least one of them is required'
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_input_type.rb b/app/graphql/types/notes/diff_position_input_type.rb
new file mode 100644
index 00000000000..02c91e173cb
--- /dev/null
+++ b/app/graphql/types/notes/diff_position_input_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ module Notes
+ # rubocop: disable Graphql/AuthorizeTypes
+ class DiffPositionInputType < DiffPositionBaseInputType
+ graphql_name 'DiffPositionInput'
+
+ argument :old_line, GraphQL::INT_TYPE, required: false,
+ description: copy_field_description(Types::Notes::DiffPositionType, :old_line)
+ argument :new_line, GraphQL::INT_TYPE, required: true,
+ description: copy_field_description(Types::Notes::DiffPositionType, :new_line)
+ end
+ # rubocop: enable Graphql/AuthorizeTypes
+ end
+end
diff --git a/app/graphql/types/notes/diff_position_type.rb b/app/graphql/types/notes/diff_position_type.rb
index ebc24451715..6a0377fbfdf 100644
--- a/app/graphql/types/notes/diff_position_type.rb
+++ b/app/graphql/types/notes/diff_position_type.rb
@@ -7,12 +7,7 @@ module Types
class DiffPositionType < BaseObject
graphql_name 'DiffPosition'
- field :head_sha, GraphQL::STRING_TYPE, null: false,
- description: "The sha of the head at the time the comment was made"
- field :base_sha, GraphQL::STRING_TYPE, null: true,
- description: "The merge base of the branch the comment was made on"
- field :start_sha, GraphQL::STRING_TYPE, null: false,
- description: "The sha of the branch being compared against"
+ field :diff_refs, Types::DiffRefsType, null: false
field :file_path, GraphQL::STRING_TYPE, null: false,
description: "The path of the file that was changed"
diff --git a/app/graphql/types/notes/discussion_type.rb b/app/graphql/types/notes/discussion_type.rb
index c4691942f2d..a3fb28298f6 100644
--- a/app/graphql/types/notes/discussion_type.rb
+++ b/app/graphql/types/notes/discussion_type.rb
@@ -8,8 +8,16 @@ module Types
authorize :read_note
field :id, GraphQL::ID_TYPE, null: false
+ field :reply_id, GraphQL::ID_TYPE, null: false, description: 'The ID used to reply to this discussion'
field :created_at, Types::TimeType, null: false
field :notes, Types::Notes::NoteType.connection_type, null: false, description: "All notes in the discussion"
+
+ # The gem we use to generate Global IDs is hard-coded to work with
+ # `id` properties. To generate a GID for the `reply_id` property,
+ # we must use the ::Gitlab::GlobalId module.
+ def reply_id
+ ::Gitlab::GlobalId.build(object, id: object.reply_id)
+ end
end
end
end
diff --git a/app/helpers/markup_helper.rb b/app/helpers/markup_helper.rb
index 8ccb39f8444..d76a0f3a3b8 100644
--- a/app/helpers/markup_helper.rb
+++ b/app/helpers/markup_helper.rb
@@ -3,7 +3,7 @@
require 'nokogiri'
module MarkupHelper
- include ActionView::Helpers::TagHelper
+ include ActionView::Helpers::TextHelper
include ::Gitlab::ActionViewOutput::Context
def plain?(filename)
@@ -154,9 +154,7 @@ module MarkupHelper
elsif asciidoc?(file_name)
asciidoc_unsafe(text, context)
elsif plain?(file_name)
- content_tag :pre, class: 'plain-readme' do
- text
- end
+ plain_unsafe(text)
else
other_markup_unsafe(file_name, text, context)
end
@@ -271,6 +269,12 @@ module MarkupHelper
Gitlab::Asciidoc.render(text, context)
end
+ def plain_unsafe(text)
+ content_tag :pre, class: 'plain-readme' do
+ text
+ end
+ end
+
def other_markup_unsafe(file_name, text, context = {})
Gitlab::OtherMarkup.render(file_name, text, context)
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8dee842a22d..8d0079a4dd3 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -662,6 +662,6 @@ module ProjectsHelper
end
def vue_file_list_enabled?
- Gitlab::Graphql.enabled? && Feature.enabled?(:vue_file_list, @project)
+ Feature.enabled?(:vue_file_list, @project)
end
end
diff --git a/app/helpers/storage_helper.rb b/app/helpers/storage_helper.rb
index ecf37bae6b3..ce810433a3a 100644
--- a/app/helpers/storage_helper.rb
+++ b/app/helpers/storage_helper.rb
@@ -17,6 +17,6 @@ module StorageHelper
counter_lfs_objects: storage_counter(statistics.lfs_objects_size)
}
- _("%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS") % counters
+ _("Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}") % counters
end
end
diff --git a/app/models/chat_team.rb b/app/models/chat_team.rb
index 52b5a7b4a91..28aab279545 100644
--- a/app/models/chat_team.rb
+++ b/app/models/chat_team.rb
@@ -12,6 +12,6 @@ class ChatTeam < ApplicationRecord
# Either the group is not found, or the user doesn't have the proper
# access on the mattermost instance. In the first case, we're done either way
# in the latter case, we can't recover by retrying, so we just log what happened
- Rails.logger.error("Mattermost team deletion failed: #{e}")
+ Rails.logger.error("Mattermost team deletion failed: #{e}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 89cc082d0bc..ae7a1108841 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -266,7 +266,7 @@ module Ci
begin
Ci::Build.retry(build, build.user)
rescue Gitlab::Access::AccessDeniedError => ex
- Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}"
+ Rails.logger.error "Unable to auto-retry job #{build.id}: #{ex}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 8cbf4bcfaf7..53dff2adfc3 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -49,7 +49,7 @@ module CacheableAttributes
current_without_cache.tap { |current_record| current_record&.cache! }
rescue => e
if Rails.env.production?
- Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}")
+ Rails.logger.warn("Cached record for #{name} couldn't be loaded, falling back to uncached record: #{e}") # rubocop:disable Gitlab/RailsLogger
else
raise e
end
diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb
new file mode 100644
index 00000000000..cb00efb06df
--- /dev/null
+++ b/app/models/concerns/project_api_compatibility.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+# Add methods used by the projects API
+module ProjectAPICompatibility
+ extend ActiveSupport::Concern
+
+ def build_git_strategy=(value)
+ write_attribute(:build_allow_git_fetch, value == 'fetch')
+ end
+
+ def auto_devops_enabled=(value)
+ self.build_auto_devops if self.auto_devops&.enabled.nil?
+ self.auto_devops.update! enabled: value
+ end
+
+ def auto_devops_deploy_strategy=(value)
+ self.build_auto_devops if self.auto_devops&.enabled.nil?
+ self.auto_devops.update! deploy_strategy: value
+ end
+end
diff --git a/app/models/concerns/project_features_compatibility.rb b/app/models/concerns/project_features_compatibility.rb
index f268a842db4..551a2e56ecf 100644
--- a/app/models/concerns/project_features_compatibility.rb
+++ b/app/models/concerns/project_features_compatibility.rb
@@ -9,32 +9,70 @@ require 'gitlab/utils'
module ProjectFeaturesCompatibility
extend ActiveSupport::Concern
+ # TODO: remove in API v5, replaced by *_access_level
def wiki_enabled=(value)
- write_feature_attribute(:wiki_access_level, value)
+ write_feature_attribute_boolean(:wiki_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def builds_enabled=(value)
- write_feature_attribute(:builds_access_level, value)
+ write_feature_attribute_boolean(:builds_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def merge_requests_enabled=(value)
- write_feature_attribute(:merge_requests_access_level, value)
+ write_feature_attribute_boolean(:merge_requests_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def issues_enabled=(value)
- write_feature_attribute(:issues_access_level, value)
+ write_feature_attribute_boolean(:issues_access_level, value)
end
+ # TODO: remove in API v5, replaced by *_access_level
def snippets_enabled=(value)
- write_feature_attribute(:snippets_access_level, value)
+ write_feature_attribute_boolean(:snippets_access_level, value)
+ end
+
+ def repository_access_level=(value)
+ write_feature_attribute_string(:repository_access_level, value)
+ end
+
+ def wiki_access_level=(value)
+ write_feature_attribute_string(:wiki_access_level, value)
+ end
+
+ def builds_access_level=(value)
+ write_feature_attribute_string(:builds_access_level, value)
+ end
+
+ def merge_requests_access_level=(value)
+ write_feature_attribute_string(:merge_requests_access_level, value)
+ end
+
+ def issues_access_level=(value)
+ write_feature_attribute_string(:issues_access_level, value)
+ end
+
+ def snippets_access_level=(value)
+ write_feature_attribute_string(:snippets_access_level, value)
end
private
- def write_feature_attribute(field, value)
+ def write_feature_attribute_boolean(field, value)
+ access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
+ write_feature_attribute_raw(field, access_level)
+ end
+
+ def write_feature_attribute_string(field, value)
+ access_level = ProjectFeature.access_level_from_str(value)
+ write_feature_attribute_raw(field, access_level)
+ end
+
+ def write_feature_attribute_raw(field, value)
build_project_feature unless project_feature
- access_level = Gitlab::Utils.to_boolean(value) ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
- project_feature.__send__(:write_attribute, field, access_level) # rubocop:disable GitlabSecurity/PublicSend
+ project_feature.__send__(:write_attribute, field, value) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb
index a15dc19e07a..78544405c49 100644
--- a/app/models/concerns/storage/legacy_namespace.rb
+++ b/app/models/concerns/storage/legacy_namespace.rb
@@ -64,7 +64,7 @@ module Storage
unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
- Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}"
+ Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb
deleted file mode 100644
index d0f5b6970b1..00000000000
--- a/app/models/cycle_analytics.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-# frozen_string_literal: true
-
-class CycleAnalytics
- STAGES = %i[issue plan code test review staging production].freeze
-
- def initialize(project, options)
- @project = project
- @options = options
- end
-
- def all_medians_per_stage
- STAGES.each_with_object({}) do |stage_name, medians_per_stage|
- medians_per_stage[stage_name] = self[stage_name].median
- end
- end
-
- def summary
- @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(@project,
- from: @options[:from],
- current_user: @options[:current_user]).data
- end
-
- def stats
- @stats ||= stats_per_stage
- end
-
- def no_stats?
- stats.all? { |hash| hash[:value].nil? }
- end
-
- def permissions(user:)
- Gitlab::CycleAnalytics::Permissions.get(user: user, project: @project)
- end
-
- def [](stage_name)
- Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
- end
-
- private
-
- def stats_per_stage
- STAGES.map do |stage_name|
- self[stage_name].as_json
- end
- end
-end
diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/base.rb
new file mode 100644
index 00000000000..d7b28cd1b67
--- /dev/null
+++ b/app/models/cycle_analytics/base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class Base
+ STAGES = %i[issue plan code test review staging production].freeze
+
+ def all_medians_by_stage
+ STAGES.each_with_object({}) do |stage_name, medians_per_stage|
+ medians_per_stage[stage_name] = self[stage_name].median
+ end
+ end
+
+ def stats
+ @stats ||= STAGES.map do |stage_name|
+ self[stage_name].as_json
+ end
+ end
+
+ def no_stats?
+ stats.all? { |hash| hash[:value].nil? }
+ end
+
+ def [](stage_name)
+ Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
+ end
+ end
+end
diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb
new file mode 100644
index 00000000000..22631cc7d41
--- /dev/null
+++ b/app/models/cycle_analytics/project_level.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class ProjectLevel < Base
+ attr_reader :project, :options
+
+ def initialize(project, options:)
+ @project = project
+ @options = options
+ end
+
+ def summary
+ @summary ||= ::Gitlab::CycleAnalytics::StageSummary.new(project,
+ from: options[:from],
+ current_user: options[:current_user]).data
+ end
+
+ def permissions(user:)
+ Gitlab::CycleAnalytics::Permissions.get(user: user, project: project)
+ end
+ end
+end
diff --git a/app/models/discussion.rb b/app/models/discussion.rb
index ae13cdfd85f..dd896f77084 100644
--- a/app/models/discussion.rb
+++ b/app/models/discussion.rb
@@ -38,6 +38,17 @@ class Discussion
grouped_notes.values.map { |notes| build(notes, context_noteable) }
end
+ def self.lazy_find(discussion_id)
+ BatchLoader.for(discussion_id).batch do |discussion_ids, loader|
+ results = Note.where(discussion_id: discussion_ids).fresh.to_a.group_by(&:discussion_id)
+ results.each do |discussion_id, notes|
+ next if notes.empty?
+
+ loader.call(discussion_id, Discussion.build(notes))
+ end
+ end
+ end
+
# Returns an alphanumeric discussion ID based on `build_discussion_id`
def self.discussion_id(note)
Digest::SHA1.hexdigest(build_discussion_id(note).join("-"))
diff --git a/app/models/environment_status.rb b/app/models/environment_status.rb
index 465a42759df..d7dc64190d6 100644
--- a/app/models/environment_status.rb
+++ b/app/models/environment_status.rb
@@ -40,7 +40,7 @@ class EnvironmentStatus
end
def changes
- return [] if project.route_map_for(sha).nil?
+ return [] unless has_route_map?
changed_files.map { |file| build_change(file) }.compact
end
@@ -50,6 +50,10 @@ class EnvironmentStatus
.merge_request_diff_files.where(deleted_file: false)
end
+ def has_route_map?
+ project.route_map_for(sha).present?
+ end
+
private
PAGE_EXTENSIONS = /\A\.(s?html?|php|asp|cgi|pl)\z/i.freeze
diff --git a/app/models/label_note.rb b/app/models/label_note.rb
index d6814f4a948..ba5f1f82a81 100644
--- a/app/models/label_note.rb
+++ b/app/models/label_note.rb
@@ -62,19 +62,27 @@ class LabelNote < Note
end
def note_text(html: false)
- added = labels_str('added', label_refs_by_action('add', html))
- removed = labels_str('removed', label_refs_by_action('remove', html))
+ added = labels_str(label_refs_by_action('add', html), prefix: 'added', suffix: added_suffix)
+ removed = labels_str(label_refs_by_action('remove', html), prefix: removed_prefix)
[added, removed].compact.join(' and ')
end
+ def removed_prefix
+ 'removed'
+ end
+
+ def added_suffix
+ ''
+ end
+
# returns string containing added/removed labels including
# count of deleted labels:
#
# added ~1 ~2 + 1 deleted label
# added 3 deleted labels
# added ~1 ~2 labels
- def labels_str(prefix, label_refs)
+ def labels_str(label_refs, prefix: '', suffix: '')
existing_refs = label_refs.select { |ref| ref.present? }.sort
refs_str = existing_refs.empty? ? nil : existing_refs.join(' ')
@@ -84,9 +92,9 @@ class LabelNote < Note
return unless refs_str || deleted_str
label_list_str = [refs_str, deleted_str].compact.join(' + ')
- suffix = 'label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
+ suffix += ' label'.pluralize(deleted > 0 ? deleted : existing_refs.count)
- "#{prefix} #{label_list_str} #{suffix}"
+ "#{prefix} #{label_list_str} #{suffix.squish}"
end
def label_refs_by_action(action, html)
diff --git a/app/models/note.rb b/app/models/note.rb
index 4e9ea146485..5c31cff9816 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -158,6 +158,8 @@ class Note < ApplicationRecord
Discussion.build_collection(all.includes(:noteable).fresh, context_noteable)
end
+ # Note: Where possible consider using Discussion#lazy_find to return
+ # Discussions in order to benefit from having records batch loaded.
def find_discussion(discussion_id)
notes = where(discussion_id: discussion_id).fresh.to_a
diff --git a/app/models/project.rb b/app/models/project.rb
index 0f4fba5d0b6..a6e0b5722b6 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -15,6 +15,7 @@ class Project < ApplicationRecord
include CaseSensitivity
include TokenAuthenticatable
include ValidAttribute
+ include ProjectAPICompatibility
include ProjectFeaturesCompatibility
include SelectForProjectAuthorization
include Presentable
@@ -783,6 +784,7 @@ class Project < ApplicationRecord
job_id
end
+ # rubocop:disable Gitlab/RailsLogger
def log_import_activity(job_id, type: :import)
job_type = type.to_s.capitalize
@@ -792,6 +794,7 @@ class Project < ApplicationRecord
Rails.logger.error("#{job_type} job failed to create for #{full_path}.")
end
end
+ # rubocop:enable Gitlab/RailsLogger
def reset_cache_and_import_attrs
run_after_commit do
@@ -1664,6 +1667,7 @@ class Project < ApplicationRecord
end
# rubocop: enable CodeReuse/ServiceClass
+ # rubocop:disable Gitlab/RailsLogger
def write_repository_config(gl_full_path: full_path)
# We'd need to keep track of project full path otherwise directory tree
# created with hashed storage enabled cannot be usefully imported using
@@ -1673,6 +1677,7 @@ class Project < ApplicationRecord
Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.")
nil
end
+ # rubocop:enable Gitlab/RailsLogger
def after_import
repository.after_import
@@ -1714,6 +1719,7 @@ class Project < ApplicationRecord
@pipeline_status ||= Gitlab::Cache::Ci::ProjectPipelineStatus.load_for_project(self)
end
+ # rubocop:disable Gitlab/RailsLogger
def add_export_job(current_user:, after_export_strategy: nil, params: {})
job_id = ProjectExportWorker.perform_async(current_user.id, self.id, after_export_strategy, params)
@@ -1723,6 +1729,7 @@ class Project < ApplicationRecord
Rails.logger.error "Export job failed to start for project ID #{self.id}"
end
end
+ # rubocop:enable Gitlab/RailsLogger
def import_export_shared
@import_export_shared ||= Gitlab::ImportExport::Shared.new(self)
@@ -1913,9 +1920,8 @@ class Project < ApplicationRecord
@route_maps_by_commit ||= Hash.new do |h, sha|
h[sha] = begin
data = repository.route_map_for(sha)
- next unless data
- Gitlab::RouteMap.new(data)
+ Gitlab::RouteMap.new(data) if data
rescue Gitlab::RouteMap::FormatError
nil
end
@@ -2144,7 +2150,7 @@ class Project < ApplicationRecord
public? &&
repository_exists? &&
Gitlab::CurrentSettings.hashed_storage_enabled &&
- Feature.enabled?(:object_pools, self)
+ Feature.enabled?(:object_pools, self, default_enabled: true)
end
def leave_pool_repository
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 0542581c6e0..7ff06655de0 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -24,6 +24,12 @@ class ProjectFeature < ApplicationRecord
FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze
PRIVATE_FEATURES_MIN_ACCESS_LEVEL = { merge_requests: Gitlab::Access::REPORTER }.freeze
+ STRING_OPTIONS = HashWithIndifferentAccess.new({
+ 'disabled' => DISABLED,
+ 'private' => PRIVATE,
+ 'enabled' => ENABLED,
+ 'public' => PUBLIC
+ }).freeze
class << self
def access_level_attribute(feature)
@@ -45,6 +51,14 @@ class ProjectFeature < ApplicationRecord
PRIVATE_FEATURES_MIN_ACCESS_LEVEL.fetch(feature, Gitlab::Access::GUEST)
end
+ def access_level_from_str(level)
+ STRING_OPTIONS.fetch(level)
+ end
+
+ def str_from_access_level(level)
+ STRING_OPTIONS.key(level)
+ end
+
private
def ensure_feature!(feature)
@@ -83,6 +97,10 @@ class ProjectFeature < ApplicationRecord
public_send(ProjectFeature.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
+ def string_access_level(feature)
+ ProjectFeature.str_from_access_level(access_level(feature))
+ end
+
def builds_enabled?
builds_access_level > DISABLED
end
diff --git a/app/models/project_import_state.rb b/app/models/project_import_state.rb
index 1605345efd5..23adffb33d8 100644
--- a/app/models/project_import_state.rb
+++ b/app/models/project_import_state.rb
@@ -65,7 +65,7 @@ class ProjectImportState < ApplicationRecord
update_column(:last_error, sanitized_message)
rescue ActiveRecord::ActiveRecordError => e
- Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}")
+ Rails.logger.error("Error setting import status to failed: #{e.message}. Original error: #{sanitized_message}") # rubocop:disable Gitlab/RailsLogger
ensure
@errors = original_errors
end
diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb
index 62090444f79..b8e7673dcf5 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -3,68 +3,7 @@
class PrometheusMetric < ApplicationRecord
belongs_to :project, validate: true, inverse_of: :prometheus_metrics
- enum group: {
- # built-in groups
- nginx_ingress_vts: -1,
- ha_proxy: -2,
- aws_elb: -3,
- nginx: -4,
- kubernetes: -5,
- nginx_ingress: -6,
-
- # custom/user groups
- business: 0,
- response: 1,
- system: 2
- }
-
- GROUP_DETAILS = {
- # built-in groups
- nginx_ingress_vts: {
- group_title: _('Response metrics (NGINX Ingress VTS)'),
- required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
- priority: 10
- }.freeze,
- nginx_ingress: {
- group_title: _('Response metrics (NGINX Ingress)'),
- required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
- priority: 10
- }.freeze,
- ha_proxy: {
- group_title: _('Response metrics (HA Proxy)'),
- required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
- priority: 10
- }.freeze,
- aws_elb: {
- group_title: _('Response metrics (AWS ELB)'),
- required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
- priority: 10
- }.freeze,
- nginx: {
- group_title: _('Response metrics (NGINX)'),
- required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
- priority: 10
- }.freeze,
- kubernetes: {
- group_title: _('System metrics (Kubernetes)'),
- required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
- priority: 5
- }.freeze,
-
- # custom/user groups
- business: {
- group_title: _('Business metrics (Custom)'),
- priority: 0
- }.freeze,
- response: {
- group_title: _('Response metrics (Custom)'),
- priority: -5
- }.freeze,
- system: {
- group_title: _('System metrics (Custom)'),
- priority: -10
- }.freeze
- }.freeze
+ enum group: PrometheusMetricEnums.groups
validates :title, presence: true
validates :query, presence: true
@@ -121,6 +60,6 @@ class PrometheusMetric < ApplicationRecord
private
def group_details(group)
- GROUP_DETAILS.fetch(group.to_sym)
+ PrometheusMetricEnums.group_details.fetch(group.to_sym)
end
end
diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb
new file mode 100644
index 00000000000..6cb22cc69cd
--- /dev/null
+++ b/app/models/prometheus_metric_enums.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module PrometheusMetricEnums
+ def self.groups
+ {
+ # built-in groups
+ nginx_ingress_vts: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+ nginx_ingress: -6,
+
+ # custom/user groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+ end
+
+ def self.group_details
+ {
+ # built-in groups
+ nginx_ingress_vts: {
+ group_title: _('Response metrics (NGINX Ingress VTS)'),
+ required_metrics: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg),
+ priority: 10
+ }.freeze,
+ nginx_ingress: {
+ group_title: _('Response metrics (NGINX Ingress)'),
+ required_metrics: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum),
+ priority: 10
+ }.freeze,
+ ha_proxy: {
+ group_title: _('Response metrics (HA Proxy)'),
+ required_metrics: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total),
+ priority: 10
+ }.freeze,
+ aws_elb: {
+ group_title: _('Response metrics (AWS ELB)'),
+ required_metrics: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum),
+ priority: 10
+ }.freeze,
+ nginx: {
+ group_title: _('Response metrics (NGINX)'),
+ required_metrics: %w(nginx_server_requests nginx_server_requestMsec),
+ priority: 10
+ }.freeze,
+ kubernetes: {
+ group_title: _('System metrics (Kubernetes)'),
+ required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
+ priority: 5
+ }.freeze,
+
+ # custom/user groups
+ business: {
+ group_title: _('Business metrics (Custom)'),
+ priority: 0
+ }.freeze,
+ response: {
+ group_title: _('Response metrics (Custom)'),
+ priority: -5
+ }.freeze,
+ system: {
+ group_title: _('System metrics (Custom)'),
+ priority: -10
+ }.freeze
+ }.freeze
+ end
+end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a25d5abfa64..187382ad182 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -273,7 +273,7 @@ class Repository
# This will still fail if the file is corrupted (e.g. 0 bytes)
raw_repository.write_ref(keep_around_ref_name(sha), sha)
rescue Gitlab::Git::CommandError => ex
- Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
+ Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}" # rubocop:disable Gitlab/RailsLogger
end
end
@@ -934,6 +934,7 @@ class Repository
async_remove_remote(remote_name) if tmp_remote_name
end
+ # rubocop:disable Gitlab/RailsLogger
def async_remove_remote(remote_name)
return unless remote_name
@@ -947,6 +948,7 @@ class Repository
job_id
end
+ # rubocop:enable Gitlab/RailsLogger
def fetch_source_branch!(source_repository, source_branch, local_ref)
raw_repository.fetch_source_branch!(source_repository.raw_repository, source_branch, local_ref)
diff --git a/app/models/resource_label_event.rb b/app/models/resource_label_event.rb
index f2c7cb6a65d..ad08f4763ae 100644
--- a/app/models/resource_label_event.rb
+++ b/app/models/resource_label_event.rb
@@ -36,10 +36,9 @@ class ResourceLabelEvent < ApplicationRecord
issue || merge_request
end
- # create same discussion id for all actions with the same user and time
def discussion_id(resource = nil)
strong_memoize(:discussion_id) do
- Digest::SHA1.hexdigest([self.class.name, created_at, user_id].join("-"))
+ Digest::SHA1.hexdigest(discussion_id_key.join("-"))
end
end
@@ -121,4 +120,8 @@ class ResourceLabelEvent < ApplicationRecord
def resource_parent
issuable.project || issuable.group
end
+
+ def discussion_id_key
+ [self.class.name, created_at, user_id]
+ end
end
diff --git a/app/models/ssh_host_key.rb b/app/models/ssh_host_key.rb
index b6fb39ee81f..9bd35d30845 100644
--- a/app/models/ssh_host_key.rb
+++ b/app/models/ssh_host_key.rb
@@ -106,7 +106,7 @@ class SshHostKey
if status.success? && !errors.present?
{ known_hosts: known_hosts }
else
- Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}")
+ Rails.logger.debug("Failed to detect SSH host keys for #{id}: #{errors}") # rubocop:disable Gitlab/RailsLogger
{ error: 'Failed to detect SSH host keys' }
end
diff --git a/app/models/storage/legacy_project.rb b/app/models/storage/legacy_project.rb
index b483c677be9..928c773c307 100644
--- a/app/models/storage/legacy_project.rb
+++ b/app/models/storage/legacy_project.rb
@@ -41,7 +41,7 @@ module Storage
gitlab_shell.mv_repository(repository_storage, "#{old_full_path}.wiki", "#{new_full_path}.wiki")
return true
rescue => e
- Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}"
+ Rails.logger.error "Exception renaming #{old_full_path} -> #{new_full_path}: #{e}" # rubocop:disable Gitlab/RailsLogger
# Returning false does not rollback after_* transaction but gives
# us information about failing some of tasks
return false
diff --git a/app/models/uploads/base.rb b/app/models/uploads/base.rb
index f9814159958..29f376670da 100644
--- a/app/models/uploads/base.rb
+++ b/app/models/uploads/base.rb
@@ -7,7 +7,7 @@ module Uploads
attr_reader :logger
def initialize(logger: nil)
- @logger ||= Rails.logger
+ @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def delete_keys_async(keys_to_delete)
diff --git a/app/services/akismet_service.rb b/app/services/akismet_service.rb
index 82ae66ab0f5..63be3c371ec 100644
--- a/app/services/akismet_service.rb
+++ b/app/services/akismet_service.rb
@@ -25,7 +25,7 @@ class AkismetService
is_spam, is_blatant = akismet_client.check(options[:ip_address], options[:user_agent], params)
is_spam || is_blatant
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check") # rubocop:disable Gitlab/RailsLogger
false
end
end
@@ -63,7 +63,7 @@ class AkismetService
akismet_client.public_send(type, options[:ip_address], options[:user_agent], params) # rubocop:disable GitlabSecurity/PublicSend
true
rescue => e
- Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!")
+ Rails.logger.error("Unable to connect to Akismet: #{e}, skipping!") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/app/services/ci/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
index a1dd00721b5..b5cfa1d019c 100644
--- a/app/services/ci/archive_trace_service.rb
+++ b/app/services/ci/archive_trace_service.rb
@@ -24,11 +24,11 @@ module Ci
def archive_error(error, job)
failed_archive_counter.increment
- Rails.logger.error "Failed to archive trace. id: #{job.id} message: #{error.message}"
+ Rails.logger.error "Failed to archive trace. id: #{job.id} message: #{error.message}" # rubocop:disable Gitlab/RailsLogger
Gitlab::Sentry
.track_exception(error,
- issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
+ issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
extra: { job_id: job.id })
end
end
diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb
index dedab98b56d..9d4cf5df713 100644
--- a/app/services/ci/register_job_service.rb
+++ b/app/services/ci/register_job_service.rb
@@ -6,7 +6,7 @@ module Ci
class RegisterJobService
attr_reader :runner
- JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300].freeze
+ JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900].freeze
JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze
Result = Struct.new(:build, :valid?)
diff --git a/app/services/concerns/exclusive_lease_guard.rb b/app/services/concerns/exclusive_lease_guard.rb
index 2cb73555d85..0c5ecca3a50 100644
--- a/app/services/concerns/exclusive_lease_guard.rb
+++ b/app/services/concerns/exclusive_lease_guard.rb
@@ -58,6 +58,6 @@ module ExclusiveLeaseGuard
end
def log_error(message, extra_args = {})
- Rails.logger.error(message)
+ Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb
index 654fe84e3dc..9e00cbbbc55 100644
--- a/app/services/groups/destroy_service.rb
+++ b/app/services/groups/destroy_service.rb
@@ -6,7 +6,7 @@ module Groups
def async_execute
job_id = GroupDestroyWorker.perform_async(group.id, current_user.id)
- Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}")
+ Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index c4beddf2294..6d215d7a3b9 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -1,7 +1,15 @@
# frozen_string_literal: true
module Issuable
- class BulkUpdateService < IssuableBaseService
+ class BulkUpdateService
+ include Gitlab::Allowable
+
+ attr_accessor :current_user, :params
+
+ def initialize(user = nil, params = {})
+ @current_user, @params = user, params.dup
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def execute(type)
model_class = type.classify.constantize
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 02de080e0ba..db673cace81 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -182,7 +182,7 @@ class IssuableBaseService < BaseService
# To be overridden by subclasses
end
- def before_update(issuable)
+ def before_update(issuable, skip_spam_check: false)
# To be overridden by subclasses
end
@@ -257,7 +257,7 @@ class IssuableBaseService < BaseService
last_edited_at: Time.now,
last_edited_by: current_user))
- before_update(issuable)
+ before_update(issuable, skip_spam_check: true)
if issuable.with_transaction_returning_status { issuable.save }
# We do not touch as it will affect a update on updated_at field
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 6b9f23f24cd..7cd825aa967 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -17,8 +17,8 @@ module Issues
super
end
- def before_update(issue)
- spam_check(issue, current_user)
+ def before_update(issue, skip_spam_check: false)
+ spam_check(issue, current_user) unless skip_spam_check
end
def handle_changes(issue, options)
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
index db710bac900..c032985be42 100644
--- a/app/services/labels/create_service.rb
+++ b/app/services/labels/create_service.rb
@@ -20,7 +20,7 @@ module Labels
label.save
label
else
- Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}")
+ Rails.logger.warn("target_params should contain :project or :group or :template, actual value: #{target_params}") # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/app/services/merge_requests/merge_base_service.rb b/app/services/merge_requests/merge_base_service.rb
index 095bdca5472..1ed396cee1e 100644
--- a/app/services/merge_requests/merge_base_service.rb
+++ b/app/services/merge_requests/merge_base_service.rb
@@ -28,6 +28,17 @@ module MergeRequests
private
+ def check_source
+ unless source
+ raise_error('No source for merge')
+ end
+ end
+
+ # Overridden in EE.
+ def check_size_limit
+ # No-op
+ end
+
# Overridden in EE.
def error_check!
# No-op
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index d8a78001b79..6309052244d 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -48,13 +48,13 @@ module MergeRequests
def error_check!
super
+ check_source
+
error =
if @merge_request.should_be_rebased?
'Only fast-forward merge is allowed for your project. Please update your source branch'
elsif !@merge_request.mergeable?
'Merge request is not mergeable'
- elsif !source
- 'No source for merge'
end
raise_error(error) if error
@@ -113,12 +113,12 @@ module MergeRequests
end
def handle_merge_error(log_message:, save_message_on_model: false)
- Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}")
+ Rails.logger.error("MergeService ERROR: #{merge_request_info} - #{log_message}") # rubocop:disable Gitlab/RailsLogger
@merge_request.update(merge_error: log_message) if save_message_on_model
end
def log_info(message)
- @logger ||= Rails.logger
+ @logger ||= Rails.logger # rubocop:disable Gitlab/RailsLogger
@logger.info("#{merge_request_info} - #{message}")
end
diff --git a/app/services/merge_requests/merge_to_ref_service.rb b/app/services/merge_requests/merge_to_ref_service.rb
index 0ea50a5dbf5..37b5805ae7e 100644
--- a/app/services/merge_requests/merge_to_ref_service.rb
+++ b/app/services/merge_requests/merge_to_ref_service.rb
@@ -16,7 +16,7 @@ module MergeRequests
def execute(merge_request)
@merge_request = merge_request
- validate!
+ error_check!
commit_id = commit
@@ -39,21 +39,9 @@ module MergeRequests
merge_request.diff_head_sha
end
- def validate!
- error_check!
- end
-
+ override :error_check!
def error_check!
- super
-
- error =
- if !hooks_validation_pass?(merge_request)
- hooks_validation_error(merge_request)
- elsif source.blank?
- 'No source for merge'
- end
-
- raise_error(error) if error
+ check_source
end
##
diff --git a/app/services/projects/after_import_service.rb b/app/services/projects/after_import_service.rb
index bbdde4408d2..e30da0f26df 100644
--- a/app/services/projects/after_import_service.rb
+++ b/app/services/projects/after_import_service.rb
@@ -13,7 +13,7 @@ module Projects
repository.delete_all_refs_except(RESERVED_REF_PREFIXES)
end
rescue Projects::HousekeepingService::LeaseTaken => e
- Rails.logger.info(
+ Rails.logger.info( # rubocop:disable Gitlab/RailsLogger
"Could not perform housekeeping for project #{@project.full_path} (#{@project.id}): #{e}")
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 9f335cceb67..89dc4375c63 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -151,7 +151,7 @@ module Projects
log_message = message.dup
log_message << " Project ID: #{@project.id}" if @project&.id
- Rails.logger.error(log_message)
+ Rails.logger.error(log_message) # rubocop:disable Gitlab/RailsLogger
if @project && @project.persisted? && @project.import_state
@project.import_state.mark_as_failed(message)
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index d8e670e40ce..b805a7f1211 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -18,7 +18,7 @@ module Projects
schedule_stale_repos_removal
job_id = ProjectDestroyWorker.perform_async(project.id, current_user.id, params)
- Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}")
+ Rails.logger.info("User #{current_user.id} scheduled destruction of project #{project.full_path} with job ID #{job_id}") # rubocop:disable Gitlab/RailsLogger
end
def execute
diff --git a/app/services/projects/hashed_storage/migrate_attachments_service.rb b/app/services/projects/hashed_storage/migrate_attachments_service.rb
index 3d0b8f58612..affe6e5668d 100644
--- a/app/services/projects/hashed_storage/migrate_attachments_service.rb
+++ b/app/services/projects/hashed_storage/migrate_attachments_service.rb
@@ -5,7 +5,7 @@ module Projects
class MigrateAttachmentsService < BaseAttachmentService
def initialize(project, old_disk_path, logger: nil)
@project = project
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@old_disk_path = old_disk_path
@skipped = false
end
diff --git a/app/services/projects/hashed_storage/rollback_attachments_service.rb b/app/services/projects/hashed_storage/rollback_attachments_service.rb
index 5c6b92f965c..fb09eaa4586 100644
--- a/app/services/projects/hashed_storage/rollback_attachments_service.rb
+++ b/app/services/projects/hashed_storage/rollback_attachments_service.rb
@@ -5,7 +5,7 @@ module Projects
class RollbackAttachmentsService < BaseAttachmentService
def initialize(project, logger: nil)
@project = project
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@old_disk_path = project.disk_path
end
diff --git a/app/services/projects/hashed_storage/rollback_service.rb b/app/services/projects/hashed_storage/rollback_service.rb
index 25767f5de5e..ee41aae64a5 100644
--- a/app/services/projects/hashed_storage/rollback_service.rb
+++ b/app/services/projects/hashed_storage/rollback_service.rb
@@ -8,7 +8,7 @@ module Projects
def initialize(project, old_disk_path, logger: nil)
@project = project
@old_disk_path = old_disk_path
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def execute
diff --git a/app/services/projects/import_export/export_service.rb b/app/services/projects/import_export/export_service.rb
index e3491282a8a..9c6d7ef41f6 100644
--- a/app/services/projects/import_export/export_service.rb
+++ b/app/services/projects/import_export/export_service.rb
@@ -62,7 +62,7 @@ module Projects
end
def cleanup_and_notify_error
- Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}")
+ Rails.logger.error("Import/Export - Project #{project.name} with ID: #{project.id} export error - #{@shared.errors.join(', ')}") # rubocop:disable Gitlab/RailsLogger
FileUtils.rm_rf(@shared.export_path)
@@ -76,7 +76,7 @@ module Projects
end
def notify_success
- Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported")
+ Rails.logger.info("Import/Export - Project #{project.name} with ID: #{project.id} successfully exported") # rubocop:disable Gitlab/RailsLogger
end
def notify_error
diff --git a/app/services/projects/propagate_service_template.rb b/app/services/projects/propagate_service_template.rb
index a25c985585b..64f9b611c40 100644
--- a/app/services/projects/propagate_service_template.rb
+++ b/app/services/projects/propagate_service_template.rb
@@ -15,7 +15,7 @@ module Projects
def propagate
return unless @template.active?
- Rails.logger.info("Propagating services for template #{@template.id}")
+ Rails.logger.info("Propagating services for template #{@template.id}") # rubocop:disable Gitlab/RailsLogger
propagate_projects_with_template
end
diff --git a/app/services/projects/update_statistics_service.rb b/app/services/projects/update_statistics_service.rb
index 28677a398f3..cc6ffa9eafc 100644
--- a/app/services/projects/update_statistics_service.rb
+++ b/app/services/projects/update_statistics_service.rb
@@ -5,7 +5,7 @@ module Projects
def execute
return unless project
- Rails.logger.info("Updating statistics for project #{project.id}")
+ Rails.logger.info("Updating statistics for project #{project.id}") # rubocop:disable Gitlab/RailsLogger
project.statistics.refresh!(only: statistics.map(&:to_sym))
end
diff --git a/app/services/submit_usage_ping_service.rb b/app/services/submit_usage_ping_service.rb
index 62222d3fd2a..4f10f220298 100644
--- a/app/services/submit_usage_ping_service.rb
+++ b/app/services/submit_usage_ping_service.rb
@@ -28,7 +28,7 @@ class SubmitUsagePingService
true
rescue Gitlab::HTTP::Error => e
- Rails.logger.info "Unable to contact GitLab, Inc.: #{e}"
+ Rails.logger.info "Unable to contact GitLab, Inc.: #{e}" # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 1fee8bfcd31..6d675c026bb 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -53,7 +53,7 @@ class WebHookService
error_message: e.to_s
)
- Rails.logger.error("WebHook Error => #{e}")
+ Rails.logger.error("WebHook Error => #{e}") # rubocop:disable Gitlab/RailsLogger
{
status: :error,
diff --git a/app/uploaders/file_mover.rb b/app/uploaders/file_mover.rb
index 12be1e2bb22..7c7953c8a0e 100644
--- a/app/uploaders/file_mover.rb
+++ b/app/uploaders/file_mover.rb
@@ -98,7 +98,7 @@ class FileMover
end
def revert
- Rails.logger.warn("Markdown not updated, file move reverted for #{to_model}")
+ Rails.logger.warn("Markdown not updated, file move reverted for #{to_model}") # rubocop:disable Gitlab/RailsLogger
if temp_file_uploader.file_storage?
FileUtils.move(file_path, temp_file_path)
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index f524d35d79e..98230684d56 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -43,11 +43,7 @@
= render_if_exists 'admin/namespace_plan_info', namespace: @group
%li
- %span.light= _('Storage:')
- %strong= storage_counter(@group.storage_size)
- (
- = storage_counters_details(@group)
- )
+ = render 'shared/storage_counter_statistics', storage_size: @group.storage_size, storage_details: @group
%li
%span.light= _('Group Git LFS status:')
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index e23accc1ea9..0fae8060b32 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -73,11 +73,7 @@
= @project.repository.relative_path
%li
- %span.light= _('Storage:')
- %strong= storage_counter(@project.statistics&.storage_size)
- - if @project.statistics
- = surround '(', ')' do
- = storage_counters_details(@project.statistics)
+ = render 'shared/storage_counter_statistics', storage_size: @project.statistics&.storage_size, storage_details: @project.statistics
%li
%span.light last commit:
diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml
index 37ba2143eba..b9be6028b72 100644
--- a/app/views/dashboard/milestones/index.html.haml
+++ b/app/views/dashboard/milestones/index.html.haml
@@ -8,7 +8,7 @@
- if current_user
.page-title-controls
= render 'shared/new_project_item_select',
- path: 'milestones/new', label: 'New milestone',
+ path: '-/milestones/new', label: 'New milestone',
include_groups: true, type: :milestones
.top-area
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 91d17cfd745..f05e269553a 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,3 +1,5 @@
+- @can_bulk_update = can?(current_user, :admin_issue, @group)
+
- page_title "Issues"
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{@group.name} issues")
@@ -9,8 +11,15 @@
= render 'shared/issuable/nav', type: :issues
.nav-controls
= render 'shared/issuable/feed_buttons'
+
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/bulk_update_button'
+
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :issues
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :issues
+
= render 'shared/issues'
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 496ec3c78b0..a5f57f5893c 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -1,5 +1,5 @@
- if @group && @group.persisted? && @group.path
- - group_data_attrs = { group_path: j(@group.path), name: @group.name, issues_path: issues_group_path(j(@group.path)), mr_path: merge_requests_group_path(j(@group.path)) }
+ - group_data_attrs = { group_path: j(@group.path), name: j(@group.name), issues_path: issues_group_path(@group), mr_path: merge_requests_group_path(@group) }
- if @project && @project.persisted?
- project_data_attrs = { project_path: j(@project.path), name: j(@project.name), issues_path: project_issues_path(@project), mr_path: project_merge_requests_path(@project), issues_disabled: !@project.issues_enabled? }
.search.search-form{ data: { track_label: "navbar_search", track_event: "activate_form_input" } }
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index e423631ec99..7541737f79c 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -36,17 +36,17 @@
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control", required: true
- if current_user.can_create_group?
.form-text.text-muted
- Want to house several dependent projects under the same namespace?
- = link_to "Create a group.", new_group_path
+ - link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
+ - project_tip = s_('ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}') % { link_start: link_start_group_path, link_end: '</a>' }
+ = project_tip.html_safe
.form-group
= f.label :description, class: 'label-bold' do
- Project description
- %span (optional)
- = f.text_area :description, placeholder: 'Description format', class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
+ = s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
+ = f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_description", track_value: "" }
= f.label :visibility_level, class: 'label-bold' do
- Visibility Level
+ = s_('ProjectsNew|Visibility Level')
= link_to icon('question-circle'), help_page_path("public_access/public_access"), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
@@ -57,9 +57,9 @@
= check_box_tag 'project[initialize_with_readme]', '1', false, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
- %strong Initialize repository with a README
+ %strong= s_('ProjectsNew|Initialize repository with a README')
.option-description
- Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.
+ = s_('ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository.')
-= f.submit 'Create project', class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" }
-= link_to 'Cancel', dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" }
+= f.submit _('Create project'), class: "btn btn-success project-submit", data: { track_label: "#{track_label}", track_event: "click_button", track_property: "create_project", track_value: "" }
+= link_to _('Cancel'), dashboard_projects_path, class: 'btn btn-cancel', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "cancel" }
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 1cfe302fdc7..33de0aa153b 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -1,7 +1,7 @@
- @hide_breadcrumbs = true
- @hide_top_links = true
-- page_title 'New Project'
-- header_title "Projects", dashboard_projects_path
+- page_title _('New Project')
+- header_title _("Projects"), dashboard_projects_path
- active_tab = local_assigns.fetch(:active_tab, 'blank')
.project-edit-container.prepend-top-default
@@ -33,16 +33,16 @@
%ul.nav.nav-tabs.nav-links.gitlab-tabs{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
%a.nav-link.active{ href: '#blank-project-pane', id: 'blank-project-tab', data: { toggle: 'tab', track_label: 'blank_project', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Blank project
- %span.d-block.d-sm-none Blank
+ %span.d-none.d-sm-block= s_('ProjectsNew|Blank project')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Blank')
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#create-from-template-pane', id: 'create-from-template-tab', data: { toggle: 'tab', track_label: 'create_from_template', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block.qa-project-create-from-template-tab Create from template
- %span.d-block.d-sm-none Template
+ %span.d-none.d-sm-block.qa-project-create-from-template-tab= s_('ProjectsNew|Create from template')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Template')
%li.nav-item{ role: 'presentation' }
%a.nav-link{ href: '#import-project-pane', id: 'import-project-tab', data: { toggle: 'tab', track_label: 'import_project', track_event: "click_tab" }, role: 'tab' }
- %span.d-none.d-sm-block Import project
- %span.d-block.d-sm-none Import
+ %span.d-none.d-sm-block= s_('ProjectsNew|Import project')
+ %span.d-block.d-sm-none= s_('ProjectsNew|Import')
= render_if_exists 'projects/new_ci_cd_only_project_tab', active_tab: active_tab
.tab-content.gitlab-tab-content
@@ -67,8 +67,8 @@
= render 'import_project_pane', active_tab: active_tab
- else
.nothing-here-block
- %h4 No import options available
- %p Contact an administrator to enable options for importing your project.
+ %h4= s_('ProjectsNew|No import options available')
+ %p= s_('ProjectsNew|Contact an administrator to enable options for importing your project.')
= render_if_exists 'projects/new_ci_cd_only_project_pane', active_tab: active_tab
@@ -76,5 +76,6 @@
.center
%h2
%i.fa.fa-spinner.fa-spin
- Creating project &amp; repository.
- %p Please wait a moment, this page will automatically refresh when ready.
+ = s_('ProjectsNew|Creating project & repository.')
+ %p
+ = s_('ProjectsNew|Please wait a moment, this page will automatically refresh when ready.')
diff --git a/app/views/shared/_storage_counter_statistics.html.haml b/app/views/shared/_storage_counter_statistics.html.haml
new file mode 100644
index 00000000000..99b2323ca82
--- /dev/null
+++ b/app/views/shared/_storage_counter_statistics.html.haml
@@ -0,0 +1,4 @@
+%span.light= _('Storage:')
+%strong= storage_counter(storage_size)
+- if storage_details
+ (#{storage_counters_details(storage_details)})
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 1bd56e064d5..214e87052da 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -17,8 +17,7 @@
= render 'shared/issuable/form/template_selector', issuable: issuable
= render 'shared/issuable/form/title', issuable: issuable, form: form, has_wip_commits: commits && commits.detect(&:work_in_progress?)
-- if Gitlab::Graphql.enabled?
- #js-suggestions{ data: { project_path: @project.full_path } }
+#js-suggestions{ data: { project_path: @project.full_path } }
= render 'shared/form_elements/description', model: issuable, form: form, project: project
diff --git a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
index 5336159e762..60dc893d9f9 100644
--- a/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
+++ b/app/views/shared/issuable/form/_metadata_issuable_assignee.html.haml
@@ -1,4 +1,4 @@
-= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-lg-4" : "col-sm-2"}"
+= form.label :assignee_id, "Assignee", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) }
.issuable-form-select-holder.selectbox
- issuable.assignees.each do |assignee|
diff --git a/app/workers/concerns/new_issuable.rb b/app/workers/concerns/new_issuable.rb
index a89451a4475..22ba7c97309 100644
--- a/app/workers/concerns/new_issuable.rb
+++ b/app/workers/concerns/new_issuable.rb
@@ -27,6 +27,6 @@ module NewIssuable
# rubocop: enable CodeReuse/ActiveRecord
def log_error(record_class, record_id)
- Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job")
+ Rails.logger.error("#{self.class}: couldn't find #{record_class} with ID=#{record_id}, skipping job") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index 7fac7822cf7..e3fb5d479ae 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -22,7 +22,7 @@ class CreateGpgSignatureWorker
commits.each do |commit|
Gitlab::Gpg::Commit.new(commit).signature
rescue => e
- Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
+ Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index 4d0295f8d2e..efa8794b214 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -9,6 +9,6 @@ class DeleteUserWorker
Users::DestroyService.new(current_user).execute(delete_user, options.symbolize_keys)
rescue Gitlab::Access::AccessDeniedError => e
- Rails.logger.warn("User could not be destroyed: #{e}")
+ Rails.logger.warn("User could not be destroyed: #{e}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index c4bcda2da16..e70bf17d5a9 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -16,7 +16,7 @@ class EmailReceiverWorker
private
def handle_failure(raw, error)
- Rails.logger.warn("Email can not be processed: #{error}\n\n#{raw}")
+ Rails.logger.warn("Email can not be processed: #{error}\n\n#{raw}") # rubocop:disable Gitlab/RailsLogger
return unless raw.present?
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 251e95c68d5..6f0e0fd33f7 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -18,7 +18,7 @@ class ExpireBuildArtifactsWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform_legacy_artifacts_removal
- Rails.logger.info 'Scheduling removal of build artifacts'
+ Rails.logger.info 'Scheduling removal of build artifacts' # rubocop:disable Gitlab/RailsLogger
build_ids = Ci::Build.with_expired_artifacts.pluck(:id)
build_ids = build_ids.map { |build_id| [build_id] }
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
index 94426dcf921..71e61dcb878 100644
--- a/app/workers/expire_build_instance_artifacts_worker.rb
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -12,7 +12,7 @@ class ExpireBuildInstanceArtifactsWorker
return unless build&.project && !build.project.pending_delete
- Rails.logger.info "Removing artifacts for build #{build.id}..."
+ Rails.logger.info "Removing artifacts for build #{build.id}..." # rubocop:disable Gitlab/RailsLogger
build.erase_erasable_artifacts!
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 98f9f45e608..1d1ea926c21 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -11,7 +11,7 @@ class NewNoteWorker
NotificationService.new.new_note(note) unless skip_notification?(note)
Notes::PostProcessService.new(note).execute
else
- Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job")
+ Rails.logger.error("NewNoteWorker: couldn't find note with ID=#{note_id}, skipping job") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index 12400d4e025..55ac7cd9b3c 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -37,6 +37,7 @@ module ObjectStorage
end
end
+ # rubocop:disable Gitlab/RailsLogger
def report!(results)
success, failures = results.partition(&:success?)
@@ -45,6 +46,7 @@ module ObjectStorage
raise MigrationFailures.new(failures.map(&:error)) if failures.any?
end
+ # rubocop:enable Gitlab/RailsLogger
def header(success, failures)
_("Migrated %{success_count}/%{total_count} files.") % { success_count: success.count, total_count: success.count + failures.count }
@@ -98,7 +100,7 @@ module ObjectStorage
report!(results)
rescue SanityCheckError => e
# do not retry: the job is insane
- Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})"
+ Rails.logger.warn "#{self.class}: Sanity check error (#{e.message})" # rubocop:disable Gitlab/RailsLogger
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index a9b88a133be..35e9c58eb13 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -35,7 +35,7 @@ class RepositoryForkWorker
def start_fork(project)
return true if start(project.import_state)
- Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.")
+ Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while forking.") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 59691f48a39..dff9c8f50bf 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -36,7 +36,7 @@ class RepositoryImportWorker
def start_import
return true if start(project.import_state)
- Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.")
+ Rails.logger.info("Project #{project.full_path} was in inconsistent state (#{project.import_status}) while importing.") # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index c0bae08ba85..03a7ff2cd7a 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -45,6 +45,6 @@ class RepositoryUpdateRemoteMirrorWorker
def fail_remote_mirror(remote_mirror, message)
remote_mirror.mark_as_failed(message)
- Rails.logger.error(message)
+ Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index 43e0b9db22f..351850e53cb 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -30,6 +30,7 @@ class RunPipelineScheduleWorker
private
+ # rubocop:disable Gitlab/RailsLogger
def error(schedule, error)
failed_creation_counter.increment
@@ -41,6 +42,7 @@ class RunPipelineScheduleWorker
issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/41231',
extra: { schedule_id: schedule.id })
end
+ # rubocop:enable Gitlab/RailsLogger
def failed_creation_counter
@failed_creation_counter ||=
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 25809f68080..30fba038937 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -14,7 +14,7 @@ class StuckCiJobsWorker
def perform
return unless try_obtain_lease
- Rails.logger.info "#{self.class}: Cleaning stuck builds"
+ Rails.logger.info "#{self.class}: Cleaning stuck builds" # rubocop:disable Gitlab/RailsLogger
drop :running, BUILD_RUNNING_OUTDATED_TIMEOUT, 'ci_builds.updated_at < ?', :stuck_or_timeout_failure
drop :pending, BUILD_PENDING_OUTDATED_TIMEOUT, 'ci_builds.updated_at < ?', :stuck_or_timeout_failure
@@ -66,7 +66,7 @@ class StuckCiJobsWorker
# rubocop: enable CodeReuse/ActiveRecord
def drop_build(type, build, status, timeout, reason)
- Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout}, reason: #{reason})"
+ Rails.logger.info "#{self.class}: Dropping #{type} build #{build.id} for runner #{build.runner_id} (status: #{status}, timeout: #{timeout}, reason: #{reason})" # rubocop:disable Gitlab/RailsLogger
Gitlab::OptimisticLocking.retry_lock(build, 3) do |b|
b.drop(reason)
end
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index c8a186ba4ce..a9ff5b22b25 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -38,7 +38,7 @@ class StuckImportJobsWorker
completed_import_states = enqueued_import_states_with_jid.where(id: completed_import_state_ids)
completed_import_state_jids = completed_import_states.map { |import_state| import_state.jid }.join(', ')
- Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}")
+ Rails.logger.info("Marked stuck import jobs as failed. JIDs: #{completed_import_state_jids}") # rubocop:disable Gitlab/RailsLogger
completed_import_states.each do |import_state|
import_state.mark_as_failed(error_message)
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index f34ed6c4844..e840ae47421 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -5,7 +5,7 @@ class StuckMergeJobsWorker
include CronjobQueue
def self.logger
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
index 3297a1fe3d0..55b599ba38f 100644
--- a/app/workers/trending_projects_worker.rb
+++ b/app/workers/trending_projects_worker.rb
@@ -5,7 +5,7 @@ class TrendingProjectsWorker
include CronjobQueue
def perform
- Rails.logger.info('Refreshing trending projects')
+ Rails.logger.info('Refreshing trending projects') # rubocop:disable Gitlab/RailsLogger
TrendingProject.refresh!
end
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index c7213df652a..6c0e472e05a 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -27,7 +27,7 @@ class UpdateMergeRequestsWorker
"ref=#{ref}"
].join(',')
- Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD
+ Rails.logger.info("UpdateMergeRequestsWorker#perform #{args_log}") if time.real > LOG_TIME_THRESHOLD # rubocop:disable Gitlab/RailsLogger
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/upload_checksum_worker.rb b/app/workers/upload_checksum_worker.rb
index 2a0536106d7..834dcaa435d 100644
--- a/app/workers/upload_checksum_worker.rb
+++ b/app/workers/upload_checksum_worker.rb
@@ -8,6 +8,6 @@ class UploadChecksumWorker
upload.calculate_checksum!
upload.save!
rescue ActiveRecord::RecordNotFound
- Rails.logger.error("UploadChecksumWorker: couldn't find upload #{upload_id}, skipping")
+ Rails.logger.error("UploadChecksumWorker: couldn't find upload #{upload_id}, skipping") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml b/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml
new file mode 100644
index 00000000000..3445057f7fb
--- /dev/null
+++ b/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml
@@ -0,0 +1,5 @@
+---
+title: Improved readability of storage statistics in group / project admin area
+merge_request: 30406
+author:
+type: other
diff --git a/changelogs/unreleased/62826-graphql-note-mutations.yml b/changelogs/unreleased/62826-graphql-note-mutations.yml
new file mode 100644
index 00000000000..85273186bb6
--- /dev/null
+++ b/changelogs/unreleased/62826-graphql-note-mutations.yml
@@ -0,0 +1,5 @@
+---
+title: GraphQL mutations for managing Notes
+merge_request: 30210
+author:
+type: added
diff --git a/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml b/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml
new file mode 100644
index 00000000000..a0ef34f3700
--- /dev/null
+++ b/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml
@@ -0,0 +1,5 @@
+---
+title: Update mixin-deep to 1.3.2
+merge_request: 30223
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/64066-fix-uneven-click-areas.yml b/changelogs/unreleased/64066-fix-uneven-click-areas.yml
new file mode 100644
index 00000000000..ce0572cad34
--- /dev/null
+++ b/changelogs/unreleased/64066-fix-uneven-click-areas.yml
@@ -0,0 +1,5 @@
+---
+title: Fix spacing issues for toasts
+merge_request: 30345
+author:
+type: fixed
diff --git a/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml b/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml
new file mode 100644
index 00000000000..825247db3e7
--- /dev/null
+++ b/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml
@@ -0,0 +1,5 @@
+---
+title: Fix wrong URL when creating milestones from instance milestones dashboard
+merge_request: 30512
+author:
+type: fixed
diff --git a/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml b/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml
new file mode 100644
index 00000000000..7d67b94869d
--- /dev/null
+++ b/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed incorrect line wrap for assignee label in issues
+merge_request: 30523
+author: Marc Schwede
+type: fixed
diff --git a/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml b/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml
new file mode 100644
index 00000000000..0500978a2e1
--- /dev/null
+++ b/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml
@@ -0,0 +1,5 @@
+---
+title: "Document the negative commit message push rule for the API."
+merge_request: 14004
+author: Maikel Vlasman
+type: added
diff --git a/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml b/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml
new file mode 100644
index 00000000000..f614e076268
--- /dev/null
+++ b/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Improve performance of fetching environments statuses
+merge_request: 30560
+author:
+type: performance
diff --git a/changelogs/unreleased/caneldem-master-patch-77839.yml b/changelogs/unreleased/caneldem-master-patch-77839.yml
new file mode 100644
index 00000000000..6239bcf67c4
--- /dev/null
+++ b/changelogs/unreleased/caneldem-master-patch-77839.yml
@@ -0,0 +1,5 @@
+---
+title: Propagate python version variable
+merge_request:
+author: Can Eldem
+type: changed
diff --git a/changelogs/unreleased/embedded-metrics-be-2.yml b/changelogs/unreleased/embedded-metrics-be-2.yml
new file mode 100644
index 00000000000..2623b4a2e0c
--- /dev/null
+++ b/changelogs/unreleased/embedded-metrics-be-2.yml
@@ -0,0 +1,5 @@
+---
+title: Expose placeholder element for metrics charts in GFM
+merge_request: 29861
+author:
+type: added
diff --git a/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml b/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml
new file mode 100644
index 00000000000..6ae6db08ba1
--- /dev/null
+++ b/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Fix median counting for cycle analytics
+merge_request: 30229
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml b/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml
new file mode 100644
index 00000000000..9a263b7f460
--- /dev/null
+++ b/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml
@@ -0,0 +1,5 @@
+---
+title: Make unicorn_workers to return meaningful results
+merge_request: 30506
+author:
+type: fixed
diff --git a/changelogs/unreleased/fj-fix-subgroup-search-url.yml b/changelogs/unreleased/fj-fix-subgroup-search-url.yml
new file mode 100644
index 00000000000..bee6e97fb6f
--- /dev/null
+++ b/changelogs/unreleased/fj-fix-subgroup-search-url.yml
@@ -0,0 +1,5 @@
+---
+title: Fix subgroup url in search drop down
+merge_request: 30457
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.52.0.yml b/changelogs/unreleased/gitaly-version-v1.52.0.yml
new file mode 100644
index 00000000000..d56a392c4ff
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.52.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.52.0
+merge_request: 30568
+author:
+type: changed
diff --git a/changelogs/unreleased/issue_64021.yml b/changelogs/unreleased/issue_64021.yml
new file mode 100644
index 00000000000..088d9348619
--- /dev/null
+++ b/changelogs/unreleased/issue_64021.yml
@@ -0,0 +1,5 @@
+---
+title: Skip spam check for task list updates
+merge_request: 30279
+author:
+type: fixed
diff --git a/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml b/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml
new file mode 100644
index 00000000000..b953d7c0fc8
--- /dev/null
+++ b/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml
@@ -0,0 +1,5 @@
+---
+title: Enable Git object pools
+merge_request: 29595
+author: jramsay
+type: changed
diff --git a/changelogs/unreleased/patch-29.yml b/changelogs/unreleased/patch-29.yml
new file mode 100644
index 00000000000..674c06e1259
--- /dev/null
+++ b/changelogs/unreleased/patch-29.yml
@@ -0,0 +1,5 @@
+---
+title: Updates PHP template to php:latest to ensure always targeting latest stable
+merge_request: 30319
+author: Paul Giberson
+type: changed
diff --git a/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml b/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml
new file mode 100644
index 00000000000..d7bfc67b208
--- /dev/null
+++ b/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml
@@ -0,0 +1,5 @@
+---
+title: Modify cycle analytics on project level
+merge_request: 30356
+author:
+type: changed
diff --git a/changelogs/unreleased/project_api.yml b/changelogs/unreleased/project_api.yml
new file mode 100644
index 00000000000..a04f9bb5608
--- /dev/null
+++ b/changelogs/unreleased/project_api.yml
@@ -0,0 +1,5 @@
+---
+title: Improve Project API
+merge_request: 28327
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/tz-update-mr-count-over-tabs.yml b/changelogs/unreleased/tz-update-mr-count-over-tabs.yml
new file mode 100644
index 00000000000..61a49dd8ecc
--- /dev/null
+++ b/changelogs/unreleased/tz-update-mr-count-over-tabs.yml
@@ -0,0 +1,6 @@
+---
+title: New API for User Counts, updates on success of an MR the count on top and in
+ other tabs
+merge_request: 29441
+author:
+type: added
diff --git a/changelogs/unreleased/update-clair-version.yml b/changelogs/unreleased/update-clair-version.yml
new file mode 100644
index 00000000000..59b6e113fd5
--- /dev/null
+++ b/changelogs/unreleased/update-clair-version.yml
@@ -0,0 +1,6 @@
+---
+title: Extract clair version as CLAIR_EXECUTABLE_VERSION variable and update clair
+ executable from v8 to v11
+merge_request: 30396
+author:
+type: changed
diff --git a/changelogs/unreleased/winh-notes-service-deleteNote.yml b/changelogs/unreleased/winh-notes-service-deleteNote.yml
new file mode 100644
index 00000000000..cf2b37755a2
--- /dev/null
+++ b/changelogs/unreleased/winh-notes-service-deleteNote.yml
@@ -0,0 +1,5 @@
+---
+title: Remove deleteNote from notes service
+merge_request: 30537
+author: Frank van Rest
+type: other
diff --git a/changelogs/unreleased/winh-notes-service-toggleAward.yml b/changelogs/unreleased/winh-notes-service-toggleAward.yml
new file mode 100644
index 00000000000..0471888c285
--- /dev/null
+++ b/changelogs/unreleased/winh-notes-service-toggleAward.yml
@@ -0,0 +1,5 @@
+---
+title: Remove toggleAward from notes service
+merge_request: 30536
+author: Frank van Rest
+type: other
diff --git a/config/application.rb b/config/application.rb
index c5ef6a2c60d..edf8b3e87f9 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -6,6 +6,7 @@ Bundler.require(:default, Rails.env)
module Gitlab
class Application < Rails::Application
+ require_dependency Rails.root.join('lib/gitlab')
require_dependency Rails.root.join('lib/gitlab/redis/wrapper')
require_dependency Rails.root.join('lib/gitlab/redis/cache')
require_dependency Rails.root.join('lib/gitlab/redis/queues')
diff --git a/config/brakeman.ignore b/config/brakeman.ignore
new file mode 100644
index 00000000000..0e4fef65781
--- /dev/null
+++ b/config/brakeman.ignore
@@ -0,0 +1,24 @@
+{
+ "ignored_warnings": [
+ {
+ "warning_type": "Cross-Site Request Forgery",
+ "warning_code": 7,
+ "fingerprint": "dc562678129557cdb8b187217da304044547a3605f05fe678093dcb4b4d8bbe4",
+ "message": "'protect_from_forgery' should be called in Oauth::GeoAuthController",
+ "file": "app/controllers/oauth/geo_auth_controller.rb",
+ "line": 1,
+ "link": "http://brakemanscanner.org/docs/warning_types/cross-site_request_forgery/",
+ "code": null,
+ "render_path": null,
+ "location": {
+ "type": "controller",
+ "controller": "Oauth::GeoAuthController"
+ },
+ "user_input": null,
+ "confidence": "High",
+ "note": ""
+ }
+ ],
+ "updated": "2017-01-20 02:06:54 +0000",
+ "brakeman_version": "3.4.1"
+}
diff --git a/config/database_geo.yml.postgresql b/config/database_geo.yml.postgresql
new file mode 100644
index 00000000000..2918879f7ed
--- /dev/null
+++ b/config/database_geo.yml.postgresql
@@ -0,0 +1,51 @@
+#
+# PRODUCTION
+#
+production:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_production
+ pool: 10
+ username: git
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+#
+# Development specific
+#
+development:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_development
+ pool: 5
+ username: postgres
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+#
+# Staging specific
+#
+staging:
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_staging
+ pool: 10
+ username: git
+ password: "secure password"
+ host: localhost
+ fdw: true
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test: &test
+ adapter: postgresql
+ encoding: unicode
+ database: gitlabhq_geo_test
+ pool: 5
+ username: postgres
+ password:
+ host: localhost
+ fdw: true
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index c82d9b5ceef..334c241bcaa 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -664,6 +664,9 @@ production: &base
# Port where the client side certificate is requested by the webserver (NGINX/Apache)
# client_certificate_required_port: 3444
+ # Browser session with smartcard sign-in is required for Git access
+ # required_for_git_access: false
+
## Kerberos settings
kerberos:
# Allow the HTTP Negotiate authentication method for Git clients
diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb
new file mode 100644
index 00000000000..f750022dfdf
--- /dev/null
+++ b/config/initializers/0_license.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+Gitlab.ee do
+ begin
+ public_key_file = File.read(Rails.root.join(".license_encryption_key.pub"))
+ public_key = OpenSSL::PKey::RSA.new(public_key_file)
+ Gitlab::License.encryption_key = public_key
+ rescue
+ warn "WARNING: No valid license encryption key provided."
+ end
+
+ # Needed to run migration
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
+ if ::License.block_changes? && message.present?
+ warn "WARNING: #{message}"
+ end
+ end
+end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index bf187e9a282..3a121addc98 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -55,7 +55,7 @@ if Settings.ldap['enabled'] || Rails.env.test?
server['tls_options'] ||= {}
if server['ssl_version'] || server['ca_file']
- Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`'
+ Rails.logger.warn 'DEPRECATED: LDAP options `ssl_version` and `ca_file` should be nested within `tls_options`' # rubocop:disable Gitlab/RailsLogger
end
if server['ssl_version']
@@ -76,6 +76,7 @@ Gitlab.ee do
Settings['smartcard'] ||= Settingslogic.new({})
Settings.smartcard['enabled'] = false if Settings.smartcard['enabled'].nil?
Settings.smartcard['client_certificate_required_port'] = 3444 if Settings.smartcard['client_certificate_required_port'].nil?
+ Settings.smartcard['required_for_git_access'] = false if Settings.smartcard['required_for_git_access'].nil?
end
Settings['omniauth'] ||= Settingslogic.new({})
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 741c8ef1ca0..53c3eac3c74 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -17,7 +17,7 @@ def prometheus_default_multiproc_dir
end
Prometheus::Client.configure do |config|
- config.logger = Rails.logger
+ config.logger = Rails.logger # rubocop:disable Gitlab/RailsLogger
config.initial_mmap_file_size = 4 * 1024
diff --git a/config/initializers/active_record_lifecycle.rb b/config/initializers/active_record_lifecycle.rb
index 7fa37121efc..61f1d299960 100644
--- a/config/initializers/active_record_lifecycle.rb
+++ b/config/initializers/active_record_lifecycle.rb
@@ -7,7 +7,7 @@ if defined?(ActiveRecord::Base) && !Sidekiq.server?
ActiveSupport.on_load(:active_record) do
ActiveRecord::Base.establish_connection
- Rails.logger.debug("ActiveRecord connection established")
+ Rails.logger.debug("ActiveRecord connection established") # rubocop:disable Gitlab/RailsLogger
end
end
end
@@ -18,6 +18,6 @@ if defined?(ActiveRecord::Base)
# as there's no need for the master process to hold a connection
ActiveRecord::Base.connection.disconnect!
- Rails.logger.debug("ActiveRecord connection disconnected")
+ Rails.logger.debug("ActiveRecord connection disconnected") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/config/initializers/ar_speed_up_migration_checking.rb b/config/initializers/ar_speed_up_migration_checking.rb
index aae774daa35..f98b246db0b 100644
--- a/config/initializers/ar_speed_up_migration_checking.rb
+++ b/config/initializers/ar_speed_up_migration_checking.rb
@@ -10,7 +10,8 @@ if Rails.env.test?
# it reads + parses `db/migrate/*` each time. Memoizing it can save 0.5
# seconds per spec.
def migrations(paths)
- (@migrations ||= migrations_unmemoized(paths)).dup
+ @migrations ||= {}
+ (@migrations[paths] ||= migrations_unmemoized(paths)).dup
end
end
end
diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb
index 05eb395028d..04c109aa844 100644
--- a/config/initializers/console_message.rb
+++ b/config/initializers/console_message.rb
@@ -2,9 +2,18 @@
if defined?(Rails::Console)
# note that this will not print out when using `spring`
justify = 15
- puts "-------------------------------------------------------------------------------------"
+
+ puts '-' * 80
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)}"
puts " #{Gitlab::Database.human_adapter_name}:".ljust(justify) + Gitlab::Database.version
- puts "-------------------------------------------------------------------------------------"
+
+ Gitlab.ee do
+ if Gitlab::Geo.enabled?
+ puts " Geo enabled:".ljust(justify) + 'yes'
+ puts " Geo server:".ljust(justify) + EE::GeoHelper.current_node_human_status
+ end
+ end
+
+ puts '-' * 80
end
diff --git a/config/initializers/deprecations.rb b/config/initializers/deprecations.rb
index 14616e726d9..0d096e34eb7 100644
--- a/config/initializers/deprecations.rb
+++ b/config/initializers/deprecations.rb
@@ -2,7 +2,7 @@ if Rails.env.development? || ENV['GITLAB_LEGACY_PATH_LOG_MESSAGE']
deprecator = ActiveSupport::Deprecation.new('11.0', 'GitLab')
deprecator.behavior = -> (message, callstack) {
- Rails.logger.warn("#{message}: #{callstack[1..20].join}")
+ Rails.logger.warn("#{message}: #{callstack[1..20].join}") # rubocop:disable Gitlab/RailsLogger
}
ActiveSupport::Deprecation.deprecate_methods(Gitlab::GitalyClient::StorageSettings, :legacy_disk_path, deprecator: deprecator)
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
new file mode 100644
index 00000000000..2ecb7956007
--- /dev/null
+++ b/config/initializers/elastic_client_setup.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# Be sure to restart your server when you modify this file.
+
+require 'gitlab/current_settings'
+
+Gitlab.ee do
+ Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
+ Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
+ Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
+
+ module Elasticsearch
+ module Model
+ module Client
+ # This mutex is only used to synchronize *creation* of a new client, so
+ # all including classes can share the same client instance
+ CLIENT_MUTEX = Mutex.new
+
+ cattr_accessor :cached_client
+ cattr_accessor :cached_config
+
+ module ClassMethods
+ # Override the default ::Elasticsearch::Model::Client implementation to
+ # return a client configured from application settings. All including
+ # classes will use the same instance, which is refreshed automatically
+ # if the settings change.
+ #
+ # _client is present to match the arity of the overridden method, where
+ # it is also not used.
+ #
+ # @return [Elasticsearch::Transport::Client]
+ def client(_client = nil)
+ store = ::Elasticsearch::Model::Client
+
+ store::CLIENT_MUTEX.synchronize do
+ config = Gitlab::CurrentSettings.elasticsearch_config
+
+ if store.cached_client.nil? || config != store.cached_config
+ store.cached_client = ::Gitlab::Elastic::Client.build(config)
+ store.cached_config = config
+ end
+ end
+
+ store.cached_client
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/config/initializers/geo.rb b/config/initializers/geo.rb
new file mode 100644
index 00000000000..4cc9fbf49b2
--- /dev/null
+++ b/config/initializers/geo.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+Gitlab.ee do
+ if File.exist?(Rails.root.join('config/database_geo.yml'))
+ Rails.application.configure do
+ config.geo_database = config_for(:database_geo)
+ end
+ end
+
+ begin
+ if Gitlab::Geo.connected? && Gitlab::Geo.primary?
+ Gitlab::Geo.current_node&.update_clone_url!
+ end
+ rescue => e
+ warn "WARNING: Unable to check/update clone_url_prefix for Geo: #{e}"
+ end
+end
diff --git a/config/initializers/health_check.rb b/config/initializers/health_check.rb
index 959daa93f78..9f466dc39de 100644
--- a/config/initializers/health_check.rb
+++ b/config/initializers/health_check.rb
@@ -1,4 +1,10 @@
HealthCheck.setup do |config|
config.standard_checks = %w(database migrations cache)
config.full_checks = %w(database migrations cache)
+
+ Gitlab.ee do
+ config.add_custom_check('geo') do
+ Gitlab::Geo::HealthCheck.new.perform_checks
+ end
+ end
end
diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb
new file mode 100644
index 00000000000..029c0ff4277
--- /dev/null
+++ b/config/initializers/load_balancing.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+# We need to run this initializer after migrations are done so it doesn't fail on CI
+
+Gitlab.ee do
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if Gitlab::Database::LoadBalancing.enable?
+ Gitlab::Database.disable_prepared_statements
+
+ Gitlab::Application.configure do |config|
+ config.middleware.use(Gitlab::Database::LoadBalancing::RackMiddleware)
+ end
+
+ Gitlab::Database::LoadBalancing.configure_proxy
+
+ # This needs to be executed after fork of clustered processes
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ # Service discovery must be started after configuring the proxy, as service
+ # discovery depends on this.
+ Gitlab::Database::LoadBalancing.start_service_discovery
+ end
+
+ end
+ end
+end
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 7b69cf11288..166fb9b6916 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -72,7 +72,7 @@ Sidekiq.configure_server do |config|
cron_jobs[k]['class'] = cron_jobs[k].delete('job_class')
else
cron_jobs.delete(k)
- Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.")
+ Rails.logger.error("Invalid cron_jobs config key: '#{k}'. Check your gitlab config file.") # rubocop:disable Gitlab/RailsLogger
end
end
Sidekiq::Cron::Job.load_from_hash! cron_jobs
@@ -83,7 +83,20 @@ Sidekiq.configure_server do |config|
Rails.application.config.database_configuration[Rails.env]
db_config['pool'] = Sidekiq.options[:concurrency]
ActiveRecord::Base.establish_connection(db_config)
- Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}")
+ Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") # rubocop:disable Gitlab/RailsLogger
+
+ Gitlab.ee do
+ Gitlab::Mirror.configure_cron_job!
+
+ Gitlab::Geo.configure_cron_jobs!
+
+ if Gitlab::Geo.geo_database_configured?
+ Rails.configuration.geo_database['pool'] = Sidekiq.options[:concurrency]
+ Geo::TrackingBase.establish_connection(Rails.configuration.geo_database)
+
+ Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{Geo::TrackingBase.connection_pool.size} (Geo tracking database)") # rubocop:disable Gitlab/RailsLogger
+ end
+ end
# Avoid autoload issue such as 'Mail::Parsers::AddressStruct'
# https://github.com/mikel/mail/issues/912#issuecomment-214850355
diff --git a/config/initializers/sidekiq_cluster.rb b/config/initializers/sidekiq_cluster.rb
new file mode 100644
index 00000000000..baa7495aa29
--- /dev/null
+++ b/config/initializers/sidekiq_cluster.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+if ENV['ENABLE_SIDEKIQ_CLUSTER'] && Gitlab.ee?
+ Thread.new do
+ Thread.current.abort_on_exception = true
+
+ parent = Process.ppid
+
+ loop do
+ sleep(5)
+
+ # In cluster mode it's possible that the master process is SIGKILL'd. In
+ # this case the parent PID changes and we need to terminate ourselves.
+ if Process.ppid != parent
+ Process.kill(:TERM, Process.pid)
+ break
+ end
+ end
+ end
+end
diff --git a/config/prometheus/cluster_metrics.yml b/config/prometheus/cluster_metrics.yml
new file mode 100644
index 00000000000..3df76b0974f
--- /dev/null
+++ b/config/prometheus/cluster_metrics.yml
@@ -0,0 +1,63 @@
+- group: Cluster Health
+ priority: 1
+ metrics:
+ - title: "CPU Usage"
+ y_label: "CPU"
+ required_metrics: ['container_cpu_usage_seconds_total']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
+ label: Usage
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Requested
+ unit: "cores"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
+ label: Capacity
+ unit: "cores"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
+ - title: "Memory usage"
+ y_label: "Memory"
+ required_metrics: ['container_memory_usage_bytes']
+ weight: 1
+ queries:
+ - query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
+ label: Usage
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Requested
+ unit: "GiB"
+ appearance:
+ line:
+ width: 2
+ area:
+ opacity: 0
+ - query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
+ label: Capacity
+ unit: "GiB"
+ appearance:
+ line:
+ type: 'dashed'
+ width: 2
+ area:
+ opacity: 0
diff --git a/config/pseudonymizer.yml b/config/pseudonymizer.yml
new file mode 100644
index 00000000000..1d85ac1db45
--- /dev/null
+++ b/config/pseudonymizer.yml
@@ -0,0 +1,475 @@
+tables:
+ approvals:
+ whitelist:
+ - id
+ - merge_request_id
+ - user_id
+ - created_at
+ - updated_at
+ approver_groups:
+ whitelist:
+ - id
+ - target_type
+ - group_id
+ - created_at
+ - updated_at
+ board_assignees:
+ whitelist:
+ - id
+ - board_id
+ - assignee_id
+ board_labels:
+ whitelist:
+ - id
+ - board_id
+ - label_id
+ boards:
+ whitelist:
+ - id
+ - project_id
+ - created_at
+ - updated_at
+ - milestone_id
+ - group_id
+ - weight
+ epic_issues:
+ whitelist:
+ - id
+ - epic_id
+ - issue_id
+ - relative_position
+ epic_metrics:
+ whitelist:
+ - id
+ - epic_id
+ - created_at
+ - updated_at
+ epics:
+ whitelist:
+ - id
+ - milestone_id
+ - group_id
+ - author_id
+ - assignee_id
+ - iid
+ - updated_by_id
+ - last_edited_by_id
+ - lock_version
+ - start_date
+ - end_date
+ - last_edited_at
+ - created_at
+ - updated_at
+ - title
+ - description
+ issue_assignees:
+ whitelist:
+ - user_id
+ - issue_id
+ issue_links:
+ whitelist:
+ - id
+ - source_id
+ - target_id
+ - created_at
+ - updated_at
+ issue_metrics:
+ whitelist:
+ - id
+ - issue_id
+ - first_mentioned_in_commit_at
+ - first_associated_with_milestone_at
+ - first_added_to_board_at
+ - created_at
+ - updated_at
+ issues:
+ whitelist:
+ - id
+ - title
+ - author_id
+ - project_id
+ - created_at
+ - confidential
+ - updated_at
+ - description
+ - milestone_id
+ - state
+ - updated_by_id
+ - weight
+ - due_date
+ - moved_to_id
+ - lock_version
+ - time_estimate
+ - last_edited_at
+ - last_edited_by_id
+ - discussion_locked
+ - closed_at
+ label_links:
+ whitelist:
+ - id
+ - label_id
+ - target_id
+ - target_type
+ - created_at
+ - updated_at
+ label_priorities:
+ whitelist:
+ - id
+ - project_id
+ - label_id
+ - priority
+ - created_at
+ - updated_at
+ labels:
+ whitelist:
+ - id
+ - title
+ - color
+ - project_id
+ - created_at
+ - updated_at
+ - template
+ - type
+ - group_id
+ licenses:
+ whitelist:
+ - id
+ - created_at
+ - updated_at
+ merge_request_diffs:
+ whitelist:
+ - id
+ - state
+ - merge_request_id
+ - created_at
+ - updated_at
+ - base_commit_sha
+ - real_size
+ - head_commit_sha
+ - start_commit_sha
+ - commits_count
+ merge_request_metrics:
+ whitelist:
+ - id
+ - merge_request_id
+ - latest_build_started_at
+ - latest_build_finished_at
+ - first_deployed_to_production_at
+ - merged_at
+ - created_at
+ - updated_at
+ - pipeline_id
+ - merged_by_id
+ - latest_closed_by_id
+ - latest_closed_at
+ merge_requests:
+ whitelist:
+ - id
+ - target_branch
+ - source_branch
+ - source_project_id
+ - author_id
+ - assignee_id
+ - created_at
+ - updated_at
+ - milestone_id
+ - state
+ - merge_status
+ - target_project_id
+ - updated_by_id
+ - merge_error
+ - merge_params
+ - merge_when_pipeline_succeeds
+ - merge_user_id
+ - approvals_before_merge
+ - lock_version
+ - time_estimate
+ - squash
+ - last_edited_at
+ - last_edited_by_id
+ - head_pipeline_id
+ - discussion_locked
+ - latest_merge_request_diff_id
+ - allow_maintainer_to_push
+ merge_requests_closing_issues:
+ whitelist:
+ - id
+ - merge_request_id
+ - issue_id
+ - created_at
+ - updated_at
+ milestones:
+ whitelist:
+ - id
+ - project_id
+ - due_date
+ - created_at
+ - updated_at
+ - state
+ - start_date
+ - group_id
+ namespace_statistics:
+ whitelist:
+ - id
+ - namespace_id
+ - shared_runners_seconds
+ - shared_runners_seconds_last_reset
+ namespaces:
+ whitelist:
+ - id
+ - name
+ - path
+ - owner_id
+ - created_at
+ - updated_at
+ - type
+ - avatar
+ - membership_lock
+ - share_with_group_lock
+ - visibility_level
+ - request_access_enabled
+ - ldap_sync_status
+ - ldap_sync_error
+ - ldap_sync_last_update_at
+ - ldap_sync_last_successful_update_at
+ - ldap_sync_last_sync_at
+ - lfs_enabled
+ - parent_id
+ - shared_runners_minutes_limit
+ - repository_size_limit
+ - require_two_factor_authentication
+ - two_factor_grace_period
+ - plan_id
+ - project_creation_level
+ members:
+ whitelist:
+ - id
+ - access_level
+ - source_id
+ - source_type
+ - user_id
+ - notification_level
+ - type
+ - created_by_id
+ - invite_email
+ - invite_accepted_at
+ - requested_at
+ - expires_at
+ - ldap
+ - override
+ notification_settings:
+ whitelist:
+ - id
+ - user_id
+ - source_id
+ - source_type
+ - level
+ - created_at
+ - updated_at
+ - new_note
+ - new_issue
+ - reopen_issue
+ - close_issue
+ - reassign_issue
+ - new_merge_request
+ - reopen_merge_request
+ - close_merge_request
+ - reassign_merge_request
+ - merge_merge_request
+ - failed_pipeline
+ - success_pipeline
+ project_authorizations:
+ whitelist:
+ - user_id
+ - project_id
+ - access_level
+ project_auto_devops:
+ whitelist:
+ - id
+ - project_id
+ - created_at
+ - updated_at
+ - enabled
+ project_custom_attributes:
+ whitelist:
+ - id
+ - created_at
+ - updated_at
+ - project_id
+ - key
+ - value
+ project_features:
+ whitelist:
+ - id
+ - project_id
+ - merge_requests_access_level
+ - issues_access_level
+ - wiki_access_level
+ - snippets_access_level
+ - builds_access_level
+ - created_at
+ - updated_at
+ - repository_access_level
+ project_group_links:
+ whitelist:
+ - id
+ - project_id
+ - group_id
+ - created_at
+ - updated_at
+ - group_access
+ - expires_at
+ project_import_data:
+ whitelist:
+ - id
+ - project_id
+ project_mirror_data:
+ whitelist:
+ - id
+ - project_id
+ - retry_count
+ - last_update_started_at
+ - last_update_scheduled_at
+ - next_execution_timestamp
+ project_repository_states:
+ whitelist:
+ - id
+ - project_id
+ - repository_verification_checksum
+ - wiki_verification_checksum
+ - last_repository_verification_failure
+ - last_wiki_verification_failure
+ project_statistics:
+ whitelist:
+ - id
+ - project_id
+ - namespace_id
+ - commit_count
+ - storage_size
+ - repository_size
+ - lfs_objects_size
+ - build_artifacts_size
+ - shared_runners_seconds
+ - shared_runners_seconds_last_reset
+ projects:
+ whitelist:
+ - id
+ - name
+ - path
+ - description
+ - created_at
+ - updated_at
+ - creator_id
+ - namespace_id
+ - last_activity_at
+ - import_url
+ - visibility_level
+ - archived
+ - avatar
+ - merge_requests_template
+ - star_count
+ - merge_requests_rebase_enabled
+ - import_type
+ - import_source
+ - approvals_before_merge
+ - reset_approvals_on_push
+ - merge_requests_ff_only_enabled
+ - issues_template
+ - mirror
+ - mirror_user_id
+ - shared_runners_enabled
+ - build_coverage_regex
+ - build_allow_git_fetch
+ - build_timeout
+ - mirror_trigger_builds
+ - pending_delete
+ - public_builds
+ - last_repository_check_failed
+ - last_repository_check_at
+ - container_registry_enabled
+ - only_allow_merge_if_pipeline_succeeds
+ - has_external_issue_tracker
+ - repository_storage
+ - repository_read_only
+ - request_access_enabled
+ - has_external_wiki
+ - ci_config_path
+ - lfs_enabled
+ - only_allow_merge_if_all_discussions_are_resolved
+ - repository_size_limit
+ - printing_merge_request_link_enabled
+ - auto_cancel_pending_pipelines
+ - service_desk_enabled
+ - delete_error
+ - last_repository_updated_at
+ - disable_overriding_approvers_per_merge_request
+ - storage_version
+ - resolve_outdated_diff_discussions
+ - remote_mirror_available_overridden
+ - only_mirror_protected_branches
+ - pull_mirror_available_overridden
+ - mirror_overwrites_diverged_branches
+ - external_authorization_classification_label
+ subscriptions:
+ whitelist:
+ - id
+ - user_id
+ - subscribable_id
+ - subscribable_type
+ - subscribed
+ - created_at
+ - updated_at
+ - project_id
+ users:
+ whitelist:
+ - id
+ - remember_created_at
+ - sign_in_count
+ - current_sign_in_at
+ - last_sign_in_at
+ - current_sign_in_ip
+ - last_sign_in_ip
+ - created_at
+ - updated_at
+ - admin
+ - projects_limit
+ - failed_attempts
+ - locked_at
+ - can_create_group
+ - can_create_team
+ - state
+ - color_scheme_id
+ - password_expires_at
+ - created_by_id
+ - last_credential_check_at
+ - avatar
+ - confirmed_at
+ - confirmation_sent_at
+ - unconfirmed_email
+ - hide_no_ssh_key
+ - website_url
+ - admin_email_unsubscribed_at
+ - notification_email
+ - hide_no_password
+ - password_automatically_set
+ - location
+ - public_email
+ - dashboard
+ - project_view
+ - consumed_timestep
+ - layout
+ - hide_project_limit
+ - note
+ - otp_grace_period_started_at
+ - external
+ - organization
+ - auditor
+ - require_two_factor_authentication_from_group
+ - two_factor_grace_period
+ - ghost
+ - last_activity_on
+ - notified_of_own_activity
+ - bot_type
+ - preferred_language
+ - theme_id
+
diff --git a/config/routes/api.rb b/config/routes/api.rb
index 3ba9176d943..d55bbdf6776 100644
--- a/config/routes/api.rb
+++ b/config/routes/api.rb
@@ -1,7 +1,5 @@
-constraints(::Constraints::FeatureConstrainer.new(:graphql, default_enabled: true)) do
- post '/api/graphql', to: 'graphql#execute'
- mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql'
-end
+post '/api/graphql', to: 'graphql#execute'
+mount GraphiQL::Rails::Engine, at: '/-/graphql-explorer', graphql_path: '/api/graphql'
-::API::API.logger Rails.logger
+::API::API.logger Rails.logger # rubocop:disable Gitlab/RailsLogger
mount ::API::API => '/'
diff --git a/config/settings.rb b/config/settings.rb
index 6df2132332c..da459afcce2 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -62,6 +62,31 @@ class Settings < Settingslogic
(base_url(gitlab) + [gitlab.relative_url_root]).join('')
end
+ def kerberos_protocol
+ kerberos.https ? "https" : "http"
+ end
+
+ def kerberos_port
+ kerberos.use_dedicated_port ? kerberos.port : gitlab.port
+ end
+
+ # Curl expects username/password for authentication. However when using GSS-Negotiate not credentials should be needed.
+ # By inserting in the Kerberos dedicated URL ":@", we give to curl an empty username and password and GSS auth goes ahead
+ # Known bug reported in http://sourceforge.net/p/curl/bugs/440/ and http://curl.haxx.se/docs/knownbugs.html
+ def build_gitlab_kerberos_url
+ [
+ kerberos_protocol,
+ "://:@",
+ gitlab.host,
+ ":#{kerberos_port}",
+ gitlab.relative_url_root
+ ].join('')
+ end
+
+ def alternative_gitlab_kerberos_url?
+ kerberos.enabled && (build_gitlab_kerberos_url != build_gitlab_url)
+ end
+
# check that values in `current` (string or integer) is a contant in `modul`.
def verify_constant_array(modul, current, default)
values = default || []
diff --git a/danger/only_documentation/Dangerfile b/danger/only_documentation/Dangerfile
index 0e5f841c891..ff65f8713d2 100644
--- a/danger/only_documentation/Dangerfile
+++ b/danger/only_documentation/Dangerfile
@@ -1,7 +1,7 @@
# rubocop:disable Style/SignalException
# frozen_string_literal: true
-has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml') }
+has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml', '.mdlrc') }
is_docs_only_branch = gitlab.branch_for_head =~ /(^docs[\/-].*|.*-docs$)/
if is_docs_only_branch && !has_only_docs_changes
diff --git a/db/fixtures/development/99_common_metrics.rb b/db/fixtures/development/99_common_metrics.rb
index 1f39c0ce5a0..d52f78ea536 100644
--- a/db/fixtures/development/99_common_metrics.rb
+++ b/db/fixtures/development/99_common_metrics.rb
@@ -1,5 +1,3 @@
# frozen_string_literal: true
-require Rails.root.join('db/importers/common_metrics_importer.rb')
-
-::Importers::CommonMetricsImporter.new.execute
+::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
diff --git a/db/fixtures/production/999_common_metrics.rb b/db/fixtures/production/999_common_metrics.rb
index 1f39c0ce5a0..d52f78ea536 100644
--- a/db/fixtures/production/999_common_metrics.rb
+++ b/db/fixtures/production/999_common_metrics.rb
@@ -1,5 +1,3 @@
# frozen_string_literal: true
-require Rails.root.join('db/importers/common_metrics_importer.rb')
-
-::Importers::CommonMetricsImporter.new.execute
+::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb
deleted file mode 100644
index 195bde8f34a..00000000000
--- a/db/importers/common_metrics_importer.rb
+++ /dev/null
@@ -1,105 +0,0 @@
-# frozen_string_literal: true
-
-module Importers
- class PrometheusMetric < ActiveRecord::Base
- enum group: {
- # built-in groups
- nginx_ingress_vts: -1,
- ha_proxy: -2,
- aws_elb: -3,
- nginx: -4,
- kubernetes: -5,
- nginx_ingress: -6,
-
- # custom groups
- business: 0,
- response: 1,
- system: 2
- }
-
- scope :common, -> { where(common: true) }
-
- GROUP_TITLES = {
- business: _('Business metrics (Custom)'),
- response: _('Response metrics (Custom)'),
- system: _('System metrics (Custom)'),
- nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
- nginx_ingress: _('Response metrics (NGINX Ingress)'),
- ha_proxy: _('Response metrics (HA Proxy)'),
- aws_elb: _('Response metrics (AWS ELB)'),
- nginx: _('Response metrics (NGINX)'),
- kubernetes: _('System metrics (Kubernetes)')
- }.freeze
- end
-
- class CommonMetricsImporter
- MissingQueryId = Class.new(StandardError)
-
- attr_reader :content
-
- def initialize(filename = 'common_metrics.yml')
- @content = YAML.load_file(Rails.root.join('config', 'prometheus', filename))
- end
-
- def execute
- PrometheusMetric.reset_column_information
-
- process_content do |id, attributes|
- find_or_build_metric!(id)
- .update!(**attributes)
- end
- end
-
- private
-
- def process_content(&blk)
- content['panel_groups'].map do |group|
- process_group(group, &blk)
- end
- end
-
- def process_group(group, &blk)
- attributes = {
- group: find_group_title_key(group['group'])
- }
-
- group['panels'].map do |panel|
- process_panel(panel, attributes, &blk)
- end
- end
-
- def process_panel(panel, attributes, &blk)
- attributes = attributes.merge(
- title: panel['title'],
- y_label: panel['y_label'])
-
- panel['metrics'].map do |metric_details|
- process_metric_details(metric_details, attributes, &blk)
- end
- end
-
- def process_metric_details(metric_details, attributes, &blk)
- attributes = attributes.merge(
- legend: metric_details['label'],
- query: metric_details['query_range'],
- unit: metric_details['unit'])
-
- yield(metric_details['id'], attributes)
- end
-
- def find_or_build_metric!(id)
- raise MissingQueryId unless id
-
- PrometheusMetric.common.find_by(identifier: id) ||
- PrometheusMetric.new(common: true, identifier: id)
- end
-
- def find_group_title_key(title)
- PrometheusMetric.groups[find_group_title(title)]
- end
-
- def find_group_title(title)
- PrometheusMetric::GROUP_TITLES.invert[title]
- end
- end
-end
diff --git a/db/migrate/20180831164910_import_common_metrics.rb b/db/migrate/20180831164910_import_common_metrics.rb
index f67d5f40aad..4e61a25c1ad 100644
--- a/db/migrate/20180831164910_import_common_metrics.rb
+++ b/db/migrate/20180831164910_import_common_metrics.rb
@@ -3,12 +3,10 @@
class ImportCommonMetrics < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
index 5cd312837df..2b238774dca 100644
--- a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
+++ b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb
@@ -1,12 +1,10 @@
class ImportCommonMetricsNginxVts < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20190326164045_import_common_metrics_knative.rb b/db/migrate/20190326164045_import_common_metrics_knative.rb
index 340ec1e1f75..6b331755774 100644
--- a/db/migrate/20190326164045_import_common_metrics_knative.rb
+++ b/db/migrate/20190326164045_import_common_metrics_knative.rb
@@ -3,12 +3,10 @@
class ImportCommonMetricsKnative < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/migrate/20190408163745_prometheus_knative05_fix.rb b/db/migrate/20190408163745_prometheus_knative05_fix.rb
index c11f6f0e29b..3d0aa782669 100644
--- a/db/migrate/20190408163745_prometheus_knative05_fix.rb
+++ b/db/migrate/20190408163745_prometheus_knative05_fix.rb
@@ -6,12 +6,10 @@
class PrometheusKnative05Fix < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
index fa332fd5c70..325895a5ddb 100644
--- a/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
+++ b/db/post_migrate/20180223124427_build_user_interacted_projects_table.rb
@@ -82,7 +82,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration[4.2]
iteration = 0
records = 0
begin
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}"
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration}" # rubocop:disable Gitlab/RailsLogger
result = execute <<~SQL
INSERT INTO user_interacted_projects (user_id, project_id)
SELECT e.user_id, e.project_id
@@ -93,7 +93,7 @@ class BuildUserInteractedProjectsTable < ActiveRecord::Migration[4.2]
SQL
iteration += 1
records += result.cmd_tuples
- Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall"
+ Rails.logger.info "Building user_interacted_projects table, batch ##{iteration} complete, created #{records} overall" # rubocop:disable Gitlab/RailsLogger
Kernel.sleep(SLEEP_TIME) if result.cmd_tuples > 0
end while result.cmd_tuples > 0
end
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index ae1f6e00ea5..a80ff330e03 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -99,7 +99,7 @@ It is possible to filter particular actions by choosing an audit data type from
the filter drop-down. You can further filter by specific group, project or user
(for authentication events).
-![audit log](audit_log.png)
+![audit log](img/audit_log.png)
### Missing events
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index daba5b4ca82..65d36612d85 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -52,7 +52,7 @@ section.
**Admin Area > Users**. You will find the option of the access level under
the 'Access' section.
- ![Admin Area Form](auditor_access_form.png)
+ ![Admin Area Form](img/auditor_access_form.png)
1. Click **Save changes** or **Create user** for the changes to take effect.
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 2e4b4efa0ac..04f52783d22 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -1,7 +1,5 @@
# GitLab Container Registry administration
-> **Notes:**
->
> - [Introduced][ce-4040] in GitLab 8.8.
> - Container Registry manifest `v1` support was added in GitLab 8.9 to support
> Docker versions earlier than 1.10.
@@ -125,21 +123,21 @@ otherwise you will run into conflicts.
1. Your `/etc/gitlab/gitlab.rb` should contain the Registry URL as well as the
path to the existing TLS certificate and key used by GitLab:
- ```ruby
- registry_external_url 'https://gitlab.example.com:4567'
- ```
+ ```ruby
+ registry_external_url 'https://gitlab.example.com:4567'
+ ```
- Note how the `registry_external_url` is listening on HTTPS under the
- existing GitLab URL, but on a different port.
+ Note how the `registry_external_url` is listening on HTTPS under the
+ existing GitLab URL, but on a different port.
- If your TLS certificate is not in `/etc/gitlab/ssl/gitlab.example.com.crt`
- and key not in `/etc/gitlab/ssl/gitlab.example.com.key` uncomment the lines
- below:
+ If your TLS certificate is not in `/etc/gitlab/ssl/gitlab.example.com.crt`
+ and key not in `/etc/gitlab/ssl/gitlab.example.com.key` uncomment the lines
+ below:
- ```ruby
- registry_nginx['ssl_certificate'] = "/path/to/certificate.pem"
- registry_nginx['ssl_certificate_key'] = "/path/to/certificate.key"
- ```
+ ```ruby
+ registry_nginx['ssl_certificate'] = "/path/to/certificate.pem"
+ registry_nginx['ssl_certificate_key'] = "/path/to/certificate.key"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -150,12 +148,12 @@ otherwise you will run into conflicts.
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
configure it with the following settings:
- ```
- registry:
- enabled: true
- host: gitlab.example.com
- port: 4567
- ```
+ ```
+ registry:
+ enabled: true
+ host: gitlab.example.com
+ port: 4567
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
@@ -188,17 +186,17 @@ Let's assume that you want the container Registry to be accessible at
`/etc/gitlab/ssl/registry.gitlab.example.com.key` and make sure they have
correct permissions:
- ```bash
- chmod 600 /etc/gitlab/ssl/registry.gitlab.example.com.*
- ```
+ ```bash
+ chmod 600 /etc/gitlab/ssl/registry.gitlab.example.com.*
+ ```
1. Once the TLS certificate is in place, edit `/etc/gitlab/gitlab.rb` with:
- ```ruby
- registry_external_url 'https://registry.gitlab.example.com'
- ```
+ ```ruby
+ registry_external_url 'https://registry.gitlab.example.com'
+ ```
- Note how the `registry_external_url` is listening on HTTPS.
+ Note how the `registry_external_url` is listening on HTTPS.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -219,11 +217,11 @@ look like:
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
configure it with the following settings:
- ```
- registry:
- enabled: true
- host: registry.gitlab.example.com
- ```
+ ```yaml
+ registry:
+ enabled: true
+ host: registry.gitlab.example.com
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
@@ -248,9 +246,9 @@ Registry application itself.
1. Open `/etc/gitlab/gitlab.rb` and set `registry['enable']` to `false`:
- ```ruby
- registry['enable'] = false
- ```
+ ```ruby
+ registry['enable'] = false
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -261,10 +259,10 @@ Registry application itself.
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
set `enabled` to `false`:
- ```
- registry:
- enabled: false
- ```
+ ```yaml
+ registry:
+ enabled: false
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -280,9 +278,9 @@ the Container Registry by themselves, follow the steps below.
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['gitlab_default_projects_features_container_registry'] = false
- ```
+ ```ruby
+ gitlab_rails['gitlab_default_projects_features_container_registry'] = false
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -293,16 +291,16 @@ the Container Registry by themselves, follow the steps below.
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `default_projects_features`
entry and configure it so that `container_registry` is set to `false`:
- ```
- ## Default project features settings
- default_projects_features:
- issues: true
- merge_requests: true
- wiki: true
- snippets: false
- builds: true
- container_registry: false
- ```
+ ```yaml
+ ## Default project features settings
+ default_projects_features:
+ issues: true
+ merge_requests: true
+ wiki: true
+ snippets: false
+ builds: true
+ container_registry: false
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -332,9 +330,9 @@ The default location where images are stored in Omnibus, is
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['registry_path'] = "/path/to/registry/storage"
- ```
+ ```ruby
+ gitlab_rails['registry_path'] = "/path/to/registry/storage"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -348,10 +346,10 @@ The default location where images are stored in source installations, is
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
change the `path` setting:
- ```
- registry:
- path: shared/registry
- ```
+ ```yaml
+ registry:
+ path: shared/registry
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -393,17 +391,17 @@ To configure the `s3` storage driver in Omnibus:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- registry['storage'] = {
- 's3' => {
- 'accesskey' => 's3-access-key',
- 'secretkey' => 's3-secret-key-for-access-key',
- 'bucket' => 'your-s3-bucket',
- 'region' => 'your-s3-region',
- 'regionendpoint' => 'your-s3-regionendpoint'
- }
- }
- ```
+ ```ruby
+ registry['storage'] = {
+ 's3' => {
+ 'accesskey' => 's3-access-key',
+ 'secretkey' => 's3-secret-key-for-access-key',
+ 'bucket' => 'your-s3-bucket',
+ 'region' => 'your-s3-region',
+ 'regionendpoint' => 'your-s3-regionendpoint'
+ }
+ }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -442,9 +440,9 @@ In the examples below we set the Registry's port to `5001`.
1. Open `/etc/gitlab/gitlab.rb` and set `registry['registry_http_addr']`:
- ```ruby
- registry['registry_http_addr'] = "localhost:5001"
- ```
+ ```ruby
+ registry['registry_http_addr'] = "localhost:5001"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -455,10 +453,10 @@ In the examples below we set the Registry's port to `5001`.
1. Open the configuration file of your Registry server and edit the
[`http:addr`][registry-http-config] value:
- ```
- http
- addr: localhost:5001
- ```
+ ```yaml
+ http
+ addr: localhost:5001
+ ```
1. Save the file and restart the Registry server.
@@ -476,14 +474,14 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
1. Open `/etc/gitlab/gitlab.rb` and set necessary configurations:
- ```ruby
- gitlab_rails['registry_enabled'] = true
- gitlab_rails['registry_host'] = "registry.gitlab.example.com"
- gitlab_rails['registry_port'] = "5005"
- gitlab_rails['registry_api_url'] = "http://localhost:5000"
- gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
- gitlab_rails['registry_issuer'] = "omnibus-gitlab-issuer"
- ```
+ ```ruby
+ gitlab_rails['registry_enabled'] = true
+ gitlab_rails['registry_host'] = "registry.gitlab.example.com"
+ gitlab_rails['registry_port'] = "5005"
+ gitlab_rails['registry_api_url'] = "http://localhost:5000"
+ gitlab_rails['registry_path'] = "/var/opt/gitlab/gitlab-rails/shared/registry"
+ gitlab_rails['registry_issuer'] = "omnibus-gitlab-issuer"
+ ```
1. A certificate keypair is required for GitLab and the Container Registry to
communicate securely. By default omnibus-gitlab will generate one keypair,
@@ -492,19 +490,19 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
custom certificate key. To do that, add the following to
`/etc/gitlab/gitlab.rb`
- ```ruby
- gitlab_rails['registry_key_path'] = "/custom/path/to/registry-key.key"
- # registry['internal_key'] should contain the contents of the custom key
- # file. Line breaks in the key file should be marked using `\n` character
- # Example:
- registry['internal_key'] = "---BEGIN RSA PRIVATE KEY---\nMIIEpQIBAA\n"
- ```
+ ```ruby
+ gitlab_rails['registry_key_path'] = "/custom/path/to/registry-key.key"
+ # registry['internal_key'] should contain the contents of the custom key
+ # file. Line breaks in the key file should be marked using `\n` character
+ # Example:
+ registry['internal_key'] = "---BEGIN RSA PRIVATE KEY---\nMIIEpQIBAA\n"
+ ```
- **Note:** The file specified at `registry_key_path` gets populated with the
- content specified by `internal_key`, each time reconfigure is executed. If
- no file is specified, omnibus-gitlab will default it to
- `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` and will populate
- it.
+ **Note:** The file specified at `registry_key_path` gets populated with the
+ content specified by `internal_key`, each time reconfigure is executed. If
+ no file is specified, omnibus-gitlab will default it to
+ `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` and will populate
+ it.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -512,18 +510,18 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
1. Open `/home/git/gitlab/config/gitlab.yml`, and edit the configuration settings under `registry`:
- ```
- ## Container Registry
+ ```yaml
+ ## Container Registry
- registry:
- enabled: true
- host: "registry.gitlab.example.com"
- port: "5005"
- api_url: "http://localhost:5000"
- path: /var/opt/gitlab/gitlab-rails/shared/registry
- key: /var/opt/gitlab/gitlab-rails/certificate.key
- issuer: omnibus-gitlab-issuer
- ```
+ registry:
+ enabled: true
+ host: "registry.gitlab.example.com"
+ port: "5005"
+ api_url: "http://localhost:5000"
+ path: /var/opt/gitlab/gitlab-rails/shared/registry
+ key: /var/opt/gitlab/gitlab-rails/certificate.key
+ issuer: omnibus-gitlab-issuer
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -550,20 +548,20 @@ To configure a notification endpoint in Omnibus:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- registry['notifications'] = [
- {
- 'name' => 'test_endpoint',
- 'url' => 'https://gitlab.example.com/notify',
- 'timeout' => '500ms',
- 'threshold' => 5,
- 'backoff' => '1s',
- 'headers' => {
- "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
- }
- }
- ]
- ```
+ ```ruby
+ registry['notifications'] = [
+ {
+ 'name' => 'test_endpoint',
+ 'url' => 'https://gitlab.example.com/notify',
+ 'timeout' => '500ms',
+ 'threshold' => 5,
+ 'backoff' => '1s',
+ 'headers' => {
+ "Authorization" => ["AUTHORIZATION_EXAMPLE_TOKEN"]
+ }
+ }
+ ]
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -629,16 +627,16 @@ Start with a value between `25000000` (25MB) and `50000000` (50MB).
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- registry['storage'] = {
- 's3' => {
- 'accesskey' => 'AKIAKIAKI',
- 'secretkey' => 'secret123',
- 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI',
- 'chunksize' => 25000000
- }
- }
- ```
+ ```ruby
+ registry['storage'] = {
+ 's3' => {
+ 'accesskey' => 'AKIAKIAKI',
+ 'secretkey' => 'secret123',
+ 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI',
+ 'chunksize' => 25000000
+ }
+ }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -648,14 +646,14 @@ Start with a value between `25000000` (25MB) and `50000000` (50MB).
1. Edit `config/gitlab.yml`:
- ```yaml
- storage:
- s3:
- accesskey: 'AKIAKIAKI'
- secretkey: 'secret123'
- bucket: 'gitlab-registry-bucket-AKIAKIAKI'
- chunksize: 25000000
- ```
+ ```yaml
+ storage:
+ s3:
+ accesskey: 'AKIAKIAKI'
+ secretkey: 'secret123'
+ bucket: 'gitlab-registry-bucket-AKIAKIAKI'
+ chunksize: 25000000
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -669,9 +667,9 @@ You can add a configuration option for backwards compatibility.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- registry['compatibility_schema1_enabled'] = true
- ```
+ ```ruby
+ registry['compatibility_schema1_enabled'] = true
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -681,11 +679,11 @@ You can add a configuration option for backwards compatibility.
1. Edit the YML configuration file you created when you [deployed the registry][registry-deploy]. Add the following snippet:
- ```yaml
- compatibility:
- schema1:
- enabled: true
- ```
+ ```yaml
+ compatibility:
+ schema1:
+ enabled: true
+ ```
1. Restart the registry for the changes to take affect.
@@ -694,9 +692,9 @@ You can add a configuration option for backwards compatibility.
A Docker connection error can occur when there are special characters in either the group,
project or branch name. Special characters can include:
-* Leading underscore
-* Trailing hyphen/dash
-* Double hyphen/dash
+- Leading underscore
+- Trailing hyphen/dash
+- Double hyphen/dash
To get around this, you can [change the group path](../user/group/index.md#changing-a-groups-path),
[change the project path](../user/project/settings/index.md#renaming-a-repository) or change the
diff --git a/doc/administration/database_load_balancing.md b/doc/administration/database_load_balancing.md
index e76d3b14a40..dc4cc401fca 100644
--- a/doc/administration/database_load_balancing.md
+++ b/doc/administration/database_load_balancing.md
@@ -74,9 +74,9 @@ the following. This will balance the load between `host1.example.com` and
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['db_load_balancing'] = { 'hosts' => ['host1.example.com', 'host2.example.com'] }
- ```
+ ```ruby
+ gitlab_rails['db_load_balancing'] = { 'hosts' => ['host1.example.com', 'host2.example.com'] }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -86,16 +86,16 @@ the following. This will balance the load between `host1.example.com` and
1. Edit `/home/git/gitlab/config/database.yml` and add or amend the following lines:
- ```yaml
- production:
- username: gitlab
- database: gitlab
- encoding: unicode
- load_balancing:
- hosts:
- - host1.example.com
- - host2.example.com
- ```
+ ```yaml
+ production:
+ username: gitlab
+ database: gitlab
+ encoding: unicode
+ load_balancing:
+ hosts:
+ - host1.example.com
+ - host2.example.com
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index c32a73080ff..0db9985acd9 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -478,88 +478,7 @@ Check the [Troubleshooting section](#troubleshooting) before proceeding.
#### Configuring the Pgbouncer node
-1. Make sure you collect [`CONSUL_SERVER_NODES`](#consul-information), [`CONSUL_PASSWORD_HASH`](#consul-information), and [`PGBOUNCER_PASSWORD_HASH`](#pgbouncer-information) before executing the next step.
-
-1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
-
- ```ruby
- # Disable all components except Pgbouncer and Consul agent
- roles ['pgbouncer_role']
-
- # Configure Pgbouncer
- pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
-
- # Configure Consul agent
- consul['watchers'] = %w(postgresql)
-
- # START user configuration
- # Please set the real values as explained in Required Information section
- # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
- # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
- pgbouncer['users'] = {
- 'gitlab-consul': {
- password: 'CONSUL_PASSWORD_HASH'
- },
- 'pgbouncer': {
- password: 'PGBOUNCER_PASSWORD_HASH'
- }
- }
- # Replace placeholders:
- #
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses gathered for CONSUL_SERVER_NODES
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
- }
- #
- # END user configuration
- ```
-
- > `pgbouncer_role` was introduced with GitLab 10.3
-
-1. [Reconfigure GitLab] for the changes to take effect.
-
-1. Create a `.pgpass` file so Consul is able to
- reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
-
- ```sh
- gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
- ```
-
-##### PGBouncer Checkpoint
-
-1. Ensure the node is talking to the current master:
-
- ```sh
- gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
- ```
-
- If there is an error `psql: ERROR: Auth failed` after typing in the
- password, ensure you previously generated the MD5 password hashes with the correct
- format. The correct format is to concatenate the password and the username:
- `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
- needed to generate an MD5 password hash for the `pgbouncer` user.
-
-1. Once the console prompt is available, run the following queries:
-
- ```sh
- show databases ; show clients ;
- ```
-
- The output should be similar to the following:
-
- ```
- name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
- ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
- gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
- pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
- (2 rows)
-
- type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
- ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
- C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
- (2 rows)
- ```
+See our [documentation for Pgbouncer](pgbouncer.md) for information on running Pgbouncer as part of an HA setup.
#### Configuring the Application nodes
diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md
index 3045be616a6..9b1b7142e83 100644
--- a/doc/administration/high_availability/gitlab.md
+++ b/doc/administration/high_availability/gitlab.md
@@ -125,7 +125,7 @@ need some extra configuration.
from running on upgrade. Only the primary GitLab application server should
handle migrations.
-1. **Optional** Configure host keys. Copy all contents(primary and public keys) inside `/etc/ssh/` on
+1. **Recommended** Configure host keys. Copy the contents (primary and public keys) of `/etc/ssh/` on
the primary application server to `/etc/ssh` on all secondary servers. This
prevents false man-in-the-middle-attack alerts when accessing servers in your
High Availability cluster behind a load balancer.
diff --git a/doc/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md
index 053dae25823..2788b087628 100644
--- a/doc/administration/high_availability/pgbouncer.md
+++ b/doc/administration/high_availability/pgbouncer.md
@@ -14,7 +14,88 @@ It is recommended to run pgbouncer alongside the `gitlab-rails` service, or on i
### Running Pgbouncer as part of an HA GitLab installation
-See our [HA documentation for PostgreSQL](database.md) for information on running pgbouncer as part of a HA setup
+1. Make sure you collect [`CONSUL_SERVER_NODES`](database.md#consul-information), [`CONSUL_PASSWORD_HASH`](database.md#consul-information), and [`PGBOUNCER_PASSWORD_HASH`](database.md#pgbouncer-information) before executing the next step.
+
+1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
+
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure Pgbouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
+ # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: 'CONSUL_PASSWORD_HASH'
+ },
+ 'pgbouncer': {
+ password: 'PGBOUNCER_PASSWORD_HASH'
+ }
+ }
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+ #
+ # END user configuration
+ ```
+
+ > `pgbouncer_role` was introduced with GitLab 10.3
+
+1. Run `gitlab-ctl reconfigure`
+
+1. Create a `.pgpass` file so Consul is able to
+ reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
+
+ ```sh
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
+
+#### PGBouncer Checkpoint
+
+1. Ensure the node is talking to the current master:
+
+ ```sh
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
+
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
+
+1. Once the console prompt is available, run the following queries:
+
+ ```sh
+ show databases ; show clients ;
+ ```
+
+ The output should be similar to the following:
+
+ ```
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
### Running Pgbouncer as part of a non-HA GitLab installation
diff --git a/doc/administration/audit_log.png b/doc/administration/img/audit_log.png
index d4f4c2abf38..d4f4c2abf38 100644
--- a/doc/administration/audit_log.png
+++ b/doc/administration/img/audit_log.png
Binary files differ
diff --git a/doc/administration/auditor_access_form.png b/doc/administration/img/auditor_access_form.png
index c179a7d3b0a..c179a7d3b0a 100644
--- a/doc/administration/auditor_access_form.png
+++ b/doc/administration/img/auditor_access_form.png
Binary files differ
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 53a61358670..73a39a6dd35 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -102,16 +102,16 @@ for a real-world example of this exploit.
1. Reconfigure GitLab for the changes to take effect:
- ```sh
- sudo gitlab-ctl reconfigure
- sudo gitlab-ctl restart
- ```
+ ```sh
+ sudo gitlab-ctl reconfigure
+ sudo gitlab-ctl restart
+ ```
1. Verify that everything is configured correctly:
- ```sh
- sudo gitlab-rake gitlab:incoming_email:check
- ```
+ ```sh
+ sudo gitlab-rake gitlab:incoming_email:check
+ ```
Reply by email should now be working.
@@ -119,31 +119,31 @@ Reply by email should now be working.
1. Go to the GitLab installation directory:
- ```sh
- cd /home/git/gitlab
- ```
+ ```sh
+ cd /home/git/gitlab
+ ```
1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature
and fill in the details for your specific IMAP server and email account (see [examples](#config-examples) below).
1. Enable `mail_room` in the init script at `/etc/default/gitlab`:
- ```sh
- sudo mkdir -p /etc/default
- echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
- ```
+ ```sh
+ sudo mkdir -p /etc/default
+ echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab
+ ```
1. Restart GitLab:
- ```sh
- sudo service gitlab restart
- ```
+ ```sh
+ sudo service gitlab restart
+ ```
1. Verify that everything is configured correctly:
- ```sh
- sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
- ```
+ ```sh
+ sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production
+ ```
Reply by email should now be working.
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index 8de7b0bc57e..7f0ec82248d 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -57,9 +57,9 @@ you can change these defaults by editing the `/etc/tomcat7/server.xml` file.
You need to enable PlantUML integration from Settings under Admin Area. To do
that, login with an Admin account and do following:
- - in GitLab go to **Admin Area**->**Settings**->**Integrations**->**PlantUML**
- - check **Enable PlantUML** checkbox
- - set the PlantUML instance as **PlantUML URL**
+- in GitLab go to **Admin Area**->**Settings**->**Integrations**->**PlantUML**
+- check **Enable PlantUML** checkbox
+- set the PlantUML instance as **PlantUML URL**
## Creating Diagrams
@@ -68,33 +68,34 @@ our AsciiDoc snippets, wikis and repos using delimited blocks:
- **Markdown**
- <pre>
- ```plantuml
- Bob -> Alice : hello
- Alice -> Bob : Go Away
- ```</pre>
+ <pre>
+ ```plantuml
+ Bob -> Alice : hello
+ Alice -> Bob : Go Away
+ ```</pre>
+
- **AsciiDoc**
- ```
- [plantuml, format="png", id="myDiagram", width="200px"]
- ----
- Bob->Alice : hello
- Alice -> Bob : Go Away
- ----
- ```
+ ```
+ [plantuml, format="png", id="myDiagram", width="200px"]
+ ----
+ Bob->Alice : hello
+ Alice -> Bob : Go Away
+ ----
+ ```
- **reStructuredText**
- ```
- .. plantuml::
- :caption: Caption with **bold** and *italic*
+ ```
+ .. plantuml::
+ :caption: Caption with **bold** and *italic*
- Bob -> Alice: hello
- Alice -> Bob: Go Away
- ```
+ Bob -> Alice: hello
+ Alice -> Bob: Go Away
+ ```
- You can also use the `uml::` directive for compatibility with [sphinxcontrib-plantuml](https://pypi.org/project/sphinxcontrib-plantuml/), but please note that we currently only support the `caption` option.
+ You can also use the `uml::` directive for compatibility with [sphinxcontrib-plantuml](https://pypi.org/project/sphinxcontrib-plantuml/), but please note that we currently only support the `caption` option.
The above blocks will be converted to an HTML img tag with source pointing to the
PlantUML instance. If the PlantUML server is correctly configured, this should
@@ -111,11 +112,11 @@ diagram delimiters `@startuml`/`@enduml` as these are replaced by the AsciiDoc `
Some parameters can be added to the AsciiDoc block definition:
- - *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
- all browsers so use with care. The default is `png`.
- - *id*: A CSS id added to the diagram HTML tag.
- - *width*: Width attribute added to the img tag.
- - *height*: Height attribute added to the img tag.
+- *format*: Can be either `png` or `svg`. Note that `svg` is not supported by
+ all browsers so use with care. The default is `png`.
+- *id*: A CSS id added to the diagram HTML tag.
+- *width*: Width attribute added to the img tag.
+- *height*: Height attribute added to the img tag.
Markdown does not support any parameters and will always use PNG format.
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
index f2877c49d05..e1bbabb2878 100644
--- a/doc/administration/issue_closing_pattern.md
+++ b/doc/administration/issue_closing_pattern.md
@@ -27,9 +27,10 @@ Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
1. Change the value of `gitlab_rails['gitlab_issue_closing_pattern']` to a regular
expression of your liking:
- ```ruby
- gitlab_rails['gitlab_issue_closing_pattern'] = "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
- ```
+ ```ruby
+ gitlab_rails['gitlab_issue_closing_pattern'] = "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+ ```
+
1. [Reconfigure] GitLab for the changes to take effect.
**For installations from source**
@@ -37,9 +38,9 @@ Because Rubular doesn't understand `%{issue_ref}`, you can replace this by
1. Open `gitlab.yml` with your editor.
1. Change the value of `issue_closing_pattern`:
- ```yaml
- issue_closing_pattern: "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
- ```
+ ```yaml
+ issue_closing_pattern: "\b((?:[Cc]los(?:e[sd]|ing)|\b[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?%{issue_ref}(?:(?:, *| +and +)?))+)"
+ ```
1. [Restart] GitLab for the changes to take effect.
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 05e15fc303b..9df7b2526e2 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -1,7 +1,5 @@
# Jobs artifacts administration
-> **Notes:**
->
> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`.
> - Starting with GitLab 8.17, builds are renamed to jobs.
@@ -21,9 +19,9 @@ To disable artifacts site-wide, follow the steps below.
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['artifacts_enabled'] = false
- ```
+ ```ruby
+ gitlab_rails['artifacts_enabled'] = false
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -33,10 +31,10 @@ To disable artifacts site-wide, follow the steps below.
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
- ```yaml
- artifacts:
- enabled: false
- ```
+ ```yaml
+ artifacts:
+ enabled: false
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -61,9 +59,9 @@ _The artifacts are stored by default in
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
- ```
+ ```ruby
+ gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -77,18 +75,16 @@ _The artifacts are stored by default in
1. To change the storage path for example to `/mnt/storage/artifacts`, edit
`/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
- ```yaml
- artifacts:
- enabled: true
- path: /mnt/storage/artifacts
- ```
+ ```yaml
+ artifacts:
+ enabled: true
+ path: /mnt/storage/artifacts
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
### Using object storage
-> **Notes:**
->
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1762) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
> - Since version 9.5, artifacts are [browsable](../user/project/pipelines/job_artifacts.md#browsing-artifacts),
@@ -141,35 +137,35 @@ _The artifacts are stored by default in
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
- ```ruby
- gitlab_rails['artifacts_enabled'] = true
- gitlab_rails['artifacts_object_store_enabled'] = true
- gitlab_rails['artifacts_object_store_remote_directory'] = "artifacts"
- gitlab_rails['artifacts_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
- 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
- }
- ```
-
- NOTE: For GitLab 9.4+, if you are using AWS IAM profiles, be sure to omit the
- AWS access key and secret access key/value pairs. For example:
-
- ```ruby
- gitlab_rails['artifacts_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'use_iam_profile' => true
- }
- ```
+ ```ruby
+ gitlab_rails['artifacts_enabled'] = true
+ gitlab_rails['artifacts_object_store_enabled'] = true
+ gitlab_rails['artifacts_object_store_remote_directory'] = "artifacts"
+ gitlab_rails['artifacts_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+ }
+ ```
+
+ NOTE: For GitLab 9.4+, if you are using AWS IAM profiles, be sure to omit the
+ AWS access key and secret access key/value pairs. For example:
+
+ ```ruby
+ gitlab_rails['artifacts_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'use_iam_profile' => true
+ }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
1. Migrate any existing local artifacts to the object storage:
- ```bash
- gitlab-rake gitlab:artifacts:migrate
- ```
+ ```bash
+ gitlab-rake gitlab:artifacts:migrate
+ ```
---
@@ -181,25 +177,25 @@ _The artifacts are stored by default in
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- artifacts:
- enabled: true
- object_store:
- enabled: true
- remote_directory: "artifacts" # The bucket name
- connection:
- provider: AWS # Only AWS supported at the moment
- aws_access_key_id: AWS_ACCESS_KEY_ID
- aws_secret_access_key: AWS_SECRET_ACCESS_KEY
- region: eu-central-1
- ```
+ ```yaml
+ artifacts:
+ enabled: true
+ object_store:
+ enabled: true
+ remote_directory: "artifacts" # The bucket name
+ connection:
+ provider: AWS # Only AWS supported at the moment
+ aws_access_key_id: AWS_ACCESS_KEY_ID
+ aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ region: eu-central-1
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local artifacts to the object storage:
- ```bash
- sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production
- ```
+ ```bash
+ sudo -u git -H bundle exec rake gitlab:artifacts:migrate RAILS_ENV=production
+ ```
## Expiring artifacts
@@ -217,9 +213,9 @@ steps below.
1. Edit `/etc/gitlab/gitlab.rb` and comment out or add the following line
- ```ruby
- gitlab_rails['expire_build_artifacts_worker_cron'] = "50 * * * *"
- ```
+ ```ruby
+ gitlab_rails['expire_build_artifacts_worker_cron'] = "50 * * * *"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -230,10 +226,10 @@ steps below.
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- expire_build_artifacts_worker:
- cron: "50 * * * *"
- ```
+ ```yaml
+ expire_build_artifacts_worker:
+ cron: "50 * * * *"
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -250,15 +246,15 @@ you can flip the feature flag from a Rails console.
1. Enter the Rails console:
- ```sh
- sudo gitlab-rails console
- ```
+ ```sh
+ sudo gitlab-rails console
+ ```
1. Flip the switch and disable it:
- ```ruby
- Feature.enable('ci_disable_validates_dependencies')
- ```
+ ```ruby
+ Feature.enable('ci_disable_validates_dependencies')
+ ```
---
@@ -266,16 +262,16 @@ you can flip the feature flag from a Rails console.
1. Enter the Rails console:
- ```sh
- cd /home/git/gitlab
- RAILS_ENV=production sudo -u git -H bundle exec rails console
- ```
+ ```sh
+ cd /home/git/gitlab
+ RAILS_ENV=production sudo -u git -H bundle exec rails console
+ ```
1. Flip the switch and disable it:
- ```ruby
- Feature.enable('ci_disable_validates_dependencies')
- ```
+ ```ruby
+ Feature.enable('ci_disable_validates_dependencies')
+ ```
## Set the maximum file size of the artifacts
diff --git a/doc/administration/job_traces.md b/doc/administration/job_traces.md
index 6556614723d..6a06eb240de 100644
--- a/doc/administration/job_traces.md
+++ b/doc/administration/job_traces.md
@@ -25,11 +25,11 @@ To change the location where the job logs will be stored, follow the steps below
**In Omnibus installations:**
-1. Edit `/etc/gitlab/gitlab.rb` and add or amend the following line:
+1. Edit `/etc/gitlab/gitlab.rb` and add or amend the following line:
- ```
- gitlab_ci['builds_directory'] = '/mnt/to/gitlab-ci/builds'
- ```
+ ```ruby
+ gitlab_ci['builds_directory'] = '/mnt/to/gitlab-ci/builds'
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -39,12 +39,12 @@ To change the location where the job logs will be stored, follow the steps below
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
- ```yaml
- gitlab_ci:
- # The location where build traces are stored (default: builds/).
- # Relative paths are relative to Rails.root.
- builds_path: path/to/builds/
- ```
+ ```yaml
+ gitlab_ci:
+ # The location where build traces are stored (default: builds/).
+ # Relative paths are relative to Rails.root.
+ builds_path: path/to/builds/
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -67,24 +67,24 @@ To archive those legacy job traces, please follow the instruction below.
1. Execute the following command
- ```bash
- gitlab-rake gitlab:traces:archive
- ```
+ ```bash
+ gitlab-rake gitlab:traces:archive
+ ```
- After you executed this task, GitLab instance queues up Sidekiq jobs (asynchronous processes)
- for migrating job trace files from local storage to object storage.
- It could take time to complete the all migration jobs. You can check the progress by the following command
+ After you executed this task, GitLab instance queues up Sidekiq jobs (asynchronous processes)
+ for migrating job trace files from local storage to object storage.
+ It could take time to complete the all migration jobs. You can check the progress by the following command
- ```bash
- sudo gitlab-rails console
- ```
+ ```bash
+ sudo gitlab-rails console
+ ```
- ```bash
- [1] pry(main)> Sidekiq::Stats.new.queues['pipeline_background:archive_trace']
- => 100
- ```
+ ```bash
+ [1] pry(main)> Sidekiq::Stats.new.queues['pipeline_background:archive_trace']
+ => 100
+ ```
- If the count becomes zero, the archiving processes are done
+ If the count becomes zero, the archiving processes are done
## How to migrate archived job traces to object storage
@@ -95,9 +95,9 @@ If job traces have already been archived into local storage, and you want to mig
1. Ensure [Object storage integration for Job Artifacts](job_artifacts.md#object-storage-settings) is enabled
1. Execute the following command
- ```bash
- gitlab-rake gitlab:traces:migrate
- ```
+ ```bash
+ gitlab-rake gitlab:traces:migrate
+ ```
## How to remove job traces
diff --git a/doc/administration/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index c328d4c3aaa..99cd9051778 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -21,18 +21,18 @@ To enable external storage of merge request diffs, follow the instructions below
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['external_diffs_enabled'] = true
- ```
+ ```ruby
+ gitlab_rails['external_diffs_enabled'] = true
+ ```
1. _The external diffs will be stored in in
`/var/opt/gitlab/gitlab-rails/shared/external-diffs`._ To change the path,
for example, to `/mnt/storage/external-diffs`, edit `/etc/gitlab/gitlab.rb`
and add the following line:
- ```ruby
- gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
- ```
+ ```ruby
+ gitlab_rails['external_diffs_storage_path'] = "/mnt/storage/external-diffs"
+ ```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -41,21 +41,21 @@ To enable external storage of merge request diffs, follow the instructions below
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- external_diffs:
- enabled: true
- ```
+ ```yaml
+ external_diffs:
+ enabled: true
+ ```
1. _The external diffs will be stored in
`/home/git/gitlab/shared/external-diffs`._ To change the path, for example,
to `/mnt/storage/external-diffs`, edit `/home/git/gitlab/config/gitlab.yml`
and add or amend the following lines:
- ```yaml
- external_diffs:
- enabled: true
- storage_path: /mnt/storage/external-diffs
- ```
+ ```yaml
+ external_diffs:
+ enabled: true
+ storage_path: /mnt/storage/external-diffs
+ ```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
@@ -101,28 +101,28 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
- ```ruby
- gitlab_rails['external_diffs_enabled'] = true
- gitlab_rails['external_diffs_object_store_enabled'] = true
- gitlab_rails['external_diffs_object_store_remote_directory'] = "external-diffs"
- gitlab_rails['external_diffs_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
- 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
- }
- ```
-
- Note that, if you are using AWS IAM profiles, be sure to omit the
- AWS access key and secret access key/value pairs. For example:
-
- ```ruby
- gitlab_rails['external_diffs_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'use_iam_profile' => true
- }
- ```
+ ```ruby
+ gitlab_rails['external_diffs_enabled'] = true
+ gitlab_rails['external_diffs_object_store_enabled'] = true
+ gitlab_rails['external_diffs_object_store_remote_directory'] = "external-diffs"
+ gitlab_rails['external_diffs_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+ }
+ ```
+
+ Note that, if you are using AWS IAM profiles, be sure to omit the
+ AWS access key and secret access key/value pairs. For example:
+
+ ```ruby
+ gitlab_rails['external_diffs_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'use_iam_profile' => true
+ }
+ ```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -131,18 +131,18 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- external_diffs:
- enabled: true
- object_store:
- enabled: true
- remote_directory: "external-diffs" # The bucket name
- connection:
- provider: AWS # Only AWS supported at the moment
- aws_access_key_id: AWS_ACCESS_KEY_ID
- aws_secret_access_key: AWS_SECRET_ACCESS_KEY
- region: eu-central-1
- ```
+ ```yaml
+ external_diffs:
+ enabled: true
+ object_store:
+ enabled: true
+ remote_directory: "external-diffs" # The bucket name
+ connection:
+ provider: AWS # Only AWS supported at the moment
+ aws_access_key_id: AWS_ACCESS_KEY_ID
+ aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ region: eu-central-1
+ ```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
@@ -157,11 +157,11 @@ To enable this feature, perform the following steps:
**In Omnibus installations:**
-1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
+1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['external_diffs_when'] = 'outdated'
- ```
+ ```ruby
+ gitlab_rails['external_diffs_when'] = 'outdated'
+ ```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure) for the changes to take effect.
@@ -170,11 +170,11 @@ To enable this feature, perform the following steps:
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- external_diffs:
- enabled: true
- when: outdated
- ```
+ ```yaml
+ external_diffs:
+ enabled: true
+ when: outdated
+ ```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source) for the changes to take effect.
diff --git a/doc/administration/monitoring/ip_whitelist.md b/doc/administration/monitoring/ip_whitelist.md
index ad2773de132..6bb2fe81b2c 100644
--- a/doc/administration/monitoring/ip_whitelist.md
+++ b/doc/administration/monitoring/ip_whitelist.md
@@ -12,9 +12,9 @@ hosts or use IP ranges:
1. Open `/etc/gitlab/gitlab.rb` and add or uncomment the following:
- ```ruby
- gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
- ```
+ ```ruby
+ gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '192.168.0.1']
+ ```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
@@ -24,13 +24,13 @@ hosts or use IP ranges:
1. Edit `config/gitlab.yml`:
- ```yaml
- monitoring:
- # by default only local IPs are allowed to access monitoring resources
- ip_whitelist:
- - 127.0.0.0/8
- - 192.168.0.1
- ```
+ ```yaml
+ monitoring:
+ # by default only local IPs are allowed to access monitoring resources
+ ip_whitelist:
+ - 127.0.0.0/8
+ - 192.168.0.1
+ ```
1. Save the file and [restart] GitLab for the changes to take effect.
diff --git a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
index f68b03d1ade..9aa4dfa5ab7 100644
--- a/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
+++ b/doc/administration/monitoring/prometheus/gitlab_monitor_exporter.md
@@ -12,9 +12,9 @@ To enable the GitLab monitor exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- gitlab_monitor['enable'] = true
- ```
+ ```ruby
+ gitlab_monitor['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index ce65d77274b..341ea3330d7 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -39,9 +39,9 @@ To disable Prometheus and all of its exporters, as well as any added in the futu
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `false`:
- ```ruby
- prometheus_monitoring['enable'] = false
- ```
+ ```ruby
+ prometheus_monitoring['enable'] = false
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
@@ -61,19 +61,19 @@ To change the address/port that Prometheus listens on:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line:
- ```ruby
- prometheus['listen_address'] = 'localhost:9090'
- ```
+ ```ruby
+ prometheus['listen_address'] = 'localhost:9090'
+ ```
- Replace `localhost:9090` with the address/port you want Prometheus to
- listen on. If you would like to allow access to Prometheus to hosts other
- than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
+ Replace `localhost:9090` with the address/port you want Prometheus to
+ listen on. If you would like to allow access to Prometheus to hosts other
+ than `localhost`, leave out the host, or use `0.0.0.0` to allow public access:
- ```ruby
- prometheus['listen_address'] = ':9090'
- # or
- prometheus['listen_address'] = '0.0.0.0:9090'
- ```
+ ```ruby
+ prometheus['listen_address'] = ':9090'
+ # or
+ prometheus['listen_address'] = '0.0.0.0:9090'
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
@@ -90,22 +90,22 @@ To use an external Prometheus server:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Disable the bundled Prometheus:
- ```ruby
- prometheus['enable'] = false
- ```
+ ```ruby
+ prometheus['enable'] = false
+ ```
1. Set each bundled service's [exporter](#bundled-software-metrics) to listen on a network address, for example:
- ```ruby
- gitlab_monitor['listen_address'] = '0.0.0.0'
- sidekiq['listen_address'] = '0.0.0.0'
- gitlab_monitor['listen_port'] = '9168'
- node_exporter['listen_address'] = '0.0.0.0:9100'
- redis_exporter['listen_address'] = '0.0.0.0:9121'
- postgres_exporter['listen_address'] = '0.0.0.0:9187'
- gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
- gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
- ```
+ ```ruby
+ gitlab_monitor['listen_address'] = '0.0.0.0'
+ sidekiq['listen_address'] = '0.0.0.0'
+ gitlab_monitor['listen_port'] = '9168'
+ node_exporter['listen_address'] = '0.0.0.0:9100'
+ redis_exporter['listen_address'] = '0.0.0.0:9121'
+ postgres_exporter['listen_address'] = '0.0.0.0:9187'
+ gitaly['prometheus_listen_addr'] = "0.0.0.0:9236"
+ gitlab_workhorse['prometheus_listen_addr'] = "0.0.0.0:9229"
+ ```
1. Install and set up a dedicated Prometheus instance, if necessary, using the [official installation instructions](https://prometheus.io/docs/prometheus/latest/installation/).
1. Add the Prometheus server IP address to the [monitoring IP whitelist](../ip_whitelist.html). For example:
@@ -117,14 +117,14 @@ To use an external Prometheus server:
1. To scrape nginx metrics, you'll also need to configure nginx to allow the Prometheus server
IP. For example:
- ```ruby
- nginx['status']['options'] = {
- "server_tokens" => "off",
- "access_log" => "off",
- "allow" => "192.168.0.1",
- "deny" => "all",
- }
- ```
+ ```ruby
+ nginx['status']['options'] = {
+ "server_tokens" => "off",
+ "access_log" => "off",
+ "allow" => "192.168.0.1",
+ "deny" => "all",
+ }
+ ```
1. [Reconfigure GitLab][reconfigure] to apply the changes
1. Edit the Prometheus server's configuration file.
@@ -132,17 +132,17 @@ To use an external Prometheus server:
[scrape target configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#%3Cscrape_config%3E).
For example, a sample snippet using `static_configs`:
- ```yaml
- scrape_configs:
- - job_name: 'gitlab_exporters'
- static_configs:
- - targets: ['1.1.1.1:9168', '1.1.1.1:9236', '1.1.1.1:9236', '1.1.1.1:9100', '1.1.1.1:9121', '1.1.1.1:9187']
+ ```yaml
+ scrape_configs:
+ - job_name: 'gitlab_exporters'
+ static_configs:
+ - targets: ['1.1.1.1:9168', '1.1.1.1:9236', '1.1.1.1:9236', '1.1.1.1:9100', '1.1.1.1:9121', '1.1.1.1:9187']
- - job_name: 'gitlab_metrics'
- metrics_path: /-/metrics
- static_configs:
- - targets: ['1.1.1.1:443']
- ```
+ - job_name: 'gitlab_metrics'
+ metrics_path: /-/metrics
+ static_configs:
+ - targets: ['1.1.1.1:443']
+ ```
1. Restart the Prometheus server.
@@ -241,9 +241,9 @@ To disable the monitoring of Kubernetes:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Add or find and uncomment the following line and set it to `false`:
- ```ruby
- prometheus['monitor_kubernetes'] = false
- ```
+ ```ruby
+ prometheus['monitor_kubernetes'] = false
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
diff --git a/doc/administration/monitoring/prometheus/node_exporter.md b/doc/administration/monitoring/prometheus/node_exporter.md
index aef7758a88f..bcacfaa3be5 100644
--- a/doc/administration/monitoring/prometheus/node_exporter.md
+++ b/doc/administration/monitoring/prometheus/node_exporter.md
@@ -13,9 +13,9 @@ To enable the node exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- node_exporter['enable'] = true
- ```
+ ```ruby
+ node_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
index d76834fdbea..6183594c69c 100644
--- a/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
+++ b/doc/administration/monitoring/prometheus/pgbouncer_exporter.md
@@ -12,9 +12,9 @@ To enable the PgBouncer exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- pgbouncer_exporter['enable'] = true
- ```
+ ```ruby
+ pgbouncer_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect.
diff --git a/doc/administration/monitoring/prometheus/postgres_exporter.md b/doc/administration/monitoring/prometheus/postgres_exporter.md
index 8e2d3162f88..3ad15b65497 100644
--- a/doc/administration/monitoring/prometheus/postgres_exporter.md
+++ b/doc/administration/monitoring/prometheus/postgres_exporter.md
@@ -12,9 +12,9 @@ To enable the postgres exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- postgres_exporter['enable'] = true
- ```
+ ```ruby
+ postgres_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/monitoring/prometheus/redis_exporter.md b/doc/administration/monitoring/prometheus/redis_exporter.md
index d54d409dbb6..1520115a38d 100644
--- a/doc/administration/monitoring/prometheus/redis_exporter.md
+++ b/doc/administration/monitoring/prometheus/redis_exporter.md
@@ -13,9 +13,9 @@ To enable the Redis exporter:
1. Edit `/etc/gitlab/gitlab.rb`
1. Add or find and uncomment the following line, making sure it's set to `true`:
- ```ruby
- redis_exporter['enable'] = true
- ```
+ ```ruby
+ redis_exporter['enable'] = true
+ ```
1. Save the file and [reconfigure GitLab][reconfigure] for the changes to
take effect
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index b329abdca08..ea69378b249 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -117,81 +117,81 @@ the database. The following instructions can be used to build OpenSSH 7.5:
1. First, download the package and install the required packages:
- ```
- sudo su -
- cd /tmp
- curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
- tar xzvf openssh-7.5p1.tar.gz
- yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
- ```
+ ```
+ sudo su -
+ cd /tmp
+ curl --remote-name https://mirrors.evowise.com/pub/OpenBSD/OpenSSH/portable/openssh-7.5p1.tar.gz
+ tar xzvf openssh-7.5p1.tar.gz
+ yum install rpm-build gcc make wget openssl-devel krb5-devel pam-devel libX11-devel xmkmf libXt-devel
+ ```
1. Prepare the build by copying files to the right place:
- ```
- mkdir -p /root/rpmbuild/{SOURCES,SPECS}
- cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
- cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
- cd /root/rpmbuild/SPECS
- ```
+ ```
+ mkdir -p /root/rpmbuild/{SOURCES,SPECS}
+ cp ./openssh-7.5p1/contrib/redhat/openssh.spec /root/rpmbuild/SPECS/
+ cp openssh-7.5p1.tar.gz /root/rpmbuild/SOURCES/
+ cd /root/rpmbuild/SPECS
+ ```
1. Next, set the spec settings properly:
- ```
- sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
- sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
- sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
- ```
+ ```
+ sed -i -e "s/%define no_gnome_askpass 0/%define no_gnome_askpass 1/g" openssh.spec
+ sed -i -e "s/%define no_x11_askpass 0/%define no_x11_askpass 1/g" openssh.spec
+ sed -i -e "s/BuildPreReq/BuildRequires/g" openssh.spec
+ ```
1. Build the RPMs:
- ```
- rpmbuild -bb openssh.spec
- ```
+ ```
+ rpmbuild -bb openssh.spec
+ ```
1. Ensure the RPMs were built:
- ```
- ls -al /root/rpmbuild/RPMS/x86_64/
- ```
+ ```
+ ls -al /root/rpmbuild/RPMS/x86_64/
+ ```
- You should see something as the following:
+ You should see something as the following:
- ```
- total 1324
- drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
- drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
- -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
- -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
- ```
+ ```
+ total 1324
+ drwxr-xr-x. 2 root root 4096 Jun 20 19:37 .
+ drwxr-xr-x. 3 root root 19 Jun 20 19:37 ..
+ -rw-r--r--. 1 root root 470828 Jun 20 19:37 openssh-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 490716 Jun 20 19:37 openssh-clients-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 17020 Jun 20 19:37 openssh-debuginfo-7.5p1-1.x86_64.rpm
+ -rw-r--r--. 1 root root 367516 Jun 20 19:37 openssh-server-7.5p1-1.x86_64.rpm
+ ```
1. Install the packages. OpenSSH packages will replace `/etc/pam.d/sshd`
with its own version, which may prevent users from logging in, so be sure
that the file is backed up and restored after installation:
- ```
- timestamp=$(date +%s)
- cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
- rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
- yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
- ```
+ ```
+ timestamp=$(date +%s)
+ cp /etc/pam.d/sshd pam-ssh-conf-$timestamp
+ rpm -Uvh /root/rpmbuild/RPMS/x86_64/*.rpm
+ yes | cp pam-ssh-conf-$timestamp /etc/pam.d/sshd
+ ```
1. Verify the installed version. In another window, attempt to login to the server:
- ```
- ssh -v <your-centos-machine>
- ```
+ ```
+ ssh -v <your-centos-machine>
+ ```
- You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
+ You should see a line that reads: "debug1: Remote protocol version 2.0, remote software version OpenSSH_7.5"
- If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
+ If not, you may need to restart sshd (e.g. `systemctl restart sshd.service`).
-1. *IMPORTANT!* Open a new SSH session to your server before exiting to make
- sure everything is working! If you need to downgrade, simple install the
- older package:
+1. *IMPORTANT!* Open a new SSH session to your server before exiting to make
+ sure everything is working! If you need to downgrade, simple install the
+ older package:
- ```
- # Only run this if you run into a problem logging in
- yum downgrade openssh-server openssh openssh-clients
- ```
+ ```
+ # Only run this if you run into a problem logging in
+ yum downgrade openssh-server openssh openssh-clients
+ ```
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
index c0c242733a2..4a6e22bdb84 100644
--- a/doc/administration/operations/filesystem_benchmarking.md
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -78,25 +78,28 @@ executed, and then read the same 1,000 files.
[repository storage path](../repository_storage_paths.md).
1. Create a temporary directory for the test so it's easy to remove the files later:
- ```sh
- mkdir test; cd test
- ```
+ ```sh
+ mkdir test; cd test
+ ```
+
1. Run the command:
- ```sh
- time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
- ```
-1. To benchmark read performance, run the command:
+ ```sh
+ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
+ ```
- ```sh
- time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
- ```
-1. Remove the test files:
+1. To benchmark read performance, run the command:
```sh
- cd ../; rm -rf test
+ time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
```
+1. Remove the test files:
+
+ ```sh
+ cd ../; rm -rf test
+ ```
+
The output of the `time for ...` commands will look similar to the following. The
important metric is the `real` time.
diff --git a/doc/administration/packages.md b/doc/administration/packages.md
index 1da8de12188..c0f8777a8c0 100644
--- a/doc/administration/packages.md
+++ b/doc/administration/packages.md
@@ -28,9 +28,9 @@ To enable the Packages feature:
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['packages_enabled'] = true
- ```
+ ```ruby
+ gitlab_rails['packages_enabled'] = true
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -39,10 +39,11 @@ To enable the Packages feature:
1. After the installation is complete, you will have to configure the `packages`
section in `config/gitlab.yml`. Set to `true` to enable it:
- ```yaml
- packages:
- enabled: true
- ```
+ ```yaml
+ packages:
+ enabled: true
+ ```
+
1. [Restart GitLab] for the changes to take effect.
## Changing the storage path
@@ -61,9 +62,9 @@ To change the local storage path:
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['packages_storage_path'] = "/mnt/packages"
- ```
+ ```ruby
+ gitlab_rails['packages_storage_path'] = "/mnt/packages"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -71,11 +72,12 @@ To change the local storage path:
1. Edit the `packages` section in `config/gitlab.yml`:
- ```yaml
- packages:
- enabled: true
- storage_path: shared/packages
- ```
+ ```yaml
+ packages:
+ enabled: true
+ storage_path: shared/packages
+ ```
+
1. [Restart GitLab] for the changes to take effect.
### Using object storage
@@ -88,31 +90,31 @@ upload packages:
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines (uncomment where
necessary):
- ```ruby
- gitlab_rails['packages_enabled'] = true
- gitlab_rails['packages_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/packages"
- gitlab_rails['packages_object_store_enabled'] = true
- gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
- gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
- gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
- gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
- gitlab_rails['packages_object_store_connection'] = {
- ##
- ## If the provider is AWS S3, uncomment the following
- ##
- #'provider' => 'AWS',
- #'region' => 'eu-west-1',
- #'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
- #'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
- ##
- ## If the provider is other than AWS (an S3-compatible one), uncomment the following
- ##
- #'host' => 's3.amazonaws.com',
- #'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
- #'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
- #'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
- }
- ```
+ ```ruby
+ gitlab_rails['packages_enabled'] = true
+ gitlab_rails['packages_storage_path'] = "/var/opt/gitlab/gitlab-rails/shared/packages"
+ gitlab_rails['packages_object_store_enabled'] = true
+ gitlab_rails['packages_object_store_remote_directory'] = "packages" # The bucket name.
+ gitlab_rails['packages_object_store_direct_upload'] = false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ gitlab_rails['packages_object_store_background_upload'] = true # Temporary option to limit automatic upload (Default: true).
+ gitlab_rails['packages_object_store_proxy_download'] = false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ gitlab_rails['packages_object_store_connection'] = {
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #'provider' => 'AWS',
+ #'region' => 'eu-west-1',
+ #'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ #'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY',
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #'host' => 's3.amazonaws.com',
+ #'aws_signature_version' => 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #'endpoint' => 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #'path_style' => false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -120,35 +122,35 @@ upload packages:
1. Edit the `packages` section in `config/gitlab.yml` (uncomment where necessary):
- ```yaml
- packages:
- enabled: true
- ##
- ## The location where build packages are stored (default: shared/packages).
- ##
- #storage_path: shared/packages
- object_store:
- enabled: false
- remote_directory: packages # The bucket name.
- #direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
- #background_upload: true # Temporary option to limit automatic upload (Default: true).
- #proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
- connection:
- ##
- ## If the provider is AWS S3, uncomment the following
- ##
- #provider: AWS
- #region: us-east-1
- #aws_access_key_id: AWS_ACCESS_KEY_ID
- #aws_secret_access_key: AWS_SECRET_ACCESS_KEY
- ##
- ## If the provider is other than AWS (an S3-compatible one), uncomment the following
- ##
- #host: 's3.amazonaws.com' # default: s3.amazonaws.com.
- #aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
- #endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
- #path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
- ```
+ ```yaml
+ packages:
+ enabled: true
+ ##
+ ## The location where build packages are stored (default: shared/packages).
+ ##
+ #storage_path: shared/packages
+ object_store:
+ enabled: false
+ remote_directory: packages # The bucket name.
+ #direct_upload: false # Use Object Storage directly for uploads instead of background uploads if enabled (Default: false).
+ #background_upload: true # Temporary option to limit automatic upload (Default: true).
+ #proxy_download: false # Passthrough all downloads via GitLab instead of using Redirects to Object Storage.
+ connection:
+ ##
+ ## If the provider is AWS S3, uncomment the following
+ ##
+ #provider: AWS
+ #region: us-east-1
+ #aws_access_key_id: AWS_ACCESS_KEY_ID
+ #aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ ##
+ ## If the provider is other than AWS (an S3-compatible one), uncomment the following
+ ##
+ #host: 's3.amazonaws.com' # default: s3.amazonaws.com.
+ #aws_signature_version: 4 # For creation of signed URLs. Set to 2 if provider does not support v4.
+ #endpoint: 'https://s3.amazonaws.com' # Useful for S3-compliant services such as DigitalOcean Spaces.
+ #path_style: false # If true, use 'host/bucket_name/object' instead of 'bucket_name.host/object'.
+ ```
1. [Restart GitLab] for the changes to take effect.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 3a7ca517d56..b5b8f124274 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -4,21 +4,24 @@ description: 'Learn how to administer GitLab Pages.'
# GitLab Pages administration
-> **Notes:**
->
> - [Introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
-> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
-> - This guide is for Omnibus GitLab installations. If you have installed
-> GitLab from source, follow the [Pages source installation document](source.md).
-> - To learn how to use GitLab Pages, read the [user documentation][pages-userguide].
-> - Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
-
-This document describes how to set up the _latest_ GitLab Pages feature. Make
-sure to read the [changelog](#changelog) if you are upgrading to a new GitLab
+> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
+> - Support for subgroup project's websites was
+> [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
+
+GitLab Pages allows for hosting of static sites. It must be configured by an
+administrator. Separate [user documentation][pages-userguide] is available.
+
+Read the [changelog](#changelog) if you are upgrading to a new GitLab
version as it may include new features and changes needed to be made in your
configuration.
+NOTE: **Note:**
+This guide is for Omnibus GitLab installations. If you have installed
+GitLab from source, see
+[GitLab Pages administration for source installations](source.md).
+
## Overview
GitLab Pages makes use of the [GitLab Pages daemon], a simple HTTP server
@@ -121,9 +124,9 @@ The Pages daemon doesn't listen to the outside world.
1. Set the external URL for GitLab Pages in `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url 'http://example.io'
- ```
+ ```shell
+ pages_external_url 'http://example.io'
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -146,16 +149,16 @@ outside world.
1. Place the certificate and key inside `/etc/gitlab/ssl`
1. In `/etc/gitlab/gitlab.rb` specify the following configuration:
- ```shell
- pages_external_url 'https://example.io'
+ ```shell
+ pages_external_url 'https://example.io'
- pages_nginx['redirect_http_to_https'] = true
- pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt"
- pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key"
- ```
+ pages_nginx['redirect_http_to_https'] = true
+ pages_nginx['ssl_certificate'] = "/etc/gitlab/ssl/pages-nginx.crt"
+ pages_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/pages-nginx.key"
+ ```
- where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
- respectively.
+ where `pages-nginx.crt` and `pages-nginx.key` are the SSL cert and key,
+ respectively.
1. [Reconfigure GitLab][reconfigure].
@@ -168,9 +171,9 @@ behavior:
1. Edit `/etc/gitlab/gitlab.rb`.
1. Set the `inplace_chroot` to `true` for GitLab Pages:
- ```shell
- gitlab_pages['inplace_chroot'] = true
- ```
+ ```shell
+ gitlab_pages['inplace_chroot'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -203,16 +206,16 @@ world. Custom domains are supported, but no TLS.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url "http://example.io"
- nginx['listen_addresses'] = ['192.0.2.1']
- pages_nginx['enable'] = false
- gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
- ```
+ ```shell
+ pages_external_url "http://example.io"
+ nginx['listen_addresses'] = ['192.0.2.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
+ ```
- where `192.0.2.1` is the primary IP address that GitLab is listening to and
- `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
- listens on. If you don't have IPv6, you can omit the IPv6 address.
+ where `192.0.2.1` is the primary IP address that GitLab is listening to and
+ `192.0.2.2` and `2001::2` are the secondary IPs the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure].
@@ -234,19 +237,19 @@ world. Custom domains and TLS are supported.
1. Edit `/etc/gitlab/gitlab.rb`:
- ```shell
- pages_external_url "https://example.io"
- nginx['listen_addresses'] = ['192.0.2.1']
- pages_nginx['enable'] = false
- gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
- gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
- gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
- gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
- ```
+ ```shell
+ pages_external_url "https://example.io"
+ nginx['listen_addresses'] = ['192.0.2.1']
+ pages_nginx['enable'] = false
+ gitlab_pages['cert'] = "/etc/gitlab/ssl/example.io.crt"
+ gitlab_pages['cert_key'] = "/etc/gitlab/ssl/example.io.key"
+ gitlab_pages['external_http'] = ['192.0.2.2:80', '[2001::2]:80']
+ gitlab_pages['external_https'] = ['192.0.2.2:443', '[2001::2]:443']
+ ```
- where `192.0.2.1` is the primary IP address that GitLab is listening to and
- `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
- listens on. If you don't have IPv6, you can omit the IPv6 address.
+ where `192.0.2.1` is the primary IP address that GitLab is listening to and
+ `192.0.2.2` and `2001::2` are the secondary IPs where the GitLab Pages daemon
+ listens on. If you don't have IPv6, you can omit the IPv6 address.
1. [Reconfigure GitLab][reconfigure].
@@ -284,9 +287,9 @@ Pages access control is disabled by default. To enable it:
1. Enable it in `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_pages['access_control'] = true
- ```
+ ```ruby
+ gitlab_pages['access_control'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
@@ -299,9 +302,9 @@ pages:
1. Configure in `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_pages['http_proxy'] = 'http://example:8080'
- ```
+ ```ruby
+ gitlab_pages['http_proxy'] = 'http://example:8080'
+ ```
1. [Reconfigure Gitlab][reconfigure] for the changes to take effect.
@@ -316,9 +319,9 @@ Follow the steps below to configure verbose logging of GitLab Pages daemon.
If you wish to make it log events with level `DEBUG` you must configure this in
`/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['log_verbose'] = true
- ```
+ ```shell
+ gitlab_pages['log_verbose'] = true
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -331,9 +334,9 @@ are stored.
If you wish to store them in another location you must set it up in
`/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
+ ```shell
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -344,19 +347,19 @@ Omnibus GitLab 11.1.
1. By default the listener is configured to listen for requests on `localhost:8090`.
- If you wish to disable it you must configure this in
- `/etc/gitlab/gitlab.rb`:
+ If you wish to disable it you must configure this in
+ `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['listen_proxy'] = nil
- ```
+ ```shell
+ gitlab_pages['listen_proxy'] = nil
+ ```
- If you wish to make it listen on a different port you must configure this also in
- `/etc/gitlab/gitlab.rb`:
+ If you wish to make it listen on a different port you must configure this also in
+ `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['listen_proxy'] = "localhost:10080"
- ```
+ ```shell
+ gitlab_pages['listen_proxy'] = "localhost:10080"
+ ```
1. [Reconfigure GitLab][reconfigure].
@@ -378,28 +381,29 @@ Follow the steps below to configure GitLab Pages in a separate server.
1. On `app2` install GitLab omnibus and modify `/etc/gitlab/gitlab.rb` this way:
- ```shell
- external_url 'http://<ip-address-of-the-server>'
- pages_external_url "http://<your-pages-domain>"
- postgresql['enable'] = false
- redis['enable'] = false
- prometheus['enable'] = false
- unicorn['enable'] = false
- sidekiq['enable'] = false
- gitlab_workhorse['enable'] = false
- gitaly['enable'] = false
- alertmanager['enable'] = false
- node_exporter['enable'] = false
- gitlab_rails['auto_migrate'] = false
- ```
+ ```shell
+ external_url 'http://<ip-address-of-the-server>'
+ pages_external_url "http://<your-pages-domain>"
+ postgresql['enable'] = false
+ redis['enable'] = false
+ prometheus['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+ gitaly['enable'] = false
+ alertmanager['enable'] = false
+ node_exporter['enable'] = false
+ gitlab_rails['auto_migrate'] = false
+ ```
+
1. Run `sudo gitlab-ctl reconfigure`.
1. On `app1` apply the following changes to `/etc/gitlab/gitlab.rb`:
- ```shell
- gitlab_pages['enable'] = false
- pages_external_url "http://<your-pages-domain>"
- gitlab_rails['pages_path'] = "/mnt/pages"
- ```
+ ```shell
+ gitlab_pages['enable'] = false
+ pages_external_url "http://<your-pages-domain>"
+ gitlab_rails['pages_path'] = "/mnt/pages"
+ ```
1. Run `sudo gitlab-ctl reconfigure`.
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index 2100f7cd707..b2cad6cf926 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -102,50 +102,50 @@ The Pages daemon doesn't listen to the outside world.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Go to the GitLab installation directory:
- ```bash
- cd /home/git/gitlab
- ```
+ ```bash
+ cd /home/git/gitlab
+ ```
1. Edit `gitlab.yml` and under the `pages` setting, set `enabled` to `true` and
the `host` to the FQDN under which GitLab Pages will be served:
- ```yaml
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 80
- https: false
- ```
+ host: example.io
+ port: 80
+ https: false
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
`-pages-domain` must match the `host` setting that you set above.
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090"
+ ```
1. Copy the `gitlab-pages` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
+ ```
1. Restart NGINX
1. [Restart GitLab][restart]
@@ -165,27 +165,27 @@ outside world.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. In `gitlab.yml`, set the port to `443` and https to `true`:
- ```bash
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```bash
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 443
- https: true
- ```
+ host: example.io
+ port: 443
+ https: true
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
@@ -193,17 +193,17 @@ outside world.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
+ ```
1. Restart NGINX
1. [Restart GitLab][restart]
@@ -231,48 +231,48 @@ world. Custom domains are supported, but no TLS.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Edit `gitlab.yml` to look like the example below. You need to change the
`host` to the FQDN under which GitLab Pages will be served. Set
`external_http` to the secondary IP on which the pages daemon will listen
for connections:
- ```yaml
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 80
- https: false
+ host: example.io
+ port: 80
+ https: false
- external_http: 192.0.2.2:80
- ```
+ external_http: 192.0.2.2:80
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
`-pages-domain` and `-listen-http` must match the `host` and `external_http`
settings that you set above respectively:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80"
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages /etc/nginx/sites-available/gitlab-pages.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages.conf
+ ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
@@ -297,33 +297,33 @@ world. Custom domains and TLS are supported.
1. Install the Pages daemon:
- ```
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
- ```
+ ```
+ cd /home/git
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+ cd gitlab-pages
+ sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+ sudo -u git -H make
+ ```
1. Edit `gitlab.yml` to look like the example below. You need to change the
`host` to the FQDN under which GitLab Pages will be served. Set
`external_http` and `external_https` to the secondary IP on which the pages
daemon will listen for connections:
- ```yaml
- ## GitLab Pages
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- # path: shared/pages
+ ```yaml
+ ## GitLab Pages
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ # path: shared/pages
- host: example.io
- port: 443
- https: true
+ host: example.io
+ port: 443
+ https: true
- external_http: 192.0.2.2:80
- external_https: 192.0.2.2:443
- ```
+ external_http: 192.0.2.2:80
+ external_https: 192.0.2.2:443
+ ```
1. Edit `/etc/default/gitlab` and set `gitlab_pages_enabled` to `true` in
order to enable the pages daemon. In `gitlab_pages_options` the
@@ -332,17 +332,17 @@ world. Custom domains and TLS are supported.
The `-root-cert` and `-root-key` settings are the wildcard TLS certificates
of the `example.io` domain:
- ```
- gitlab_pages_enabled=true
- gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
- ```
+ ```
+ gitlab_pages_enabled=true
+ gitlab_pages_options="-pages-domain example.io -pages-root $app_root/shared/pages -listen-proxy 127.0.0.1:8090 -listen-http 192.0.2.2:80 -listen-https 192.0.2.2:443 -root-cert /path/to/example.io.crt -root-key /path/to/example.io.key
+ ```
1. Copy the `gitlab-pages-ssl` Nginx configuration file:
- ```bash
- sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
- sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
- ```
+ ```bash
+ sudo cp lib/support/nginx/gitlab-pages-ssl /etc/nginx/sites-available/gitlab-pages-ssl.conf
+ sudo ln -sf /etc/nginx/sites-{available,enabled}/gitlab-pages-ssl.conf
+ ```
1. Edit all GitLab related configs in `/etc/nginx/site-available/` and replace
`0.0.0.0` with `192.0.2.1`, where `192.0.2.1` the primary IP where GitLab
@@ -359,9 +359,9 @@ are stored.
If you wish to store them in another location you must set it up in
`/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['pages_path'] = "/mnt/storage/pages"
- ```
+ ```ruby
+ gitlab_rails['pages_path'] = "/mnt/storage/pages"
+ ```
1. [Reconfigure GitLab][reconfigure]
@@ -414,10 +414,10 @@ Pages access control is disabled by default. To enable it:
1. Modify your `config/gitlab.yml` file:
- ```yaml
- pages:
- access_control: true
- ```
+ ```yaml
+ pages:
+ access_control: true
+ ```
1. [Restart GitLab][restart].
1. Create a new [system OAuth application](../../integration/oauth_provider.md#adding-an-application-through-the-profile).
@@ -426,12 +426,12 @@ Pages access control is disabled by default. To enable it:
application, but it does need the "api" scope.
1. Start the Pages daemon with the following additional arguments:
- ```shell
- -auth-client-secret <OAuth code generated by GitLab> \
- -auth-redirect-uri http://projects.example.io/auth \
- -auth-secret <40 random hex characters> \
- -auth-server <URL of the GitLab instance>
- ```
+ ```shell
+ -auth-client-secret <OAuth code generated by GitLab> \
+ -auth-redirect-uri http://projects.example.io/auth \
+ -auth-secret <40 random hex characters> \
+ -auth-server <URL of the GitLab instance>
+ ```
1. Users can now configure it in their [projects' settings](../../user/project/pages/introduction.md#gitlab-pages-access-control-core-only).
@@ -444,12 +444,12 @@ are stored.
If you wish to store them in another location you must set it up in
`gitlab.yml` under the `pages` section:
- ```yaml
- pages:
- enabled: true
- # The location where pages are stored (default: shared/pages).
- path: /mnt/storage/pages
- ```
+ ```yaml
+ pages:
+ enabled: true
+ # The location where pages are stored (default: shared/pages).
+ path: /mnt/storage/pages
+ ```
1. [Restart GitLab][restart]
diff --git a/doc/administration/pseudonymizer.md b/doc/administration/pseudonymizer.md
index 3278409d4b5..78b2751da13 100644
--- a/doc/administration/pseudonymizer.md
+++ b/doc/administration/pseudonymizer.md
@@ -31,27 +31,27 @@ To configure the pseudonymizer, you need to:
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
- ```ruby
- gitlab_rails['pseudonymizer_manifest'] = 'config/pseudonymizer.yml'
- gitlab_rails['pseudonymizer_upload_remote_directory'] = 'gitlab-elt' # bucket name
- gitlab_rails['pseudonymizer_upload_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
- 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
- }
- ```
-
- NOTE: **Note:**
- If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
-
- ```ruby
- gitlab_rails['pseudonymizer_upload_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'use_iam_profile' => true
- }
- ```
+ ```ruby
+ gitlab_rails['pseudonymizer_manifest'] = 'config/pseudonymizer.yml'
+ gitlab_rails['pseudonymizer_upload_remote_directory'] = 'gitlab-elt' # bucket name
+ gitlab_rails['pseudonymizer_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+ }
+ ```
+
+ NOTE: **Note:**
+ If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
+
+ ```ruby
+ gitlab_rails['pseudonymizer_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'use_iam_profile' => true
+ }
+ ```
1. Save the file and [reconfigure GitLab](restart_gitlab.md#omnibus-gitlab-reconfigure)
for the changes to take effect.
@@ -63,17 +63,17 @@ To configure the pseudonymizer, you need to:
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- pseudonymizer:
- manifest: config/pseudonymizer.yml
- upload:
- remote_directory: 'gitlab-elt' # bucket name
- connection:
- provider: AWS
- aws_access_key_id: AWS_ACCESS_KEY_ID
- aws_secret_access_key: AWS_SECRET_ACCESS_KEY
- region: eu-central-1
- ```
+ ```yaml
+ pseudonymizer:
+ manifest: config/pseudonymizer.yml
+ upload:
+ remote_directory: 'gitlab-elt' # bucket name
+ connection:
+ provider: AWS
+ aws_access_key_id: AWS_ACCESS_KEY_ID
+ aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ region: eu-central-1
+ ```
1. Save the file and [restart GitLab](restart_gitlab.md#installations-from-source)
for the changes to take effect.
diff --git a/doc/administration/raketasks/uploads/sanitize.md b/doc/administration/raketasks/uploads/sanitize.md
index 54a423b9571..ae5ccfb9e37 100644
--- a/doc/administration/raketasks/uploads/sanitize.md
+++ b/doc/administration/raketasks/uploads/sanitize.md
@@ -4,16 +4,16 @@
You need `exiftool` installed on your system. If you installed GitLab:
-- Using the Omnibus package, you're all set.
-- From source, make sure `exiftool` is installed:
+- Using the Omnibus package, you're all set.
+- From source, make sure `exiftool` is installed:
- ```sh
- # Debian/Ubuntu
- sudo apt-get install libimage-exiftool-perl
+ ```sh
+ # Debian/Ubuntu
+ sudo apt-get install libimage-exiftool-perl
- # RHEL/CentOS
- sudo yum install perl-Image-ExifTool
- ```
+ # RHEL/CentOS
+ sudo yum install perl-Image-ExifTool
+ ```
## Remove EXIF data from existing uploads
diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md
index d57fc67c83e..406f7e8a034 100644
--- a/doc/administration/reply_by_email_postfix_setup.md
+++ b/doc/administration/reply_by_email_postfix_setup.md
@@ -14,109 +14,109 @@ The instructions make the assumption that you will be using the email address `i
1. Install the `postfix` package if it is not installed already:
- ```sh
- sudo apt-get install postfix
- ```
+ ```sh
+ sudo apt-get install postfix
+ ```
- When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`.
+ When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches gitlab.example.com`.
1. Install the `mailutils` package.
- ```sh
- sudo apt-get install mailutils
- ```
+ ```sh
+ sudo apt-get install mailutils
+ ```
## Create user
1. Create a user for incoming email.
- ```sh
- sudo useradd -m -s /bin/bash incoming
- ```
+ ```sh
+ sudo useradd -m -s /bin/bash incoming
+ ```
1. Set a password for this user.
- ```sh
- sudo passwd incoming
- ```
+ ```sh
+ sudo passwd incoming
+ ```
- Be sure not to forget this, you'll need it later.
+ Be sure not to forget this, you'll need it later.
## Test the out-of-the-box setup
1. Connect to the local SMTP server:
- ```sh
- telnet localhost 25
- ```
+ ```sh
+ telnet localhost 25
+ ```
- You should see a prompt like this:
+ You should see a prompt like this:
- ```sh
- Trying 127.0.0.1...
- Connected to localhost.
- Escape character is '^]'.
- 220 gitlab.example.com ESMTP Postfix (Ubuntu)
- ```
+ ```sh
+ Trying 127.0.0.1...
+ Connected to localhost.
+ Escape character is '^]'.
+ 220 gitlab.example.com ESMTP Postfix (Ubuntu)
+ ```
- If you get a `Connection refused` error instead, verify that `postfix` is running:
+ If you get a `Connection refused` error instead, verify that `postfix` is running:
- ```sh
- sudo postfix status
- ```
+ ```sh
+ sudo postfix status
+ ```
- If it is not, start it:
+ If it is not, start it:
- ```sh
- sudo postfix start
- ```
+ ```sh
+ sudo postfix start
+ ```
1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt:
- ```
- ehlo localhost
- mail from: root@localhost
- rcpt to: incoming@localhost
- data
- Subject: Re: Some issue
+ ```
+ ehlo localhost
+ mail from: root@localhost
+ rcpt to: incoming@localhost
+ data
+ Subject: Re: Some issue
- Sounds good!
- .
- quit
- ```
+ Sounds good!
+ .
+ quit
+ ```
- _**Note:** The `.` is a literal period on its own line._
+ _**Note:** The `.` is a literal period on its own line._
- _**Note:** If you receive an error after entering `rcpt to: incoming@localhost`
- then your Postfix `my_network` configuration is not correct. The error will
- say 'Temporary lookup failure'. See
- [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._
+ _**Note:** If you receive an error after entering `rcpt to: incoming@localhost`
+ then your Postfix `my_network` configuration is not correct. The error will
+ say 'Temporary lookup failure'. See
+ [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._
1. Check if the `incoming` user received the email:
- ```sh
- su - incoming
- mail
- ```
+ ```sh
+ su - incoming
+ mail
+ ```
- You should see output like this:
+ You should see output like this:
- ```
- "/var/mail/incoming": 1 message 1 unread
- >U 1 root@localhost 59/2842 Re: Some issue
- ```
+ ```
+ "/var/mail/incoming": 1 message 1 unread
+ >U 1 root@localhost 59/2842 Re: Some issue
+ ```
- Quit the mail app:
+ Quit the mail app:
- ```sh
- q
- ```
+ ```sh
+ q
+ ```
1. Log out of the `incoming` account and go back to being `root`:
- ```sh
- logout
- ```
+ ```sh
+ logout
+ ```
## Configure Postfix to use Maildir-style mailboxes
@@ -124,208 +124,212 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
1. Configure Postfix to use Maildir-style mailboxes:
- ```sh
- sudo postconf -e "home_mailbox = Maildir/"
- ```
+ ```sh
+ sudo postconf -e "home_mailbox = Maildir/"
+ ```
1. Restart Postfix:
- ```sh
- sudo /etc/init.d/postfix restart
- ```
+ ```sh
+ sudo /etc/init.d/postfix restart
+ ```
1. Test the new setup:
- 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_.
- 1. Check if the `incoming` user received the email:
+ 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_.
+ 1. Check if the `incoming` user received the email:
- ```sh
- su - incoming
- MAIL=/home/incoming/Maildir
- mail
- ```
+ ```sh
+ su - incoming
+ MAIL=/home/incoming/Maildir
+ mail
+ ```
- You should see output like this:
+ You should see output like this:
- ```
- "/home/incoming/Maildir": 1 message 1 unread
- >U 1 root@localhost 59/2842 Re: Some issue
- ```
+ ```
+ "/home/incoming/Maildir": 1 message 1 unread
+ >U 1 root@localhost 59/2842 Re: Some issue
+ ```
- Quit the mail app:
+ Quit the mail app:
- ```sh
- q
- ```
+ ```sh
+ q
+ ```
- _**Note:** If `mail` returns an error `Maildir: Is a directory` then your
- version of `mail` doesn't support Maildir style mailboxes. Install
- `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then,
- try the above steps again, substituting `heirloom-mailx` for the `mail`
- command._
+ _**Note:** If `mail` returns an error `Maildir: Is a directory` then your
+ version of `mail` doesn't support Maildir style mailboxes. Install
+ `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then,
+ try the above steps again, substituting `heirloom-mailx` for the `mail`
+ command._
1. Log out of the `incoming` account and go back to being `root`:
- ```sh
- logout
- ```
+ ```sh
+ logout
+ ```
## Install the Courier IMAP server
1. Install the `courier-imap` package:
- ```sh
- sudo apt-get install courier-imap
- ```
+ ```sh
+ sudo apt-get install courier-imap
+ ```
- And start `imapd`:
- ```sh
- imapd start
- ```
+ And start `imapd`:
+
+ ```sh
+ imapd start
+ ```
1. The courier-authdaemon isn't started after installation. Without it, imap authentication will fail:
- ```sh
- sudo service courier-authdaemon start
- ```
- You can also configure courier-authdaemon to start on boot:
- ```sh
- sudo systemctl enable courier-authdaemon
- ```
+
+ ```sh
+ sudo service courier-authdaemon start
+ ```
+
+ You can also configure courier-authdaemon to start on boot:
+
+ ```sh
+ sudo systemctl enable courier-authdaemon
+ ```
## Configure Postfix to receive email from the internet
1. Let Postfix know about the domains that it should consider local:
- ```sh
- sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost"
- ```
+ ```sh
+ sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost"
+ ```
1. Let Postfix know about the IPs that it should consider part of the LAN:
- We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network.
+ We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network.
- ```sh
- sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24"
- ```
+ ```sh
+ sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24"
+ ```
1. Configure Postfix to receive mail on all interfaces, which includes the internet:
- ```sh
- sudo postconf -e "inet_interfaces = all"
- ```
+ ```sh
+ sudo postconf -e "inet_interfaces = all"
+ ```
1. Configure Postfix to use the `+` delimiter for sub-addressing:
- ```sh
- sudo postconf -e "recipient_delimiter = +"
- ```
+ ```sh
+ sudo postconf -e "recipient_delimiter = +"
+ ```
1. Restart Postfix:
- ```sh
- sudo service postfix restart
- ```
+ ```sh
+ sudo service postfix restart
+ ```
## Test the final setup
1. Test SMTP under the new setup:
- 1. Connect to the SMTP server:
+ 1. Connect to the SMTP server:
- ```sh
- telnet gitlab.example.com 25
- ```
+ ```sh
+ telnet gitlab.example.com 25
+ ```
- You should see a prompt like this:
+ You should see a prompt like this:
- ```sh
- Trying 123.123.123.123...
- Connected to gitlab.example.com.
- Escape character is '^]'.
- 220 gitlab.example.com ESMTP Postfix (Ubuntu)
- ```
+ ```sh
+ Trying 123.123.123.123...
+ Connected to gitlab.example.com.
+ Escape character is '^]'.
+ 220 gitlab.example.com ESMTP Postfix (Ubuntu)
+ ```
- If you get a `Connection refused` error instead, make sure your firewall is set up to allow inbound traffic on port 25.
+ If you get a `Connection refused` error instead, make sure your firewall is set up to allow inbound traffic on port 25.
- 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt:
+ 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt:
- ```
- ehlo gitlab.example.com
- mail from: root@gitlab.example.com
- rcpt to: incoming@gitlab.example.com
- data
- Subject: Re: Some issue
+ ```
+ ehlo gitlab.example.com
+ mail from: root@gitlab.example.com
+ rcpt to: incoming@gitlab.example.com
+ data
+ Subject: Re: Some issue
- Sounds good!
- .
- quit
- ```
+ Sounds good!
+ .
+ quit
+ ```
- (Note: The `.` is a literal period on its own line)
+ (Note: The `.` is a literal period on its own line)
- 1. Check if the `incoming` user received the email:
+ 1. Check if the `incoming` user received the email:
- ```sh
- su - incoming
- MAIL=/home/incoming/Maildir
- mail
- ```
+ ```sh
+ su - incoming
+ MAIL=/home/incoming/Maildir
+ mail
+ ```
- You should see output like this:
+ You should see output like this:
- ```
- "/home/incoming/Maildir": 1 message 1 unread
- >U 1 root@gitlab.example.com 59/2842 Re: Some issue
- ```
+ ```
+ "/home/incoming/Maildir": 1 message 1 unread
+ >U 1 root@gitlab.example.com 59/2842 Re: Some issue
+ ```
- Quit the mail app:
+ Quit the mail app:
- ```sh
- q
- ```
+ ```sh
+ q
+ ```
- 1. Log out of the `incoming` account and go back to being `root`:
+ 1. Log out of the `incoming` account and go back to being `root`:
- ```sh
- logout
- ```
+ ```sh
+ logout
+ ```
1. Test IMAP under the new setup:
- 1. Connect to the IMAP server:
+ 1. Connect to the IMAP server:
- ```sh
- telnet gitlab.example.com 143
- ```
+ ```sh
+ telnet gitlab.example.com 143
+ ```
- You should see a prompt like this:
+ You should see a prompt like this:
- ```sh
- Trying 123.123.123.123...
- Connected to mail.example.gitlab.com.
- Escape character is '^]'.
- - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information.
- ```
+ ```sh
+ Trying 123.123.123.123...
+ Connected to mail.example.gitlab.com.
+ Escape character is '^]'.
+ - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information.
+ ```
- 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt:
+ 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt:
- ```
- a login incoming PASSWORD
- ```
+ ```
+ a login incoming PASSWORD
+ ```
- Replace PASSWORD with the password you set on the `incoming` user earlier.
+ Replace PASSWORD with the password you set on the `incoming` user earlier.
- You should see output like this:
+ You should see output like this:
- ```
- a OK LOGIN Ok.
- ```
+ ```
+ a OK LOGIN Ok.
+ ```
- 1. Disconnect from the IMAP server:
+ 1. Disconnect from the IMAP server:
- ```sh
- a logout
- ```
+ ```sh
+ a logout
+ ```
## Done!
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index 4aafc06cfdc..3de860f9240 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -68,18 +68,18 @@ NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS
1. Edit `gitlab.yml` and add the storage paths:
- ```yaml
- repositories:
- # Paths where repositories can be stored. Give the canonicalized absolute pathname.
- # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
- storages: # You must have at least a 'default' storage path.
- default:
- path: /home/git/repositories
- nfs:
- path: /mnt/nfs/repositories
- cephfs:
- path: /mnt/cephfs/repositories
- ```
+ ```yaml
+ repositories:
+ # Paths where repositories can be stored. Give the canonicalized absolute pathname.
+ # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!!
+ storages: # You must have at least a 'default' storage path.
+ default:
+ path: /home/git/repositories
+ nfs:
+ path: /mnt/nfs/repositories
+ cephfs:
+ path: /mnt/cephfs/repositories
+ ```
1. [Restart GitLab][restart-gitlab] for the changes to take effect.
@@ -97,16 +97,16 @@ working, you can remove the `repos_path` line.
1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the
default one:
- ```ruby
- git_data_dirs({
- "default" => { "path" => "/var/opt/gitlab/git-data" },
- "nfs" => { "path" => "/mnt/nfs/git-data" },
- "cephfs" => { "path" => "/mnt/cephfs/git-data" }
- })
- ```
+ ```ruby
+ git_data_dirs({
+ "default" => { "path" => "/var/opt/gitlab/git-data" },
+ "nfs" => { "path" => "/mnt/nfs/git-data" },
+ "cephfs" => { "path" => "/mnt/cephfs/git-data" }
+ })
+ ```
- Note that Omnibus stores the repositories in a `repositories` subdirectory
- of the `git-data` directory.
+ Note that Omnibus stores the repositories in a `repositories` subdirectory
+ of the `git-data` directory.
## Choose where new project repositories will be stored
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index f4cb89c84a4..9dea6074a3f 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -80,25 +80,20 @@ by another folder with the next 2 characters. They are both stored in a special
### Hashed object pools
-CAUTION: **Beta:**
-Hashed objects pools are considered beta, and are not ready for production use.
-Follow [gitaly#1548](https://gitlab.com/gitlab-org/gitaly/issues/1548) for
-updates.
+> [Introduced](https://gitlab.com/gitlab-org/gitaly/issues/1606) in GitLab 12.1.
-For deduplication of public forks and their parent repository, objects are pooled
-in an object pool. These object pools are a third repository where shared objects
-are stored.
+Forks of public projects are deduplicated by creating a third repository, the object pool, containing the objects from the source project. Using `objects/info/alternates`, the source project and forks use the object pool for shared objects. Objects are moved from the source project to the object pool when housekeeping is run on the source project.
```ruby
# object pool paths
"@pools/#{hash[0..1]}/#{hash[2..3]}/#{hash}.git"
```
-The object pool feature is behind the `object_pools` feature flag, and can be
-enabled for individual projects by executing
-`Feature.enable(:object_pools, Project.find(<id>))`. Note that the project has to
-be on hashed storage, should not be a fork itself, and hashed storage should be
-enabled for all new projects.
+Object pools can be disabled using the `object_pools` feature flag, and can be
+disabled for individual projects by executing
+`Feature.disable(:object_pools, Project.find(<id>))`. Disabling object pools
+will not change existing deduplicated forks, but will prevent new forks from
+being deduplicated.
DANGER: **Danger:**
Do not run `git prune` or `git gc` in pool repositories! This can
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 8f7280d5128..098d946a9fa 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -10,43 +10,43 @@ an SMTP server, but you're not seeing mail delivered. Here's how to check the se
1. Run a Rails console:
- ```sh
- sudo gitlab-rails console production
- ```
+ ```sh
+ sudo gitlab-rails console production
+ ```
- or for source installs:
+ or for source installs:
- ```sh
- bundle exec rails console production
- ```
+ ```sh
+ bundle exec rails console production
+ ```
1. Look at the ActionMailer `delivery_method` to make sure it matches what you
intended. If you configured SMTP, it should say `:smtp`. If you're using
Sendmail, it should say `:sendmail`:
- ```ruby
- irb(main):001:0> ActionMailer::Base.delivery_method
- => :smtp
- ```
+ ```ruby
+ irb(main):001:0> ActionMailer::Base.delivery_method
+ => :smtp
+ ```
1. If you're using SMTP, check the mail settings:
- ```ruby
- irb(main):002:0> ActionMailer::Base.smtp_settings
- => {:address=>"localhost", :port=>25, :domain=>"localhost.localdomain", :user_name=>nil, :password=>nil, :authentication=>nil, :enable_starttls_auto=>true}```
- ```
+ ```ruby
+ irb(main):002:0> ActionMailer::Base.smtp_settings
+ => {:address=>"localhost", :port=>25, :domain=>"localhost.localdomain", :user_name=>nil, :password=>nil, :authentication=>nil, :enable_starttls_auto=>true}```
+ ```
- In the example above, the SMTP server is configured for the local machine. If this is intended, you may need to check your local mail
- logs (e.g. `/var/log/mail.log`) for more details.
+ In the example above, the SMTP server is configured for the local machine. If this is intended, you may need to check your local mail
+ logs (e.g. `/var/log/mail.log`) for more details.
-1. Send a test message via the console.
+1. Send a test message via the console.
- ```ruby
- irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
- ```
+ ```ruby
+ irb(main):003:0> Notify.test_email('youremail@email.com', 'Hello World', 'This is a test message').deliver_now
+ ```
- If you do not receive an e-mail and/or see an error message, then check
- your mail server settings.
+ If you do not receive an e-mail and/or see an error message, then check
+ your mail server settings.
## Advanced Issues
@@ -103,37 +103,37 @@ downtime. Otherwise skip to the next section.
1. Run `sudo gdb -p <PID>` to attach to the unicorn process.
1. In the gdb window, type:
- ```
- call (void) rb_backtrace()
- ```
+ ```
+ call (void) rb_backtrace()
+ ```
1. This forces the process to generate a Ruby backtrace. Check
`/var/log/gitlab/unicorn/unicorn_stderr.log` for the backtace. For example, you may see:
- ```ruby
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
- from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
- ```
+ ```ruby
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `block in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:33:in `loop'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:36:in `block (2 levels) in start'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:44:in `sample'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each_with_object'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:68:in `each'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `block in sample_objects'
+ from /opt/gitlab/embedded/service/gitlab-rails/lib/gitlab/metrics/sampler.rb:69:in `name'
+ ```
1. To see the current threads, run:
- ```
- thread apply all bt
- ```
+ ```
+ thread apply all bt
+ ```
1. Once you're done debugging with `gdb`, be sure to detach from the process and exit:
- ```
- detach
- exit
- ```
+ ```
+ detach
+ exit
+ ```
Note that if the unicorn process terminates before you are able to run these
commands, gdb will report an error. To buy more time, you can always raise the
@@ -162,21 +162,21 @@ separate Rails process to debug the issue:
1. Create a Personal Access Token for your user (Profile Settings -> Access Tokens).
1. Bring up the GitLab Rails console. For omnibus users, run:
- ```
- sudo gitlab-rails console
- ```
+ ```
+ sudo gitlab-rails console
+ ```
1. At the Rails console, run:
- ```ruby
- [1] pry(main)> app.get '<URL FROM STEP 2>/?private_token=<TOKEN FROM STEP 3>'
- ```
+ ```ruby
+ [1] pry(main)> app.get '<URL FROM STEP 2>/?private_token=<TOKEN FROM STEP 3>'
+ ```
- For example:
+ For example:
- ```ruby
- [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
- ```
+ ```ruby
+ [1] pry(main)> app.get 'https://gitlab.com/gitlab-org/gitlab-ce/issues/1?private_token=123456'
+ ```
1. In a new window, run `top`. It should show this ruby process using 100% CPU. Write down the PID.
1. Follow step 2 from the previous section on using gdb.
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index c6103a28204..c6529812ec3 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -23,10 +23,10 @@ _The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
1. To change the storage path for example to `/mnt/storage/uploads`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
- ```ruby
- gitlab_rails['uploads_storage_path'] = "/mnt/storage/"
- gitlab_rails['uploads_base_dir'] = "uploads"
- ```
+ ```ruby
+ gitlab_rails['uploads_storage_path'] = "/mnt/storage/"
+ gitlab_rails['uploads_base_dir'] = "uploads"
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
@@ -40,11 +40,11 @@ _The uploads are stored by default in
1. To change the storage path for example to `/mnt/storage/uploads`, edit
`/home/git/gitlab/config/gitlab.yml` and add or amend the following lines:
- ```yaml
- uploads:
- storage_path: /mnt/storage
- base_dir: uploads
- ```
+ ```yaml
+ uploads:
+ storage_path: /mnt/storage
+ base_dir: uploads
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
@@ -97,27 +97,27 @@ _The uploads are stored by default in
1. Edit `/etc/gitlab/gitlab.rb` and add the following lines by replacing with
the values you want:
- ```ruby
- gitlab_rails['uploads_object_store_enabled'] = true
- gitlab_rails['uploads_object_store_remote_directory'] = "uploads"
- gitlab_rails['uploads_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
- 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
- }
- ```
-
- >**Note:**
- If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
-
- ```ruby
- gitlab_rails['uploads_object_store_connection'] = {
- 'provider' => 'AWS',
- 'region' => 'eu-central-1',
- 'use_iam_profile' => true
- }
- ```
+ ```ruby
+ gitlab_rails['uploads_object_store_enabled'] = true
+ gitlab_rails['uploads_object_store_remote_directory'] = "uploads"
+ gitlab_rails['uploads_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'aws_access_key_id' => 'AWS_ACCESS_KEY_ID',
+ 'aws_secret_access_key' => 'AWS_SECRET_ACCESS_KEY'
+ }
+ ```
+
+ >**Note:**
+ >If you are using AWS IAM profiles, be sure to omit the AWS access key and secret access key/value pairs.
+
+ ```ruby
+ gitlab_rails['uploads_object_store_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'eu-central-1',
+ 'use_iam_profile' => true
+ }
+ ```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
@@ -132,17 +132,17 @@ _The uploads are stored by default in
1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following
lines:
- ```yaml
- uploads:
- object_store:
- enabled: true
- remote_directory: "uploads" # The bucket name
- connection:
- provider: AWS # Only AWS supported at the moment
- aws_access_key_id: AWS_ACESS_KEY_ID
- aws_secret_access_key: AWS_SECRET_ACCESS_KEY
- region: eu-central-1
- ```
+ ```yaml
+ uploads:
+ object_store:
+ enabled: true
+ remote_directory: "uploads" # The bucket name
+ connection:
+ provider: AWS # Only AWS supported at the moment
+ aws_access_key_id: AWS_ACESS_KEY_ID
+ aws_secret_access_key: AWS_SECRET_ACCESS_KEY
+ region: eu-central-1
+ ```
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
diff --git a/doc/api/README.md b/doc/api/README.md
index 6284bc82e27..b2bb55f2fcf 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -154,9 +154,10 @@ the `/Users` endpoint. The base URL is: `/api/scim/v2/groups/:group_path/Users/`
## Road to GraphQL
-Going forward, we will start on moving to
-[GraphQL](graphql/index.md) and deprecate the use of
-controller-specific endpoints. GraphQL has a number of benefits:
+[GraphQL](graphql/index.md) is available in GitLab, which will
+allow deprecation of controller-specific endpoints.
+
+GraphQL has a number of benefits:
1. We avoid having to maintain two different APIs.
1. Callers of the API can request only what they need.
@@ -311,9 +312,9 @@ By default, impersonation is enabled. To disable impersonation:
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- gitlab_rails['impersonation_enabled'] = false
- ```
+ ```ruby
+ gitlab_rails['impersonation_enabled'] = false
+ ```
1. Save the file and [reconfigure](../administration/restart_gitlab.md#omnibus-gitlab-reconfigure)
GitLab for the changes to take effect.
@@ -326,10 +327,10 @@ To re-enable impersonation, remove this configuration and reconfigure GitLab.
1. Edit `config/gitlab.yml`:
- ```yaml
- gitlab:
- impersonation_enabled: false
- ```
+ ```yaml
+ gitlab:
+ impersonation_enabled: false
+ ```
1. Save the file and [restart](../administration/restart_gitlab.md#installations-from-source)
GitLab for the changes to take effect.
diff --git a/doc/api/graphql/index.md b/doc/api/graphql/index.md
index 3bf96e279b7..4e4b9418e47 100644
--- a/doc/api/graphql/index.md
+++ b/doc/api/graphql/index.md
@@ -1,6 +1,8 @@
-# GraphQL API (Alpha)
+# GraphQL API
-> [Introduced][ce-19008] in GitLab 11.0.
+> - [Introduced][ce-19008] in GitLab 11.0 (enabled by feature flag `graphql`).
+> - [Always enabled](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30444)
+ in GitLab 12.1.
[GraphQL](https://graphql.org/) is a query language for APIs that
allows clients to request exactly the data they need, making it
@@ -23,24 +25,12 @@ programmatically with GitLab. To achieve this, it needs full coverage - anything
possible in the REST API should also be possible in the GraphQL API.
To help us meet this vision, the frontend should use GraphQL in preference to
-the REST API for new features, although the alpha status of GraphQL may prevent
-this from being a possibility at times.
+the REST API for new features.
There are no plans to deprecate the REST API. To reduce the technical burden of
supporting two APIs in parallel, they should share implementations as much as
possible.
-## Enabling the GraphQL feature
-
-The GraphQL API itself is currently in Alpha, and therefore hidden behind a
-feature flag. You can enable the feature using the [features api][features-api] on a self-hosted instance.
-
-For example:
-
-```shell
-curl --data "value=100" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/graphql
-```
-
## Available queries
A first iteration of a GraphQL API includes the following queries
diff --git a/doc/api/lint.md b/doc/api/lint.md
index 71c09d35b8c..b9b49f3df27 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -22,30 +22,30 @@ Example responses:
- Valid content:
- ```json
- {
- "status": "valid",
- "errors": []
- }
- ```
+ ```json
+ {
+ "status": "valid",
+ "errors": []
+ }
+ ```
- Invalid content:
- ```json
- {
- "status": "invalid",
- "errors": [
- "variables config should be a hash of key value pairs"
- ]
- }
- ```
+ ```json
+ {
+ "status": "invalid",
+ "errors": [
+ "variables config should be a hash of key value pairs"
+ ]
+ }
+ ```
- Without the content attribute:
- ```json
- {
- "error": "content is missing"
- }
- ```
+ ```json
+ {
+ "error": "content is missing"
+ }
+ ```
[ce-5953]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5953
diff --git a/doc/api/projects.md b/doc/api/projects.md
index da05b090699..781192fb92e 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -708,11 +708,17 @@ POST /projects
| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) |
| `default_branch` | string | no | `master` by default |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -721,13 +727,19 @@ POST /projects
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge requests by default |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
@@ -753,11 +765,17 @@ POST /projects/user/:user_id
| `path` | string | no | Custom repository name for new project. By default generated based on name |
| `namespace_id` | integer | no | Namespace for the new project (defaults to the current user's namespace) |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -766,13 +784,19 @@ POST /projects/user/:user_id
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
| `printing_merge_request_link_enabled` | boolean | no | Show link to create/view merge request when pushing from the command line |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge requests by default |
| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
@@ -798,11 +822,17 @@ PUT /projects/:id
| `path` | string | no | Custom repository name for the project. By default generated based on name |
| `default_branch` | string | no | `master` by default |
| `description` | string | no | Short project description |
-| `issues_enabled` | boolean | no | Enable issues for this project |
-| `merge_requests_enabled` | boolean | no | Enable merge requests for this project |
-| `jobs_enabled` | boolean | no | Enable jobs for this project |
-| `wiki_enabled` | boolean | no | Enable wiki for this project |
-| `snippets_enabled` | boolean | no | Enable snippets for this project |
+| `issues_enabled` | boolean | no | (deprecated) Enable issues for this project. Use `issues_access_level` instead |
+| `merge_requests_enabled` | boolean | no | (deprecated) Enable merge requests for this project. Use `merge_requests_access_level` instead |
+| `jobs_enabled` | boolean | no | (deprecated) Enable jobs for this project. Use `builds_access_level` instead |
+| `wiki_enabled` | boolean | no | (deprecated) Enable wiki for this project. Use `wiki_access_level` instead |
+| `snippets_enabled` | boolean | no | (deprecated) Enable snippets for this project. Use `snippets_access_level` instead |
+| `issues_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `repository_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `merge_requests_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `builds_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `wiki_access_level` | string | no | One of `disabled`, `private` or `enabled` |
+| `snippets_access_level` | string | no | One of `disabled`, `private` or `enabled` |
| `resolve_outdated_diff_discussions` | boolean | no | Automatically resolve merge request diffs discussions on lines changed with a push |
| `container_registry_enabled` | boolean | no | Enable container registry for this project |
| `shared_runners_enabled` | boolean | no | Enable shared runners for this project |
@@ -811,13 +841,19 @@ PUT /projects/:id
| `public_builds` | boolean | no | If `true`, jobs can be viewed by non-project-members |
| `only_allow_merge_if_pipeline_succeeds` | boolean | no | Set whether merge requests can only be merged with successful jobs |
| `only_allow_merge_if_all_discussions_are_resolved` | boolean | no | Set whether merge requests can only be merged when all the discussions are resolved |
-| `merge_method` | string | no | Set the merge method used |
+| `merge_method` | string | no | Set the [merge method](#project-merge-method) used |
| `lfs_enabled` | boolean | no | Enable LFS |
| `request_access_enabled` | boolean | no | Allow users to request member access |
| `tag_list` | array | no | The list of tags for a project; put array of tags, that should be finally assigned to a project |
| `avatar` | mixed | no | Image file for avatar of the project |
+| `build_git_strategy` | string | no | The Git strategy. Defaults to `fetch` |
+| `build_timeout` | integer | no | The maximum amount of time in minutes that a job is able run (in seconds) |
+| `auto_cancel_pending_pipelines` | string | no | Auto-cancel pending pipelines (Note: this is not a boolean, but enabled/disabled |
+| `build_coverage_regex` | string | no | Test coverage parsing |
| `ci_config_path` | string | no | The path to CI config file |
| `ci_default_git_depth` | integer | no | Default number of revisions for [shallow cloning](../user/project/pipelines/settings.md#git-shallow-clone) |
+| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for this project |
+| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge request by default |
| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
@@ -1631,6 +1667,7 @@ GET /projects/:id/push_rule
"id": 1,
"project_id": 3,
"commit_message_regex": "Fixes \d+\..*",
+ "commit_message_negative_regex": "ssh\:\/\/",
"branch_name_regex": "",
"deny_delete_tag": false,
"created_at": "2012-10-12T17:04:47Z",
@@ -1663,18 +1700,19 @@ Adds a push rule to a specified project.
POST /projects/:id/push_rule
```
-| Attribute | Type | Required | Description |
-| -------------------------------------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
-| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
-| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
-| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
-| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
-| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
-| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
-| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
-| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
-| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| Attribute | Type | Required | Description |
+| --------------------------------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
+| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
+| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
+| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
+| `commit_message_negative_regex` **(STARTER)** | string | no | No commit message is allowed to match this, e.g. `ssh\:\/\/` |
+| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
+| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
+| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
+| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
+| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
### Edit project push rule
@@ -1684,18 +1722,19 @@ Edits a push rule for a specified project.
PUT /projects/:id/push_rule
```
-| Attribute | Type | Required | Description |
-| -------------------------------------- | -------------- | -------- | ----------- |
-| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
-| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
-| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
-| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
-| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
-| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
-| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
-| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
-| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
-| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| Attribute | Type | Required | Description |
+| --------------------------------------------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | The ID of the project or NAMESPACE/PROJECT_NAME |
+| `deny_delete_tag` **(STARTER)** | boolean | no | Deny deleting a tag |
+| `member_check` **(STARTER)** | boolean | no | Restrict commits by author (email) to existing GitLab users |
+| `prevent_secrets` **(STARTER)** | boolean | no | GitLab will reject any files that are likely to contain secrets |
+| `commit_message_regex` **(STARTER)** | string | no | All commit messages must match this, e.g. `Fixed \d+\..*` |
+| `commit_message_negative_regex` **(STARTER)** | string | no | No commit message is allowed to match this, e.g. `ssh\:\/\/` |
+| `branch_name_regex` **(STARTER)** | string | no | All branch names must match this, e.g. `(feature|hotfix)\/*` |
+| `author_email_regex` **(STARTER)** | string | no | All commit author emails must match this, e.g. `@my-company.com$` |
+| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
+| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
+| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
### Delete project push rule
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index 681dc72c934..ffae5c17310 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -121,9 +121,9 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `sha` (optional) - The commit SHA to download. A tag, branch reference, or SHA can be used. This defaults to the tip of the default branch if not specified. For example:
- ```sh
- curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/projects/<project_id>/repository/archive?sha=<commit_sha>
- ```
+```sh
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.com/api/v4/projects/<project_id>/repository/archive?sha=<commit_sha>
+```
## Compare branches, tags or commits
diff --git a/doc/api/runners.md b/doc/api/runners.md
index 1318b9ca828..e6962d17a98 100644
--- a/doc/api/runners.md
+++ b/doc/api/runners.md
@@ -20,10 +20,10 @@ Here's an example of how the two tokens are used in Runner registration:
1. You use that authentication token and add it to the
[Runner's configuration file](https://docs.gitlab.com/runner/commands/#configuration-file):
- ```toml
- [[runners]]
- token = "<authentication_token>"
- ```
+ ```toml
+ [[runners]]
+ token = "<authentication_token>"
+ ```
GitLab and Runner are then connected.
diff --git a/doc/api/users.md b/doc/api/users.md
index 4e427766750..213d1865aca 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -164,6 +164,7 @@ the `group_saml` provider option:
...
}
]
+```
You can lookup users by external UID and provider:
@@ -592,6 +593,30 @@ Example responses
}
```
+## User counts
+
+Get the counts (same as in top right menu) of the currently signed in user.
+
+| Attribute | Type | Description |
+| --------- | ---- | ----------- |
+| `merge_requests` | number | Merge requests that are active and assigned to current user. |
+
+```
+GET /user_counts
+```
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/user_counts"
+```
+
+Example response:
+
+```json
+{
+ "merge_requests": 4
+}
+```
+
## List user projects
Please refer to the [List of user projects](projects.md#list-user-projects).
diff --git a/doc/api/vulnerabilities.md b/doc/api/vulnerabilities.md
index cc6e5d3960b..eaa4c13de55 100644
--- a/doc/api/vulnerabilities.md
+++ b/doc/api/vulnerabilities.md
@@ -30,6 +30,7 @@ GET /projects/:id/vulnerabilities?scope=all
GET /projects/:id/vulnerabilities?scope=dismissed
GET /projects/:id/vulnerabilities?severity=high
GET /projects/:id/vulnerabilities?confidence=unknown,experimental
+GET /projects/:id/vulnerabilities?pipeline_id=42
```
| Attribute | Type | Required | Description |
@@ -39,6 +40,7 @@ GET /projects/:id/vulnerabilities?confidence=unknown,experimental
| `scope` | string | no | Returns vulnerabilities for the given scope: `all` or `dismissed`. Defaults to `dismissed` |
| `severity` | string array | no | Returns vulnerabilities belonging to specified severity level: `undefined`, `info`, `unknown`, `low`, `medium`, `high`, or `critical`. Defaults to all' |
| `confidence` | string array | no | Returns vulnerabilities belonging to specified confidence level: `undefined`, `ignore`, `unknown`, `experimental`, `low`, `medium`, `high`, or `confirmed`. Defaults to all |
+| `pipeline_id` | integer/string | no | Returns vulnerabilities belonging to specified pipeline. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/vulnerabilities
diff --git a/doc/ci/git_submodules.md b/doc/ci/git_submodules.md
index 551044dd76f..1354a26d6e2 100644
--- a/doc/ci/git_submodules.md
+++ b/doc/ci/git_submodules.md
@@ -79,14 +79,14 @@ correctly with your CI jobs:
1. If you are using an older version of `gitlab-runner`, then use
`git submodule sync/update` in `before_script`:
- ```yaml
- before_script:
- - git submodule sync --recursive
- - git submodule update --init --recursive
- ```
-
- `--recursive` should be used in either both or none (`sync/update`) depending on
- whether you have recursive submodules.
+ ```yaml
+ before_script:
+ - git submodule sync --recursive
+ - git submodule update --init --recursive
+ ```
+
+ `--recursive` should be used in either both or none (`sync/update`) depending on
+ whether you have recursive submodules.
The rationale to set the `sync` and `update` in `before_script` is because of
the way Git submodules work. On a fresh Runner workspace, Git will set the
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 8319603fea2..b645a72567c 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -484,9 +484,11 @@ When making a request to an HTTP Endpoint (think `/users/sign_in`) the request w
Below we describe the different pathing that HTTP vs. SSH Git requests will take. There is some overlap with the Web Request Cycle but also some differences.
### Web Request (80/443)
+
TODO
### SSH Request (22)
+
TODO
## System Layout
@@ -505,7 +507,9 @@ To summarize here's the [directory structure of the `git` user home directory](.
### Processes
- ps aux | grep '^git'
+```sh
+ps aux | grep '^git'
+```
GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or Nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process).
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 418e58b22d5..cbdc0a3a174 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -18,7 +18,7 @@ In addition to this page, the following resources to help craft and contribute d
## Source files and rendered web locations
-Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance.
+Documentation for GitLab Community Edition (CE) and Enterprise Edition (EE), along with GitLab Runner and Omnibus, is published to [docs.gitlab.com](https://docs.gitlab.com). The documentation for CE and EE is also published within the application at `/help` on the domain of the GitLab instance, though there are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this practice and instead link out from the GitLab application to docs.gitlab.com URLs.
At `/help`, only content for your current edition and version is included, whereas multiple versions' content is available at docs.gitlab.com.
@@ -274,8 +274,11 @@ Follow this [method for cherry-picking from CE to EE](../automatic_ce_ee_merge.m
## GitLab `/help`
-Every GitLab instance includes the documentation, which is available from `/help`
-(`http://my-instance.com/help`), e.g., <https://gitlab.com/help>.
+Every GitLab instance includes the documentation, which is available at `/help`
+(`https://gitlab.example.com/help`). For example, <https://gitlab.com/help>.
+
+There are [plans](https://gitlab.com/groups/gitlab-org/-/epics/693) to end this
+practice and instead link out from the GitLab application to docs.gitlab.com URLs.
The documentation available online on docs.gitlab.com is continuously
deployed every hour from the `master` branch of CE, EE, Omnibus, and Runner. Therefore,
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index dc17b59a9a0..7131b717353 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -182,52 +182,52 @@ There are a few gotchas with it:
pattern](https://en.wikipedia.org/wiki/Template_method_pattern).
For example, given this base:
- ```ruby
- class Base
- def execute
- return unless enabled?
+ ```ruby
+ class Base
+ def execute
+ return unless enabled?
- # ...
- # ...
- end
+ # ...
+ # ...
end
- ```
+ end
+ ```
- Instead of just overriding `Base#execute`, we should update it and extract
- the behaviour into another method:
+ Instead of just overriding `Base#execute`, we should update it and extract
+ the behaviour into another method:
- ```ruby
- class Base
- def execute
- return unless enabled?
+ ```ruby
+ class Base
+ def execute
+ return unless enabled?
- do_something
- end
+ do_something
+ end
- private
+ private
- def do_something
- # ...
- # ...
- end
+ def do_something
+ # ...
+ # ...
end
- ```
+ end
+ ```
- Then we're free to override that `do_something` without worrying about the
- guards:
+ Then we're free to override that `do_something` without worrying about the
+ guards:
- ```ruby
- module EE::Base
- extend ::Gitlab::Utils::Override
+ ```ruby
+ module EE::Base
+ extend ::Gitlab::Utils::Override
- override :do_something
- def do_something
- # Follow the above pattern to call super and extend it
- end
+ override :do_something
+ def do_something
+ # Follow the above pattern to call super and extend it
end
- ```
+ end
+ ```
- This would require updating CE first, or make sure this is back ported to CE.
+ This would require updating CE first, or make sure this is back ported to CE.
When prepending, place them in the `ee/` specific sub-directory, and
wrap class or module in `module EE` to avoid naming conflicts.
@@ -1038,8 +1038,8 @@ to avoid conflicts during CE to EE merge.
Until the work completed to merge the ce and ee codebases, which is tracked on [epic &802](https://gitlab.com/groups/gitlab-org/-/epics/802), there exists times in which some changes for EE require specific changes to the CE
code base. Examples of backports include the following:
-* Features intended or originally built for EE that are later decided to move to CE
-* Sometimes some code in CE may impact the EE feature
+- Features intended or originally built for EE that are later decided to move to CE
+- Sometimes some code in CE may impact the EE feature
Here is a workflow to make sure those changes end up backported safely into CE too.
diff --git a/doc/development/emails.md b/doc/development/emails.md
index 1fd4139c91e..e6af075a282 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -26,51 +26,51 @@ See the [Rails guides] for more info.
feature and fill in the details for your specific IMAP server and email
account:
- Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com
-
- ```yaml
- incoming_email:
- enabled: true
-
- # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
- # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
- address: "gitlab-incoming+%{key}@gmail.com"
-
- # Email account username
- # With third party providers, this is usually the full email address.
- # With self-hosted email servers, this is usually the user part of the email address.
- user: "gitlab-incoming@gmail.com"
- # Email account password
- password: "[REDACTED]"
-
- # IMAP server host
- host: "imap.gmail.com"
- # IMAP server port
- port: 993
- # Whether the IMAP server uses SSL
- ssl: true
- # Whether the IMAP server uses StartTLS
- start_tls: false
-
- # The mailbox where incoming mail will end up. Usually "inbox".
- mailbox: "inbox"
- # The IDLE command timeout.
- idle_timeout: 60
- ```
-
- As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
+ Configuration for Gmail / Google Apps, assumes mailbox `gitlab-incoming@gmail.com`:
+
+ ```yaml
+ incoming_email:
+ enabled: true
+
+ # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to.
+ # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`).
+ address: "gitlab-incoming+%{key}@gmail.com"
+
+ # Email account username
+ # With third party providers, this is usually the full email address.
+ # With self-hosted email servers, this is usually the user part of the email address.
+ user: "gitlab-incoming@gmail.com"
+ # Email account password
+ password: "[REDACTED]"
+
+ # IMAP server host
+ host: "imap.gmail.com"
+ # IMAP server port
+ port: 993
+ # Whether the IMAP server uses SSL
+ ssl: true
+ # Whether the IMAP server uses StartTLS
+ start_tls: false
+
+ # The mailbox where incoming mail will end up. Usually "inbox".
+ mailbox: "inbox"
+ # The IDLE command timeout.
+ idle_timeout: 60
+ ```
+
+ As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`.
1. Run this command in the GitLab root directory to launch `mail_room`:
- ```sh
- bundle exec mail_room -q -c config/mail_room.yml
- ```
+ ```sh
+ bundle exec mail_room -q -c config/mail_room.yml
+ ```
1. Verify that everything is configured correctly:
- ```sh
- bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
- ```
+ ```sh
+ bundle exec rake gitlab:incoming_email:check RAILS_ENV=development
+ ```
1. Reply by email should now be working.
@@ -91,10 +91,10 @@ for the format of the email key:
Examples of valid email keys:
- - `gitlab-org-gitlab-ce-20-Author_Token12345678-issue` (create a new issue)
- - `gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request` (create a new merge request)
- - `1234567890abcdef1234567890abcdef-unsubscribe` (unsubscribe from a conversation)
- - `1234567890abcdef1234567890abcdef` (reply to a conversation)
+- `gitlab-org-gitlab-ce-20-Author_Token12345678-issue` (create a new issue)
+- `gitlab-org-gitlab-ce-20-Author_Token12345678-merge-request` (create a new merge request)
+- `1234567890abcdef1234567890abcdef-unsubscribe` (unsubscribe from a conversation)
+- `1234567890abcdef1234567890abcdef` (reply to a conversation)
Please note that the action `-issue-` is used in GitLab Premium as the handler for the Service Desk feature.
@@ -103,10 +103,10 @@ Please note that the action `-issue-` is used in GitLab Premium as the handler f
Although we continue to support the older legacy format, no new features should use a legacy format.
These are the only valid legacy formats for an email handler:
- - `path/to/project+namespace`
- - `path/to/project+namespace+action`
- - `namespace`
- - `namespace+action`
+- `path/to/project+namespace`
+- `path/to/project+namespace+action`
+- `namespace`
+- `namespace+action`
Please note that `path/to/project` is used in GitLab Premium as handler for the Service Desk feature.
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 1b9ebb50c29..13dda17bb7d 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -101,10 +101,10 @@ end
in a prepended module, which is very likely the case in EE. We could see
error like this:
- ```
- 1.1) Failure/Error: expect_any_instance_of(ApplicationSetting).to receive_messages(messages)
- Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported.
- ```
+ ```
+ 1.1) Failure/Error: expect_any_instance_of(ApplicationSetting).to receive_messages(messages)
+ Using `any_instance` to stub a method (elasticsearch_indexing) that has been defined on a prepended module (EE::ApplicationSetting) is not supported.
+ ```
### Alternative: `expect_next_instance_of`
diff --git a/doc/development/logging.md b/doc/development/logging.md
index d61441813b2..4f63c84fc0e 100644
--- a/doc/development/logging.md
+++ b/doc/development/logging.md
@@ -30,8 +30,8 @@ Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms)
These logs suffer from a number of problems:
1. They often lack timestamps or other contextual information (e.g. project ID, user)
-2. They may span multiple lines, which make them hard to find via Elasticsearch.
-3. They lack a common structure, which make them hard to parse by log
+1. They may span multiple lines, which make them hard to find via Elasticsearch.
+1. They lack a common structure, which make them hard to parse by log
forwarders, such as Logstash or Fluentd. This also makes them hard to
search.
@@ -67,46 +67,46 @@ importer progresses. Here's what to do:
make it easy for people to search pertinent logs in one place. For
example, `geo.log` contains all logs pertaining to GitLab Geo.
To create a new file:
- 1. Choose a filename (e.g. `importer_json.log`).
- 1. Create a new subclass of `Gitlab::JsonLogger`:
-
- ```ruby
- module Gitlab
- module Import
- class Logger < ::Gitlab::JsonLogger
- def self.file_name_noext
- 'importer'
- end
+ 1. Choose a filename (e.g. `importer_json.log`).
+ 1. Create a new subclass of `Gitlab::JsonLogger`:
+
+ ```ruby
+ module Gitlab
+ module Import
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'importer'
end
- end
- end
- ```
+ end
+ end
+ end
+ ```
- 1. In your class where you want to log, you might initialize the logger as an instance variable:
+ 1. In your class where you want to log, you might initialize the logger as an instance variable:
- ```ruby
- attr_accessor :logger
+ ```ruby
+ attr_accessor :logger
- def initialize
- @logger = Gitlab::Import::Logger.build
- end
- ```
+ def initialize
+ @logger = Gitlab::Import::Logger.build
+ end
+ ```
- Note that it's useful to memoize this because creating a new logger
- each time you log will open a file, adding unnecessary overhead.
+ Note that it's useful to memoize this because creating a new logger
+ each time you log will open a file, adding unnecessary overhead.
1. Now insert log messages into your code. When adding logs,
make sure to include all the context as key-value pairs:
- ```ruby
- # BAD
- logger.info("Unable to create project #{project.id}")
- ```
+ ```ruby
+ # BAD
+ logger.info("Unable to create project #{project.id}")
+ ```
- ```ruby
- # GOOD
- logger.info(message: "Unable to create project", project_id: project.id)
- ```
+ ```ruby
+ # GOOD
+ logger.info(message: "Unable to create project", project_id: project.id)
+ ```
1. Be sure to create a common base structure of your log messages. For example,
all messages might have `current_user_id` and `project_id` to make it easier
@@ -116,16 +116,16 @@ importer progresses. Here's what to do:
logs properly if you [mix integer and string
types](https://www.elastic.co/guide/en/elasticsearch/guide/current/mapping.html#_avoiding_type_gotchas):
- ```ruby
- # BAD
- logger.info(message: "Import error", error: 1)
- logger.info(message: "Import error", error: "I/O failure")
- ```
+ ```ruby
+ # BAD
+ logger.info(message: "Import error", error: 1)
+ logger.info(message: "Import error", error: "I/O failure")
+ ```
- ```ruby
- # GOOD
- logger.info(message: "Import error", error_code: 1, error: "I/O failure")
- ```
+ ```ruby
+ # GOOD
+ logger.info(message: "Import error", error_code: 1, error: "I/O failure")
+ ```
## Additional steps with new log files
diff --git a/doc/development/profiling.md b/doc/development/profiling.md
index 795523b82aa..e1d1d2e33fa 100644
--- a/doc/development/profiling.md
+++ b/doc/development/profiling.md
@@ -95,7 +95,9 @@ Sherlock is a custom profiling tool built into GitLab. Sherlock is _only_
available when running GitLab in development mode _and_ when setting the
environment variable `ENABLE_SHERLOCK` to a non empty value. For example:
- ENABLE_SHERLOCK=1 bundle exec rails s
+```sh
+ENABLE_SHERLOCK=1 bundle exec rails s
+```
Recorded transactions can be found by navigating to `/sherlock/transactions`.
@@ -106,7 +108,9 @@ Bullet adds quite a bit of logging noise it's disabled by default. To enable
Bullet, set the environment variable `ENABLE_BULLET` to a non-empty value before
starting GitLab. For example:
- ENABLE_BULLET=true bundle exec rails s
+```sh
+ENABLE_BULLET=true bundle exec rails s
+```
Bullet will log query problems to both the Rails log as well as the Chrome
console.
diff --git a/doc/development/prometheus_metrics.md b/doc/development/prometheus_metrics.md
index 0511e735843..576601372a3 100644
--- a/doc/development/prometheus_metrics.md
+++ b/doc/development/prometheus_metrics.md
@@ -33,12 +33,10 @@ For example: you might be interested in migrating all dependent data to a differ
class ImportCommonMetrics < ActiveRecord::Migration[4.2]
include Gitlab::Database::MigrationHelpers
- require Rails.root.join('db/importers/common_metrics_importer.rb')
-
DOWNTIME = false
def up
- Importers::CommonMetricsImporter.new.execute
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
def down
diff --git a/doc/development/routing.md b/doc/development/routing.md
index e9c0ad8d4e8..a25eb48b73c 100644
--- a/doc/development/routing.md
+++ b/doc/development/routing.md
@@ -7,11 +7,15 @@ support subgroups, GitLab project and group routes use the wildcard
character to match project and group routes. For example, we might have
a path such as:
- /gitlab-com/customer-success/north-america/west/customerA
+```
+/gitlab-com/customer-success/north-america/west/customerA
+```
However, paths can be ambiguous. Consider the following example:
- /gitlab-com/edit
+```
+/gitlab-com/edit
+```
It's ambiguous whether there is a subgroup named `edit` or whether
this is a special endpoint to edit the `gitlab-com` group.
@@ -25,8 +29,10 @@ number of [reserved names](../user/reserved_names.md).
We have a number of global routes. For example:
- /-/health
- /-/metrics
+```
+/-/health
+/-/metrics
+```
## Group routes
@@ -34,10 +40,12 @@ Every group route must be under the `/-/` scope.
Examples:
- gitlab-org/-/edit
- gitlab-org/-/activity
- gitlab-org/-/security/dashboard
- gitlab-org/serverless/-/activity
+```
+gitlab-org/-/edit
+gitlab-org/-/activity
+gitlab-org/-/security/dashboard
+gitlab-org/serverless/-/activity
+```
To achieve that, use the `scope '-'` method.
@@ -48,10 +56,12 @@ client or other software requires something different.
Examples:
- gitlab-org/gitlab-ce/-/activity
- gitlab-org/gitlab-ce/-/jobs/123
- gitlab-org/gitlab-ce/-/settings/repository
- gitlab-org/serverless/runtimes/-/settings/repository
+```
+gitlab-org/gitlab-ce/-/activity
+gitlab-org/gitlab-ce/-/jobs/123
+gitlab-org/gitlab-ce/-/settings/repository
+gitlab-org/serverless/runtimes/-/settings/repository
+```
Currently, only some project routes are placed under the `/-/` scope. However,
you can help us migrate more of them! To migrate project routes:
diff --git a/doc/development/sql.md b/doc/development/sql.md
index edeca7fb298..a256fd46c09 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -94,7 +94,9 @@ on the amount of data indexed).
To keep naming of these indexes consistent please use the following naming
pattern:
- index_TABLE_on_COLUMN_trigram
+```
+index_TABLE_on_COLUMN_trigram
+```
For example, a GIN/trigram index for `issues.title` would be called
`index_issues_on_title_trigram`.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e2d51f673b5..503ad784a77 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -316,7 +316,7 @@ If a project's repository contains a `Dockerfile`, Auto Build will use
If you are also using Auto Review Apps and Auto Deploy and choose to provide
your own `Dockerfile`, make sure you expose your application to port
`5000` as this is the port assumed by the
-[default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
+[default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app). Alternatively you can override the default values by [customizing the Auto Deploy helm chart](#custom-helm-chart)
#### Auto Build using Heroku buildpacks
@@ -452,7 +452,7 @@ be deleted.
Review apps are deployed using the
[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
-Helm. The app will be deployed into the [Kubernetes
+Helm, which can be [customized](#custom-helm-chart). The app will be deployed into the [Kubernetes
namespace](../../user/project/clusters/index.md#deployment-variables)
for the environment.
@@ -514,7 +514,7 @@ Auto Deploy doesn't include deployments to staging or canary by default, but the
enable them.
You can make use of [environment variables](#environment-variables) to automatically
-scale your pod replicas.
+scale your pod replicas and to apply custom arguments to the Auto DevOps `helm upgrade` commands. This is an easy way to [customize the Auto Deploy helm chart](#custom-helm-chart).
Apps are deployed using the
[auto-deploy-app](https://gitlab.com/gitlab-org/charts/auto-deploy-app) chart with
@@ -655,6 +655,9 @@ repo or by specifying a project variable:
- **Project variable** - Create a [project variable](../../ci/variables/README.md#gitlab-cicd-environment-variables)
`AUTO_DEVOPS_CHART` with the URL of a custom chart to use or create two project variables `AUTO_DEVOPS_CHART_REPOSITORY` with the URL of a custom chart repository and `AUTO_DEVOPS_CHART` with the path to the chart.
+You can also make use of the `HELM_UPGRADE_EXTRA_ARGS` environment variable to override the default values in the `values.yaml` file in the [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
+To apply your own `values.yaml` file to all Helm upgrade commands in Auto Deploy set `HELM_UPGRADE_EXTRA_ARGS` to `--values my-values.yaml`.
+
### Custom Helm chart per environment **(PREMIUM)**
You can specify the use of a custom Helm chart per environment by scoping the environment variable
@@ -761,7 +764,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
-| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. |
+| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](https://docs.gitlab.com/ee/topics/autodevops/index.html#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
TIP: **Tip:**
Set up the replica variables using a
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index f2156720af7..c6bc580fb8f 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -444,7 +444,7 @@ Clicking on the **Reply to comment** button will bring the reply area into focus
![Reply to comment feature](img/reply_to_comment.gif)
-Relying to a non-discussion comment will convert the non-discussion comment to a
+Replying to a non-discussion comment will convert the non-discussion comment to a
threaded discussion once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 01b6650bfab..765aa91b00f 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -121,7 +121,70 @@ GitLab supports a limited set of [CI variables](../../../ci/variables/README.htm
To specify a variable in a query, enclose it in curly braces with a leading percent. For example: `%{ci_environment_slug}`.
-### Setting up alerts for Prometheus metrics **(ULTIMATE)**
+### Defining Dashboards for Prometheus Metrics per Project
+
+All projects include a GitLab-defined system dashboard, which includes a few key metrics. Optionally, additional dashboards can also be defined by including configuration files in the project repository under `.gitlab/dashboards`. Configuration files nested under subdirectories will not be available in the UI. Each file should define the layout of the dashboard and the prometheus queries used to populate data. Dashboards can be selected from the dropdown in the UI.
+
+#### Relationship to Custom Metrics
+
+[Custom Metrics](#adding-additional-metrics-premium) are defined through the UI and, at this point, are unique from metrics defined in dashboard configuration files. Custom Metrics will appear on the system dashboard, as well as support alerting, whereas metrics defined in configuration files do not yet support alerts.
+
+#### Dashboard Configuration
+
+Dashboards have several components. A dashboard has many panel groups, which are comprised of panels, which support one or more metrics. The dashboard should be saved with the `.yml` extension.
+
+Sample YML Configuration
+```
+dashboard: 'Dashboard Title'
+priority: 2
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ metrics:
+ - id: metric_of_ages
+ query_range: 'http_requests_total'
+ label: "Metric of Ages"
+ unit: "count"
+```
+
+The above sample dashboard would display a single area chart. The following sections outline the details of expected properties.
+
+##### Dashboard Properties
+| Property | Type | Required? | Meaning |
+| ------ | ------ | ------ | ------ |
+| `dashboard` | string | required | Heading for the dashboard. Only one dashboard should be defined per file. |
+| `priority` | number | optional, default to definition order | Order to appear in dashboard dropdown, higher priority should be higher in the dropdown. Numbers do not need to be consecutive. |
+| `panel_groups` | array | required | The panel groups which should be on the dashboard. |
+
+##### Panel Group Properties
+| Property | Type | Required? | Meaning |
+| ------ | ------ | ------ | ------ |
+| `group` | string | required | Heading for the panel group. |
+| `priority` | number | optional, defaults to order in file | Order to appear on the dashboard, higher priority will be higher on the page. Numbers do not need to be consecutive. |
+| `panels` | array | required | The panels which should be in the panel group. |
+
+##### Panel Properties
+| Property | Type | Required? | Meaning |
+| ------ | ------ | ------ | ------- |
+| `type` | enum | optional, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. |
+| `title` | string | required | Heading for the panel. |
+| `y_label` | string | optional, but highly encouraged | Y-Axis label for the panel. |
+| `weight` | number | optional, defaults to order in file | Order to appear within the grouping, higher priority will be higher on the page. Numbers do not need to be consecutive. |
+| `metrics` | array | required | The metrics which should be displayed in the panel. |
+
+##### Metric Properties
+| Property | Type | Required? | Meaning |
+| ------ | ------ | ------ | ------ |
+| `id` | string | optional | Used for associating dashboard metrics with database records. Must be unique across dashboard configuration files. Required for [alerting](#setting-up-alerts-for-prometheus-metrics-ultimate) (support not yet enabled, see [relevant issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60319)). |
+| `unit` | string | required | Defines the unit of the query's return data. |
+| `label` | string | optional, but highly encouraged | Defines the legend-label for the query. Should be unique within the panel's metrics. |
+| `query` | string | required unless `query_range` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+| `query_range` | string | required unless `query` is defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+
+### Setting up alerts for Prometheus metrics **[ULTIMATE]**
#### Managed Prometheus instances
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index b2cfe10836f..481b1ce0337 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -11,11 +11,6 @@ project can have its own space to store NPM packages.
NOTE: **Note:**
Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported.
-
-NOTE: **Note:**
-As `@group/subgroup/project` is not a valid NPM package name, publishing a package
-within a subgroup is not supported yet.
-
## Enabling the NPM Registry
NOTE: **Note:**
@@ -36,12 +31,15 @@ get familiar with the package naming convention.
## Package naming convention
-**Only packages that have the same path as the project** are supported. For
- example:
+**Packages must be scoped in the root namespace of the project**. The package
+name may be anything but it is preferred that the project name be used unless
+it is not possible due to a naming collision. For example:
| Project | Package | Supported |
| ---------------------- | ----------------------- | --------- |
| `foo/bar` | `@foo/bar` | Yes |
+| `foo/bar/baz` | `@foo/baz` | Yes |
+| `foo/bar/buz` | `@foo/anything` | Yes |
| `gitlab-org/gitlab-ce` | `@gitlab-org/gitlab-ce` | Yes |
| `gitlab-org/gitlab-ce` | `@foo/bar` | No |
@@ -113,6 +111,9 @@ npm publish
You can then navigate to your project's **Packages** page and see the uploaded
packages or even delete them.
+If you attempt to publish a package with a name that already exists within
+a given scope, you will receive a `403 Forbidden!` error.
+
## Uploading a package with the same version twice
If you upload a package with a same name and version twice, GitLab will show
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 819515d7a4c..98bcc7cc09f 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -74,6 +74,13 @@ The following items will NOT be exported:
- CI variables
- Webhooks
- Any encrypted tokens
+- Merge Request Approvers
+- Push Rules
+- Awards
+
+NOTE: **Note:**
+For more details on the specific data persisted in a project export, see the
+[`import_export.yml`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/import_export/import_export.yml) file.
## Exporting a project and its data
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index da51c0f2c93..60a4d0f19de 100644
--- a/doc/workflow/timezone.md
+++ b/doc/workflow/timezone.md
@@ -2,13 +2,12 @@
The global time zone configuration parameter can be changed in `config/gitlab.yml`:
-```
+```text
# time_zone: 'UTC'
```
Uncomment and customize if you want to change the default time zone of the GitLab application.
-
## Viewing available timezones
To see all available time zones, run `bundle exec rake time:zones:all`.
@@ -26,14 +25,13 @@ To obtain a list of timezones, log in to your GitLab application server and run
To update, add the timezone that best applies to your location. For example:
-```
+```ruby
gitlab_rails['time_zone'] = 'America/New_York'
```
After adding the configuration parameter, reconfigure and restart your GitLab instance:
-```
+```sh
gitlab-ctl reconfigure
gitlab-ctl restart
```
-
diff --git a/jest.config.js b/jest.config.js
index 84481642250..986b8465eef 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -15,9 +15,18 @@ if (process.env.CI) {
]);
}
+let testMatch = ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'];
+
+// workaround for eslint-import-resolver-jest only resolving in test files
+// see https://github.com/JoinColony/eslint-import-resolver-jest#note
+const isESLint = module.parent.path.includes('/eslint-import-resolver-jest/');
+if (isESLint) {
+ testMatch = testMatch.map(path => path.replace('_spec.js', ''));
+}
+
// eslint-disable-next-line import/no-commonjs
module.exports = {
- testMatch: ['<rootDir>/spec/frontend/**/*_spec.js', '<rootDir>/ee/spec/frontend/**/*_spec.js'],
+ testMatch,
moduleFileExtensions: ['js', 'json', 'vue'],
moduleNameMapper: {
'^~(/.*)$': '<rootDir>/app/assets/javascripts$1',
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 42499c5b41e..7016a66593d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -166,6 +166,7 @@ module API
mount ::API::Templates
mount ::API::Todos
mount ::API::Triggers
+ mount ::API::UserCounts
mount ::API::Users
mount ::API::Variables
mount ::API::Version
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b9aa387ba61..765819e6bf1 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -201,6 +201,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
projects_relation.preload(:project_feature, :route)
.preload(:import_state, :tags)
+ .preload(:auto_devops)
.preload(namespace: [:route, :owner])
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -247,12 +248,20 @@ module API
expose :container_registry_enabled
# Expose old field names with the new permissions methods to keep API compatible
+ # TODO: remove in API v5, replaced by *_access_level
expose(:issues_enabled) { |project, options| project.feature_available?(:issues, options[:current_user]) }
expose(:merge_requests_enabled) { |project, options| project.feature_available?(:merge_requests, options[:current_user]) }
expose(:wiki_enabled) { |project, options| project.feature_available?(:wiki, options[:current_user]) }
expose(:jobs_enabled) { |project, options| project.feature_available?(:builds, options[:current_user]) }
expose(:snippets_enabled) { |project, options| project.feature_available?(:snippets, options[:current_user]) }
+ expose(:issues_access_level) { |project, options| project.project_feature.string_access_level(:issues) }
+ expose(:repository_access_level) { |project, options| project.project_feature.string_access_level(:repository) }
+ expose(:merge_requests_access_level) { |project, options| project.project_feature.string_access_level(:merge_requests) }
+ expose(:wiki_access_level) { |project, options| project.project_feature.string_access_level(:wiki) }
+ expose(:builds_access_level) { |project, options| project.project_feature.string_access_level(:builds) }
+ expose(:snippets_access_level) { |project, options| project.project_feature.string_access_level(:snippets) }
+
expose :shared_runners_enabled
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
@@ -267,6 +276,12 @@ module API
expose :runners_token, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :ci_default_git_depth
expose :public_builds, as: :public_jobs
+ expose :build_git_strategy, if: lambda { |project, options| options[:user_can_admin_project] } do |project, options|
+ project.build_allow_git_fetch ? 'fetch' : 'clone'
+ end
+ expose :build_timeout
+ expose :auto_cancel_pending_pipelines
+ expose :build_coverage_regex
expose :ci_config_path, if: -> (project, options) { Ability.allowed?(options[:current_user], :download_code, project) }
expose :shared_with_groups do |project, options|
SharedGroup.represent(project.project_group_links, options)
@@ -280,6 +295,10 @@ module API
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
expose :external_authorization_classification_label
+ expose :auto_devops_enabled?, as: :auto_devops_enabled
+ expose :auto_devops_deploy_strategy do |project, options|
+ project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
+ end
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
@@ -289,6 +308,7 @@ module API
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
.preload(:ci_cd_settings)
+ .preload(:auto_devops)
.preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
@@ -491,7 +511,7 @@ module API
end
end
- class ProjectEntity < Grape::Entity
+ class IssuableEntity < Grape::Entity
expose :id, :iid
expose(:project_id) { |entity| entity&.project.try(:id) }
expose :title, :description
@@ -544,7 +564,7 @@ module API
end
end
- class IssueBasic < ProjectEntity
+ class IssueBasic < IssuableEntity
expose :closed_at
expose :closed_by, using: Entities::UserBasic
@@ -650,14 +670,14 @@ module API
end
end
- class MergeRequestSimple < ProjectEntity
+ class MergeRequestSimple < IssuableEntity
expose :title
expose :web_url do |merge_request, options|
Gitlab::UrlBuilder.build(merge_request)
end
end
- class MergeRequestBasic < ProjectEntity
+ class MergeRequestBasic < IssuableEntity
expose :merged_by, using: Entities::UserBasic do |merge_request, _options|
merge_request.metrics&.merged_by
end
diff --git a/lib/api/helpers/graphql_helpers.rb b/lib/api/helpers/graphql_helpers.rb
index 94010ab1bc2..bd60470fbd6 100644
--- a/lib/api/helpers/graphql_helpers.rb
+++ b/lib/api/helpers/graphql_helpers.rb
@@ -7,8 +7,6 @@ module API
# should be in app/graphql/ or lib/gitlab/graphql/
module GraphqlHelpers
def conditionally_graphql!(fallback:, query:, context: {}, transform: nil)
- return fallback.call unless Feature.enabled?(:graphql)
-
result = GitlabSchema.execute(query, context: context)
if transform
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index c318f5b9127..9afe6c5b027 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -72,7 +72,7 @@ module API
result == 'PONG'
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}")
+ Rails.logger.warn("GitLab: An unexpected error occurred in pinging to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
false
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index f242f1fea0e..0e21a7a66fd 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -8,12 +8,26 @@ module API
params :optional_project_params_ce do
optional :description, type: String, desc: 'The description of the project'
+ optional :build_git_strategy, type: String, values: %w(fetch clone), desc: 'The Git strategy. Defaults to `fetch`'
+ optional :build_timeout, type: Integer, desc: 'Build timeout'
+ optional :auto_cancel_pending_pipelines, type: String, values: %w(disabled enabled), desc: 'Auto-cancel pending pipelines'
+ optional :build_coverage_regex, type: String, desc: 'Test coverage parsing'
optional :ci_config_path, type: String, desc: 'The path to CI config file. Defaults to `.gitlab-ci.yml`'
+
+ # TODO: remove in API v5, replaced by *_access_level
optional :issues_enabled, type: Boolean, desc: 'Flag indication if the issue tracker is enabled'
optional :merge_requests_enabled, type: Boolean, desc: 'Flag indication if merge requests are enabled'
optional :wiki_enabled, type: Boolean, desc: 'Flag indication if the wiki is enabled'
optional :jobs_enabled, type: Boolean, desc: 'Flag indication if jobs are enabled'
optional :snippets_enabled, type: Boolean, desc: 'Flag indication if snippets are enabled'
+
+ optional :issues_access_level, type: String, values: %w(disabled private enabled), desc: 'Issues access level. One of `disabled`, `private` or `enabled`'
+ optional :repository_access_level, type: String, values: %w(disabled private enabled), desc: 'Repository access level. One of `disabled`, `private` or `enabled`'
+ optional :merge_requests_access_level, type: String, values: %w(disabled private enabled), desc: 'Merge requests access level. One of `disabled`, `private` or `enabled`'
+ optional :wiki_access_level, type: String, values: %w(disabled private enabled), desc: 'Wiki access level. One of `disabled`, `private` or `enabled`'
+ optional :builds_access_level, type: String, values: %w(disabled private enabled), desc: 'Builds access level. One of `disabled`, `private` or `enabled`'
+ optional :snippets_access_level, type: String, values: %w(disabled private enabled), desc: 'Snippets access level. One of `disabled`, `private` or `enabled`'
+
optional :shared_runners_enabled, type: Boolean, desc: 'Flag indication if shared runners are enabled for that project'
optional :resolve_outdated_diff_discussions, type: Boolean, desc: 'Automatically resolve merge request diffs discussions on lines changed with a push'
optional :container_registry_enabled, type: Boolean, desc: 'Flag indication if the container registry is enabled for that project'
@@ -30,6 +44,8 @@ module API
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
+ optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled'
+ optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy'
end
params :optional_project_params_ee do
@@ -48,15 +64,20 @@ module API
def self.update_params_at_least_one_of
[
- :jobs_enabled,
- :resolve_outdated_diff_discussions,
+ :auto_devops_enabled,
+ :auto_devops_deploy_strategy,
+ :auto_cancel_pending_pipelines,
+ :build_coverage_regex,
+ :build_git_strategy,
+ :build_timeout,
+ :builds_access_level,
:ci_config_path,
:container_registry_enabled,
:default_branch,
:description,
- :issues_enabled,
+ :issues_access_level,
:lfs_enabled,
- :merge_requests_enabled,
+ :merge_requests_access_level,
:merge_method,
:name,
:only_allow_merge_if_all_discussions_are_resolved,
@@ -64,14 +85,24 @@ module API
:path,
:printing_merge_request_link_enabled,
:public_builds,
+ :repository_access_level,
:request_access_enabled,
+ :resolve_outdated_diff_discussions,
:shared_runners_enabled,
- :snippets_enabled,
+ :snippets_access_level,
:tag_list,
:visibility,
- :wiki_enabled,
+ :wiki_access_level,
:avatar,
- :external_authorization_classification_label
+ :external_authorization_classification_label,
+
+ # TODO: remove in API v5, replaced by *_access_level
+ :issues_enabled,
+ :jobs_enabled,
+ :merge_requests_enabled,
+ :wiki_enabled,
+ :jobs_enabled,
+ :snippets_enabled
]
end
end
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
index e7504051808..21d4928193e 100644
--- a/lib/api/import_github.rb
+++ b/lib/api/import_github.rb
@@ -28,7 +28,7 @@ module API
desc 'Import a GitHub project' do
detail 'This feature was introduced in GitLab 11.3.4.'
- success Entities::ProjectEntity
+ success ::ProjectEntity
end
params do
requires :personal_access_token, type: String, desc: 'GitHub personal access token'
diff --git a/lib/api/user_counts.rb b/lib/api/user_counts.rb
new file mode 100644
index 00000000000..8df4b381bbf
--- /dev/null
+++ b/lib/api/user_counts.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+module API
+ class UserCounts < Grape::API
+ resource :user_counts do
+ desc 'Return the user specific counts' do
+ detail 'Open MR Count'
+ end
+ get do
+ unauthorized! unless current_user
+
+ {
+ merge_requests: current_user.assigned_open_merge_requests_count
+ }
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_embeds_filter.rb b/lib/banzai/filter/inline_embeds_filter.rb
new file mode 100644
index 00000000000..97394fd8f82
--- /dev/null
+++ b/lib/banzai/filter/inline_embeds_filter.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that inserts a node for each occurence of
+ # a given link format. To transform references to DB
+ # resources in place, prefer to inherit from AbstractReferenceFilter.
+ class InlineEmbedsFilter < HTML::Pipeline::Filter
+ # Find every relevant link, create a new node based on
+ # the link, and insert this node after any html content
+ # surrounding the link.
+ def call
+ return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
+
+ doc.xpath(xpath_search).each do |node|
+ next unless element = element_to_embed(node)
+
+ # We want this to follow any surrounding content. For example,
+ # if a link is inline in a paragraph.
+ node.parent.children.last.add_next_sibling(element)
+ end
+
+ doc
+ end
+
+ # Implement in child class.
+ #
+ # Return a Nokogiri::XML::Element to embed in the
+ # markdown.
+ def create_element(params)
+ end
+
+ # Implement in child class unless overriding #embed_params
+ #
+ # Returns the regex pattern used to filter
+ # to only matching urls.
+ def link_pattern
+ end
+
+ # Returns the xpath query string used to select nodes
+ # from the html document on which the embed is based.
+ #
+ # Override to select nodes other than links.
+ def xpath_search
+ 'descendant-or-self::a[@href]'
+ end
+
+ # Creates a new element based on the parameters
+ # obtained from the target link
+ def element_to_embed(node)
+ return unless params = embed_params(node)
+
+ create_element(params)
+ end
+
+ # Returns a hash of named parameters based on the
+ # provided regex with string keys.
+ #
+ # Override to select nodes other than links.
+ def embed_params(node)
+ url = node['href']
+
+ link_pattern.match(url) { |m| m.named_captures }
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_filter.rb b/lib/banzai/filter/inline_metrics_filter.rb
new file mode 100644
index 00000000000..0120cc37d6f
--- /dev/null
+++ b/lib/banzai/filter/inline_metrics_filter.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that inserts a placeholder element for each
+ # reference to a metrics dashboard.
+ class InlineMetricsFilter < Banzai::Filter::InlineEmbedsFilter
+ # Placeholder element for the frontend to use as an
+ # injection point for charts.
+ def create_element(params)
+ doc.document.create_element(
+ 'div',
+ class: 'js-render-metrics',
+ 'data-dashboard-url': metrics_dashboard_url(params)
+ )
+ end
+
+ # Endpoint FE should hit to collect the appropriate
+ # chart information
+ def metrics_dashboard_url(params)
+ Gitlab::Metrics::Dashboard::Url.build_dashboard_url(
+ params['namespace'],
+ params['project'],
+ params['environment'],
+ embedded: true
+ )
+ end
+
+ # Search params for selecting metrics links. A few
+ # simple checks is enough to boost performance without
+ # the cost of doing a full regex match.
+ def xpath_search
+ "descendant-or-self::a[contains(@href,'metrics') and \
+ starts-with(@href, '#{Gitlab.config.gitlab.url}')]"
+ end
+
+ # Regular expression matching metrics urls
+ def link_pattern
+ Gitlab::Metrics::Dashboard::Url.regex
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
new file mode 100644
index 00000000000..ff91be2cbb7
--- /dev/null
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ # HTML filter that removes embeded elements that the current user does
+ # not have permission to view.
+ class InlineMetricsRedactorFilter < HTML::Pipeline::Filter
+ include Gitlab::Utils::StrongMemoize
+
+ METRICS_CSS_CLASS = '.js-render-metrics'
+
+ # Finds all embeds based on the css class the FE
+ # uses to identify the embedded content, removing
+ # only unnecessary nodes.
+ def call
+ return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
+
+ nodes.each do |node|
+ path = paths_by_node[node]
+ user_has_access = user_access_by_path[path]
+
+ node.remove unless user_has_access
+ end
+
+ doc
+ end
+
+ private
+
+ def user
+ context[:current_user]
+ end
+
+ # Returns all nodes which the FE will identify as
+ # a metrics dashboard placeholder element
+ #
+ # @return [Nokogiri::XML::NodeSet]
+ def nodes
+ @nodes ||= doc.css(METRICS_CSS_CLASS)
+ end
+
+ # Maps a node to the full path of a project.
+ # Memoized so we only need to run the regex to get
+ # the project full path from the url once per node.
+ #
+ # @return [Hash<Nokogiri::XML::Node, String>]
+ def paths_by_node
+ strong_memoize(:paths_by_node) do
+ nodes.each_with_object({}) do |node, paths|
+ paths[node] = path_for_node(node)
+ end
+ end
+ end
+
+ # Gets a project's full_path from the dashboard url
+ # in the placeholder node. The FE will use the attr
+ # `data-dashboard-url`, so we want to check against that
+ # attribute directly in case a user has manually
+ # created a metrics element (rather than supporting
+ # an alternate attr in InlineMetricsFilter).
+ #
+ # @return [String]
+ def path_for_node(node)
+ url = node.attribute('data-dashboard-url').to_s
+
+ Gitlab::Metrics::Dashboard::Url.regex.match(url) do |m|
+ "#{$~[:namespace]}/#{$~[:project]}"
+ end
+ end
+
+ # Maps a project's full path to a Project object.
+ # Contains all of the Projects referenced in the
+ # metrics placeholder elements of the current document
+ #
+ # @return [Hash<String, Project>]
+ def projects_by_path
+ strong_memoize(:projects_by_path) do
+ Project.eager_load(:route, namespace: [:route])
+ .where_full_path_in(paths_by_node.values.uniq)
+ .index_by(&:full_path)
+ end
+ end
+
+ # Returns a mapping representing whether the current user
+ # has permission to view the metrics for the project.
+ # Determined in a batch
+ #
+ # @return [Hash<Project, Boolean>]
+ def user_access_by_path
+ strong_memoize(:user_access_by_path) do
+ projects_by_path.each_with_object({}) do |(path, project), access|
+ access[path] = Ability.allowed?(user, :read_environment, project)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index d67f461be57..2c1006f708a 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -25,6 +25,7 @@ module Banzai
Filter::VideoLinkFilter,
Filter::ImageLazyLoadFilter,
Filter::ImageLinkFilter,
+ Filter::InlineMetricsFilter,
Filter::TableOfContentsFilter,
Filter::AutolinkFilter,
Filter::ExternalLinkFilter,
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 7eaad6d7560..5c199453638 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -13,6 +13,7 @@ module Banzai
def self.internal_link_filters
[
Filter::RedactorFilter,
+ Filter::InlineMetricsRedactorFilter,
Filter::RelativeLinkFilter,
Filter::IssuableStateFilter,
Filter::SuggestionFilter
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 4317992d933..82e0c7ceeaa 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -94,6 +94,7 @@ module Gitlab
end
end
+ # rubocop:disable Gitlab/RailsLogger
def rate_limit!(ip, success:, login:)
rate_limiter = Gitlab::Auth::IpRateLimiter.new(ip)
return unless rate_limiter.enabled?
@@ -114,6 +115,7 @@ module Gitlab
end
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
diff --git a/lib/gitlab/auth/ldap/adapter.rb b/lib/gitlab/auth/ldap/adapter.rb
index 15b9d5ad6e9..bcb0ecccdf9 100644
--- a/lib/gitlab/auth/ldap/adapter.rb
+++ b/lib/gitlab/auth/ldap/adapter.rb
@@ -55,7 +55,7 @@ module Gitlab
response = ldap.get_operation_result
unless response.code.zero?
- Rails.logger.warn("LDAP search error: #{response.message}")
+ Rails.logger.warn("LDAP search error: #{response.message}") # rubocop:disable Gitlab/RailsLogger
end
[]
@@ -67,7 +67,7 @@ module Gitlab
retries += 1
error_message = connection_error_message(error)
- Rails.logger.warn(error_message)
+ Rails.logger.warn(error_message) # rubocop:disable Gitlab/RailsLogger
if retries < MAX_SEARCH_RETRIES
renew_connection_adapter
diff --git a/lib/gitlab/auth/ldap/config.rb b/lib/gitlab/auth/ldap/config.rb
index 47d63eb53cf..354f91306f9 100644
--- a/lib/gitlab/auth/ldap/config.rb
+++ b/lib/gitlab/auth/ldap/config.rb
@@ -240,7 +240,7 @@ module Gitlab
begin
custom_options[:cert] = OpenSSL::X509::Certificate.new(custom_options[:cert])
rescue OpenSSL::X509::CertificateError => e
- Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}"
+ Rails.logger.error "LDAP TLS Options 'cert' is invalid for provider #{provider}: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
@@ -248,7 +248,7 @@ module Gitlab
begin
custom_options[:key] = OpenSSL::PKey.read(custom_options[:key])
rescue OpenSSL::PKey::PKeyError => e
- Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}"
+ Rails.logger.error "LDAP TLS Options 'key' is invalid for provider #{provider}: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index c1517222956..11a4052a109 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -45,7 +45,7 @@ module Gitlab
def self.normalize_dn(dn)
::Gitlab::Auth::LDAP::DN.new(dn).to_normalized_s
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}")
+ Rails.logger.info("Returning original DN \"#{dn}\" due to error during normalization attempt: #{e.message}") # rubocop:disable Gitlab/RailsLogger
dn
end
@@ -57,13 +57,13 @@ module Gitlab
def self.normalize_uid(uid)
::Gitlab::Auth::LDAP::DN.normalize_value(uid)
rescue ::Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}")
+ Rails.logger.info("Returning original UID \"#{uid}\" due to error during normalization attempt: #{e.message}") # rubocop:disable Gitlab/RailsLogger
uid
end
def initialize(entry, provider)
- Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
+ Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } # rubocop:disable Gitlab/RailsLogger
@entry = entry
@provider = provider
end
diff --git a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
index cb2bdea755c..c912628d0fc 100644
--- a/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
+++ b/lib/gitlab/background_migration/add_merge_request_diff_commits_count.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def perform(start_id, stop_id)
- Rails.logger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}")
+ Rails.logger.info("Setting commits_count for merge request diffs: #{start_id} - #{stop_id}") # rubocop:disable Gitlab/RailsLogger
update = '
commits_count = (
diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb
index 7ee783b8489..3c26982729d 100644
--- a/lib/gitlab/background_migration/archive_legacy_traces.rb
+++ b/lib/gitlab/background_migration/archive_legacy_traces.rb
@@ -14,7 +14,7 @@ module Gitlab
build.trace.archive!
rescue => e
- Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
+ Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/calculate_wiki_sizes.rb b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
index 886c41a2b9d..e62f5edd0e7 100644
--- a/lib/gitlab/background_migration/calculate_wiki_sizes.rb
+++ b/lib/gitlab/background_migration/calculate_wiki_sizes.rb
@@ -10,7 +10,7 @@ module Gitlab
.includes(project: [:route, :group, namespace: [:owner]]).find_each do |statistics|
statistics.refresh!(only: [:wiki_size])
rescue => e
- Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}"
+ Rails.logger.error "Failed to update wiki statistics. id: #{statistics.id} message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
index 0e93b2cb2fa..6046d33aeac 100644
--- a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
+++ b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
@@ -32,7 +32,7 @@ module Gitlab
)
end
rescue => e
- Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}"
+ Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/background_migration/fix_cross_project_label_links.rb b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
index bf5d7f5f322..20a98c8e141 100644
--- a/lib/gitlab/background_migration/fix_cross_project_label_links.rb
+++ b/lib/gitlab/background_migration/fix_cross_project_label_links.rb
@@ -108,7 +108,7 @@ module Gitlab
next unless matching_label
- Rails.logger.info "#{resource.class.name.demodulize} #{resource.id}: replacing #{label.label_id} with #{matching_label.id}"
+ Rails.logger.info "#{resource.class.name.demodulize} #{resource.id}: replacing #{label.label_id} with #{matching_label.id}" # rubocop:disable Gitlab/RailsLogger
LabelLink.update(label.label_link_id, label_id: matching_label.id)
end
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index 755b5ee725a..d2924d10225 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -42,7 +42,7 @@ module Gitlab
#{e.message}
#{e.backtrace.join("\n ")}
MSG
- Rails.logger.error(msg)
+ Rails.logger.error(msg) # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index 1ee44a3a5a9..cce2a82c098 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -111,7 +111,7 @@ module Gitlab
cmd = %W[#{ionice} -c Idle] + cmd if ionice
log_msg = "PrepareUntrackedUploads find command: \"#{cmd.join(' ')}\""
- Rails.logger.info log_msg
+ Rails.logger.info log_msg # rubocop:disable Gitlab/RailsLogger
cmd
end
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index dcf8254ef94..108f0119ae1 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -246,7 +246,6 @@ rollout 100%:
auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
export DATABASE_URL=${DATABASE_URL-$auto_database_url}
export TILLER_NAMESPACE=$KUBE_NAMESPACE
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
function get_replicas() {
track="${1:-stable}"
diff --git a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
index b9fee2d5731..25ea20e454f 100644
--- a/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/PHP.gitlab-ci.yml
@@ -1,5 +1,5 @@
# Select image from https://hub.docker.com/_/php/
-image: php:7.1.1
+image: php:latest
# Select what we should cache between builds
cache:
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index d1a34c515fa..5ad624bb15f 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -23,6 +23,7 @@ container_scanning:
DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
# https://hub.docker.com/r/arminc/clair-local-scan/tags
CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
+ CLAIR_EXECUTABLE_VERSION: v11
## Disable the proxy for clair-local-scan, otherwise Container Scanning will
## fail when a proxy is used.
NO_PROXY: ${DOCKER_SERVICE},localhost
@@ -41,7 +42,7 @@ container_scanning:
- docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:${CLAIR_LOCAL_SCAN_VERSION}
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- - wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
+ - wget https://github.com/arminc/clair-scanner/releases/download/${CLAIR_EXECUTABLE_VERSION}/clair-scanner_linux_amd64
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index 8dd9775c583..f176771775e 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -40,6 +40,7 @@ dependency_scanning:
DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
DS_RUN_ANALYZER_TIMEOUT \
+ DS_PYTHON_VERSION \
) \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
diff --git a/lib/gitlab/cleanup/orphan_job_artifact_files.rb b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
index ee7164b3e55..808814c39e0 100644
--- a/lib/gitlab/cleanup/orphan_job_artifact_files.rb
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files.rb
@@ -17,7 +17,7 @@ module Gitlab
@limit = limit
@dry_run = dry_run
@niceness = niceness || DEFAULT_NICENESS
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
@total_found = @total_cleaned = 0
new_batch!
diff --git a/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
index 5c30258c0fc..53e0c83046e 100644
--- a/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
+++ b/lib/gitlab/cleanup/orphan_job_artifact_files_batch.rb
@@ -22,7 +22,7 @@ module Gitlab
attr_reader :batch_size, :dry_run
attr_accessor :artifact_files
- def initialize(batch_size:, dry_run: true, logger: Rails.logger)
+ def initialize(batch_size:, dry_run: true, logger: Rails.logger) # rubocop:disable Gitlab/RailsLogger
@batch_size = batch_size
@dry_run = dry_run
@logger = logger
diff --git a/lib/gitlab/cleanup/project_upload_file_finder.rb b/lib/gitlab/cleanup/project_upload_file_finder.rb
index 2ee8b60e76a..5aace564c2d 100644
--- a/lib/gitlab/cleanup/project_upload_file_finder.rb
+++ b/lib/gitlab/cleanup/project_upload_file_finder.rb
@@ -49,7 +49,7 @@ module Gitlab
cmd = %W[#{ionice} -c Idle] + cmd if ionice
log_msg = "find command: \"#{cmd.join(' ')}\""
- Rails.logger.info log_msg
+ Rails.logger.info log_msg # rubocop:disable Gitlab/RailsLogger
cmd
end
diff --git a/lib/gitlab/cleanup/project_uploads.rb b/lib/gitlab/cleanup/project_uploads.rb
index 82a405362c2..056e075cb21 100644
--- a/lib/gitlab/cleanup/project_uploads.rb
+++ b/lib/gitlab/cleanup/project_uploads.rb
@@ -8,7 +8,7 @@ module Gitlab
attr_reader :logger
def initialize(logger: nil)
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def run!(dry_run: true)
diff --git a/lib/gitlab/cleanup/remote_uploads.rb b/lib/gitlab/cleanup/remote_uploads.rb
index 03298d960a4..42c93b7aecb 100644
--- a/lib/gitlab/cleanup/remote_uploads.rb
+++ b/lib/gitlab/cleanup/remote_uploads.rb
@@ -7,7 +7,7 @@ module Gitlab
BATCH_SIZE = 100
def initialize(logger: nil)
- @logger = logger || Rails.logger
+ @logger = logger || Rails.logger # rubocop:disable Gitlab/RailsLogger
end
def run!(dry_run: false)
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 304d60996a6..0cacef5b278 100644
--- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
@@ -5,11 +5,11 @@ module Gitlab
class BaseEventFetcher
include BaseQuery
- attr_reader :projections, :query, :stage, :order
+ attr_reader :projections, :query, :stage, :order, :project, :options
MAX_EVENTS = 50
- def initialize(project:, stage:, options:)
+ def initialize(project: nil, stage:, options:)
@project = project
@stage = stage
@options = options
@@ -40,13 +40,13 @@ module Gitlab
end
def events_query
- diff_fn = subtract_datetimes_diff(base_query, @options[:start_time_attrs], @options[:end_time_attrs])
+ diff_fn = subtract_datetimes_diff(base_query, options[:start_time_attrs], options[:end_time_attrs])
base_query.project(extract_diff_epoch(diff_fn).as('total_time'), *projections).order(order.desc).take(MAX_EVENTS)
end
def default_order
- [@options[:start_time_attrs]].flatten.first
+ [options[:start_time_attrs]].flatten.first
end
def serialize(_event)
@@ -59,13 +59,21 @@ module Gitlab
def allowed_ids
@allowed_ids ||= allowed_ids_finder_class
- .new(@options[:current_user], project_id: @project.id)
+ .new(options[:current_user], allowed_ids_source)
.execute.where(id: event_result_ids).pluck(:id)
end
def event_result_ids
event_result.map { |event| event['id'] }
end
+
+ def allowed_ids_source
+ { project_id: project.id }
+ end
+
+ def projects
+ [project]
+ end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb
index 36231b187cd..39fc1759cfc 100644
--- a/lib/gitlab/cycle_analytics/base_query.rb
+++ b/lib/gitlab/cycle_analytics/base_query.rb
@@ -10,7 +10,7 @@ module Gitlab
private
def base_query
- @base_query ||= stage_query(@project.id) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @base_query ||= stage_query(projects.map(&:id))
end
def stage_query(project_ids)
@@ -18,7 +18,7 @@ module Gitlab
.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .where(issue_table[:created_at].gteq(options[:from]))
# Load merge_requests
query = query.join(mr_table, Arel::Nodes::OuterJoin)
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index e2d6a301734..98b86e54340 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -5,7 +5,9 @@ module Gitlab
class BaseStage
include BaseQuery
- def initialize(project:, options:)
+ attr_reader :project, :options
+
+ def initialize(project: nil, options:)
@project = project
@options = options
end
@@ -14,8 +16,8 @@ module Gitlab
event_fetcher.fetch
end
- def as_json
- AnalyticsStageSerializer.new.represent(self)
+ def as_json(serializer: AnalyticsStageSerializer)
+ serializer.new.represent(self)
end
def title
@@ -23,21 +25,14 @@ module Gitlab
end
def median
- BatchLoader.for(@project.id).batch(key: name) do |project_ids, loader|
- cte_table = Arel::Table.new("cte_table_for_#{name}")
-
- # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
- # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
- # We compute the (end_time - start_time) interval, and give it an alias based on the current
- # cycle analytics stage.
- interval_query = Arel::Nodes::As.new(cte_table,
- subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s))
+ return if project.nil?
+ BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
if project_ids.one?
- loader.call(@project.id, median_datetime(cte_table, interval_query, name))
+ loader.call(project.id, median_query(project_ids))
else
begin
- median_datetimes(cte_table, interval_query, name, :project_id)&.each do |project_id, median|
+ median_datetimes(cte_table, interval_query(project_ids), name, :project_id)&.each do |project_id, median|
loader.call(project_id, median)
end
rescue NotSupportedError
@@ -47,20 +42,42 @@ module Gitlab
end
end
+ def median_query(project_ids)
+ # Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
+ # Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
+ # We compute the (end_time - start_time) interval, and give it an alias based on the current
+ # cycle analytics stage.
+
+ median_datetime(cte_table, interval_query(project_ids), name)
+ end
+
def name
raise NotImplementedError.new("Expected #{self.name} to implement name")
end
+ def cte_table
+ Arel::Table.new("cte_table_for_#{name}")
+ end
+
+ def interval_query(project_ids)
+ Arel::Nodes::As.new(cte_table,
+ subtract_datetimes(stage_query(project_ids), start_time_attrs, end_time_attrs, name.to_s))
+ end
+
private
def event_fetcher
- @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: @project,
+ @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: project,
stage: name,
options: event_options)
end
def event_options
- @options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs)
+ options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs)
+ end
+
+ def projects
+ [project]
end
end
end
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 6c348f1862d..9e7ca529579 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -20,7 +20,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
+ AnalyticsMergeRequestSerializer.new(project: project).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
index 8a870f2e2a3..bb3520ae920 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: @project).represent(event)
+ AnalyticsIssueSerializer.new(project: project).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb
index c9266341378..ac836b8bf0f 100644
--- a/lib/gitlab/cycle_analytics/issue_helper.rb
+++ b/lib/gitlab/cycle_analytics/issue_helper.rb
@@ -7,7 +7,7 @@ module Gitlab
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .where(issue_table[:created_at].gteq(options[:from]))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
query
diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb
index afefd09b614..03ba98b4dfb 100644
--- a/lib/gitlab/cycle_analytics/permissions.rb
+++ b/lib/gitlab/cycle_analytics/permissions.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def get
- ::CycleAnalytics::STAGES.each do |stage|
+ ::CycleAnalytics::Base::STAGES.each do |stage|
@stage_permission_hash[stage] = authorized_stage?(stage)
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index d924f956dcd..49a6b099f34 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: @project).represent(event)
+ AnalyticsIssueSerializer.new(project: project).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb
index 30fc2ce6d40..ae578d45ad5 100644
--- a/lib/gitlab/cycle_analytics/plan_helper.rb
+++ b/lib/gitlab/cycle_analytics/plan_helper.rb
@@ -7,7 +7,7 @@ module Gitlab
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
.project(issue_table[:project_id].as("project_id"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .where(issue_table[:created_at].gteq(options[:from]))
.where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
.where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
index 6bcbe0412a9..949119d69a0 100644
--- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: @project).represent(event)
+ AnalyticsIssueSerializer.new(project: project).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/production_helper.rb b/lib/gitlab/cycle_analytics/production_helper.rb
index aff65b150fb..778757a9ede 100644
--- a/lib/gitlab/cycle_analytics/production_helper.rb
+++ b/lib/gitlab/cycle_analytics/production_helper.rb
@@ -6,7 +6,7 @@ module Gitlab
def stage_query(project_ids)
super(project_ids)
.where(mr_metrics_table[:first_deployed_to_production_at]
- .gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ .gteq(options[:from]))
end
end
end
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index b6354b5ffad..d31736e755d 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -19,7 +19,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: @project).represent(event)
+ AnalyticsMergeRequestSerializer.new(project: project).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/test_helper.rb b/lib/gitlab/cycle_analytics/test_helper.rb
index 32fca7fa898..d9124d62c7c 100644
--- a/lib/gitlab/cycle_analytics/test_helper.rb
+++ b/lib/gitlab/cycle_analytics/test_helper.rb
@@ -14,7 +14,7 @@ module Gitlab
private
def branch
- @branch ||= @options[:branch] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ @branch ||= options[:branch]
end
end
end
diff --git a/lib/gitlab/cycle_analytics/usage_data.rb b/lib/gitlab/cycle_analytics/usage_data.rb
index 913ee373f54..644300caead 100644
--- a/lib/gitlab/cycle_analytics/usage_data.rb
+++ b/lib/gitlab/cycle_analytics/usage_data.rb
@@ -32,7 +32,7 @@ module Gitlab
def medians_per_stage
projects.each_with_object({}) do |project, hsh|
- ::CycleAnalytics.new(project, options).all_medians_per_stage.each do |stage_name, median|
+ ::CycleAnalytics::ProjectLevel.new(project, options: options).all_medians_by_stage.each do |stage_name, median|
hsh[stage_name] ||= []
hsh[stage_name] << median
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index 34c1e6ad8ca..1177f8ea99e 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -310,7 +310,7 @@ module Gitlab
gitlab_database_transaction_seconds.observe(labels, duration_seconds)
rescue Prometheus::Client::LabelSetValidator::LabelSetError => err
# Ensure that errors in recording these metrics don't affect the operation of the application
- Rails.logger.error("Unable to observe database transaction duration: #{err}")
+ Rails.logger.error("Unable to observe database transaction duration: #{err}") # rubocop:disable Gitlab/RailsLogger
end
# MonkeyPatch for ActiveRecord::Base for adding observability
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index 1455e410d4b..b8d895dee7d 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -158,7 +158,7 @@ module Gitlab
Arel::Nodes::Window.new.order(arel_table[column_sym])
).as('row_id')
- count = arel_table.project("COUNT(1)").as('ct')
+ count = arel_table.where(arel_table[column_sym].gteq(zero_interval)).project("COUNT(1)").as('ct')
[column_row, row_id, count]
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index e2cbf91f281..0c5f33e1b2a 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -61,7 +61,7 @@ module Gitlab
end
if index_exists?(table_name, column_name, options)
- Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
+ Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -91,7 +91,7 @@ module Gitlab
end
unless index_exists?(table_name, column_name, options)
- Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}"
+ Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -121,7 +121,7 @@ module Gitlab
end
unless index_exists_by_name?(table_name, index_name)
- Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}"
+ Rails.logger.warn "Index not removed because it does not exist (this may be due to an aborted migration or similar): table_name: #{table_name}, index_name: #{index_name}" # rubocop:disable Gitlab/RailsLogger
return
end
@@ -149,6 +149,8 @@ module Gitlab
# column - The name of the column to create the foreign key on.
# on_delete - The action to perform when associated data is removed,
# defaults to "CASCADE".
+ #
+ # rubocop:disable Gitlab/RailsLogger
def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil)
# Transactions would result in ALTER TABLE locks being held for the
# duration of the transaction, defeating the purpose of this method.
@@ -208,6 +210,7 @@ module Gitlab
execute("ALTER TABLE #{source} VALIDATE CONSTRAINT #{key_name};")
end
end
+ # rubocop:enable Gitlab/RailsLogger
def foreign_key_exists?(source, target = nil, column: nil)
foreign_keys(source).any? do |key|
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
index 6bbad707f0f..3e8a9b89998 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces.rb
@@ -70,7 +70,7 @@ module Gitlab
unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
- Rails.logger.error message
+ Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
index 580be9fe267..4dc7a62797a 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects.rb
@@ -56,7 +56,7 @@ module Gitlab
unless gitlab_shell.mv_repository(project.repository_storage,
old_path,
new_path)
- Rails.logger.error "Error moving #{old_path} to #{new_path}"
+ Rails.logger.error "Error moving #{old_path} to #{new_path}" # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/database_importers/common_metrics.rb b/lib/gitlab/database_importers/common_metrics.rb
new file mode 100644
index 00000000000..f964ae8a275
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/importer.rb b/lib/gitlab/database_importers/common_metrics/importer.rb
new file mode 100644
index 00000000000..6c61e05674e
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/importer.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ class Importer
+ MissingQueryId = Class.new(StandardError)
+
+ attr_reader :content
+
+ def initialize(filename = 'common_metrics.yml')
+ @content = YAML.load_file(Rails.root.join('config', 'prometheus', filename))
+ end
+
+ def execute
+ CommonMetrics::PrometheusMetric.reset_column_information
+
+ process_content do |id, attributes|
+ find_or_build_metric!(id)
+ .update!(**attributes)
+ end
+ end
+
+ private
+
+ def process_content(&blk)
+ content['panel_groups'].map do |group|
+ process_group(group, &blk)
+ end
+ end
+
+ def process_group(group, &blk)
+ attributes = {
+ group: find_group_title_key(group['group'])
+ }
+
+ group['panels'].map do |panel|
+ process_panel(panel, attributes, &blk)
+ end
+ end
+
+ def process_panel(panel, attributes, &blk)
+ attributes = attributes.merge(
+ title: panel['title'],
+ y_label: panel['y_label'])
+
+ panel['metrics'].map do |metric_details|
+ process_metric_details(metric_details, attributes, &blk)
+ end
+ end
+
+ def process_metric_details(metric_details, attributes, &blk)
+ attributes = attributes.merge(
+ legend: metric_details['label'],
+ query: metric_details['query_range'],
+ unit: metric_details['unit'])
+
+ yield(metric_details['id'], attributes)
+ end
+
+ def find_or_build_metric!(id)
+ raise MissingQueryId unless id
+
+ CommonMetrics::PrometheusMetric.common.find_by(identifier: id) ||
+ CommonMetrics::PrometheusMetric.new(common: true, identifier: id)
+ end
+
+ def find_group_title_key(title)
+ CommonMetrics::PrometheusMetricEnums.groups[find_group_title(title)]
+ end
+
+ def find_group_title(title)
+ CommonMetrics::PrometheusMetricEnums.group_titles.invert[title]
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb b/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb
new file mode 100644
index 00000000000..b4a392cbea9
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/prometheus_metric.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ class PrometheusMetric < ApplicationRecord
+ enum group: PrometheusMetricEnums.groups
+ scope :common, -> { where(common: true) }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
new file mode 100644
index 00000000000..c9e957ec7c0
--- /dev/null
+++ b/lib/gitlab/database_importers/common_metrics/prometheus_metric_enums.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module CommonMetrics
+ module PrometheusMetricEnums
+ def self.groups
+ {
+ # built-in groups
+ nginx_ingress_vts: -1,
+ ha_proxy: -2,
+ aws_elb: -3,
+ nginx: -4,
+ kubernetes: -5,
+ nginx_ingress: -6,
+
+ # custom groups
+ business: 0,
+ response: 1,
+ system: 2
+ }
+ end
+
+ def self.group_titles
+ {
+ business: _('Business metrics (Custom)'),
+ response: _('Response metrics (Custom)'),
+ system: _('System metrics (Custom)'),
+ nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'),
+ nginx_ingress: _('Response metrics (NGINX Ingress)'),
+ ha_proxy: _('Response metrics (HA Proxy)'),
+ aws_elb: _('Response metrics (AWS ELB)'),
+ nginx: _('Response metrics (NGINX)'),
+ kubernetes: _('System metrics (Kubernetes)')
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/hook/disable_email_interceptor.rb b/lib/gitlab/email/hook/disable_email_interceptor.rb
index 6b6b1d85109..58dc1527c7a 100644
--- a/lib/gitlab/email/hook/disable_email_interceptor.rb
+++ b/lib/gitlab/email/hook/disable_email_interceptor.rb
@@ -7,7 +7,7 @@ module Gitlab
def self.delivering_email(message)
message.perform_deliveries = false
- Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb
index 5a61a7f5d60..88729babb2b 100644
--- a/lib/gitlab/encoding_helper.rb
+++ b/lib/gitlab/encoding_helper.rb
@@ -59,7 +59,7 @@ module Gitlab
begin
CharlockHolmes::Converter.convert(message, detect[:encoding], 'UTF-8')
rescue ArgumentError => e
- Rails.logger.warn("Ignoring error converting #{detect[:encoding]} into UTF8: #{e.message}")
+ Rails.logger.warn("Ignoring error converting #{detect[:encoding]} into UTF8: #{e.message}") # rubocop:disable Gitlab/RailsLogger
''
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 060a29be782..b7b7578cef9 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -936,7 +936,7 @@ module Gitlab
gitaly_repository_client.cleanup if exists?
end
rescue Gitlab::Git::CommandError => e # Don't fail if we can't cleanup
- Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}")
+ Rails.logger.error("Unable to clean repository on storage #{storage} with relative path #{relative_path}: #{e.message}") # rubocop:disable Gitlab/RailsLogger
Gitlab::Metrics.counter(
:failed_repository_cleanup_total,
'Number of failed repository cleanup events'
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index cf0157269a8..091351e5cb2 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -67,7 +67,7 @@ module Gitlab
File.read(cert_file).scan(PEM_REGEX).map do |cert|
OpenSSL::X509::Certificate.new(cert).to_pem
rescue OpenSSL::OpenSSLError => e
- Rails.logger.error "Could not load certificate #{cert_file} #{e}"
+ Rails.logger.error "Could not load certificate #{cert_file} #{e}" # rubocop:disable Gitlab/RailsLogger
Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
nil
end.compact
@@ -388,20 +388,21 @@ module Gitlab
end
def self.can_use_disk?(storage)
- cached_value = MUTEX.synchronize do
- @can_use_disk ||= {}
- @can_use_disk[storage]
- end
+ false
+ # cached_value = MUTEX.synchronize do
+ # @can_use_disk ||= {}
+ # @can_use_disk[storage]
+ # end
- return cached_value unless cached_value.nil?
+ # return cached_value unless cached_value.nil?
- gitaly_filesystem_id = filesystem_id(storage)
- direct_filesystem_id = filesystem_id_from_disk(storage)
+ # gitaly_filesystem_id = filesystem_id(storage)
+ # direct_filesystem_id = filesystem_id_from_disk(storage)
- MUTEX.synchronize do
- @can_use_disk[storage] = gitaly_filesystem_id.present? &&
- gitaly_filesystem_id == direct_filesystem_id
- end
+ # MUTEX.synchronize do
+ # @can_use_disk[storage] = gitaly_filesystem_id.present? &&
+ # gitaly_filesystem_id == direct_filesystem_id
+ # end
end
def self.filesystem_id(storage)
diff --git a/lib/gitlab/github_import/importer/lfs_objects_importer.rb b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
index 6046e30d4ef..30763492235 100644
--- a/lib/gitlab/github_import/importer/lfs_objects_importer.rb
+++ b/lib/gitlab/github_import/importer/lfs_objects_importer.rb
@@ -29,7 +29,7 @@ module Gitlab
yield object
end
rescue StandardError => e
- Rails.logger.error("The Lfs import process failed. #{e.message}")
+ Rails.logger.error("The Lfs import process failed. #{e.message}") # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/gitlab/github_import/importer/pull_requests_importer.rb b/lib/gitlab/github_import/importer/pull_requests_importer.rb
index a52866c4b08..929fceaacf2 100644
--- a/lib/gitlab/github_import/importer/pull_requests_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_requests_importer.rb
@@ -40,7 +40,7 @@ module Gitlab
pname = project.path_with_namespace
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
.info("GitHub importer finished updating repository for #{pname}")
repository_updates_counter.increment
diff --git a/lib/gitlab/global_id.rb b/lib/gitlab/global_id.rb
new file mode 100644
index 00000000000..cc82b6c5897
--- /dev/null
+++ b/lib/gitlab/global_id.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module GlobalId
+ def self.build(object = nil, model_name: nil, id: nil, params: nil)
+ if object
+ model_name ||= object.class.name
+ id ||= object.id
+ end
+
+ ::URI::GID.build(app: GlobalID.app, model_name: model_name, model_id: id, params: params)
+ end
+ end
+end
diff --git a/lib/gitlab/graphql.rb b/lib/gitlab/graphql.rb
index 8a59e83974f..74c04e5380e 100644
--- a/lib/gitlab/graphql.rb
+++ b/lib/gitlab/graphql.rb
@@ -3,9 +3,5 @@
module Gitlab
module Graphql
StandardGraphqlError = Class.new(StandardError)
-
- def self.enabled?
- Feature.enabled?(:graphql, default_enabled: true)
- end
end
end
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index 1f0deebea39..6a8e16f5a85 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -62,6 +62,7 @@ module Gitlab
# Flag a project to be migrated to Hashed Storage
#
# @param [Project] project that will be migrated
+ # rubocop:disable Gitlab/RailsLogger
def migrate(project)
Rails.logger.info "Starting storage migration of #{project.full_path} (ID=#{project.id})..."
@@ -69,10 +70,12 @@ module Gitlab
rescue => err
Rails.logger.error("#{err.message} migrating storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # rubocop:enable Gitlab/RailsLogger
# Flag a project to be rolled-back to Legacy Storage
#
# @param [Project] project that will be rolled-back
+ # rubocop:disable Gitlab/RailsLogger
def rollback(project)
Rails.logger.info "Starting storage rollback of #{project.full_path} (ID=#{project.id})..."
@@ -80,6 +83,7 @@ module Gitlab
rescue => err
Rails.logger.error("#{err.message} rolling-back storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # rubocop:enable Gitlab/RailsLogger
# Returns whether we have any pending storage migration
#
diff --git a/lib/gitlab/health_checks/simple_abstract_check.rb b/lib/gitlab/health_checks/simple_abstract_check.rb
index 3588260d6eb..5a1e8c2a1dd 100644
--- a/lib/gitlab/health_checks/simple_abstract_check.rb
+++ b/lib/gitlab/health_checks/simple_abstract_check.rb
@@ -18,7 +18,7 @@ module Gitlab
def metrics
result, elapsed = with_timing(&method(:check))
- Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless successful?(result)
+ Rails.logger.error("#{human_name} check returned unexpected result #{result}") unless successful?(result) # rubocop:disable Gitlab/RailsLogger
[
metric("#{metric_prefix}_timeout", result.is_a?(Timeout::Error) ? 1 : 0),
metric("#{metric_prefix}_success", successful?(result) ? 1 : 0),
diff --git a/lib/gitlab/import_export/merge_request_parser.rb b/lib/gitlab/import_export/merge_request_parser.rb
index deb2f59f05f..0b534a5bafc 100644
--- a/lib/gitlab/import_export/merge_request_parser.rb
+++ b/lib/gitlab/import_export/merge_request_parser.rb
@@ -43,7 +43,7 @@ module Gitlab
target_ref = Gitlab::Git::BRANCH_REF_PREFIX + @merge_request.source_branch
unless @project.repository.fetch_source_branch!(@project.repository, @diff_head_sha, target_ref)
- Rails.logger.warn("Import/Export warning: Failed to create #{target_ref} for MR: #{@merge_request.iid}")
+ Rails.logger.warn("Import/Export warning: Failed to create #{target_ref} for MR: #{@merge_request.iid}") # rubocop:disable Gitlab/RailsLogger
end
end
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index 20caadb89c0..dec99c23a2d 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -20,7 +20,7 @@ module Gitlab
json = IO.read(@path)
@tree_hash = ActiveSupport::JSON.decode(json)
rescue => e
- Rails.logger.error("Import/Export error: #{e.message}")
+ Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
end
diff --git a/lib/gitlab/import_export/saver.rb b/lib/gitlab/import_export/saver.rb
index 72f575db095..bea7a7cce65 100644
--- a/lib/gitlab/import_export/saver.rb
+++ b/lib/gitlab/import_export/saver.rb
@@ -18,7 +18,7 @@ module Gitlab
if compress_and_save
remove_export_path
- Rails.logger.info("Saved project export #{archive_file}")
+ Rails.logger.info("Saved project export #{archive_file}") # rubocop:disable Gitlab/RailsLogger
save_upload
else
diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb
index 6d978d00ea5..86ea7a30e69 100644
--- a/lib/gitlab/import_export/version_checker.rb
+++ b/lib/gitlab/import_export/version_checker.rb
@@ -36,7 +36,7 @@ module Gitlab
def different_version?(version)
Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version)
rescue => e
- Rails.logger.error("Import/Export error: #{e.message}")
+ Rails.logger.error("Import/Export error: #{e.message}") # rubocop:disable Gitlab/RailsLogger
raise Gitlab::ImportExport::Error.new('Incorrect VERSION format')
end
end
diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb
new file mode 100644
index 00000000000..b197e7ca86b
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/url.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+# Manages url matching for metrics dashboards.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class Url
+ class << self
+ # Matches urls for a metrics dashboard. This could be
+ # either the /metrics endpoint or the /metrics_dashboard
+ # endpoint.
+ #
+ # EX - https://<host>/<namespace>/<project>/environments/<env_id>/metrics
+ def regex
+ %r{
+ (?<url>
+ #{Regexp.escape(Gitlab.config.gitlab.url)}
+ \/#{Project.reference_pattern}
+ (?:\/\-)?
+ \/environments
+ \/(?<environment>\d+)
+ \/metrics
+ (?<query>
+ \?[a-z0-9_=-]+
+ (&[a-z0-9_=-]+)*
+ )?
+ (?<anchor>\#[a-z0-9_-]+)?
+ )
+ }x
+ end
+
+ # Builds a metrics dashboard url based on the passed in arguments
+ def build_dashboard_url(*args)
+ Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/base_sampler.rb b/lib/gitlab/metrics/samplers/base_sampler.rb
index 6a062e93f0f..d7d848d2833 100644
--- a/lib/gitlab/metrics/samplers/base_sampler.rb
+++ b/lib/gitlab/metrics/samplers/base_sampler.rb
@@ -19,7 +19,7 @@ module Gitlab
def safe_sample
sample
rescue => e
- Rails.logger.warn("#{self.class}: #{e}, stopping")
+ Rails.logger.warn("#{self.class}: #{e}, stopping") # rubocop:disable Gitlab/RailsLogger
stop
end
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
index 25e40c70230..4e835f37c04 100644
--- a/lib/gitlab/metrics/samplers/puma_sampler.rb
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -43,7 +43,7 @@ module Gitlab
def puma_stats
Puma.stats
rescue NoMethodError
- Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot"
+ Rails.logger.info "PumaSampler: stats are not available yet, waiting for Puma to boot" # rubocop:disable Gitlab/RailsLogger
nil
end
diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index 9af7e0afed4..355f938704e 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -54,7 +54,16 @@ module Gitlab
end
def unicorn_workers_count
- `pgrep -f '[u]nicorn_rails worker.+ #{Rails.root.to_s}'`.split.count
+ http_servers.sum(&:worker_processes) # rubocop: disable CodeReuse/ActiveRecord
+ end
+
+ # Traversal of ObjectSpace is expensive, on fully loaded application
+ # it takes around 80ms. The instances of HttpServers are not a subject
+ # to change so we can cache the list of servers.
+ def http_servers
+ return [] unless defined?(::Unicorn::HttpServer)
+
+ @http_servers ||= ObjectSpace.each_object(::Unicorn::HttpServer).to_a
end
end
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index 817db12ac55..53e55269c6e 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -25,7 +25,7 @@ module Gitlab
def call
if disallowed_request? && Gitlab::Database.read_only?
- Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation')
+ Rails.logger.debug('GitLab ReadOnly: preventing possible non read-only operation') # rubocop:disable Gitlab/RailsLogger
if json_request?
return [403, { 'Content-Type' => APPLICATION_JSON }, [{ 'message' => ERROR_MESSAGE }.to_json]]
diff --git a/lib/gitlab/pages_client.rb b/lib/gitlab/pages_client.rb
index d74fdba2241..281eafb142f 100644
--- a/lib/gitlab/pages_client.rb
+++ b/lib/gitlab/pages_client.rb
@@ -110,7 +110,7 @@ module Gitlab
end
rescue Errno::EACCES => ex
# TODO stop rescuing this exception in GitLab 11.0 https://gitlab.com/gitlab-org/gitlab-ce/issues/45672
- Rails.logger.error("Could not write pages admin token file: #{ex}")
+ Rails.logger.error("Could not write pages admin token file: #{ex}") # rubocop:disable Gitlab/RailsLogger
rescue Errno::EEXIST
# Another process wrote the token file concurrently with us. Use their token, not ours.
end
diff --git a/lib/gitlab/reference_counter.rb b/lib/gitlab/reference_counter.rb
index d2dbc6f5ef5..1c43de35816 100644
--- a/lib/gitlab/reference_counter.rb
+++ b/lib/gitlab/reference_counter.rb
@@ -22,6 +22,7 @@ module Gitlab
end
end
+ # rubocop:disable Gitlab/RailsLogger
def decrease
redis_cmd do |redis|
current_value = redis.decr(key)
@@ -32,6 +33,7 @@ module Gitlab
end
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
@@ -39,7 +41,7 @@ module Gitlab
Gitlab::Redis::SharedState.with { |redis| yield(redis) }
true
rescue => e
- Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}")
+ Rails.logger.warn("GitLab: An unexpected error occurred in writing to Redis: #{e}") # rubocop:disable Gitlab/RailsLogger
false
end
end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index 931298b5117..e40c366ed02 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -145,7 +145,7 @@ module Gitlab
def expire_method_caches(methods)
methods.each do |name|
unless cached_methods.include?(name.to_sym)
- Rails.logger.error "Requested to expire non-existent method '#{name}' for Repository"
+ Rails.logger.error "Requested to expire non-existent method '#{name}' for Repository" # rubocop:disable Gitlab/RailsLogger
next
end
diff --git a/lib/gitlab/sanitizers/exif.rb b/lib/gitlab/sanitizers/exif.rb
index 0928ccdc324..bb4e4ce7bbc 100644
--- a/lib/gitlab/sanitizers/exif.rb
+++ b/lib/gitlab/sanitizers/exif.rb
@@ -48,7 +48,7 @@ module Gitlab
attr_reader :logger
- def initialize(logger: Rails.logger)
+ def initialize(logger: Rails.logger) # rubocop:disable Gitlab/RailsLogger
@logger = logger
end
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 93182607616..0fa17b3f559 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -78,7 +78,7 @@ module Gitlab
true
rescue => err # Once the Rugged codes gets removes this can be improved
- Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}")
+ Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -153,7 +153,7 @@ module Gitlab
!!rm_directory(storage, "#{name}.git")
rescue ArgumentError => e
- Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git")
+ Rails.logger.warn("Repository does not exist: #{e} at: #{name}.git") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -238,7 +238,7 @@ module Gitlab
def remove_keys_not_found_in_db
return unless self.authorized_keys_enabled?
- Rails.logger.info("Removing keys not found in DB")
+ Rails.logger.info("Removing keys not found in DB") # rubocop:disable Gitlab/RailsLogger
batch_read_key_ids do |ids_in_file|
ids_in_file.uniq!
@@ -248,7 +248,7 @@ module Gitlab
ids_to_remove = ids_in_file - keys_in_db.pluck(:id)
ids_to_remove.each do |id|
- Rails.logger.info("Removing key-#{id} not found in DB")
+ Rails.logger.info("Removing key-#{id} not found in DB") # rubocop:disable Gitlab/RailsLogger
remove_key("key-#{id}")
end
end
@@ -368,7 +368,7 @@ module Gitlab
return true if status.zero?
- Rails.logger.error("gitlab-shell failed with error #{status}: #{output}")
+ Rails.logger.error("gitlab-shell failed with error #{status}: #{output}") # rubocop:disable Gitlab/RailsLogger
false
end
@@ -465,7 +465,7 @@ module Gitlab
end
def logger
- Rails.logger
+ Rails.logger # rubocop:disable Gitlab/RailsLogger
end
end
end
diff --git a/lib/mattermost/session.rb b/lib/mattermost/session.rb
index 722e3e04d1c..eea7daa3d8e 100644
--- a/lib/mattermost/session.rb
+++ b/lib/mattermost/session.rb
@@ -41,7 +41,7 @@ module Mattermost
begin
yield self
rescue Errno::ECONNREFUSED => e
- Rails.logger.error(e.message + "\n" + e.backtrace.join("\n"))
+ Rails.logger.error(e.message + "\n" + e.backtrace.join("\n")) # rubocop:disable Gitlab/RailsLogger
raise Mattermost::NoSessionError
ensure
destroy
diff --git a/lib/microsoft_teams/notifier.rb b/lib/microsoft_teams/notifier.rb
index c7dec09ba6b..a7dcd322e27 100644
--- a/lib/microsoft_teams/notifier.rb
+++ b/lib/microsoft_teams/notifier.rb
@@ -20,7 +20,7 @@ module MicrosoftTeams
result = true if response
rescue Gitlab::HTTP::Error, StandardError => error
- Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}")
+ Rails.logger.info("#{self.class.name}: Error while connecting to #{@webhook}: #{error.message}") # rubocop:disable Gitlab/RailsLogger
end
result
diff --git a/lib/peek/views/redis.rb b/lib/peek/views/redis.rb
index ad3c3c9fe01..73de8672fa4 100644
--- a/lib/peek/views/redis.rb
+++ b/lib/peek/views/redis.rb
@@ -37,6 +37,8 @@ end
module Peek
module Views
module RedisDetailed
+ REDACTED_MARKER = "<redacted>"
+
def results
super.merge(details: details)
end
@@ -57,10 +59,12 @@ module Peek
end
def format_command(cmd)
+ if cmd.length >= 2 && cmd.first =~ /^auth$/i
+ cmd[-1] = REDACTED_MARKER
# Scrub out the value of the SET calls to avoid binary
# data or large data from spilling into the view
- if cmd.length >= 2 && cmd.first =~ /set/i
- cmd[-1] = "<redacted>"
+ elsif cmd.length >= 3 && cmd.first =~ /set/i
+ cmd[2..-1] = REDACTED_MARKER
end
cmd.join(' ')
diff --git a/lib/rspec_flaky/listener.rb b/lib/rspec_flaky/listener.rb
index 19cc0baa2d3..bf15130d17e 100644
--- a/lib/rspec_flaky/listener.rb
+++ b/lib/rspec_flaky/listener.rb
@@ -32,6 +32,7 @@ module RspecFlaky
flaky_examples[current_example.uid] = flaky_example
end
+ # rubocop:disable Gitlab/RailsLogger
def dump_summary(_)
RspecFlaky::Report.new(flaky_examples).write(RspecFlaky::Config.flaky_examples_report_path)
# write_report_file(flaky_examples, RspecFlaky::Config.flaky_examples_report_path)
@@ -45,6 +46,7 @@ module RspecFlaky
# write_report_file(new_flaky_examples, RspecFlaky::Config.new_flaky_examples_report_path)
end
end
+ # rubocop:enable Gitlab/RailsLogger
private
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 105ef417df3..88172e26c67 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -147,6 +147,7 @@ namespace :gitlab do
ENV['NICENESS'].presence
end
+ # rubocop:disable Gitlab/RailsLogger
def logger
return @logger if defined?(@logger)
@@ -159,5 +160,6 @@ namespace :gitlab do
Rails.logger
end
end
+ # rubocop:enable Gitlab/RailsLogger
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 07534e9c941..a4fee96753d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -124,9 +124,6 @@ msgstr ""
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
-msgid "%{counter_repositories} repositories, %{counter_wikis} wikis, %{counter_build_artifacts} build artifacts, %{counter_lfs_objects} LFS"
-msgstr ""
-
msgid "%{count} more"
msgstr ""
@@ -3266,6 +3263,9 @@ msgstr ""
msgid "Create new..."
msgstr ""
+msgid "Create project"
+msgstr ""
+
msgid "Create project label"
msgstr ""
@@ -6755,6 +6755,9 @@ msgstr ""
msgid "New Pipeline Schedule"
msgstr ""
+msgid "New Project"
+msgstr ""
+
msgid "New Snippet"
msgstr ""
@@ -8428,6 +8431,54 @@ msgstr ""
msgid "ProjectsDropdown|This feature requires browser localStorage support"
msgstr ""
+msgid "ProjectsNew|Allows you to immediately clone this project’s repository. Skip this if you plan to push up an existing repository."
+msgstr ""
+
+msgid "ProjectsNew|Blank"
+msgstr ""
+
+msgid "ProjectsNew|Blank project"
+msgstr ""
+
+msgid "ProjectsNew|Contact an administrator to enable options for importing your project."
+msgstr ""
+
+msgid "ProjectsNew|Create from template"
+msgstr ""
+
+msgid "ProjectsNew|Creating project & repository."
+msgstr ""
+
+msgid "ProjectsNew|Description format"
+msgstr ""
+
+msgid "ProjectsNew|Import"
+msgstr ""
+
+msgid "ProjectsNew|Import project"
+msgstr ""
+
+msgid "ProjectsNew|Initialize repository with a README"
+msgstr ""
+
+msgid "ProjectsNew|No import options available"
+msgstr ""
+
+msgid "ProjectsNew|Please wait a moment, this page will automatically refresh when ready."
+msgstr ""
+
+msgid "ProjectsNew|Project description %{tag_start}(optional)%{tag_end}"
+msgstr ""
+
+msgid "ProjectsNew|Template"
+msgstr ""
+
+msgid "ProjectsNew|Visibility Level"
+msgstr ""
+
+msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
+msgstr ""
+
msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr ""
@@ -8877,6 +8928,9 @@ msgstr ""
msgid "Repository storage"
msgstr ""
+msgid "Repository: %{counter_repositories} / Wikis: %{counter_wikis} / Build Artifacts: %{counter_build_artifacts} / LFS: %{counter_lfs_objects}"
+msgstr ""
+
msgid "Request Access"
msgstr ""
@@ -11224,10 +11278,10 @@ msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
msgstr ""
-msgid "To protect this issues confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private."
+msgid "To protect this issue's confidentiality, %{link_start}fork the project%{link_end} and set the forks visiblity to private."
msgstr ""
-msgid "To protect this issues confidentiality, a private fork of this project was selected."
+msgid "To protect this issue's confidentiality, a private fork of this project was selected."
msgstr ""
msgid "To see all the user's personal access tokens you must impersonate them first."
diff --git a/package.json b/package.json
index 5b255f55e33..5955790488a 100644
--- a/package.json
+++ b/package.json
@@ -38,7 +38,7 @@
"@babel/preset-env": "^7.4.4",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.3.2",
+ "@gitlab/ui": "^5.5.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
diff --git a/qa/.rspec_parallel b/qa/.rspec_parallel
new file mode 100644
index 00000000000..e5927927eaa
--- /dev/null
+++ b/qa/.rspec_parallel
@@ -0,0 +1,5 @@
+--color
+--format documentation
+--format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log
+--format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log
+--require spec_helper
diff --git a/qa/Gemfile b/qa/Gemfile
index 12994b85322..c46be8a0362 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,5 +1,6 @@
source 'https://rubygems.org'
+gem 'gitlab-qa'
gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'capybara', '~> 2.16.1'
gem 'capybara-screenshot', '~> 1.0.18'
@@ -11,3 +12,4 @@ gem 'nokogiri', '~> 1.10.3'
gem 'rspec-retry', '~> 0.6.1'
gem 'faker', '~> 1.6', '>= 1.6.6'
gem 'knapsack', '~> 1.17'
+gem 'parallel_tests', '~> 2.29'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 6b0635ed0e2..73aabf2c6ad 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -35,6 +35,7 @@ GEM
faker (1.9.3)
i18n (>= 0.7)
ffi (1.9.25)
+ gitlab-qa (4.0.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
i18n (0.9.1)
@@ -53,6 +54,9 @@ GEM
netrc (0.11.0)
nokogiri (1.10.3)
mini_portile2 (~> 2.4.0)
+ parallel (1.17.0)
+ parallel_tests (2.29.0)
+ parallel
pry (0.11.3)
coderay (~> 1.1.0)
method_source (~> 0.9.0)
@@ -104,8 +108,10 @@ DEPENDENCIES
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
faker (~> 1.6, >= 1.6.6)
+ gitlab-qa
knapsack (~> 1.17)
nokogiri (~> 1.10.3)
+ parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
diff --git a/qa/qa.rb b/qa/qa.rb
index 10d44b6f6d9..be73776425b 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -360,6 +360,7 @@ module QA
module Specs
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
+ autoload :ParallelRunner, 'qa/specs/parallel_runner'
module Helpers
autoload :Quarantine, 'qa/specs/helpers/quarantine'
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index defd85a5740..0918445d119 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -18,7 +18,7 @@ module QA
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern
- element :project_create_button, "submit 'Create project'" # rubocop:disable QA/ElementWithPattern
+ element :project_create_button, "submit _('Create project')" # rubocop:disable QA/ElementWithPattern
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
end
diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb
index ed0779b93cc..2987bb1a213 100644
--- a/qa/qa/runtime/browser.rb
+++ b/qa/qa/runtime/browser.rb
@@ -13,6 +13,8 @@ module QA
NotRespondingError = Class.new(RuntimeError)
+ CAPYBARA_MAX_WAIT_TIME = 10
+
def initialize
self.class.configure!
end
@@ -43,6 +45,8 @@ module QA
end
end
+ Capybara.server_port = 9887 + ENV['TEST_ENV_NUMBER'].to_i
+
return if Capybara.drivers.include?(:chrome)
Capybara.register_driver QA::Runtime::Env.browser do |app|
@@ -119,7 +123,7 @@ module QA
Capybara.configure do |config|
config.default_driver = QA::Runtime::Env.browser
config.javascript_driver = QA::Runtime::Env.browser
- config.default_max_wait_time = 10
+ config.default_max_wait_time = CAPYBARA_MAX_WAIT_TIME
# https://github.com/mattheworiordan/capybara-screenshot/issues/164
config.save_path = ::File.expand_path('../../tmp', __dir__)
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 96f337dc081..e78b9bece19 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'gitlab/qa'
+
module QA
module Runtime
module Env
@@ -7,6 +9,8 @@ module QA
attr_writer :personal_access_token, :ldap_username, :ldap_password
+ ENV_VARIABLES = Gitlab::QA::Runtime::Env::ENV_VARIABLES
+
# The environment variables used to indicate if the environment under test
# supports the given feature
SUPPORTED_FEATURES = {
@@ -173,8 +177,8 @@ module QA
ENV.fetch("GCLOUD_ACCOUNT_EMAIL")
end
- def gcloud_zone
- ENV.fetch('GCLOUD_ZONE')
+ def gcloud_region
+ ENV.fetch('GCLOUD_REGION')
end
def has_gcloud_credentials?
@@ -201,6 +205,10 @@ module QA
enabled?(ENV[SUPPORTED_FEATURES[feature]], default: true)
end
+ def runtime_scenario_attributes
+ ENV['QA_RUNTIME_SCENARIO_ATTRIBUTES']
+ end
+
private
def remote_grid_credentials
diff --git a/qa/qa/runtime/scenario.rb b/qa/qa/runtime/scenario.rb
index 5067322804b..3662ebe671b 100644
--- a/qa/qa/runtime/scenario.rb
+++ b/qa/qa/runtime/scenario.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'json'
+
module QA
module Runtime
##
@@ -24,6 +26,10 @@ module QA
end
end
+ def from_env(var)
+ JSON.parse(Runtime::Env.runtime_scenario_attributes).each { |k, v| define(k, v) }
+ end
+
def method_missing(name, *)
raise ArgumentError, "Scenario attribute `#{name}` not defined!"
end
diff --git a/qa/qa/scenario/shared_attributes.rb b/qa/qa/scenario/shared_attributes.rb
index 40d5c6b1ff1..52f50ec8c27 100644
--- a/qa/qa/scenario/shared_attributes.rb
+++ b/qa/qa/scenario/shared_attributes.rb
@@ -7,6 +7,7 @@ module QA
attribute :gitlab_address, '--address URL', 'Address of the instance to test'
attribute :enable_feature, '--enable-feature FEATURE_FLAG', 'Enable a feature before running tests'
+ attribute :parallel, '--parallel', 'Execute tests in parallel'
end
end
end
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index 16a89591eb2..40263e94065 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -28,17 +28,17 @@ module QA
create #{cluster_name}
#{auth_options}
--enable-basic-auth
- --zone #{Runtime::Env.gcloud_zone}
+ --region #{Runtime::Env.gcloud_region}
&& gcloud container clusters
get-credentials
- --zone #{Runtime::Env.gcloud_zone}
+ --region #{Runtime::Env.gcloud_region}
#{cluster_name}
CMD
@api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'`
@admin_user = "#{cluster_name}-admin"
- master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --zone #{Runtime::Env.gcloud_zone} --format 'json(masterAuth.username, masterAuth.password)'`)
+ master_auth = JSON.parse(`gcloud container clusters describe #{cluster_name} --region #{Runtime::Env.gcloud_region} --format 'json(masterAuth.username, masterAuth.password)'`)
shell <<~CMD.tr("\n", ' ')
kubectl config set-credentials #{@admin_user}
--username #{master_auth['masterAuth']['username']}
@@ -66,10 +66,10 @@ module QA
def remove!
shell <<~CMD.tr("\n", ' ')
gcloud container clusters delete
- --zone #{Runtime::Env.gcloud_zone}
- #{cluster_name}
- --quiet --async
- CMD
+ --region #{Runtime::Env.gcloud_region}
+ #{cluster_name}
+ --quiet --async
+ CMD
end
private
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index a9de64e357a..2363836d5e3 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -7,15 +7,15 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- source_group = Resource::Group.fabricate! do |group|
+ source_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'source-group'
end
- target_group = Resource::Group.fabricate! do |group|
+ target_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'target-group'
end
- project = Resource::Project.fabricate! do |project|
+ project = Resource::Project.fabricate_via_api! do |project|
project.group = source_group
project.name = 'transfer-project'
project.initialize_with_readme = true
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index d1747280227..f51c16f472c 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -9,7 +9,7 @@ module QA
user = Resource::User.fabricate_or_use(Runtime::Env.gitlab_qa_username_1, Runtime::Env.gitlab_qa_password_1)
- project = Resource::Project.fabricate! do |resource|
+ project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'add-member-project'
end
project.visit!
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 247cde38e52..11fd570d131 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create', :requires_admin do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/113
+ context 'Create', :requires_admin, :quarantine do
describe 'push after setting the file size limit via admin/application_settings' do
before(:context) do
@project = Resource::Project.fabricate_via_api! do |p|
diff --git a/qa/qa/specs/parallel_runner.rb b/qa/qa/specs/parallel_runner.rb
new file mode 100644
index 00000000000..b92fdb610b6
--- /dev/null
+++ b/qa/qa/specs/parallel_runner.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'open3'
+
+module QA
+ module Specs
+ module ParallelRunner
+ module_function
+
+ def run(args)
+ unless args.include?('--')
+ index = args.index { |opt| opt.include?('features') }
+
+ args.insert(index, '--') if index
+ end
+
+ env = {}
+ Runtime::Env::ENV_VARIABLES.each_key do |key|
+ env[key] = ENV[key] if ENV[key]
+ end
+ env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json
+ env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN']
+
+ cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}"
+ ::Open3.popen2e(env, cmd) do |_, out, wait|
+ out.each { |line| puts line }
+
+ exit wait.value.exitstatus
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb
index f1cb9378de8..6aa08cf77b4 100644
--- a/qa/qa/specs/runner.rb
+++ b/qa/qa/specs/runner.rb
@@ -1,8 +1,8 @@
# frozen_string_literal: true
+require 'knapsack'
require 'rspec/core'
require 'rspec/expectations'
-require 'knapsack'
module QA
module Specs
@@ -17,44 +17,56 @@ module QA
@options = []
end
- def perform
- args = []
- args.push('--tty') if tty
+ def paths_from_knapsack
+ allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
+
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Report specs:'
+ QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+ QA::Runtime::Logger.info 'Leftover specs:'
+ QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
+ QA::Runtime::Logger.info ''
+
+ ['--', allocator.node_tests]
+ end
+
+ def rspec_tags
+ tags_for_rspec = []
if tags.any?
- tags.each { |tag| args.push(['--tag', tag.to_s]) }
+ tags.each { |tag| tags_for_rspec.push(['--tag', tag.to_s]) }
else
- args.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any?
+ tags_for_rspec.push(%w[--tag ~orchestrated]) unless (%w[-t --tag] & options).any?
end
- args.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
+ tags_for_rspec.push(%w[--tag ~skip_signup_disabled]) if QA::Runtime::Env.signup_disabled?
QA::Runtime::Env.supported_features.each_key do |key|
- args.push(["--tag", "~requires_#{key}"]) unless QA::Runtime::Env.can_test? key
+ tags_for_rspec.push(%W[--tag ~requires_#{key}]) unless QA::Runtime::Env.can_test? key
end
- args.push(options)
+ tags_for_rspec
+ end
- Runtime::Browser.configure!
+ def perform
+ args = []
+ args.push('--tty') if tty
+ args.push(rspec_tags)
+ args.push(options)
if Runtime::Env.knapsack?
- allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator
-
- QA::Runtime::Logger.info ''
- QA::Runtime::Logger.info 'Report specs:'
- QA::Runtime::Logger.info allocator.report_node_tests.join(', ')
- QA::Runtime::Logger.info ''
- QA::Runtime::Logger.info 'Leftover specs:'
- QA::Runtime::Logger.info allocator.leftover_node_tests.join(', ')
- QA::Runtime::Logger.info ''
-
- args.push(['--', allocator.node_tests])
+ args.push(paths_from_knapsack)
else
args.push(DEFAULT_TEST_PATH_ARGS) unless options.any? { |opt| opt =~ %r{/features/} }
end
- RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
- abort if status.nonzero?
+ if Runtime::Scenario.attributes[:parallel]
+ ParallelRunner.run(args.flatten)
+ else
+ RSpec::Core::Runner.run(args.flatten, $stderr, $stdout).tap do |status|
+ abort if status.nonzero?
+ end
end
end
end
diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb
index b0477951967..26bcb2fe958 100644
--- a/qa/qa/tools/generate_perf_testdata.rb
+++ b/qa/qa/tools/generate_perf_testdata.rb
@@ -59,8 +59,8 @@ module QA
group_search_response = create_a_group_api_req(@group_name, @visibility)
group = JSON.parse(group_search_response.body)
@urls[:group_page] = group["web_url"]
- group["id"]
STDOUT.puts "Created a group: #{@urls[:group_page]}"
+ group["id"]
end
def create_project(group_id)
@@ -196,6 +196,7 @@ module QA
project_path = "#{@group_name}%2F#{@project_name}"
branch_name = "branch_with_many_commits-#{SecureRandom.hex(8)}"
file_name = "file_for_many_commits.txt"
+
create_a_branch_api_req(branch_name, project_path)
create_a_new_file_api_req(file_name, branch_name, project_path, "Initial commit for new file", "Initial file content")
create_mr_response = create_a_merge_request_api_req(project_path, branch_name, "master", "MR with many commits-#{SecureRandom.hex(8)}")
@@ -203,7 +204,7 @@ module QA
100.times do |i|
update_file_api_req(file_name, branch_name, project_path, Faker::Lorem.sentences(5).join(" "), Faker::Lorem.sentences(500).join("\n"))
end
- STDOUT.puts "Created an MR with many commits: #{@urls[:mr_with_many_commits]}"
+ STDOUT.puts "Using branch: #{branch_name}, created an MR with many commits: #{@urls[:mr_with_many_commits]}"
end
private
@@ -211,56 +212,88 @@ module QA
# API Requests
def create_a_discussion_on_issue_api_req(project_path_or_id, issue_id, body)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/discussions").url, "body=\"#{body}\""
+ end
end
def update_a_discussion_on_issue_api_req(project_path_or_id, mr_iid, discussion_id, resolved_status)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}"
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions/#{discussion_id}").url, "resolved=#{resolved_status}"
+ end
end
def create_a_discussion_on_mr_api_req(project_path_or_id, mr_iid, body)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url,
- "body=\"#{body}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests/#{mr_iid}/discussions").url, "body=\"#{body}\""
+ end
end
def create_a_label_api_req(project_path_or_id, name, color)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/labels").url, "name=#{name}&color=#{color}"
+ end
end
def create_a_todo_api_req(project_path_or_id, issue_id)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}/todo").url, nil
+ end
end
def create_an_issue_api_req(project_path_or_id, title, description)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues").url, "title=#{title}&description=#{description}"
+ end
end
def update_an_issue_api_req(project_path_or_id, issue_id, description, labels_list)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}"
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/issues/#{issue_id}").url, "description=#{description}&labels=#{labels_list}"
+ end
end
def create_a_project_api_req(project_name, group_id, visibility)
- post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{project_name}&namespace_id=#{group_id}&visibility=#{visibility}"
+ end
end
def create_a_group_api_req(group_name, visibility)
- post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{group_name}&path=#{group_name}&visibility=#{visibility}"
+ end
end
def create_a_branch_api_req(branch_name, project_path_or_id)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/branches").url, "branch=#{branch_name}&ref=master"
+ end
end
def create_a_new_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ end
end
def create_a_merge_request_api_req(project_path_or_id, source_branch, target_branch, mr_title)
- post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}"
+ call_api(expected_response_code: 201) do
+ post Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/merge_requests").url, "source_branch=#{source_branch}&target_branch=#{target_branch}&title=#{mr_title}"
+ end
end
def update_file_api_req(file_path, branch_name, project_path_or_id, commit_message, content)
- put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ call_api(expected_response_code: 200) do
+ put Runtime::API::Request.new(@api_client, "/projects/#{project_path_or_id}/repository/files/#{file_path}").url, "branch=#{branch_name}&commit_message=\"#{commit_message}\"&content=\"#{content}\""
+ end
+ end
+
+ def call_api(expected_response_code: 200)
+ response = yield
+ raise "API call failed with response code: #{response.code} and body: #{response.body}" unless response.code == expected_response_code
+
+ response
end
end
end
diff --git a/qa/spec/page/logging_spec.rb b/qa/spec/page/logging_spec.rb
index 0f1ed039149..92a4f7b40e6 100644
--- a/qa/spec/page/logging_spec.rb
+++ b/qa/spec/page/logging_spec.rb
@@ -91,26 +91,26 @@ describe QA::Support::Page::Logging do
it 'logs has_element?' do
expect { subject.has_element?(:element) }
- .to output(/has_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_element? with text' do
expect { subject.has_element?(:element, text: "some text") }
- .to output(/has_element\? :element with text \"some text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_element\? :element with text \"some text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element?' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element) }
- .to output(/has_no_element\? :element \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_no_element\? :element \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_no_element? with text' do
allow(page).to receive(:has_no_css?).and_return(true)
expect { subject.has_no_element?(:element, text: "more text") }
- .to output(/has_no_element\? :element with text \"more text\" \(wait: 2\) returned: true/).to_stdout_from_any_process
+ .to output(/has_no_element\? :element with text \"more text\" \(wait: #{QA::Runtime::Browser::CAPYBARA_MAX_WAIT_TIME}\) returned: true/).to_stdout_from_any_process
end
it 'logs has_text?' do
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index f25dbf3a8ab..21bfd2876a9 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -8,6 +8,10 @@ if ENV['CI'] && QA::Runtime::Env.knapsack? && !ENV['NO_KNAPSACK']
Knapsack::Adapters::RSpecAdapter.bind
end
+QA::Runtime::Browser.configure!
+
+QA::Runtime::Scenario.from_env(QA::Runtime::Env.runtime_scenario_attributes) if QA::Runtime::Env.runtime_scenario_attributes
+
%w[helpers shared_examples].each do |d|
Dir[::File.join(__dir__, d, '**', '*.rb')].each { |f| require f }
end
diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb
new file mode 100644
index 00000000000..67d94a1f648
--- /dev/null
+++ b/qa/spec/specs/parallel_runner_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+describe QA::Specs::ParallelRunner do
+ include Helpers::StubENV
+
+ before do
+ allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true)
+ stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation')
+ end
+
+ it 'passes args to parallel_tests' do
+ expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
+
+ subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS])
+ end
+
+ it 'passes a given test path to parallel_tests and adds a separator' do
+ expect_cli_arguments(%w[-- qa/specs/features/foo])
+
+ subject.run(%w[qa/specs/features/foo])
+ end
+
+ it 'passes tags and test paths to parallel_tests and adds a separator' do
+ expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+
+ subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar])
+ end
+
+ it 'passes tags and test paths with separators to parallel_tests' do
+ expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+
+ subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar])
+ end
+
+ it 'passes supported environment variables' do
+ # Test only env vars starting with GITLAB because some of the others
+ # affect how the runner behaves, and we're not concerned with those
+ # behaviors in this test
+ gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') }
+
+ gitlab_env_vars.each do |k, v|
+ stub_env(k, v)
+ end
+
+ gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}'
+
+ expect_cli_arguments([], gitlab_env_vars)
+
+ subject.run([])
+ end
+
+ def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' })
+ cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}"
+ expect(Open3).to receive(:popen2e)
+ .with(hash_including(env), cmd)
+ .and_return(0)
+ end
+end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index f94145d148e..6c533c6dc7d 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -58,11 +58,11 @@ describe QA::Specs::Runner do
end
end
- context 'when "-- qa/specs/features/foo" is set as options' do
- subject { described_class.new.tap { |runner| runner.options = %w[-- qa/specs/features/foo] } }
+ context 'when "--tag smoke" and "qa/specs/features/foo" are set as options' do
+ subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } }
- it 'passes the given tests path and excludes the orchestrated tag' do
- expect_rspec_runner_arguments(['--tag', '~orchestrated', '--', 'qa/specs/features/foo'])
+ it 'focuses on the given tag and includes the path without excluding the orchestrated tag' do
+ expect_rspec_runner_arguments(['--tag', 'smoke', 'qa/specs/features/foo'])
subject.perform
end
diff --git a/rubocop/cop/gitlab/rails_logger.rb b/rubocop/cop/gitlab/rails_logger.rb
new file mode 100644
index 00000000000..d1a06a9a100
--- /dev/null
+++ b/rubocop/cop/gitlab/rails_logger.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require_relative '../../code_reuse_helpers'
+
+module RuboCop
+ module Cop
+ module Gitlab
+ class RailsLogger < ::RuboCop::Cop::Cop
+ include CodeReuseHelpers
+
+ # This cop checks for the Rails.logger in the codebase
+ #
+ # @example
+ #
+ # # bad
+ # Rails.logger.error("Project #{project.full_path} could not be saved")
+ #
+ # # good
+ # Gitlab::AppLogger.error("Project %{project_path} could not be saved" % { project_path: project.full_path })
+ MSG = 'Use a structured JSON logger instead of `Rails.logger`. ' \
+ 'https://docs.gitlab.com/ee/development/logging.html'.freeze
+
+ def_node_matcher :rails_logger?, <<~PATTERN
+ (send (const nil? :Rails) :logger ... )
+ PATTERN
+
+ WHITELISTED_DIRECTORIES = %w[
+ spec
+ ].freeze
+
+ def on_send(node)
+ return if in_whitelisted_directory?(node)
+ return unless rails_logger?(node)
+
+ add_offense(node, location: :expression)
+ end
+
+ def in_whitelisted_directory?(node)
+ path = file_path_for_node(node)
+
+ WHITELISTED_DIRECTORIES.any? do |directory|
+ path.start_with?(
+ File.join(rails_root, directory),
+ File.join(rails_root, 'ee', directory)
+ )
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 27c63d92ae5..ba61a634d97 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -3,6 +3,7 @@ require_relative 'cop/gitlab/predicate_memoization'
require_relative 'cop/gitlab/httparty'
require_relative 'cop/gitlab/finder_with_find_by'
require_relative 'cop/gitlab/union'
+require_relative 'cop/gitlab/rails_logger'
require_relative 'cop/include_action_view_context'
require_relative 'cop/include_sidekiq_worker'
require_relative 'cop/safe_params'
diff --git a/scripts/trigger-build-docs b/scripts/trigger-build-docs
index dfc8ee6050a..0fc8f6fbd4b 100755
--- a/scripts/trigger-build-docs
+++ b/scripts/trigger-build-docs
@@ -105,8 +105,8 @@ def trigger_pipeline
puts ""
puts app_url
puts ""
- puts "=> For more information, read the documentation"
- puts "=> https://docs.gitlab.com/ee/development/writing_documentation.html#previewing-the-changes-live"
+ puts "=> For more information, see the documentation"
+ puts "=> https://docs.gitlab.com/ee/development/documentation/index.html#previewing-the-changes-live"
puts ""
puts "=> If something doesn't work, drop a line in the #docs chat channel."
puts ""
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index 9878f88a395..cc6adc0a6c6 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -878,7 +878,7 @@ describe Projects::MergeRequestsController do
expect(control_count).to be <= 137
end
- it 'has no N+1 issues for environments', :request_store, retry: 0 do
+ it 'has no N+1 SQL issues for environments', :request_store, retry: 0 do
# First run to insert test data from lets, which does take up some 30 queries
get_ci_environments_status
@@ -893,18 +893,65 @@ describe Projects::MergeRequestsController do
leeway = 11
expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway)
end
+ end
- def get_ci_environments_status(extra_params = {})
- params = {
- namespace_id: merge_request.project.namespace.to_param,
- project_id: merge_request.project,
- id: merge_request.iid,
- format: 'json'
- }
+ context 'when a merge request has multiple environments with deployments' do
+ let(:sha) { merge_request.diff_head_sha }
+ let(:ref) { merge_request.source_branch }
+
+ let!(:build) { create(:ci_build, pipeline: pipeline) }
+ let!(:pipeline) { create(:ci_pipeline, sha: sha, project: project) }
+ let!(:environment) { create(:environment, name: 'env_a', project: project) }
+ let!(:another_environment) { create(:environment, name: 'env_b', project: project) }
+
+ before do
+ merge_request.update_head_pipeline
+
+ create(:deployment, :succeed, environment: environment, sha: sha, ref: ref, deployable: build)
+ create(:deployment, :succeed, environment: another_environment, sha: sha, ref: ref, deployable: build)
+ end
+
+ it 'exposes multiple environment statuses' do
+ get_ci_environments_status
+
+ expect(json_response.count).to eq 2
+ end
+
+ context 'when route map is not present in the project' do
+ it 'does not have N+1 Gitaly requests for environments', :request_store do
+ expect(merge_request).to be_present
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
+ end
- get :ci_environments_status, params: params.merge(extra_params)
+ context 'when there is route map present in a project' do
+ before do
+ allow_any_instance_of(EnvironmentStatus)
+ .to receive(:has_route_map?)
+ .and_return(true)
+ end
+
+ it 'does not have N+1 Gitaly requests for diff files', :request_store do
+ expect(merge_request.merge_request_diff.merge_request_diff_files).to be_many
+
+ expect { get_ci_environments_status }
+ .to change { Gitlab::GitalyClient.get_request_count }.by_at_most(1)
+ end
end
end
+
+ def get_ci_environments_status(extra_params = {})
+ params = {
+ namespace_id: merge_request.project.namespace.to_param,
+ project_id: merge_request.project,
+ id: merge_request.iid,
+ format: 'json'
+ }
+
+ get :ci_environments_status, params: params.merge(extra_params)
+ end
end
describe 'GET pipeline_status.json' do
diff --git a/spec/features/admin/admin_sees_project_statistics_spec.rb b/spec/features/admin/admin_sees_project_statistics_spec.rb
index b5323a1c76d..ecd0aab925b 100644
--- a/spec/features/admin/admin_sees_project_statistics_spec.rb
+++ b/spec/features/admin/admin_sees_project_statistics_spec.rb
@@ -15,7 +15,7 @@ describe "Admin > Admin sees project statistics" do
let(:project) { create(:project, :repository) }
it "shows project statistics" do
- expect(page).to have_content("Storage: 0 Bytes (0 Bytes repositories, 0 Bytes wikis, 0 Bytes build artifacts, 0 Bytes LFS)")
+ expect(page).to have_content("Storage: 0 Bytes (Repository: 0 Bytes / Wikis: 0 Bytes / Build Artifacts: 0 Bytes / LFS: 0 Bytes)")
end
end
diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index 8fb2e37e269..c3310a4a132 100644
--- a/spec/features/dashboard/milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -29,5 +29,19 @@ describe 'Dashboard > Milestones' do
expect(page).to have_content(milestone.title)
expect(page).to have_content(group.name)
end
+
+ describe 'new milestones dropdown', :js do
+ it 'takes user to a new milestone page', :js do
+ find('.new-project-item-select-button').click
+
+ page.within('.select2-results') do
+ first('.select2-result-label').click
+ end
+
+ find('.new-project-item-link').click
+
+ expect(current_path).to eq(new_group_milestone_path(group))
+ end
+ end
end
end
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 1cc47cd6bd1..7ddd5c12cdf 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'User uses header search field' do
+describe 'User uses header search field', :js do
include FilteredSearchHelpers
let(:project) { create(:project) }
@@ -11,57 +11,12 @@ describe 'User uses header search field' do
sign_in(user)
end
- context 'when user is in a global scope', :js do
+ shared_examples 'search field examples' do
before do
- visit(root_path)
- page.find('#search').click
+ visit(url)
end
- context 'when clicking issues' do
- it 'shows assigned issues' do
- find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created issues' do
- find('.search-input-container .dropdown-menu').click_link("Issues I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
-
- context 'when clicking merge requests' do
- let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
-
- it 'shows assigned merge requests' do
- find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([assignee_token(user.name)])
- expect_filtered_search_input_empty
- end
-
- it 'shows created merge requests' do
- find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
-
- expect(page).to have_selector('.filtered-search')
- expect_tokens([author_token(user.name)])
- expect_filtered_search_input_empty
- end
- end
- end
-
- context 'when user is in a project scope' do
- before do
- visit(project_path(project))
- end
-
- it 'starts searching by pressing the enter key', :js do
+ it 'starts searching by pressing the enter key' do
fill_in('search', with: 'gitlab')
find('#search').native.send_keys(:enter)
@@ -70,30 +25,31 @@ describe 'User uses header search field' do
end
end
- context 'when clicking the search field', :js do
+ context 'when clicking the search field' do
before do
page.find('#search').click
+ wait_for_all_requests
end
it 'shows category search dropdown' do
- expect(page).to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
context 'when clicking issues' do
let!(:issue) { create(:issue, project: project, author: user, assignees: [user]) }
it 'shows assigned issues' do
- find('.dropdown-menu').click_link('Issues assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Issues assigned to me')
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created issues' do
- find('.dropdown-menu').click_link("Issues I've created")
+ find('.search-input-container .dropdown-menu').click_link("Issues I've created")
- expect(page).to have_selector('.filtered-search')
+ expect(page).to have_selector('.issues-list .issue')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
@@ -103,33 +59,77 @@ describe 'User uses header search field' do
let!(:merge_request) { create(:merge_request, source_project: project, author: user, assignees: [user]) }
it 'shows assigned merge requests' do
- find('.dropdown-menu').click_link('Merge requests assigned to me')
+ find('.search-input-container .dropdown-menu').click_link('Merge requests assigned to me')
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([assignee_token(user.name)])
expect_filtered_search_input_empty
end
it 'shows created merge requests' do
- find('.dropdown-menu').click_link("Merge requests I've created")
+ find('.search-input-container .dropdown-menu').click_link("Merge requests I've created")
- expect(page).to have_selector('.merge-requests-holder')
+ expect(page).to have_selector('.mr-list .merge-request')
expect_tokens([author_token(user.name)])
expect_filtered_search_input_empty
end
end
end
- context 'when entering text into the search field', :js do
+ context 'when entering text into the search field' do
before do
page.within('.search-input-wrap') do
- fill_in('search', with: project.name[0..3])
+ fill_in('search', with: scope_name.first(4))
end
end
it 'does not display the category search dropdown' do
- expect(page).not_to have_selector('.dropdown-header', text: /#{project.name}/i)
+ expect(page).not_to have_selector('.dropdown-header', text: /#{scope_name}/i)
end
end
end
+
+ context 'when user is in a global scope' do
+ include_examples 'search field examples' do
+ let(:url) { root_path }
+ let(:scope_name) { 'All GitLab' }
+ end
+ end
+
+ context 'when user is in a project scope' do
+ include_examples 'search field examples' do
+ let(:url) { project_path(project) }
+ let(:scope_name) { project.name }
+ end
+ end
+
+ context 'when user is in a group scope' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(group) }
+ let(:scope_name) { group.name }
+ end
+ end
+
+ context 'when user is in a subgroup scope' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, :public, parent: group) }
+ let(:project) { create(:project, namespace: subgroup) }
+
+ before do
+ group.add_owner(user)
+ subgroup.add_owner(user)
+ end
+
+ include_examples 'search field examples' do
+ let(:url) { group_path(subgroup) }
+ let(:scope_name) { subgroup.name }
+ end
+ end
end
diff --git a/spec/frontend/api_spec.js b/spec/frontend/api_spec.js
index 0188d12a57d..7004373be0e 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -412,6 +412,22 @@ describe('Api', () => {
});
});
+ describe('user counts', () => {
+ it('fetches single user counts', done => {
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/user_counts`;
+ mock.onGet(expectedUrl).reply(200, {
+ merge_requests: 4,
+ });
+
+ Api.userCounts()
+ .then(({ data }) => {
+ expect(data.merge_requests).toBe(4);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('user status', () => {
it('fetches single user status', done => {
const userId = '123456';
diff --git a/spec/frontend/commons/nav/user_merge_requests_spec.js b/spec/frontend/commons/nav/user_merge_requests_spec.js
new file mode 100644
index 00000000000..4da6d53557a
--- /dev/null
+++ b/spec/frontend/commons/nav/user_merge_requests_spec.js
@@ -0,0 +1,113 @@
+import {
+ openUserCountsBroadcast,
+ closeUserCountsBroadcast,
+ refreshUserMergeRequestCounts,
+} from '~/commons/nav/user_merge_requests';
+import Api from '~/api';
+
+jest.mock('~/api');
+
+const TEST_COUNT = 1000;
+const MR_COUNT_CLASS = 'merge-requests-count';
+
+describe('User Merge Requests', () => {
+ let channelMock;
+ let newBroadcastChannelMock;
+
+ beforeEach(() => {
+ global.gon.current_user_id = 123;
+
+ channelMock = {
+ postMessage: jest.fn(),
+ close: jest.fn(),
+ };
+ newBroadcastChannelMock = jest.fn().mockImplementation(() => channelMock);
+
+ global.BroadcastChannel = newBroadcastChannelMock;
+ setFixtures(`<div class="${MR_COUNT_CLASS}">0</div>`);
+ });
+
+ const findMRCountText = () => document.body.querySelector(`.${MR_COUNT_CLASS}`).textContent;
+
+ describe('refreshUserMergeRequestCounts', () => {
+ beforeEach(() => {
+ Api.userCounts.mockReturnValue(
+ Promise.resolve({
+ data: { merge_requests: TEST_COUNT },
+ }),
+ );
+ });
+
+ describe('with open broadcast channel', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+
+ return refreshUserMergeRequestCounts();
+ });
+
+ it('updates the top count of merge requests', () => {
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('calls the API', () => {
+ expect(Api.userCounts).toHaveBeenCalled();
+ });
+
+ it('posts count to BroadcastChannel', () => {
+ expect(channelMock.postMessage).toHaveBeenCalledWith(TEST_COUNT);
+ });
+ });
+
+ describe('without open broadcast channel', () => {
+ beforeEach(() => refreshUserMergeRequestCounts());
+
+ it('does not post anything', () => {
+ expect(channelMock.postMessage).not.toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('openUserCountsBroadcast', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('creates BroadcastChannel that updates DOM on message received', () => {
+ expect(findMRCountText()).toEqual('0');
+
+ channelMock.onmessage({ data: TEST_COUNT });
+
+ expect(findMRCountText()).toEqual(TEST_COUNT.toLocaleString());
+ });
+
+ it('closes if called while already open', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ openUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+
+ describe('closeUserCountsBroadcast', () => {
+ describe('when not opened', () => {
+ it('does nothing', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('when opened', () => {
+ beforeEach(() => {
+ openUserCountsBroadcast();
+ });
+
+ it('closes', () => {
+ expect(channelMock.close).not.toHaveBeenCalled();
+
+ closeUserCountsBroadcast();
+
+ expect(channelMock.close).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
index a241c764df7..47bdc677068 100644
--- a/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
+++ b/spec/frontend/confidential_merge_request/components/__snapshots__/project_form_group_spec.js.snap
@@ -2,7 +2,7 @@
exports[`Confidential merge request project form group component renders empty state when response is empty 1`] = `
<div
- class="form-group"
+ class="confidential-merge-request-fork-group form-group"
>
<label>
Project
@@ -19,7 +19,7 @@ exports[`Confidential merge request project form group component renders empty s
<br />
<span>
- To protect this issues confidentiality,
+ To protect this issue's confidentiality,
<a
class="help-link"
href="https://test.com"
@@ -30,7 +30,7 @@ exports[`Confidential merge request project form group component renders empty s
</span>
<gllink-stub
- class="help-link"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
href="/help"
target="_blank"
>
@@ -52,7 +52,7 @@ exports[`Confidential merge request project form group component renders empty s
exports[`Confidential merge request project form group component renders fork dropdown 1`] = `
<div
- class="form-group"
+ class="confidential-merge-request-fork-group form-group"
>
<label>
Project
@@ -69,7 +69,7 @@ exports[`Confidential merge request project form group component renders fork dr
<br />
<span>
- To protect this issues confidentiality,
+ To protect this issue's confidentiality,
<a
class="help-link"
href="https://test.com"
@@ -80,7 +80,7 @@ exports[`Confidential merge request project form group component renders fork dr
</span>
<gllink-stub
- class="help-link"
+ class="w-auto p-0 d-inline-block text-primary bg-transparent"
href="/help"
target="_blank"
>
diff --git a/spec/frontend/error_tracking_settings/mock.js b/spec/frontend/error_tracking_settings/mock.js
index 42233f82d54..8c5bfd08beb 100644
--- a/spec/frontend/error_tracking_settings/mock.js
+++ b/spec/frontend/error_tracking_settings/mock.js
@@ -1,5 +1,5 @@
import createStore from '~/error_tracking_settings/store';
-import { TEST_HOST } from '../helpers/test_constants';
+import { TEST_HOST } from 'helpers/test_constants';
const defaultStore = createStore();
diff --git a/spec/graphql/gitlab_schema_spec.rb b/spec/graphql/gitlab_schema_spec.rb
index 93b86b9b812..dec6b23d72a 100644
--- a/spec/graphql/gitlab_schema_spec.rb
+++ b/spec/graphql/gitlab_schema_spec.rb
@@ -143,6 +143,26 @@ describe GitlabSchema do
end
end
+ context 'for classes that are not ActiveRecord subclasses and have implemented .lazy_find' do
+ it 'returns the correct record' do
+ note = create(:discussion_note_on_merge_request)
+
+ result = described_class.object_from_id(note.to_global_id)
+
+ expect(result.__sync).to eq(note)
+ end
+
+ it 'batchloads the queries' do
+ note1 = create(:discussion_note_on_merge_request)
+ note2 = create(:discussion_note_on_merge_request)
+
+ expect do
+ [described_class.object_from_id(note1.to_global_id),
+ described_class.object_from_id(note2.to_global_id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
context 'for other classes' do
# We cannot use an anonymous class here as `GlobalID` expects `.name` not
# to return `nil`
diff --git a/spec/graphql/types/diff_refs_type_spec.rb b/spec/graphql/types/diff_refs_type_spec.rb
new file mode 100644
index 00000000000..91017c827ad
--- /dev/null
+++ b/spec/graphql/types/diff_refs_type_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['DiffRefs'] do
+ it { expect(described_class.graphql_name).to eq('DiffRefs') }
+
+ it { expect(described_class).to have_graphql_fields(:base_sha, :head_sha, :start_sha) }
+end
diff --git a/spec/graphql/types/merge_request_type_spec.rb b/spec/graphql/types/merge_request_type_spec.rb
index f73bd062369..59bd0123d88 100644
--- a/spec/graphql/types/merge_request_type_spec.rb
+++ b/spec/graphql/types/merge_request_type_spec.rb
@@ -13,7 +13,7 @@ describe GitlabSchema.types['MergeRequest'] do
description_html state created_at updated_at source_project target_project
project project_id source_project_id target_project_id source_branch
target_branch work_in_progress merge_when_pipeline_succeeds diff_head_sha
- merge_commit_sha user_notes_count should_remove_source_branch
+ merge_commit_sha user_notes_count should_remove_source_branch diff_refs
force_remove_source_branch merge_status in_progress_merge_commit_sha
merge_error allow_collaboration should_be_rebased rebase_commit_sha
rebase_in_progress merge_commit_message default_merge_commit_message
diff --git a/spec/graphql/types/notes/diff_position_type_spec.rb b/spec/graphql/types/notes/diff_position_type_spec.rb
index 2f8724d7f0d..345bca8f702 100644
--- a/spec/graphql/types/notes/diff_position_type_spec.rb
+++ b/spec/graphql/types/notes/diff_position_type_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe GitlabSchema.types['DiffPosition'] do
it 'exposes the expected fields' do
- expected_fields = [:head_sha, :base_sha, :start_sha, :file_path, :old_path,
+ expected_fields = [:diff_refs, :file_path, :old_path,
:new_path, :position_type, :old_line, :new_line, :x, :y,
:width, :height]
diff --git a/spec/graphql/types/notes/discussion_type_spec.rb b/spec/graphql/types/notes/discussion_type_spec.rb
index 2a1eb0efd35..ba7fc961212 100644
--- a/spec/graphql/types/notes/discussion_type_spec.rb
+++ b/spec/graphql/types/notes/discussion_type_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
describe GitlabSchema.types['Discussion'] do
- it { is_expected.to have_graphql_fields(:id, :created_at, :notes) }
+ it { is_expected.to have_graphql_fields(:id, :created_at, :notes, :reply_id) }
it { is_expected.to require_graphql_authorizations(:read_note) }
end
diff --git a/spec/helpers/markup_helper_spec.rb b/spec/helpers/markup_helper_spec.rb
index 6c6410cee9b..f6e1720e113 100644
--- a/spec/helpers/markup_helper_spec.rb
+++ b/spec/helpers/markup_helper_spec.rb
@@ -268,7 +268,7 @@ describe MarkupHelper do
end
end
- describe 'markup' do
+ describe '#markup' do
let(:content) { 'Noël' }
it 'preserves encoding' do
@@ -302,6 +302,77 @@ describe MarkupHelper do
end
end
+ describe '#markup_unsafe' do
+ subject { helper.markup_unsafe(file_name, text, context) }
+
+ let(:file_name) { 'foo.bar' }
+ let(:text) { 'Noël' }
+ let(:project_base) { build(:project, :repository) }
+ let(:context) { { project: project_base } }
+
+ context 'when text is missing' do
+ let(:text) { nil }
+
+ it 'returns an empty string' do
+ is_expected.to eq('')
+ end
+ end
+
+ context 'when file is a markdown file' do
+ let(:file_name) { 'foo.md' }
+
+ it 'returns html (rendered by Banzai)' do
+ expected_html = '<p data-sourcepos="1:1-1:5" dir="auto">Noël</p>'
+
+ expect(Banzai).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+
+ context 'when renderer returns an error' do
+ before do
+ allow(Banzai).to receive(:render).and_raise("An error")
+ end
+
+ it 'returns html (rendered by ActionView:TextHelper)' do
+ is_expected.to eq('<p>Noël</p>')
+ end
+ end
+ end
+
+ context 'when file is asciidoc file' do
+ let(:file_name) { 'foo.adoc' }
+
+ it 'returns html (rendered by Gitlab::Asciidoc)' do
+ expected_html = "<div>\n<p>Noël</p>\n</div>"
+
+ expect(Gitlab::Asciidoc).to receive(:render).with(text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+
+ context 'when file is a regular text file' do
+ let(:file_name) { 'foo.txt' }
+
+ it 'returns html (rendered by ActionView::TagHelper)' do
+ is_expected.to eq('<pre class="plain-readme">Noël</pre>')
+ end
+ end
+
+ context 'when file has an unknown type' do
+ let(:file_name) { 'foo' }
+
+ it 'returns html (rendered by Gitlab::OtherMarkup)' do
+ expected_html = 'Noël'
+
+ expect(Gitlab::OtherMarkup).to receive(:render).with(file_name, text, context) { expected_html }
+
+ is_expected.to eq(expected_html)
+ end
+ end
+ end
+
describe '#first_line_in_markdown' do
shared_examples_for 'common markdown examples' do
let(:project_base) { build(:project, :repository) }
diff --git a/spec/helpers/storage_helper_spec.rb b/spec/helpers/storage_helper_spec.rb
index 62c00964524..4bd0fbb76ca 100644
--- a/spec/helpers/storage_helper_spec.rb
+++ b/spec/helpers/storage_helper_spec.rb
@@ -31,7 +31,7 @@ describe StorageHelper do
build_artifacts_size: 30.megabytes))
end
- let(:message) { '10 KB repositories, 10 Bytes wikis, 30 MB build artifacts, 20 GB LFS' }
+ let(:message) { 'Repository: 10 KB / Wikis: 10 Bytes / Build Artifacts: 30 MB / LFS: 20 GB' }
it 'works on ProjectStatistics' do
expect(helper.storage_counters_details(project.statistics)).to eq(message)
diff --git a/spec/javascripts/issuable_spec.js b/spec/javascripts/issuable_spec.js
index 25543053eba..4d57bfb1b33 100644
--- a/spec/javascripts/issuable_spec.js
+++ b/spec/javascripts/issuable_spec.js
@@ -2,14 +2,14 @@ import $ from 'jquery';
import MockAdaptor from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import IssuableIndex from '~/issuable_index';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
describe('Issuable', () => {
- let Issuable;
describe('initBulkUpdate', () => {
it('should not set bulkUpdateSidebar', () => {
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).not.toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeNull();
});
it('should set bulkUpdateSidebar', () => {
@@ -17,9 +17,9 @@ describe('Issuable', () => {
element.classList.add('issues-bulk-update');
document.body.appendChild(element);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
- expect(Issuable.bulkUpdateSidebar).toBeDefined();
+ expect(issuableInitBulkUpdateSidebar.bulkUpdateSidebar).toBeDefined();
});
});
@@ -36,7 +36,7 @@ describe('Issuable', () => {
input.setAttribute('id', 'issuable_email');
document.body.appendChild(input);
- Issuable = new IssuableIndex('issue_');
+ new IssuableIndex('issue_'); // eslint-disable-line no-new
mock = new MockAdaptor(axios);
diff --git a/spec/javascripts/monitoring/charts/single_stat_spec.js b/spec/javascripts/monitoring/charts/single_stat_spec.js
index 12b73002f97..127a4a7955a 100644
--- a/spec/javascripts/monitoring/charts/single_stat_spec.js
+++ b/spec/javascripts/monitoring/charts/single_stat_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import SingleStatChart from '~/monitoring/components/charts/single_stat.vue';
+import { graphDataPrometheusQuery } from '../mock_data';
describe('Single Stat Chart component', () => {
let singleStatChart;
@@ -7,9 +8,7 @@ describe('Single Stat Chart component', () => {
beforeEach(() => {
singleStatChart = shallowMount(SingleStatChart, {
propsData: {
- title: 'Time to render',
- value: 1,
- unit: 'sec',
+ graphData: graphDataPrometheusQuery,
},
});
});
@@ -19,9 +18,9 @@ describe('Single Stat Chart component', () => {
});
describe('computed', () => {
- describe('valueWithUnit', () => {
+ describe('engineeringNotation', () => {
it('should interpolate the value and unit props', () => {
- expect(singleStatChart.vm.valueWithUnit).toBe('1sec');
+ expect(singleStatChart.vm.engineeringNotation).toBe('91MB');
});
});
});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 7bbb215475a..85e660d3925 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -935,3 +935,75 @@ export const dashboardGitResponse = [
default: false,
},
];
+
+export const graphDataPrometheusQuery = {
+ title: 'Super Chart A2',
+ type: 'single-stat',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: { job: 'prometheus' },
+ value: ['2019-06-26T21:03:20.881Z', 91],
+ },
+ ],
+ },
+ ],
+};
+
+export const graphDataPrometheusQueryRange = {
+ title: 'Super Chart A1',
+ type: 'area',
+ weight: 2,
+ metrics: [
+ {
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ },
+ ],
+ queries: [
+ {
+ metricId: null,
+ id: 'metric_a1',
+ metric_id: 2,
+ query_range:
+ 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ unit: 'MB',
+ label: 'Total Consumption',
+ prometheus_endpoint_path:
+ '/root/kubernetes-gke-project/environments/35/prometheus/api/v1/query?query=max%28go_memstats_alloc_bytes%7Bjob%3D%22prometheus%22%7D%29+by+%28job%29+%2F1024%2F1024',
+ result: [
+ {
+ metric: {},
+ values: [[1495700554.925, '8.0390625'], [1495700614.925, '8.0390625']],
+ },
+ ],
+ },
+ ],
+};
diff --git a/spec/javascripts/monitoring/store/mutations_spec.js b/spec/javascripts/monitoring/store/mutations_spec.js
index 91580366531..43776b1b7f2 100644
--- a/spec/javascripts/monitoring/store/mutations_spec.js
+++ b/spec/javascripts/monitoring/store/mutations_spec.js
@@ -115,12 +115,14 @@ describe('Monitoring mutations', () => {
environmentsEndpoint: 'environments.json',
deploymentsEndpoint: 'deployments.json',
dashboardEndpoint: 'dashboard.json',
+ projectPath: '/gitlab-org/gitlab-ce',
});
expect(stateCopy.metricsEndpoint).toEqual('additional_metrics.json');
expect(stateCopy.environmentsEndpoint).toEqual('environments.json');
expect(stateCopy.deploymentsEndpoint).toEqual('deployments.json');
expect(stateCopy.dashboardEndpoint).toEqual('dashboard.json');
+ expect(stateCopy.projectPath).toEqual('/gitlab-org/gitlab-ce');
});
});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index e3c455d1686..5570d57b8b2 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,5 +1,6 @@
-import { getTimeDiff } from '~/monitoring/utils';
+import { getTimeDiff, graphDataValidatorForValues } from '~/monitoring/utils';
import { timeWindows } from '~/monitoring/constants';
+import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
it('defaults to an 8 hour (28800s) difference', () => {
@@ -27,3 +28,27 @@ describe('getTimeDiff', () => {
});
});
});
+
+describe('graphDataValidatorForValues', () => {
+ /*
+ * When dealing with a metric using the query format, e.g.
+ * query: 'max(go_memstats_alloc_bytes{job="prometheus"}) by (job) /1024/1024'
+ * the validator will look for the `value` key instead of `values`
+ */
+ it('validates data with the query format', () => {
+ const validGraphData = graphDataValidatorForValues(true, graphDataPrometheusQuery);
+
+ expect(validGraphData).toBe(true);
+ });
+
+ /*
+ * When dealing with a metric using the query?range format, e.g.
+ * query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024',
+ * the validator will look for the `values` key instead of `value`
+ */
+ it('validates data with the query_range format', () => {
+ const validGraphData = graphDataValidatorForValues(false, graphDataPrometheusQueryRange);
+
+ expect(validGraphData).toBe(true);
+ });
+});
diff --git a/spec/javascripts/notes/components/comment_form_spec.js b/spec/javascripts/notes/components/comment_form_spec.js
index 362963ddaf4..88c86746992 100644
--- a/spec/javascripts/notes/components/comment_form_spec.js
+++ b/spec/javascripts/notes/components/comment_form_spec.js
@@ -251,6 +251,21 @@ describe('issue_comment_form component', () => {
});
});
});
+
+ describe('when toggling state', () => {
+ it('should update MR count', done => {
+ spyOn(vm, 'closeIssue').and.returnValue(Promise.resolve());
+
+ const updateMrCountSpy = spyOnDependency(CommentForm, 'refreshUserMergeRequestCounts');
+ vm.toggleIssueState();
+
+ Vue.nextTick(() => {
+ expect(updateMrCountSpy).toHaveBeenCalled();
+
+ done();
+ });
+ });
+ });
});
describe('issue is confidential', () => {
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index c97ff5236ec..c461c28a37b 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -18,6 +18,8 @@ import {
noteableDataMock,
individualNote,
} from '../mock_data';
+import AxiosMockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
const TEST_ERROR_MESSAGE = 'Test error message';
@@ -335,28 +337,24 @@ describe('Actions Notes Store', () => {
});
describe('deleteNote', () => {
- const interceptor = (request, next) => {
- next(
- request.respondWith(JSON.stringify({}), {
- status: 200,
- }),
- );
- };
+ const endpoint = `${TEST_HOST}/note`;
+ let axiosMock;
beforeEach(() => {
- Vue.http.interceptors.push(interceptor);
+ axiosMock = new AxiosMockAdapter(axios);
+ axiosMock.onDelete(endpoint).replyOnce(200, {});
$('body').attr('data-page', '');
});
afterEach(() => {
- Vue.http.interceptors = _.without(Vue.http.interceptors, interceptor);
+ axiosMock.restore();
$('body').attr('data-page', '');
});
it('commits DELETE_NOTE and dispatches updateMergeRequestWidget', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
testAction(
actions.deleteNote,
@@ -381,7 +379,7 @@ describe('Actions Notes Store', () => {
});
it('dispatches removeDiscussionsFromDiff on merge request page', done => {
- const note = { path: `${gl.TEST_HOST}`, id: 1 };
+ const note = { path: endpoint, id: 1 };
$('body').attr('data-page', 'projects:merge_requests:show');
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index bb76616be56..ba3ba01944d 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -58,9 +58,11 @@ const createComponent = (customConfig = {}) => {
describe('ReadyToMerge', () => {
let vm;
+ let updateMrCountSpy;
beforeEach(() => {
vm = createComponent();
+ updateMrCountSpy = spyOnDependency(ReadyToMerge, 'refreshUserMergeRequestCounts');
});
afterEach(() => {
@@ -461,6 +463,7 @@ describe('ReadyToMerge', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent');
expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled();
+ expect(updateMrCountSpy).toHaveBeenCalled();
expect(cpc).toBeFalsy();
expect(spc).toBeTruthy();
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
new file mode 100644
index 00000000000..772c94e3180
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsFilter do
+ include FilterSpecHelper
+
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'when the document has an external link' do
+ let(:url) { 'https://foo.com' }
+
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'when the document has a metrics dashboard link' do
+ let(:params) { ['foo', 'bar', 12] }
+ let(:url) { urls.metrics_namespace_project_environment_url(*params) }
+
+ it 'leaves the original link unchanged' do
+ expect(doc.at_css('a').to_s).to eq input
+ end
+
+ it 'appends a metrics charts placeholder with dashboard url after metrics links' do
+ node = doc.at_css('.js-render-metrics')
+ expect(node).to be_present
+
+ dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params, embedded: true)
+ expect(node.attribute('data-dashboard-url').to_s).to eq dashboard_url
+ end
+
+ context 'when the metrics dashboard link is part of a paragraph' do
+ let(:paragraph) { %(This is an <a href="#{url}">example</a> of metrics.) }
+ let(:input) { %(<p>#{paragraph}</p>) }
+
+ it 'appends the charts placeholder after the enclosing paragraph' do
+ expect(doc.at_css('p').to_s).to include paragraph
+ expect(doc.at_css('.js-render-metrics')).to be_present
+ end
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(gfm_embedded_metrics: false)
+ end
+
+ it 'does nothing' do
+ expect(doc.to_s).to eq input
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
new file mode 100644
index 00000000000..fb2186e9d12
--- /dev/null
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::InlineMetricsRedactorFilter do
+ include FilterSpecHelper
+
+ set(:project) { create(:project) }
+
+ let(:url) { urls.metrics_dashboard_project_environment_url(project, 1, embedded: true) }
+ let(:input) { %(<a href="#{url}">example</a>) }
+ let(:doc) { filter(input) }
+
+ context 'when the feature is disabled' do
+ before do
+ stub_feature_flags(gfm_embedded_metrics: false)
+ end
+
+ it 'does nothing' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'without a metrics charts placeholder' do
+ it 'leaves regular non-metrics links unchanged' do
+ expect(doc.to_s).to eq input
+ end
+ end
+
+ context 'with a metrics charts placeholder' do
+ let(:input) { %(<div class="js-render-metrics" data-dashboard-url="#{url}"></div>) }
+
+ context 'no user is logged in' do
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user does not have permission do see charts' do
+ let(:doc) { filter(input, current_user: build(:user)) }
+
+ it 'redacts the placeholder' do
+ expect(doc.to_s).to be_empty
+ end
+ end
+
+ context 'the user has requisite permissions' do
+ let(:user) { create(:user) }
+ let(:doc) { filter(input, current_user: user) }
+
+ it 'leaves the placeholder' do
+ project.add_maintainer(user)
+
+ expect(doc.to_s).to eq input
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index f8b103c0fab..5ee02650e49 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -7,7 +7,7 @@ describe 'cycle analytics events' do
let!(:context) { create(:issue, project: project, created_at: 2.days.ago) }
let(:events) do
- CycleAnalytics.new(project, { from: from_date, current_user: user })[stage].events
+ CycleAnalytics::ProjectLevel.new(project, options: { from: from_date, current_user: user })[stage].events
end
before do
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index eacde22cd56..8633a63849f 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -3,6 +3,40 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
+ let(:project) { create(:project) }
+ let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
it_behaves_like 'base stage'
+
+ describe '#median' do
+ before do
+ issue_1 = create(:issue, project: project, created_at: 90.minutes.ago)
+ issue_2 = create(:issue, project: project, created_at: 60.minutes.ago)
+ issue_3 = create(:issue, project: project, created_at: 60.minutes.ago)
+ mr_1 = create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago)
+ mr_2 = create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A')
+ mr_3 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
+ mr_4 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C')
+ mr_5 = create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'D')
+ mr_1.metrics.update!(latest_build_started_at: 32.minutes.ago, latest_build_finished_at: 2.minutes.ago)
+ mr_2.metrics.update!(latest_build_started_at: 62.minutes.ago, latest_build_finished_at: 32.minutes.ago)
+ mr_3.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_4.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+ mr_5.metrics.update!(latest_build_started_at: nil, latest_build_finished_at: nil)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_4, issue: issue_3)
+ create(:merge_requests_closing_issues, merge_request: mr_5, issue: issue_3)
+ end
+
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.median).to eq(ISSUES_MEDIAN)
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index a785b17f682..8122e85a981 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -34,7 +34,7 @@ describe Gitlab::CycleAnalytics::UsageData do
expect(result).to have_key(:avg_cycle_analytics)
- CycleAnalytics::STAGES.each do |stage|
+ CycleAnalytics::Base::STAGES.each do |stage|
expect(result[:avg_cycle_analytics]).to have_key(stage)
stage_values = result[:avg_cycle_analytics][stage]
diff --git a/spec/db/importers/common_metrics_importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index a717c8cd04d..57c8bafd488 100644
--- a/spec/db/importers/common_metrics_importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -1,25 +1,8 @@
# frozen_string_literal: true
require 'rails_helper'
-require Rails.root.join("db", "importers", "common_metrics_importer.rb")
-describe Importers::PrometheusMetric do
- let(:existing_group_titles) do
- ::PrometheusMetric::GROUP_DETAILS.each_with_object({}) do |(key, value), memo|
- memo[key] = value[:group_title]
- end
- end
-
- it 'group enum equals ::PrometheusMetric' do
- expect(described_class.groups).to eq(::PrometheusMetric.groups)
- end
-
- it 'GROUP_TITLES equals ::PrometheusMetric' do
- expect(described_class::GROUP_TITLES).to eq(existing_group_titles)
- end
-end
-
-describe Importers::CommonMetricsImporter do
+describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
subject { described_class.new }
context "does import common_metrics.yml" do
@@ -104,7 +87,7 @@ describe Importers::CommonMetricsImporter do
let(:query_identifier) { }
it 'raises exception' do
- expect { subject.execute }.to raise_error(described_class::MissingQueryId)
+ expect { subject.execute }.to raise_error(Gitlab::DatabaseImporters::CommonMetrics::Importer::MissingQueryId)
end
end
diff --git a/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
new file mode 100644
index 00000000000..94f544e59b3
--- /dev/null
+++ b/spec/lib/gitlab/database_importers/common_metrics/prometheus_metric_spec.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetric do
+ it 'group enum equals ::PrometheusMetric' do
+ expect(described_class.groups).to eq(::PrometheusMetric.groups)
+ end
+
+ it '.group_titles equals ::PrometheusMetric' do
+ existing_group_titles = ::PrometheusMetricEnums.group_details.each_with_object({}) do |(key, value), memo|
+ memo[key] = value[:group_title]
+ end
+ expect(Gitlab::DatabaseImporters::CommonMetrics::PrometheusMetricEnums.group_titles).to eq(existing_group_titles)
+ end
+end
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index f957ed00945..e7ef9d08f80 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -30,6 +30,7 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
it 'returns true when gitaly matches disk' do
+ pending('temporary disabled because of https://gitlab.com/gitlab-org/gitlab-ce/issues/64338')
expect(subject.use_rugged?(repository, feature_flag_name)).to be true
end
@@ -48,6 +49,7 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
+ pending('temporary disabled because of https://gitlab.com/gitlab-org/gitlab-ce/issues/64338')
expect(subject.use_rugged?(repository, feature_flag_name)).to be true
expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
diff --git a/spec/lib/gitlab/global_id_spec.rb b/spec/lib/gitlab/global_id_spec.rb
new file mode 100644
index 00000000000..d35b5da0b75
--- /dev/null
+++ b/spec/lib/gitlab/global_id_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::GlobalId do
+ describe '.build' do
+ set(:object) { create(:issue) }
+
+ it 'returns a standard GlobalId if only object is passed' do
+ expect(described_class.build(object).to_s).to eq(object.to_global_id.to_s)
+ end
+
+ it 'returns a GlobalId from params' do
+ expect(described_class.build(model_name: 'MyModel', id: 'myid').to_s).to eq(
+ 'gid://gitlab/MyModel/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `id` param' do
+ expect(described_class.build(object, id: 'myid').to_s).to eq(
+ 'gid://gitlab/Issue/myid'
+ )
+ end
+
+ it 'returns a GlobalId from object and `model_name` param' do
+ expect(described_class.build(object, model_name: 'MyModel').to_s).to eq(
+ "gid://gitlab/MyModel/#{object.id}"
+ )
+ end
+
+ it 'returns an error if model_name and id are not able to be determined' do
+ expect { described_class.build(id: 'myid') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build(model_name: 'MyModel') }.to raise_error(URI::InvalidComponentError)
+ expect { described_class.build }.to raise_error(URI::InvalidComponentError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
new file mode 100644
index 00000000000..34bc6359414
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Url do
+ describe '#regex' do
+ it 'returns a regular expression' do
+ expect(described_class.regex).to be_a Regexp
+ end
+
+ it 'matches a metrics dashboard link with named params' do
+ url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url('foo', 'bar', 1, start: 123345456, anchor: 'title')
+
+ expected_params = {
+ 'url' => url,
+ 'namespace' => 'foo',
+ 'project' => 'bar',
+ 'environment' => '1',
+ 'query' => '?start=123345456',
+ 'anchor' => '#title'
+ }
+
+ expect(described_class.regex).to match url
+
+ described_class.regex.match(url) do |m|
+ expect(m.named_captures).to eq expected_params
+ end
+ end
+
+ it 'does not match other gitlab urls that contain the term metrics' do
+ url = Gitlab::Routing.url_helpers.active_common_namespace_project_prometheus_metrics_url('foo', 'bar', :json)
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match other gitlab urls' do
+ url = Gitlab.config.gitlab.url
+
+ expect(described_class.regex).not_to match url
+ end
+
+ it 'does not match non-gitlab urls' do
+ url = 'https://www.super_awesome_site.com/'
+
+ expect(described_class.regex).not_to match url
+ end
+ end
+
+ describe '#build_dashboard_url' do
+ it 'builds the url for the dashboard endpoint' do
+ url = described_class.build_dashboard_url('foo', 'bar', 1)
+
+ expect(url).to match described_class.regex
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 090e456644f..4b697b2ba0f 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
subject { described_class.new(1.second) }
describe '#sample' do
- let(:unicorn) { double('unicorn') }
+ let(:unicorn) { Module.new }
let(:raindrops) { double('raindrops') }
let(:stats) { double('stats') }
@@ -78,19 +78,32 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
end
end
- context 'additional metrics' do
- let(:unicorn_workers) { 2 }
-
+ context 'unicorn workers' do
before do
- allow(unicorn).to receive(:listener_names).and_return([""])
- allow(::Gitlab::Metrics::System).to receive(:cpu_time).and_return(3.14)
- allow(subject).to receive(:unicorn_workers_count).and_return(unicorn_workers)
+ allow(unicorn).to receive(:listener_names).and_return([])
end
- it "sets additional metrics" do
- expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, unicorn_workers)
+ context 'without http server' do
+ it "does set unicorn_workers to 0" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 0)
- subject.sample
+ subject.sample
+ end
+ end
+
+ context 'with http server' do
+ let(:http_server_class) { Struct.new(:worker_processes) }
+ let!(:http_server) { http_server_class.new(5) }
+
+ before do
+ stub_const('Unicorn::HttpServer', http_server_class)
+ end
+
+ it "sets additional metrics" do
+ expect(subject.metrics[:unicorn_workers]).to receive(:set).with({}, 5)
+
+ subject.sample
+ end
end
end
end
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
new file mode 100644
index 00000000000..da13b6df53b
--- /dev/null
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Peek::Views::RedisDetailed do
+ let(:redis_detailed_class) do
+ Class.new do
+ include Peek::Views::RedisDetailed
+ end
+ end
+
+ subject { redis_detailed_class.new }
+
+ using RSpec::Parameterized::TableSyntax
+
+ where(:cmd, :expected) do
+ [:auth, 'test'] | 'auth <redacted>'
+ [:set, 'key', 'value'] | 'set key <redacted>'
+ [:set, 'bad'] | 'set bad'
+ [:hmset, 'key1', 'value1', 'key2', 'value2'] | 'hmset key1 <redacted>'
+ [:get, 'key'] | 'get key'
+ end
+
+ with_them do
+ it 'scrubs Redis commands', :request_store do
+ subject.detail_store << { cmd: cmd, duration: 1.second }
+
+ expect(subject.details.count).to eq(1)
+ expect(subject.details.first)
+ .to eq({
+ cmd: expected,
+ duration: 1000
+ })
+ end
+ end
+end
diff --git a/spec/models/concerns/project_api_compatibility_spec.rb b/spec/models/concerns/project_api_compatibility_spec.rb
new file mode 100644
index 00000000000..8cecd4fe7bc
--- /dev/null
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ProjectAPICompatibility do
+ let(:project) { create(:project) }
+
+ # git_strategy
+ it "converts build_git_strategy=fetch to build_allow_git_fetch=true" do
+ project.update!(build_git_strategy: 'fetch')
+ expect(project.build_allow_git_fetch).to eq(true)
+ end
+
+ it "converts build_git_strategy=clone to build_allow_git_fetch=false" do
+ project.update!(build_git_strategy: 'clone')
+ expect(project.build_allow_git_fetch).to eq(false)
+ end
+
+ # auto_devops_enabled
+ it "converts auto_devops_enabled=false to auto_devops_enabled?=false" do
+ expect(project.auto_devops_enabled?).to eq(true)
+ project.update!(auto_devops_enabled: false)
+ expect(project.auto_devops_enabled?).to eq(false)
+ end
+
+ it "converts auto_devops_enabled=true to auto_devops_enabled?=true" do
+ expect(project.auto_devops_enabled?).to eq(true)
+ project.update!(auto_devops_enabled: true)
+ expect(project.auto_devops_enabled?).to eq(true)
+ end
+
+ # auto_devops_deploy_strategy
+ it "converts auto_devops_deploy_strategy=timed_incremental to auto_devops.deploy_strategy=timed_incremental" do
+ expect(project.auto_devops).to be_nil
+ project.update!(auto_devops_deploy_strategy: 'timed_incremental')
+ expect(project.auto_devops.deploy_strategy).to eq('timed_incremental')
+ end
+end
diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb
index 5aa43b58217..1fe176ab5af 100644
--- a/spec/models/concerns/project_features_compatibility_spec.rb
+++ b/spec/models/concerns/project_features_compatibility_spec.rb
@@ -4,7 +4,8 @@ require 'spec_helper'
describe ProjectFeaturesCompatibility do
let(:project) { create(:project) }
- let(:features) { %w(issues wiki builds merge_requests snippets) }
+ let(:features_except_repository) { %w(issues wiki builds merge_requests snippets) }
+ let(:features) { features_except_repository + ['repository'] }
# We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table
# All those fields got moved to a new table called project_feature and are now integers instead of booleans
@@ -12,30 +13,37 @@ describe ProjectFeaturesCompatibility do
# So we can keep it compatible
it "converts fields from 'true' to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "true")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from 'false' to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, "false")
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
it "converts fields from true to ProjectFeature::ENABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, true)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::ENABLED)
end
end
it "converts fields from false to ProjectFeature::DISABLED" do
- features.each do |feature|
+ features_except_repository.each do |feature|
project.update_attribute("#{feature}_enabled".to_sym, false)
expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::DISABLED)
end
end
+
+ it "accepts private as ProjectFeature::PRIVATE" do
+ features.each do |feature|
+ project.update!("#{feature}_access_level".to_sym => 'private')
+ expect(project.project_feature.public_send("#{feature}_access_level")).to eq(ProjectFeature::PRIVATE)
+ end
+ end
end
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index b22a0340015..db6e70973ae 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#code' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
context 'with deployment' do
generate_cycle_analytics_spec(
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 07d60be091a..7b18e24a351 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#issue' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :issue,
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index 3d22a284264..73fc98ba6cf 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#plan' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :plan,
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index 383727cd8f7..ddd199362d1 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#production' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :production,
diff --git a/spec/models/cycle_analytics_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 5d8b5b573cf..77bd0bfeb9c 100644
--- a/spec/models/cycle_analytics_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe CycleAnalytics do
+describe CycleAnalytics::ProjectLevel do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
@@ -11,9 +11,9 @@ describe CycleAnalytics do
let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
- subject { described_class.new(project, from: from_date) }
+ subject { described_class.new(project, options: { from: from_date }) }
- describe '#all_medians_per_stage' do
+ describe '#all_medians_by_stage' do
before do
allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
@@ -26,7 +26,7 @@ describe CycleAnalytics do
hsh[stage_name] = subject[stage_name].median.presence
end
- expect(subject.all_medians_per_stage).to eq(values)
+ expect(subject.all_medians_by_stage).to eq(values)
end
end
end
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 1af5f9cc1f4..63c481ed465 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#review' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :review,
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index 8375944f03c..c134b97553f 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -9,7 +9,7 @@ describe 'CycleAnalytics#staging' do
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :staging,
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index b78258df564..a6ea73b2699 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -8,7 +8,8 @@ describe 'CycleAnalytics#test' do
let(:project) { create(:project, :repository) }
let(:from_date) { 10.days.ago }
let(:user) { create(:user, :admin) }
- subject { CycleAnalytics.new(project, from: from_date) }
+
+ subject { CycleAnalytics::ProjectLevel.new(project, options: { from: from_date }) }
generate_cycle_analytics_spec(
phase: :test,
diff --git a/spec/models/discussion_spec.rb b/spec/models/discussion_spec.rb
index 22d4dab0617..950bdec4d00 100644
--- a/spec/models/discussion_spec.rb
+++ b/spec/models/discussion_spec.rb
@@ -10,6 +10,20 @@ describe Discussion do
let(:second_note) { create(:diff_note_on_merge_request, in_reply_to: first_note) }
let(:third_note) { create(:diff_note_on_merge_request) }
+ describe '.lazy_find' do
+ let!(:note1) { create(:discussion_note_on_merge_request).to_discussion }
+ let!(:note2) { create(:discussion_note_on_merge_request, in_reply_to: note1).to_discussion }
+
+ subject { [note1, note2].map { |note| described_class.lazy_find(note.discussion_id) } }
+
+ it 'batches requests' do
+ expect do
+ [described_class.lazy_find(note1.id),
+ described_class.lazy_find(note2.id)].map(&:__sync)
+ end.not_to exceed_query_limit(1)
+ end
+ end
+
describe '.build' do
it 'returns a discussion of the right type' do
discussion = described_class.build([first_note, second_note], merge_request)
diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
new file mode 100644
index 00000000000..b04fcb9aece
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/ruby/popen.rb',
+ new_path: 'files/ruby/popen2.rb'
+ },
+ new_line: 14,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['positionType']).to eq('text')
+ expect(mutation_position_response['filePath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['oldPath']).to eq('files/ruby/popen.rb')
+ expect(mutation_position_response['newPath']).to eq('files/ruby/popen2.rb')
+ expect(mutation_position_response['newLine']).to eq(14)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
new file mode 100644
index 00000000000..3ba6c689024
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/image_diff_note_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding an image DiffNote' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project, :repository) }
+ let(:diff_refs) { noteable.diff_refs }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ body: 'Body text',
+ position: {
+ paths: {
+ old_path: 'files/images/any_image.png',
+ new_path: 'files/images/any_image2.png'
+ },
+ width: 100,
+ height: 200,
+ x: 1,
+ y: 2,
+ base_sha: diff_refs.base_sha,
+ head_sha: diff_refs.head_sha,
+ start_sha: diff_refs.start_sha
+ }
+ }
+
+ graphql_mutation(:create_image_diff_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_image_diff_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote
+
+ context do
+ let(:diff_refs) { build(:merge_request).diff_refs } # Allow fake diff refs so arguments are valid
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+ end
+
+ it 'returns the note with the correct position' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ mutation_position_response = mutation_response['note']['position']
+ expect(mutation_position_response['filePath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['oldPath']).to eq('files/images/any_image.png')
+ expect(mutation_position_response['newPath']).to eq('files/images/any_image2.png')
+ expect(mutation_position_response['positionType']).to eq('image')
+ expect(mutation_position_response['width']).to eq(100)
+ expect(mutation_position_response['height']).to eq(200)
+ expect(mutation_position_response['x']).to eq(1)
+ expect(mutation_position_response['y']).to eq(2)
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/create/note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
new file mode 100644
index 00000000000..14aaa430ac9
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/create/note_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Adding a Note' do
+ include GraphqlHelpers
+
+ set(:current_user) { create(:user) }
+ let(:noteable) { create(:merge_request, source_project: project, target_project: project) }
+ let(:project) { create(:project) }
+ let(:discussion) { nil }
+ let(:mutation) do
+ variables = {
+ noteable_id: GitlabSchema.id_from_object(noteable).to_s,
+ discussion_id: (GitlabSchema.id_from_object(discussion).to_s if discussion),
+ body: 'Body text'
+ }
+
+ graphql_mutation(:create_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:create_note)
+ end
+
+ it_behaves_like 'a Note mutation when the user does not have permission'
+
+ context 'when the user has permission' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ it_behaves_like 'a Note mutation that creates a Note'
+
+ it_behaves_like 'a Note mutation when there are active record validation errors'
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Noteable'
+
+ it 'returns the note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq('Body text')
+ end
+
+ describe 'creating Notes in reply to a discussion' do
+ context 'when the user does not have permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note).to_discussion }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ["The discussion does not exist or you don't have permission to perform this action"]
+ end
+
+ context 'when the user has permission to create notes on the discussion' do
+ let(:discussion) { create(:discussion_note, project: project).to_discussion }
+
+ it 'creates a Note in a discussion' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['discussion']['id']).to eq(discussion.to_global_id.to_s)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/destroy_spec.rb b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
new file mode 100644
index 00000000000..337a6e6f6e6
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/destroy_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Destroying a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note) }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s
+ }
+
+ graphql_mutation(:destroy_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:destroy_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not destroy the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'destroys the Note' do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(-1)
+ end
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/mutations/notes/update_spec.rb b/spec/requests/api/graphql/mutations/notes/update_spec.rb
new file mode 100644
index 00000000000..958f640995a
--- /dev/null
+++ b/spec/requests/api/graphql/mutations/notes/update_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Updating a Note' do
+ include GraphqlHelpers
+
+ let!(:note) { create(:note, note: original_body) }
+ let(:original_body) { 'Initial body text' }
+ let(:updated_body) { 'Updated body text' }
+ let(:mutation) do
+ variables = {
+ id: GitlabSchema.id_from_object(note).to_s,
+ body: updated_body
+ }
+
+ graphql_mutation(:update_note, variables)
+ end
+
+ def mutation_response
+ graphql_mutation_response(:update_note)
+ end
+
+ context 'when the user does not have permission' do
+ let(:current_user) { create(:user) }
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+ end
+
+ context 'when the user has permission' do
+ let(:current_user) { note.author }
+
+ it_behaves_like 'a Note mutation when the given resource id is not for a Note'
+
+ it 'updates the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(updated_body)
+ end
+
+ it 'returns the updated Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(updated_body)
+ end
+
+ context 'when there are ActiveRecord validation errors' do
+ let(:updated_body) { '' }
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ["Note can't be blank"]
+
+ it 'does not update the Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(note.reload.note).to eq(original_body)
+ end
+
+ it 'returns the Note with its original body' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response['note']['body']).to eq(original_body)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql_spec.rb b/spec/requests/api/graphql_spec.rb
index 67371cb35b6..54401ec4085 100644
--- a/spec/requests/api/graphql_spec.rb
+++ b/spec/requests/api/graphql_spec.rb
@@ -6,16 +6,6 @@ describe 'GraphQL' do
let(:query) { graphql_query_for('echo', 'text' => 'Hello world' ) }
- context 'graphql is disabled by feature flag' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not generate a route for GraphQL' do
- expect { post_graphql(query) }.to raise_error(ActionController::RoutingError)
- end
- end
-
context 'logging' do
shared_examples 'logging a graphql query' do
let(:expected_params) do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index c67412a44c1..a2aae257352 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1102,6 +1102,12 @@ describe API::Projects do
expect(json_response['wiki_enabled']).to be_present
expect(json_response['jobs_enabled']).to be_present
expect(json_response['snippets_enabled']).to be_present
+ expect(json_response['snippets_access_level']).to be_present
+ expect(json_response['repository_access_level']).to be_present
+ expect(json_response['issues_access_level']).to be_present
+ expect(json_response['merge_requests_access_level']).to be_present
+ expect(json_response['wiki_access_level']).to be_present
+ expect(json_response['builds_access_level']).to be_present
expect(json_response['resolve_outdated_diff_discussions']).to eq(project.resolve_outdated_diff_discussions)
expect(json_response['container_registry_enabled']).to be_present
expect(json_response['created_at']).to be_present
@@ -1913,6 +1919,34 @@ describe API::Projects do
end
end
+ it 'updates builds_access_level' do
+ project_param = { builds_access_level: 'private' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['builds_access_level']).to eq('private')
+ end
+
+ it 'updates build_git_strategy' do
+ project_param = { build_git_strategy: 'clone' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['build_git_strategy']).to eq('clone')
+ end
+
+ it 'rejects to update build_git_strategy when build_git_strategy is invalid' do
+ project_param = { build_git_strategy: 'invalid' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
it 'updates merge_method' do
project_param = { merge_method: 'ff' }
@@ -1946,6 +1980,26 @@ describe API::Projects do
'-/system/project/avatar/'\
"#{project3.id}/banana_sample.gif")
end
+
+ it 'updates auto_devops_deploy_strategy' do
+ project_param = { auto_devops_deploy_strategy: 'timed_incremental' }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_deploy_strategy']).to eq('timed_incremental')
+ end
+
+ it 'updates auto_devops_enabled' do
+ project_param = { auto_devops_enabled: false }
+
+ put api("/projects/#{project3.id}", user), params: project_param
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response['auto_devops_enabled']).to eq(false)
+ end
end
context 'when authenticated as project maintainer' do
diff --git a/spec/requests/api/user_counts_spec.rb b/spec/requests/api/user_counts_spec.rb
new file mode 100644
index 00000000000..c833bd047e2
--- /dev/null
+++ b/spec/requests/api/user_counts_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::UserCounts do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ let!(:merge_request) { create(:merge_request, :simple, author: user, assignees: [user], source_project: project, title: "Test") }
+
+ describe 'GET /user_counts' do
+ context 'when unauthenticated' do
+ it 'returns authentication error' do
+ get api('/user_counts')
+
+ expect(response.status).to eq(401)
+ end
+ end
+
+ context 'when authenticated' do
+ it 'returns open counts for current user' do
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(1)
+ end
+
+ it 'updates the mr count when a new mr is assigned' do
+ create(:merge_request, source_project: project, author: user, assignees: [user])
+
+ get api('/user_counts', user)
+
+ expect(response.status).to eq(200)
+ expect(json_response).to be_a Hash
+ expect(json_response['merge_requests']).to eq(2)
+ end
+ end
+ end
+end
diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
deleted file mode 100644
index 3c48ead4ff2..00000000000
--- a/spec/routing/api_routing_spec.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'spec_helper'
-
-describe 'api', 'routing' do
- context 'when graphql is disabled' do
- before do
- stub_feature_flags(graphql: false)
- end
-
- it 'does not route to the GraphqlController' do
- expect(post('/api/graphql')).not_to route_to('graphql#execute')
- end
- end
-
- context 'when graphql is enabled' do
- before do
- stub_feature_flags(graphql: true)
- end
-
- it 'routes to the GraphqlController' do
- expect(post('/api/graphql')).to route_to('graphql#execute')
- end
- end
-end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 6dde40d1cb6..8a3de2a52fc 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -713,4 +713,10 @@ describe 'project routing' do
end
end
end
+
+ describe Projects::DeployTokensController, 'routing' do
+ it 'routes to deploy_tokens#revoke' do
+ expect(put("/gitlab/gitlabhq/-/deploy_tokens/1/revoke")).to route_to("projects/deploy_tokens#revoke", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+ end
end
diff --git a/spec/rubocop/cop/gitlab/rails_logger_spec.rb b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
new file mode 100644
index 00000000000..f0158ddcc5c
--- /dev/null
+++ b/spec/rubocop/cop/gitlab/rails_logger_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+require_relative '../../../../rubocop/cop/gitlab/rails_logger'
+
+describe RuboCop::Cop::Gitlab::RailsLogger do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ it 'flags the use of Rails.logger.error with a constant receiver' do
+ inspect_source("Rails.logger.error('some error')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.info with a constant receiver' do
+ inspect_source("Rails.logger.info('some info')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'flags the use of Rails.logger.warn with a constant receiver' do
+ inspect_source("Rails.logger.warn('some warning')")
+
+ expect(cop.offenses.size).to eq(1)
+ end
+
+ it 'does not flag the use of Rails.logger with a constant that is not Rails' do
+ inspect_source("AppLogger.error('some error')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ it 'does not flag the use of logger with a send receiver' do
+ inspect_source("file_logger.info('important info')")
+
+ expect(cop.offenses.size).to eq(0)
+ end
+end
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index aebc5ba2874..3d2d4b5f216 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -11,343 +11,371 @@ describe Issuable::BulkUpdateService do
.reverse_merge(issuable_ids: Array(issuables).map(&:id).join(','))
type = Array(issuables).first.model_name.param_key
- Issuable::BulkUpdateService.new(project, user, bulk_update_params).execute(type)
+ Issuable::BulkUpdateService.new(user, bulk_update_params).execute(type)
end
- describe 'close issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
-
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'close')
+ shared_examples 'updates milestones' do
+ it 'succeeds' do
+ result = bulk_update(issues, milestone_id: milestone.id)
expect(result[:success]).to be_truthy
expect(result[:count]).to eq(issues.count)
end
- it 'closes all the issues passed' do
- bulk_update(issues, state_event: 'close')
+ it 'updates the issues milestone' do
+ bulk_update(issues, milestone_id: milestone.id)
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
+ issues.each do |issue|
+ expect(issue.reload.milestone).to eq(milestone)
+ end
end
+ end
- context 'when issue for a different project is created' do
- let(:private_project) { create(:project, :private) }
- let(:issue) { create(:issue, project: private_project, author: user) }
+ context 'with project issues' do
+ describe 'close issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
- context 'when user has access to the project' do
- it 'closes all issues passed' do
- private_project.add_maintainer(user)
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'close')
- bulk_update(issues + [issue], state_event: 'close')
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).not_to be_empty
- end
+ it 'closes all the issues passed' do
+ bulk_update(issues, state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
end
- context 'when user does not have access to project' do
- it 'only closes all issues that the user has access to' do
- bulk_update(issues + [issue], state_event: 'close')
+ context 'when issue for a different project is created' do
+ let(:private_project) { create(:project, :private) }
+ let(:issue) { create(:issue, project: private_project, author: user) }
+
+ context 'when user has access to the project' do
+ it 'closes all issues passed' do
+ private_project.add_maintainer(user)
+
+ bulk_update(issues + [issue], state_event: 'close')
+
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).not_to be_empty
+ end
+ end
+
+ context 'when user does not have access to project' do
+ it 'only closes all issues that the user has access to' do
+ bulk_update(issues + [issue], state_event: 'close')
- expect(project.issues.opened).to be_empty
- expect(project.issues.closed).not_to be_empty
- expect(private_project.issues.closed).to be_empty
+ expect(project.issues.opened).to be_empty
+ expect(project.issues.closed).not_to be_empty
+ expect(private_project.issues.closed).to be_empty
+ end
end
end
end
- end
- describe 'reopen issues' do
- let(:issues) { create_list(:closed_issue, 2, project: project) }
+ describe 'reopen issues' do
+ let(:issues) { create_list(:closed_issue, 2, project: project) }
- it 'succeeds and returns the correct number of issues updated' do
- result = bulk_update(issues, state_event: 'reopen')
+ it 'succeeds and returns the correct number of issues updated' do
+ result = bulk_update(issues, state_event: 'reopen')
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(issues.count)
+ end
- it 'reopens all the issues passed' do
- bulk_update(issues, state_event: 'reopen')
+ it 'reopens all the issues passed' do
+ bulk_update(issues, state_event: 'reopen')
- expect(project.issues.closed).to be_empty
- expect(project.issues.opened).not_to be_empty
+ expect(project.issues.closed).to be_empty
+ expect(project.issues.opened).not_to be_empty
+ end
end
- end
- describe 'updating merge request assignee' do
- let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
+ describe 'updating merge request assignee' do
+ let(:merge_request) { create(:merge_request, target_project: project, source_project: project, assignees: [user]) }
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
- result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
+ result = bulk_update(merge_request, assignee_ids: [user.id, new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
- expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
- .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
+ expect { bulk_update(merge_request, assignee_ids: [assignee.id]) }
+ .to change { merge_request.reload.assignee_ids }.from([user.id]).to([assignee.id])
+ end
end
- end
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it 'unassigns the issues' do
- expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
- .to change { merge_request.reload.assignee_ids }.to([])
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it 'unassigns the issues' do
+ expect { bulk_update(merge_request, assignee_ids: [IssuableFinder::NONE]) }
+ .to change { merge_request.reload.assignee_ids }.to([])
+ end
end
- end
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(merge_request, assignee_ids: []) }
- .not_to change { merge_request.reload.assignee_ids }
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(merge_request, assignee_ids: []) }
+ .not_to change { merge_request.reload.assignee_ids }
+ end
end
end
- end
- describe 'updating issue assignee' do
- let(:issue) { create(:issue, project: project, assignees: [user]) }
+ describe 'updating issue assignee' do
+ let(:issue) { create(:issue, project: project, assignees: [user]) }
- context 'when the new assignee ID is a valid user' do
- it 'succeeds' do
- new_assignee = create(:user)
- project.add_developer(new_assignee)
+ context 'when the new assignee ID is a valid user' do
+ it 'succeeds' do
+ new_assignee = create(:user)
+ project.add_developer(new_assignee)
- result = bulk_update(issue, assignee_ids: [new_assignee.id])
+ result = bulk_update(issue, assignee_ids: [new_assignee.id])
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
- end
+ expect(result[:success]).to be_truthy
+ expect(result[:count]).to eq(1)
+ end
- it 'updates the assignee to the user ID passed' do
- assignee = create(:user)
- project.add_developer(assignee)
- expect { bulk_update(issue, assignee_ids: [assignee.id]) }
- .to change { issue.reload.assignees.first }.from(user).to(assignee)
+ it 'updates the assignee to the user ID passed' do
+ assignee = create(:user)
+ project.add_developer(assignee)
+ expect { bulk_update(issue, assignee_ids: [assignee.id]) }
+ .to change { issue.reload.assignees.first }.from(user).to(assignee)
+ end
end
- end
- context "when the new assignee ID is #{IssuableFinder::NONE}" do
- it "unassigns the issues" do
- expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
- .to change { issue.reload.assignees.count }.from(1).to(0)
+ context "when the new assignee ID is #{IssuableFinder::NONE}" do
+ it "unassigns the issues" do
+ expect { bulk_update(issue, assignee_ids: [IssuableFinder::NONE.to_s]) }
+ .to change { issue.reload.assignees.count }.from(1).to(0)
+ end
end
- end
- context 'when the new assignee ID is not present' do
- it 'does not unassign' do
- expect { bulk_update(issue, assignee_ids: []) }
- .not_to change { issue.reload.assignees }
+ context 'when the new assignee ID is not present' do
+ it 'does not unassign' do
+ expect { bulk_update(issue, assignee_ids: []) }
+ .not_to change { issue.reload.assignees }
+ end
end
end
- end
-
- describe 'updating milestones' do
- let(:issue) { create(:issue, project: project) }
- let(:milestone) { create(:milestone, project: project) }
- it 'succeeds' do
- result = bulk_update(issue, milestone_id: milestone.id)
+ describe 'updating milestones' do
+ let(:issues) { [create(:issue, project: project)] }
+ let(:milestone) { create(:milestone, project: project) }
- expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(1)
+ it_behaves_like 'updates milestones'
end
- it 'updates the issue milestone' do
- expect { bulk_update(issue, milestone_id: milestone.id) }
- .to change { issue.reload.milestone }.from(nil).to(milestone)
- end
- end
-
- describe 'updating labels' do
- def create_issue_with_labels(labels)
- create(:labeled_issue, project: project, labels: labels)
- end
+ describe 'updating labels' do
+ def create_issue_with_labels(labels)
+ create(:labeled_issue, project: project, labels: labels)
+ end
- let(:bug) { create(:label, project: project) }
- let(:regression) { create(:label, project: project) }
- let(:merge_requests) { create(:label, project: project) }
-
- let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
- let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
- let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
- let(:issue_no_labels) { create(:issue, project: project) }
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
-
- let(:labels) { [] }
- let(:add_labels) { [] }
- let(:remove_labels) { [] }
-
- let(:bulk_update_params) do
- {
- label_ids: labels.map(&:id),
- add_label_ids: add_labels.map(&:id),
- remove_label_ids: remove_labels.map(&:id)
- }
- end
+ let(:bug) { create(:label, project: project) }
+ let(:regression) { create(:label, project: project) }
+ let(:merge_requests) { create(:label, project: project) }
- before do
- bulk_update(issues, bulk_update_params)
- end
+ let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
+ let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
+ let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
+ let(:issue_no_labels) { create(:issue, project: project) }
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
- context 'when label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_no_labels] }
- let(:labels) { [bug, regression] }
+ let(:labels) { [] }
+ let(:add_labels) { [] }
+ let(:remove_labels) { [] }
- it 'updates the labels of all issues passed to the labels passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
+ let(:bulk_update_params) do
+ {
+ label_ids: labels.map(&:id),
+ add_label_ids: add_labels.map(&:id),
+ remove_label_ids: remove_labels.map(&:id)
+ }
end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ before do
+ bulk_update(issues, bulk_update_params)
end
- context 'when those label IDs are empty' do
- let(:labels) { [] }
+ context 'when label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_no_labels] }
+ let(:labels) { [bug, regression] }
- it 'updates the issues passed to have no labels' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ it 'updates the labels of all issues passed to the labels passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
end
- end
- end
- context 'when add_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug, regression, merge_requests] }
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
- it 'adds those label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
- end
+ context 'when those label IDs are empty' do
+ let(:labels) { [] }
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'updates the issues passed to have no labels' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+ end
end
- end
- context 'when remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:remove_labels) { [bug, regression, merge_requests] }
+ context 'when add_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug, regression, merge_requests] }
- it 'removes those label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
- end
+ it 'adds those label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
+ end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- end
- context 'when add_label_ids and remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug] }
- let(:remove_labels) { [merge_requests] }
+ context 'when remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:remove_labels) { [bug, regression, merge_requests] }
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
+ it 'removes those label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
- context 'when add_label_ids and label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
- let(:labels) { [merge_requests] }
- let(:add_labels) { [regression] }
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
- end
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_no_labels.label_ids).to be_empty
- end
- end
+ context 'when add_label_ids and label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
+ let(:labels) { [merge_requests] }
+ let(:add_labels) { [regression] }
- context 'when remove_label_ids and label_ids are passed' do
- let(:issues) { [issue_no_labels, issue_bug_and_regression] }
- let(:labels) { [merge_requests] }
- let(:remove_labels) { [regression] }
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
+ end
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
- end
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ it 'does not update issues not passed in' do
+ expect(issue_no_labels.label_ids).to be_empty
+ end
end
- it 'does not update issues not passed in' do
- expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
- end
- end
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:issues) { [issue_no_labels, issue_bug_and_regression] }
+ let(:labels) { [merge_requests] }
+ let(:remove_labels) { [regression] }
- context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
- let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
- let(:labels) { [regression] }
- let(:add_labels) { [bug] }
- let(:remove_labels) { [merge_requests] }
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ end
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ it 'does not update issues not passed in' do
+ expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
+ end
end
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
- end
+ context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
+ let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
+ let(:labels) { [regression] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
end
end
- end
- describe 'subscribe to issues' do
- let(:issues) { create_list(:issue, 2, project: project) }
+ describe 'subscribe to issues' do
+ let(:issues) { create_list(:issue, 2, project: project) }
- it 'subscribes the given user' do
- bulk_update(issues, subscription_event: 'subscribe')
+ it 'subscribes the given user' do
+ bulk_update(issues, subscription_event: 'subscribe')
- expect(issues).to all(be_subscribed(user, project))
+ expect(issues).to all(be_subscribed(user, project))
+ end
end
- end
- describe 'unsubscribe from issues' do
- let(:issues) do
- create_list(:closed_issue, 2, project: project) do |issue|
- issue.subscriptions.create(user: user, project: project, subscribed: true)
+ describe 'unsubscribe from issues' do
+ let(:issues) do
+ create_list(:closed_issue, 2, project: project) do |issue|
+ issue.subscriptions.create(user: user, project: project, subscribed: true)
+ end
+ end
+
+ it 'unsubscribes the given user' do
+ bulk_update(issues, subscription_event: 'unsubscribe')
+
+ issues.each do |issue|
+ expect(issue).not_to be_subscribed(user, project)
+ end
end
end
+ end
- it 'unsubscribes the given user' do
- bulk_update(issues, subscription_event: 'unsubscribe')
+ context 'with group issues' do
+ let(:group) { create(:group) }
- issues.each do |issue|
- expect(issue).not_to be_subscribed(user, project)
+ context 'updating milestone' do
+ let(:milestone) { create(:milestone, group: group) }
+ let(:project1) { create(:project, :repository, group: group) }
+ let(:project2) { create(:project, :repository, group: group) }
+ let(:issue1) { create(:issue, project: project1) }
+ let(:issue2) { create(:issue, project: project2) }
+ let(:issues) { [issue1, issue2] }
+
+ before do
+ group.add_maintainer(user)
end
+
+ it_behaves_like 'updates milestones'
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 28fa5d12d9c..468e7c286d5 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -480,6 +480,22 @@ describe Issues::UpdateService, :mailer do
update_issue(description: "- [x] Task 1\n- [X] Task 2")
end
+ it 'does not check for spam on task status change' do
+ params = {
+ update_task: {
+ index: 1,
+ checked: false,
+ line_source: '- [x] Task 1',
+ line_number: 1
+ }
+ }
+ service = described_class.new(project, user, params)
+
+ expect(service).not_to receive(:spam_check)
+
+ service.execute(issue)
+ end
+
it 'creates system note about task status change' do
note1 = find_note('marked the task **Task 1** as completed')
note2 = find_note('marked the task **Task 2** as completed')
diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb
index aa759ac9edc..22578436c18 100644
--- a/spec/services/merge_requests/merge_service_spec.rb
+++ b/spec/services/merge_requests/merge_service_spec.rb
@@ -214,6 +214,19 @@ describe MergeRequests::MergeService do
allow(Rails.logger).to receive(:error)
end
+ context 'when source is missing' do
+ it 'logs and saves error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ service.execute(merge_request)
+
+ expect(merge_request.merge_error).to eq(error_message)
+ expect(Rails.logger).to have_received(:error).with(a_string_matching(error_message))
+ end
+ end
+
it 'logs and saves error if there is an exception' do
error_message = 'error message'
diff --git a/spec/services/merge_requests/merge_to_ref_service_spec.rb b/spec/services/merge_requests/merge_to_ref_service_spec.rb
index 14012b4ea2d..758679edc45 100644
--- a/spec/services/merge_requests/merge_to_ref_service_spec.rb
+++ b/spec/services/merge_requests/merge_to_ref_service_spec.rb
@@ -191,6 +191,19 @@ describe MergeRequests::MergeToRefService do
it { expect(todo).not_to be_done }
end
+ context 'when source is missing' do
+ it 'returns error' do
+ allow(merge_request).to receive(:diff_head_sha) { nil }
+
+ error_message = 'No source for merge'
+
+ result = service.execute(merge_request)
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:message]).to eq(error_message)
+ end
+ end
+
context 'when target ref is passed as a parameter' do
let(:params) { { commit_message: 'merge train', target_ref: target_ref } }
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index cd49bb148f2..c83860d7b51 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -1,4 +1,8 @@
+require_relative 'workhorse_helpers'
+
module GitHttpHelpers
+ include WorkhorseHelpers
+
def clone_get(project, options = {})
get "/#{project}/info/refs", params: { service: 'git-upload-pack' }, headers: auth_env(*options.values_at(:user, :password, :spnego_request_token))
end
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 1a09d48f4cd..ec3c460cd37 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -57,7 +57,8 @@ module GraphqlHelpers
end
def variables_for_mutation(name, input)
- graphql_input = input.map { |name, value| [GraphqlHelpers.fieldnamerize(name), value] }.to_h
+ graphql_input = prepare_input_for_mutation(input)
+
result = { input_variable_name_for_mutation(name) => graphql_input }
# Avoid trying to serialize multipart data into JSON
@@ -68,6 +69,18 @@ module GraphqlHelpers
end
end
+ # Recursively convert a Hash with Ruby-style keys to GraphQL fieldname-style keys
+ #
+ # prepare_input_for_mutation({ 'my_key' => 1 })
+ # => { 'myKey' => 1}
+ def prepare_input_for_mutation(input)
+ input.map do |name, value|
+ value = prepare_input_for_mutation(value) if value.is_a?(Hash)
+
+ [GraphqlHelpers.fieldnamerize(name), value]
+ end.to_h
+ end
+
def input_variable_name_for_mutation(mutation_name)
mutation_name = GraphqlHelpers.fieldnamerize(mutation_name)
mutation_field = GitlabSchema.mutation.fields[mutation_name]
diff --git a/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
new file mode 100644
index 00000000000..f2e1a95345b
--- /dev/null
+++ b/spec/support/shared_examples/graphql/notes_creation_shared_examples.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a Note mutation that does not create a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.not_to change { Note.count }
+ end
+end
+
+RSpec.shared_examples 'a Note mutation that creates a Note' do
+ it do
+ expect do
+ post_graphql_mutation(mutation, current_user: current_user)
+ end.to change { Note.count }.by(1)
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the user does not have permission' do
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors',
+ errors: ['The resource that you are attempting to access does not exist or you don\'t have permission to perform this action']
+end
+
+RSpec.shared_examples 'a Note mutation when there are active record validation errors' do |model: Note|
+ before do
+ expect_next_instance_of(model) do |note|
+ expect(note).to receive(:valid?).at_least(:once).and_return(false)
+ expect(note).to receive_message_chain(
+ :errors,
+ :full_messages
+ ).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns errors in the response', errors: ['Error 1', 'Error 2']
+
+ it 'returns an empty Note' do
+ post_graphql_mutation(mutation, current_user: current_user)
+
+ expect(mutation_response).to have_key('note')
+ expect(mutation_response['note']).to be_nil
+ end
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Noteable' do
+ let(:noteable) { create(:label, project: project) }
+
+ it_behaves_like 'a Note mutation that does not create a Note'
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Cannot add notes to this resource']
+end
+
+RSpec.shared_examples 'a Note mutation when the given resource id is not for a Note' do
+ let(:note) { create(:issue) }
+
+ it_behaves_like 'a mutation that returns top-level errors', errors: ['Resource is not a note']
+end
diff --git a/yarn.lock b/yarn.lock
index 825a1785c3e..dc5e0662396 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -705,10 +705,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
-"@gitlab/ui@^5.3.2":
- version "5.3.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.3.2.tgz#8ee906cf0586834de0077e165f25764c0bf8a9e9"
- integrity sha512-VgxlDXqG2q+u72Km+/Ljdvjh0DzvljvsztiXTxnOO+Eb+/I26JBWfdboqFr3E02JzT8W4s4rRinhRttLWfcM/A==
+"@gitlab/ui@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.5.0.tgz#2000b2ed0c3825dd8c4430191023f4d03c923ecb"
+ integrity sha512-6e/AFFLDk/gm4wKnHM9rcpTRqCsWkPKW/Vjsnd6h4wyfif2/TusHeIn/jedQxUaORbO/XZKzg4V5COhXXbCx4w==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -7476,9 +7476,9 @@ mississippi@^3.0.0:
through2 "^2.0.0"
mixin-deep@^1.2.0:
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe"
- integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566"
+ integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==
dependencies:
for-in "^1.0.2"
is-extendable "^1.0.1"