summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAchilleas Pipinellis <axil@gitlab.com>2019-08-21 11:24:55 +0200
committerAchilleas Pipinellis <axil@gitlab.com>2019-08-21 11:24:55 +0200
commita39228db8027c966e8117d40766b0cef4fbd50f6 (patch)
tree143045af7214ae6746e7b7c9df647c71ec2bd675
parent05f50c9b52fde54513fe55fef97499b35719eae2 (diff)
parentaed489bf901745ced6618e680913d0d213998923 (diff)
downloadgitlab-ce-docs-zm002-gitlab-case.tar.gz
Merge branch 'master' into docs-zm002-gitlab-casedocs-zm002-gitlab-case
-rw-r--r--.dockerignore72
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml10
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md1
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue41
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue88
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js2
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_expansion_cell.vue12
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue1
-rw-r--r--app/assets/javascripts/ide/stores/utils.js2
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js8
-rw-r--r--app/assets/javascripts/members.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue10
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue41
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue28
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue15
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--app/assets/javascripts/mr_notes/init_notes.js4
-rw-r--r--app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue4
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue15
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js42
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue7
-rw-r--r--app/assets/javascripts/vue_shared/directives/autofocusonshow.js39
-rw-r--r--app/assets/stylesheets/errors.scss2
-rw-r--r--app/assets/stylesheets/framework/badges.scss2
-rw-r--r--app/assets/stylesheets/framework/common.scss1
-rw-r--r--app/assets/stylesheets/framework/flash.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/highlight/common.scss13
-rw-r--r--app/assets/stylesheets/highlight/themes/dark.scss4
-rw-r--r--app/assets/stylesheets/highlight/themes/monokai.scss4
-rw-r--r--app/assets/stylesheets/highlight/themes/none.scss5
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-dark.scss4
-rw-r--r--app/assets/stylesheets/highlight/themes/solarized-light.scss4
-rw-r--r--app/assets/stylesheets/highlight/white_base.scss18
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss50
-rw-r--r--app/assets/stylesheets/pages/diff.scss3
-rw-r--r--app/assets/stylesheets/pages/issuable.scss1
-rw-r--r--app/assets/stylesheets/pages/reports.scss5
-rw-r--r--app/controllers/application_controller.rb1
-rw-r--r--app/controllers/concerns/confirm_email_warning.rb25
-rw-r--r--app/controllers/concerns/invisible_captcha.rb4
-rw-r--r--app/controllers/confirmations_controller.rb2
-rw-r--r--app/controllers/projects/blob_controller.rb6
-rw-r--r--app/controllers/projects/starrers_controller.rb22
-rw-r--r--app/controllers/projects/wikis_controller.rb31
-rw-r--r--app/controllers/registrations_controller.rb12
-rw-r--r--app/helpers/groups_helper.rb5
-rw-r--r--app/helpers/notifications_helper.rb14
-rw-r--r--app/helpers/projects_helper.rb10
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/pipeline.rb4
-rw-r--r--app/models/clusters/clusters_hierarchy.rb4
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb20
-rw-r--r--app/models/user.rb7
-rw-r--r--app/models/users_star_project.rb1
-rw-r--r--app/presenters/blobs/unfold_presenter.rb43
-rw-r--r--app/serializers/deployment_entity.rb1
-rw-r--r--app/serializers/deployment_serializer.rb2
-rw-r--r--app/serializers/issuable_sidebar_basic_entity.rb4
-rw-r--r--app/services/ci/process_pipeline_service.rb4
-rw-r--r--app/services/git/base_hooks_service.rb31
-rw-r--r--app/services/git/branch_hooks_service.rb25
-rw-r--r--app/services/system_note_service.rb4
-rw-r--r--app/views/admin/applications/_delete_form.html.haml2
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.haml2
-rw-r--r--app/views/errors/access_denied.html.haml4
-rw-r--r--app/views/groups/_home_panel.html.haml3
-rw-r--r--app/views/groups/settings/_permissions.html.haml11
-rw-r--r--app/views/layouts/errors.html.haml6
-rw-r--r--app/views/layouts/header/_default.html.haml7
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml9
-rw-r--r--app/views/profiles/notifications/_project_settings.html.haml9
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml4
-rw-r--r--app/views/projects/_home_panel.html.haml4
-rw-r--r--app/views/projects/commit/_ajax_signature.html.haml2
-rw-r--r--app/views/projects/commit/_signature_badge.html.haml2
-rw-r--r--app/views/projects/cycle_analytics/show.html.haml29
-rw-r--r--app/views/projects/pages_domains/_certificate.html.haml18
-rw-r--r--app/views/projects/pages_domains/show.html.haml7
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml18
-rw-r--r--app/views/projects/wikis/_main_links.html.haml6
-rw-r--r--app/views/projects/wikis/_new.html.haml18
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml11
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/members/_group.html.haml2
-rw-r--r--app/views/shared/members/_member.html.haml2
-rw-r--r--app/views/shared/notifications/_button.html.haml15
-rw-r--r--app/views/shared/notifications/_new_button.html.haml13
-rw-r--r--app/workers/post_receive.rb30
-rw-r--r--app/workers/process_commit_worker.rb17
-rw-r--r--app/workers/project_cache_worker.rb6
-rw-r--r--changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml5
-rw-r--r--changelogs/unreleased/46299-wiki-page-creation.yml5
-rw-r--r--changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml5
-rw-r--r--changelogs/unreleased/50070-legacy-attachments.yml5
-rw-r--r--changelogs/unreleased/56130-deployment-date.yml5
-rw-r--r--changelogs/unreleased/59786-show-renamed-file-in-mr.yml5
-rw-r--r--changelogs/unreleased/61335-fix-file-icon-status.yml5
-rw-r--r--changelogs/unreleased/62373-badge-counter-very-low-contrast-between-foreground-and-background-c.yml6
-rw-r--r--changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml5
-rw-r--r--changelogs/unreleased/63905-discussion-expand-collapse-button-is-only-clickable-on-one-side.yml5
-rw-r--r--changelogs/unreleased/64251-branch-name-set-cache.yml5
-rw-r--r--changelogs/unreleased/64630-add-warning-to-pages-domains-that-obtaining-deploying-ssl-certifica.yml6
-rw-r--r--changelogs/unreleased/64677-delete-directory-webide.yml5
-rw-r--r--changelogs/unreleased/65427-improve-system-notes-for-zoom-links.yml5
-rw-r--r--changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml5
-rw-r--r--changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml5
-rw-r--r--changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml5
-rw-r--r--changelogs/unreleased/66161-replace-expand-icons.yml5
-rw-r--r--changelogs/unreleased/an-fix-sidekiq-histogram-buckets.yml5
-rw-r--r--changelogs/unreleased/bvl-mr-commit-note-counter.yml5
-rw-r--r--changelogs/unreleased/ci-config-on-policy.yml5
-rw-r--r--changelogs/unreleased/clean-obsolete-css.yml5
-rw-r--r--changelogs/unreleased/dm-process-commit-worker-n-1.yml5
-rw-r--r--changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml5
-rw-r--r--changelogs/unreleased/fix-commits-api-empty-refname.yml5
-rw-r--r--changelogs/unreleased/fix-file-row-styling.yml5
-rw-r--r--changelogs/unreleased/optimize-note-indexes.yml5
-rw-r--r--changelogs/unreleased/pb-update-gitlab-shell-9-4-0.yml5
-rw-r--r--changelogs/unreleased/remove-margin-from-user-header.yml5
-rw-r--r--changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml5
-rw-r--r--changelogs/unreleased/sh-fix-pipelines-not-being-created.yml5
-rw-r--r--changelogs/unreleased/sh-post-receive-cache-clear-once.yml5
-rw-r--r--changelogs/unreleased/tr-embed-metric-links.yml5
-rw-r--r--changelogs/unreleased/tr-param-undefined-fix.yml5
-rw-r--r--changelogs/unreleased/update-babel-to-7-5-5.yml5
-rw-r--r--config.ru18
-rw-r--r--config/initializers/0_inject_enterprise_edition_module.rb2
-rw-r--r--config/initializers/7_prometheus_metrics.rb3
-rw-r--r--config/initializers/8_devise.rb2
-rw-r--r--config/initializers/rack_attack_logging.rb4
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/wiki.rb1
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--db/fixtures/development/14_pipelines.rb75
-rw-r--r--db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb22
-rw-r--r--db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb30
-rw-r--r--db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb29
-rw-r--r--db/schema.rb13
-rw-r--r--doc/administration/geo/disaster_recovery/index.md2
-rw-r--r--doc/administration/geo/replication/index.md4
-rw-r--r--doc/administration/integration/plantuml.md4
-rw-r--r--doc/administration/logs.md16
-rw-r--r--doc/administration/raketasks/uploads/migrate.md10
-rw-r--r--doc/administration/troubleshooting/kubernetes_cheat_sheet.md26
-rw-r--r--doc/api/README.md11
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/group_boards.md2
-rw-r--r--doc/api/issues.md4
-rw-r--r--doc/api/merge_request_approvals.md294
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/ci/directed_acyclic_graph/index.md6
-rw-r--r--doc/ci/docker/using_docker_images.md19
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md5
-rw-r--r--doc/ci/multi_project_pipelines.md15
-rw-r--r--doc/ci/quick_start/img/build_log.pngbin35256 -> 138388 bytes
-rw-r--r--doc/ci/quick_start/img/builds_status.pngbin19107 -> 47887 bytes
-rw-r--r--doc/ci/quick_start/img/pipelines_status.pngbin22872 -> 64605 bytes
-rw-r--r--doc/ci/quick_start/img/runners_activated.pngbin18215 -> 104545 bytes
-rw-r--r--doc/ci/runners/README.md8
-rw-r--r--doc/ci/ssh_keys/README.md2
-rw-r--r--doc/ci/variables/README.md7
-rw-r--r--doc/ci/yaml/README.md22
-rw-r--r--doc/development/architecture.md22
-rw-r--r--doc/development/automatic_ce_ee_merge.md2
-rw-r--r--doc/development/contributing/issue_workflow.md31
-rw-r--r--doc/development/documentation/styleguide.md22
-rw-r--r--doc/development/fe_guide/axios.md2
-rw-r--r--doc/development/fe_guide/style_guide_js.md522
-rw-r--r--doc/development/fe_guide/vuex.md2
-rw-r--r--doc/development/go_guide/index.md26
-rw-r--r--doc/development/hash_indexes.md2
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/development/interacting_components.md4
-rw-r--r--doc/development/new_fe_guide/development/testing.md363
-rw-r--r--doc/development/new_fe_guide/modules/dirty_submit.md9
-rw-r--r--doc/development/new_fe_guide/style/javascript.md2
-rw-r--r--doc/development/query_recorder.md7
-rw-r--r--doc/development/rake_tasks.md3
-rw-r--r--doc/development/sha1_as_binary.md2
-rw-r--r--doc/development/shell_scripting_guide/index.md9
-rw-r--r--doc/development/sql.md30
-rw-r--r--doc/development/testing_guide/best_practices.md23
-rw-r--r--doc/development/testing_guide/ci.md1
-rw-r--r--doc/development/testing_guide/flaky_tests.md4
-rw-r--r--doc/development/testing_guide/frontend_testing.md363
-rw-r--r--doc/development/testing_guide/review_apps.md4
-rw-r--r--doc/development/testing_guide/testing_levels.md3
-rw-r--r--doc/development/verifying_database_capabilities.md8
-rw-r--r--doc/development/what_requires_downtime.md25
-rw-r--r--doc/integration/elasticsearch.md4
-rw-r--r--doc/security/rate_limits.md5
-rw-r--r--doc/topics/autodevops/index.md56
-rw-r--r--doc/university/training/end-user/README.md81
-rw-r--r--doc/university/training/topics/bisect.md4
-rw-r--r--doc/university/training/topics/cherry_picking.md6
-rw-r--r--doc/university/training/topics/feature_branching.md4
-rw-r--r--doc/university/training/topics/getting_started.md4
-rw-r--r--doc/university/training/topics/git_add.md4
-rw-r--r--doc/university/training/topics/merge_conflicts.md4
-rw-r--r--doc/university/training/topics/merge_requests.md2
-rw-r--r--doc/university/training/topics/stash.md6
-rw-r--r--doc/university/training/topics/tags.md12
-rw-r--r--doc/university/training/topics/unstage.md2
-rw-r--r--doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.pngbin0 -> 58254 bytes
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md22
-rw-r--r--doc/user/analytics/cycle_analytics.md182
-rw-r--r--doc/user/analytics/index.md22
-rw-r--r--doc/user/analytics/productivity_analytics.md69
-rw-r--r--doc/user/asciidoc.md4
-rw-r--r--doc/user/discussions/img/make_suggestion.pngbin28447 -> 115084 bytes
-rw-r--r--doc/user/discussions/img/suggestion.pngbin39775 -> 149758 bytes
-rw-r--r--doc/user/gitlab_com/index.md83
-rw-r--r--doc/user/group/index.md43
-rw-r--r--doc/user/group/saml_sso/scim_setup.md15
-rw-r--r--doc/user/permissions.md21
-rw-r--r--doc/user/profile/preferences.md10
-rw-r--r--doc/user/project/clusters/index.md3
-rw-r--r--doc/user/project/cycle_analytics.md182
-rw-r--r--doc/user/project/img/cycle_analytics_landing_page.pngbin64872 -> 0 bytes
-rw-r--r--doc/user/project/import/gitlab_com.md14
-rw-r--r--doc/user/project/import/img/gitlab_new_project_page.pngbin21251 -> 0 bytes
-rw-r--r--doc/user/project/import/img/gitlab_new_project_page_v12_2.pngbin0 -> 214213 bytes
-rw-r--r--doc/user/project/integrations/img/generate_link_to_chart.pngbin0 -> 35573 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page.pngbin22464 -> 0 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page_v12_2.pngbin0 -> 57327 bytes
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/integrations/prometheus.md4
-rw-r--r--doc/user/project/issues/design_management.md14
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md10
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.png (renamed from doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png)bin19461 -> 19461 bytes
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_edit_v12_2.png (renamed from doc/user/project/merge_requests/img/cross-project-dependencies-edit.png)bin19302 -> 19302 bytes
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_view_v12_2.png (renamed from doc/user/project/merge_requests/img/cross-project-dependencies-view.png)bin37528 -> 37528 bytes
-rw-r--r--doc/user/project/merge_requests/img/ff_merge_mr.pngbin0 -> 21380 bytes
-rw-r--r--doc/user/project/merge_requests/img/incrementally_expand_merge_request_diffs_v12_2.pngbin0 -> 96982 bytes
-rw-r--r--doc/user/project/merge_requests/index.md18
-rw-r--r--doc/user/project/merge_requests/merge_request_dependencies.md30
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md29
-rw-r--r--doc/user/project/operations/feature_flags.md15
-rw-r--r--doc/user/project/operations/img/target_users_v12_2.pngbin0 -> 42768 bytes
-rw-r--r--doc/user/project/quick_actions.md18
-rw-r--r--doc/user/project/repository/img/repository_languages.pngbin26516 -> 0 bytes
-rw-r--r--doc/user/project/repository/img/repository_languages_v12_2.gifbin0 -> 159195 bytes
-rw-r--r--doc/user/project/repository/index.md2
-rw-r--r--doc/user/project/settings/index.md6
-rw-r--r--doc/user/project/wiki/index.md9
-rw-r--r--doc/workflow/lfs/lfs_administration.md41
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md27
-rw-r--r--doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md2
-rw-r--r--doc/workflow/notifications.md8
-rw-r--r--doc/workflow/repository_mirroring.md31
-rw-r--r--doc/workflow/todos.md4
-rw-r--r--lib/api/api.rb2
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/gitlab/action_rate_limiter.rb4
-rw-r--r--lib/gitlab/background_migration/legacy_upload_mover.rb140
-rw-r--r--lib/gitlab/background_migration/legacy_uploads_migrator.rb23
-rw-r--r--lib/gitlab/background_migration/logger.rb12
-rw-r--r--lib/gitlab/ci/build/policy/variables.rb2
-rw-r--r--lib/gitlab/ci/build/rules.rb37
-rw-r--r--lib/gitlab/ci/build/rules/rule.rb32
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause.rb31
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause/changes.rb23
-rw-r--r--lib/gitlab/ci/build/rules/rule/clause/if.rb19
-rw-r--r--lib/gitlab/ci/config/entry/job.rb31
-rw-r--r--lib/gitlab/ci/config/entry/rules.rb33
-rw-r--r--lib/gitlab/ci/config/entry/rules/rule.rb42
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb70
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/yaml_processor.rb3
-rw-r--r--lib/gitlab/config/entry/validators.rb24
-rw-r--r--lib/gitlab/data_builder/push.rb2
-rw-r--r--lib/gitlab/git_post_receive.rb11
-rw-r--r--lib/gitlab/grape_logging/loggers/client_env_logger.rb16
-rw-r--r--lib/gitlab/repository_cache_adapter.rb53
-rw-r--r--lib/gitlab/repository_set_cache.rb67
-rw-r--r--lib/gitlab/sentry.rb17
-rw-r--r--lib/gitlab/sidekiq_middleware/metrics.rb6
-rw-r--r--lib/gitlab/usage_data.rb8
-rw-r--r--lib/gitlab/usage_data_counters/note_counter.rb8
-rw-r--r--lib/gt_one_coercion.rb7
-rw-r--r--lib/prometheus/cleanup_multiproc_dir_service.rb23
-rw-r--r--lib/tasks/gitlab/uploads/legacy.rake27
-rw-r--r--locale/gitlab.pot81
-rw-r--r--package.json8
-rw-r--r--qa/Dockerfile12
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock13
-rw-r--r--qa/README.md5
-rw-r--r--qa/qa.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb3
-rw-r--r--spec/controllers/concerns/confirm_email_warning_spec.rb98
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb2
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/health_check_controller_spec.rb12
-rw-r--r--spec/controllers/help_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb6
-rw-r--r--spec/controllers/projects/ci/lints_controller_spec.rb8
-rw-r--r--spec/controllers/projects/commit_controller_spec.rb10
-rw-r--r--spec/controllers/projects/commits_controller_spec.rb4
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb10
-rw-r--r--spec/controllers/projects/cycle_analytics/events_controller_spec.rb6
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb6
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb8
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb8
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb18
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb4
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/controllers/projects/starrers_controller_spec.rb166
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb77
-rw-r--r--spec/controllers/registrations_controller_spec.rb35
-rw-r--r--spec/controllers/users_controller_spec.rb6
-rw-r--r--spec/factories/ci/bridge.rb9
-rw-r--r--spec/factories/group_members.rb4
-rw-r--r--spec/factories/uploads.rb5
-rw-r--r--spec/features/boards/multiple_boards_spec.rb15
-rw-r--r--spec/features/groups/show_spec.rb23
-rw-r--r--spec/features/invites_spec.rb72
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb10
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb8
-rw-r--r--spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb7
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb8
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb12
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb8
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb93
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb68
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb16
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb5
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb30
-rw-r--r--spec/features/signed_commits_spec.rb18
-rw-r--r--spec/features/users/login_spec.rb35
-rw-r--r--spec/features/users/signup_spec.rb49
-rw-r--r--spec/fixtures/api/schemas/deployment.json2
-rw-r--r--spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json422
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json178
-rw-r--r--spec/fixtures/security-reports/deprecated/gl-sast-report.json964
-rw-r--r--spec/fixtures/security-reports/feature-branch.zipbin7140 -> 0 bytes
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json16
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dast-report.json40
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json181
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-license-management-report.json42
-rw-r--r--spec/fixtures/security-reports/feature-branch/gl-sast-report.json947
-rw-r--r--spec/fixtures/security-reports/master.zipbin9413 -> 0 bytes
-rw-r--r--spec/fixtures/security-reports/master/gl-container-scanning-report.json105
-rw-r--r--spec/fixtures/security-reports/master/gl-dast-report.json42
-rw-r--r--spec/fixtures/security-reports/master/gl-dependency-scanning-report.json181
-rw-r--r--spec/fixtures/security-reports/master/gl-license-management-report.json817
-rw-r--r--spec/fixtures/security-reports/master/gl-sast-report.json967
-rw-r--r--spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json104
-rw-r--r--spec/fixtures/security-reports/remediations/remediation.patch180
-rw-r--r--spec/fixtures/security-reports/remediations/yarn.lock104
-rw-r--r--spec/frontend/autosave_spec.js11
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js177
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js6
-rw-r--r--spec/frontend/mocks/mocks_helper_spec.js4
-rw-r--r--spec/frontend/notes/components/discussion_keyboard_navigator_spec.js27
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js123
-rw-r--r--spec/frontend/wikis_spec.js74
-rw-r--r--spec/helpers/groups_helper_spec.rb40
-rw-r--r--spec/helpers/notifications_helper_spec.rb11
-rw-r--r--spec/helpers/projects_helper_spec.rb25
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js2
-rw-r--r--spec/javascripts/ide/stores/utils_spec.js35
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js2
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js (renamed from spec/javascripts/monitoring/dashboard_spec.js)74
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js36
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js63
-rw-r--r--spec/javascripts/vue_shared/directives/autofocusonshow_spec.js38
-rw-r--r--spec/lib/gitlab/action_rate_limiter_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb296
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb63
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/build/rules/rule_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/build/rules_spec.rb168
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb111
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb12
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb208
-rw-r--r--spec/lib/gitlab/ci/config/entry/rules_spec.rb135
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb311
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb119
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb45
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb73
-rw-r--r--spec/lib/gitlab/sentry_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb24
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb21
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb51
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/pipeline_spec.rb7
-rw-r--r--spec/models/members/group_member_spec.rb36
-rw-r--r--spec/models/repository_spec.rb72
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb25
-rw-r--r--spec/requests/api/commits_spec.rb6
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb8
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb1
-rw-r--r--spec/requests/api/issues/issues_spec.rb4
-rw-r--r--spec/requests/rack_attack_global_spec.rb12
-rw-r--r--spec/serializers/deployment_entity_spec.rb4
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb80
-rw-r--r--spec/services/git/branch_push_service_spec.rb16
-rw-r--r--spec/services/issues/update_service_spec.rb4
-rw-r--r--spec/services/system_note_service_spec.rb4
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb4
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb144
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb2
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb66
-rw-r--r--spec/workers/post_receive_spec.rb71
-rw-r--r--spec/workers/process_commit_worker_spec.rb40
-rw-r--r--spec/workers/project_cache_worker_spec.rb10
-rw-r--r--yarn.lock36
432 files changed, 7201 insertions, 7777 deletions
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000000..b8e239e4049
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,72 @@
+# `build_from_dir` can't find Dockerfile when `.dockerignore` is "*"
+# See https://github.com/swipely/docker-api/issues/484
+# Ignore all folders except qa/, config/initializers and the root of lib/ since
+# the files we need to build the QA image are in these folders.
+# Following are the files we need:
+# - ./config/initializers/0_inject_enterprise_edition_module.rb
+# - ./lib/gitlab.rb
+# - ./qa/
+# - ./INSTALLATION_TYPE
+# - ./VERSION
+
+/app/
+/bin/
+/builds/
+/changelogs/
+/config/environments/
+/config/helpers/
+/config/knative/
+/config/locales/
+/config/prometheus/
+/config/routes/
+/danger/
+/db/
+/doc/
+/docker/
+/ee/
+/fixtures/
+/templates/
+/lint/
+/lib/api/
+/lib/assets/
+/lib/backup/
+/lib/banzai/
+/lib/bitbucket/
+/lib/server/
+/lib/constraints/
+/lib/registry/
+/lib/policy/
+/lib/feature/
+/lib/flowdock/
+/lib/generators/
+/lib/gitaly/
+/lib/gitlab/
+/lib/api/
+/lib/token/
+/lib/mattermost/
+/lib/teams/
+/lib/storage/
+/lib/auth/
+/lib/peek/
+/lib/prometheus/
+/lib/quality/
+/lib/rouge/
+/lib/flaky/
+/lib/zip/
+/lib/sentry/
+/lib/serializers/
+/lib/support/
+/lib/check/
+/lib/tasks/
+/locale/
+/log/
+/modules/
+/plugins/
+/public/
+/rubocop/
+/scripts/
+/shared/
+/spec/
+/symbol/
+/tmp/
+/vendor/
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index ca55bbd32a7..fc0e93ee9f1 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -2,6 +2,7 @@ include:
- template: Code-Quality.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
+ - template: Security/DAST.gitlab-ci.yml
code_quality:
extends: .dedicated-no-docs
@@ -24,3 +25,12 @@ dependency_scanning:
tags: []
before_script: []
cache: {}
+
+dast:
+ extends: .dedicated-no-docs
+ stage: qa
+ dependencies:
+ - review-deploy
+ before_script:
+ - export DAST_WEBSITE="$(cat review_app_url.txt)"
+ cache: {}
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index 4a9269ffd82..3cbfa32d9a5 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -50,7 +50,7 @@ build-qa-image:
<<: *review-docker
stage: test
script:
- - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
+ - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} --file ./qa/Dockerfile ./
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE}
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
index b7db5a33faf..0cac769bd55 100644
--- a/.gitlab/issue_templates/Feature Flag Roll Out.md
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -39,5 +39,6 @@ If applicable, any groups/projects that are happy to have this feature turned on
- [ ] Cross post chatops slack command to `#support_gitlab-com` and in your team channel
- [ ] Announce on the issue that the flag has been enabled
- [ ] Remove feature flag and add changelog entry
+- [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
/label ~"feature flag"
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index b13d146a7b0..ccfb75e5120 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-9.3.0
+9.4.1
diff --git a/Gemfile b/Gemfile
index a91399ab3ad..116c94bdaf8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -275,7 +275,6 @@ gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2'
gem 'request_store', '~> 1.3'
-gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
# Sentry integration
@@ -375,8 +374,6 @@ group :development, :test do
gem 'license_finder', '~> 5.4', require: false
gem 'knapsack', '~> 1.17'
- gem 'activerecord_sane_schema_dumper', '1.0'
-
gem 'stackprof', '~> 0.2.10', require: false
gem 'simple_po_parser', '~> 1.1.2', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index fcc0fb64897..0ecf3aa2840 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -41,8 +41,6 @@ GEM
activerecord-explain-analyze (0.1.0)
activerecord (>= 4)
pg
- activerecord_sane_schema_dumper (1.0)
- rails (>= 5, < 6)
activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
@@ -1028,7 +1026,6 @@ DEPENDENCIES
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.2)
activerecord-explain-analyze (~> 0.1)
- activerecord_sane_schema_dumper (= 1.0)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
@@ -1255,7 +1252,6 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
validates_hostname (~> 1.0.6)
version_sorter (~> 2.2.4)
- virtus (~> 1.0.1)
vmstat (~> 2.3.0)
webmock (~> 3.5.1)
webpack-rails (~> 0.9.10)
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue
new file mode 100644
index 00000000000..d946594a069
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue
@@ -0,0 +1,41 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { GlButton } from '@gitlab/ui';
+
+export default {
+ name: 'StageCardListItem',
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ isActive: {
+ type: Boolean,
+ required: true,
+ },
+ canEdit: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+};
+</script>
+
+<template>
+ <div :class="{ active: isActive }" class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded">
+ <slot></slot>
+ <div v-if="canEdit" class="dropdown">
+ <gl-button
+ :title="__('More actions')"
+ class="more-actions-toggle btn btn-transparent p-0"
+ data-toggle="dropdown"
+ >
+ <icon css-classes="icon" name="ellipsis_v" />
+ </gl-button>
+ <ul class="more-actions-dropdown dropdown-menu dropdown-open-left">
+ <slot name="dropdown-options"></slot>
+ </ul>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
new file mode 100644
index 00000000000..004d335f572
--- /dev/null
+++ b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
@@ -0,0 +1,88 @@
+<script>
+import StageCardListItem from './stage_card_list_item.vue';
+
+export default {
+ name: 'StageNavItem',
+ components: {
+ StageCardListItem,
+ },
+ props: {
+ isDefaultStage: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ isActive: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ isUserAllowed: {
+ type: Boolean,
+ required: true,
+ },
+ title: {
+ type: String,
+ required: true,
+ },
+ value: {
+ type: String,
+ default: '',
+ required: false,
+ },
+ canEdit: {
+ type: Boolean,
+ default: false,
+ required: false,
+ },
+ },
+ computed: {
+ hasValue() {
+ return this.value && this.value.length > 0;
+ },
+ editable() {
+ return this.isUserAllowed && this.canEdit;
+ },
+ },
+};
+</script>
+
+<template>
+ <li @click="$emit('select')">
+ <stage-card-list-item :is-active="isActive" :can-edit="editable">
+ <div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }">
+ {{ title }}
+ </div>
+ <div class="stage-nav-item-cell stage-median mr-4">
+ <template v-if="isUserAllowed">
+ <span v-if="hasValue">{{ value }}</span>
+ <span v-else class="stage-empty">{{ __('Not enough data') }}</span>
+ </template>
+ <template v-else>
+ <span class="not-available">{{ __('Not available') }}</span>
+ </template>
+ </div>
+ <template v-slot:dropdown-options>
+ <template v-if="isDefaultStage">
+ <li>
+ <button type="button" class="btn-default btn-transparent">
+ {{ __('Hide stage') }}
+ </button>
+ </li>
+ </template>
+ <template v-else>
+ <li>
+ <button type="button" class="btn-default btn-transparent">
+ {{ __('Edit stage') }}
+ </button>
+ </li>
+ <li>
+ <button type="button" class="btn-danger danger">
+ {{ __('Remove stage') }}
+ </button>
+ </li>
+ </template>
+ </template>
+ </stage-card-list-item>
+ </li>
+</template>
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
index 671405602cc..b3ae47af750 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -12,6 +12,7 @@ import stageComponent from './components/stage_component.vue';
import stageReviewComponent from './components/stage_review_component.vue';
import stageStagingComponent from './components/stage_staging_component.vue';
import stageTestComponent from './components/stage_test_component.vue';
+import stageNavItem from './components/stage_nav_item.vue';
import CycleAnalyticsService from './cycle_analytics_service';
import CycleAnalyticsStore from './cycle_analytics_store';
@@ -41,6 +42,7 @@ export default () => {
import('ee_component/analytics/shared/components/projects_dropdown_filter.vue'),
DateRangeDropdown: () =>
import('ee_component/analytics/shared/components/date_range_dropdown.vue'),
+ 'stage-nav-item': stageNavItem,
},
mixins: [filterMixins],
data() {
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 81da0754752..19b85710710 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -305,7 +305,7 @@ export default {
<div
v-show="showTreeList"
:style="{ width: `${treeWidth}px` }"
- class="diff-tree-list js-diff-tree-list"
+ class="diff-tree-list js-diff-tree-list mr-3"
>
<panel-resizer
:size.sync="treeWidth"
diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
index 925385fa98a..839ab542377 100644
--- a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
+++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
@@ -212,19 +212,18 @@ export default {
</script>
<template>
- <td :colspan="colspan">
+ <td :colspan="colspan" class="text-center">
<div class="content js-line-expansion-content">
<a
v-if="canExpandUp"
v-tooltip
- class="cursor-pointer js-unfold unfold-icon"
+ class="cursor-pointer js-unfold unfold-icon d-inline-block pt-2 pb-2"
data-placement="top"
data-container="body"
:title="__('Expand up')"
@click="handleExpandLines(EXPAND_UP)"
>
- <!-- TODO: remove style & replace with correct icon, waiting for MR https://gitlab.com/gitlab-org/gitlab-design/issues/499 -->
- <icon :size="12" name="expand-left" aria-hidden="true" style="transform: rotate(270deg);" />
+ <icon :size="12" name="expand-up" aria-hidden="true" />
</a>
<a class="mx-2 cursor-pointer js-unfold-all" @click="handleExpandLines()">
<span>{{ s__('Diffs|Show all lines') }}</span>
@@ -232,14 +231,13 @@ export default {
<a
v-if="canExpandDown"
v-tooltip
- class="cursor-pointer js-unfold-down has-tooltip unfold-icon"
+ class="cursor-pointer js-unfold-down has-tooltip unfold-icon d-inline-block pt-2 pb-2"
data-placement="top"
data-container="body"
:title="__('Expand down')"
@click="handleExpandLines(EXPAND_DOWN)"
>
- <!-- TODO: remove style & replace with correct icon, waiting for MR https://gitlab.com/gitlab-org/gitlab-design/issues/499 -->
- <icon :size="12" name="expand-left" aria-hidden="true" style="transform: rotate(90deg);" />
+ <icon :size="12" name="expand-down" aria-hidden="true" />
</a>
</div>
</td>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 32fbeaaa905..69ec6ab8600 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -130,7 +130,7 @@ export default {
return `\`${this.diffFile.file_path}\``;
},
isFileRenamed() {
- return this.diffFile.viewer.name === diffViewerModes.renamed;
+ return this.diffFile.renamed_file;
},
isModeChanged() {
return this.diffFile.viewer.name === diffViewerModes.mode_changed;
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 80a6ab9598a..7254c50a568 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -87,7 +87,6 @@ export default {
:file="file"
:show-tooltip="true"
:show-staged-icon="true"
- :force-modified-icon="true"
/>
<new-dropdown
:type="file.type"
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 04e86afb268..52200ce7847 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -129,7 +129,7 @@ export const commitActionForFile = file => {
export const getCommitFiles = stagedFiles =>
stagedFiles.reduce((acc, file) => {
- if (file.moved) return acc;
+ if (file.moved || file.type === 'tree') return acc;
return acc.concat({
...file,
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 1336b6a5461..7ead9d46fbb 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,5 +1,11 @@
import { join as joinPaths } from 'path';
+// Returns a decoded url parameter value
+// - Treats '+' as '%20'
+function decodeUrlParameter(val) {
+ return decodeURIComponent(val.replace(/\+/g, '%20'));
+}
+
// Returns an array containing the value(s) of the
// of the key passed as an argument
export function getParameterValues(sParam, url = window.location) {
@@ -30,7 +36,7 @@ export function mergeUrlParams(params, url) {
.forEach(part => {
if (part.length) {
const kv = part.split('=');
- merged[decodeURIComponent(kv[0])] = decodeURIComponent(kv.slice(1).join('='));
+ merged[decodeUrlParameter(kv[0])] = decodeUrlParameter(kv.slice(1).join('='));
}
});
}
diff --git a/app/assets/javascripts/members.js b/app/assets/javascripts/members.js
index af2697444f2..d719fd8748d 100644
--- a/app/assets/javascripts/members.js
+++ b/app/assets/javascripts/members.js
@@ -17,6 +17,8 @@ export default class Members {
}
dropdownClicked(options) {
+ options.e.preventDefault();
+
this.formSubmit(null, options.$el);
}
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 838447e6c75..90c764587a3 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -45,6 +45,11 @@ export default {
required: false,
default: () => false,
},
+ singleEmbed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
thresholds: {
type: Array,
required: false,
@@ -240,7 +245,10 @@ export default {
</script>
<template>
- <div class="prometheus-graph col-12 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+ <div
+ class="prometheus-graph col-12"
+ :class="[showBorder ? 'p-2' : 'p-0', { 'col-lg-6': !singleEmbed }]"
+ >
<div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 587392adbc3..ebd610af7b6 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -10,13 +10,13 @@ import {
} from '@gitlab/ui';
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import { getParameterValues } from '~/lib/utils/url_utility';
+import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
+import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
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 { sidebarAnimationDuration, timeWindows } from '../constants';
@@ -141,6 +141,16 @@ export default {
required: false,
default: false,
},
+ alertsEndpoint: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ prometheusAlertsAvailable: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -168,8 +178,11 @@ export default {
'multipleDashboardsEnabled',
'additionalPanelTypesEnabled',
]),
+ firstDashboard() {
+ return this.allDashboards[0] || {};
+ },
selectedDashboardText() {
- return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
+ return this.currentDashboard || this.firstDashboard.display_name;
},
addingMetricsAvailable() {
return IS_EE && this.canAddMetrics && !this.showEmptyState;
@@ -258,7 +271,15 @@ export default {
getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries));
},
+ showToast() {
+ this.$toast.show(__('Link copied to clipboard'));
+ },
// TODO: END
+ generateLink(group, title, yLabel) {
+ const dashboard = this.currentDashboard || this.firstDashboard.path;
+ const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
+ return mergeUrlParams(params, window.location.href);
+ },
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -435,8 +456,11 @@ export default {
<panel-type
v-for="(graphData, graphIndex) in groupData.metrics"
:key="`panel-type-${graphIndex}`"
+ :clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
:graph-data="graphData"
:dashboard-width="elWidth"
+ :alerts-endpoint="alertsEndpoint"
+ :prometheus-alerts-available="prometheusAlertsAvailable"
:index="`${index}-${graphIndex}`"
/>
</template>
@@ -475,6 +499,15 @@ export default {
{{ __('Download CSV') }}
</gl-dropdown-item>
<gl-dropdown-item
+ class="js-chart-link"
+ :data-clipboard-text="
+ generateLink(groupData.group, graphData.title, graphData.y_label)
+ "
+ @click="showToast"
+ >
+ {{ __('Generate link to chart') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}-${graphIndex}`"
>
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index 9e85b0633fe..e3256147618 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -36,12 +36,15 @@ export default {
},
computed: {
...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
- groupData() {
- const groupsWithData = this.groups.filter(group => this.chartsWithData(group.metrics).length);
- if (groupsWithData.length) {
- return groupsWithData[0];
- }
- return null;
+ charts() {
+ const groupWithMetrics = this.groups.find(group =>
+ group.metrics.find(chart => this.chartHasData(chart)),
+ ) || { metrics: [] };
+
+ return groupWithMetrics.metrics.filter(chart => this.chartHasData(chart));
+ },
+ isSingleChart() {
+ return this.charts.length === 1;
},
},
mounted() {
@@ -66,10 +69,8 @@ export default {
'setFeatureFlags',
'setShowErrorBanner',
]),
- chartsWithData(charts) {
- return charts.filter(chart =>
- chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
- );
+ chartHasData(chart) {
+ return chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id));
},
onSidebarMutation() {
setTimeout(() => {
@@ -89,16 +90,17 @@ export default {
};
</script>
<template>
- <div class="metrics-embed">
- <div v-if="groupData" class="row w-100 m-n2 pb-4">
+ <div class="metrics-embed" :class="{ 'd-inline-flex col-lg-6 p-0': isSingleChart }">
+ <div v-if="charts.length" class="row w-100 m-n2 pb-4">
<monitor-area-chart
- v-for="graphData in chartsWithData(groupData.metrics)"
+ v-for="graphData in charts"
:key="graphData.title"
:graph-data="graphData"
:container-width="elWidth"
group-id="monitor-area-chart"
:project-path="null"
:show-border="true"
+ :single-embed="isSingleChart"
/>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 3fbac71f3d7..96f62bc85ee 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -1,6 +1,7 @@
<script>
import { mapState } from 'vuex';
import _ from 'underscore';
+import { __ } from '~/locale';
import {
GlDropdown,
GlDropdownItem,
@@ -28,6 +29,10 @@ export default {
GlTooltip: GlTooltipDirective,
},
props: {
+ clipboardText: {
+ type: String,
+ required: true,
+ },
graphData: {
type: Object,
required: true,
@@ -76,6 +81,9 @@ export default {
isPanelType(type) {
return this.graphData.type && this.graphData.type === type;
},
+ showToast() {
+ this.$toast.show(__('Link copied to clipboard'));
+ },
},
};
</script>
@@ -116,6 +124,13 @@ export default {
<gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
{{ __('Download CSV') }}
</gl-dropdown-item>
+ <gl-dropdown-item
+ class="js-chart-link"
+ :data-clipboard-text="clipboardText"
+ @click="showToast"
+ >
+ {{ __('Generate link to chart') }}
+ </gl-dropdown-item>
<gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
{{ __('Alerts') }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index c0fee1ebb99..51cef20455c 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,9 +1,12 @@
import Vue from 'vue';
+import { GlToast } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
import store from './stores';
+Vue.use(GlToast);
+
export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
diff --git a/app/assets/javascripts/mr_notes/init_notes.js b/app/assets/javascripts/mr_notes/init_notes.js
index 8caac68e0d4..622db360d1f 100644
--- a/app/assets/javascripts/mr_notes/init_notes.js
+++ b/app/assets/javascripts/mr_notes/init_notes.js
@@ -59,6 +59,10 @@ export default () => {
render(createElement) {
const isDiffView = this.activeTab === 'diffs';
+ // NOTE: Even though `discussionKeyboardNavigator` is added to the `notes-app`,
+ // it adds a global key listener so it works on the diffs tab as well.
+ // If we create a single Vue app for all of the MR tabs, we should move this
+ // up the tree, to the root.
return createElement(discussionKeyboardNavigator, { props: { isDiffView } }, [
createElement('notes-app', {
props: {
diff --git a/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue b/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue
index 5fc2b6ba04c..7fbfe8eebb2 100644
--- a/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue
+++ b/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue
@@ -25,6 +25,10 @@ export default {
Mousetrap.bind('n', () => this.jumpToNextDiscussion());
Mousetrap.bind('p', () => this.jumpToPreviousDiscussion());
},
+ beforeDestroy() {
+ Mousetrap.unbind('n');
+ Mousetrap.unbind('p');
+ },
methods: {
...mapActions(['expandDiscussion']),
jumpToNextDiscussion() {
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 627d37bac68..a223a8f5b08 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -28,6 +28,11 @@ export default {
type: Object,
required: true,
},
+ canDisableEmails: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
canChangeVisibilityLevel: {
type: Boolean,
required: false,
@@ -104,6 +109,7 @@ export default {
lfsEnabled: true,
requestAccessEnabled: true,
highlightChangesClass: false,
+ emailsDisabled: false,
};
return { ...defaults, ...this.currentSettings };
@@ -341,5 +347,14 @@ export default {
/>
</project-setting-row>
</div>
+ <project-setting-row v-if="canDisableEmails" class="mb-3">
+ <label class="js-emails-disabled">
+ <input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" />
+ <input v-model="emailsDisabled" type="checkbox" /> {{ __('Disable email notifications') }}
+ </label>
+ <span class="form-text text-muted">{{
+ __('This setting will override user notification preferences for all project members.')
+ }}</span>
+ </project-setting-row>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index 9b58d42b47d..d41199f6374 100644
--- a/app/assets/javascripts/pages/projects/wikis/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,6 +1,5 @@
import bp from '../../../breakpoints';
-import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
-import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility';
+import { s__, sprintf } from '~/locale';
export default class Wikis {
constructor() {
@@ -12,32 +11,37 @@ export default class Wikis {
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
}
- this.newWikiForm = document.querySelector('form.new-wiki-page');
- if (this.newWikiForm) {
- this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
+ this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page'));
+ this.editTitleInput = document.querySelector('form.wiki-form #wiki_title');
+ this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message');
+ this.commitMessageI18n = this.isNewWikiPage
+ ? s__('WikiPageCreate|Create %{pageTitle}')
+ : s__('WikiPageEdit|Update %{pageTitle}');
+
+ if (this.editTitleInput) {
+ // Initialize the commit message on load
+ if (this.editTitleInput.value) this.setWikiCommitMessage(this.editTitleInput.value);
+
+ // Set the commit message as the page title is changed
+ this.editTitleInput.addEventListener('keyup', e => this.handleWikiTitleChange(e));
}
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
}
- handleNewWikiSubmit(e) {
- if (!this.newWikiForm) return;
-
- const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
-
- const slug = slugInput.value;
+ handleWikiTitleChange(e) {
+ this.setWikiCommitMessage(e.target.value);
+ }
- if (slug.length > 0) {
- const wikisPath = slugInput.getAttribute('data-wikis-path');
+ setWikiCommitMessage(rawTitle) {
+ let title = rawTitle;
- // If the wiki is empty, we need to merge the current URL params to keep the "create" view.
- const params = parseQueryStringIntoObject(window.location.search.substr(1));
- const url = mergeUrlParams(params, `${wikisPath}/${slug}`);
- redirectTo(url);
+ // Replace hyphens with spaces
+ if (title) title = title.replace(/-+/g, ' ');
- e.preventDefault();
- }
+ const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: title });
+ this.commitMessageInput.value = newCommitMessage;
}
handleToggleSidebar(e) {
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index e01080b04d6..4efc1d2408a 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -104,7 +104,7 @@ export default {
v-gl-tooltip
:title="
__(
- 'The code of a detached pipeline is tested against the source branch instead of merged results',
+ 'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results.',
)
"
class="js-pipeline-url-detached badge badge-info"
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index cdf2d1020ba..beb2ac09992 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -26,11 +26,6 @@ export default {
required: false,
default: false,
},
- forceModifiedIcon: {
- type: Boolean,
- required: false,
- default: false,
- },
size: {
type: Number,
required: false,
@@ -48,8 +43,6 @@ export default {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : '';
- if (this.forceModifiedIcon) return `file-modified${suffix}`;
-
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
changedIconClass() {
@@ -88,7 +81,7 @@ export default {
v-gl-tooltip.right
:title="tooltipTitle"
:class="{ 'ml-auto': isCentered }"
- class="file-changed-icon"
+ class="file-changed-icon d-inline-block"
>
<icon v-if="showIcon" :name="changedIcon" :size="size" :css-classes="changedIconClass" />
</span>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index fe5289ff371..f49e69c473b 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -146,6 +146,7 @@ export default {
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
<file-icon
v-if="!showChangedIcon || file.type === 'tree'"
+ class="file-row-icon"
:file-name="file.name"
:loading="file.loading"
:folder="isTree"
@@ -223,13 +224,8 @@ export default {
white-space: nowrap;
}
-.file-row-name svg {
+.file-row-name .file-row-icon {
margin-right: 2px;
vertical-align: middle;
}
-
-.file-row-name .loading-container {
- display: inline-block;
- margin-right: 4px;
-}
</style>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index df19906309c..f0aae20477b 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -30,9 +30,16 @@ export default {
},
mounted() {
+ if (window.recaptchaDialogCallback) {
+ throw new Error('recaptchaDialogCallback is already defined!');
+ }
window.recaptchaDialogCallback = this.submit.bind(this);
},
+ beforeDestroy() {
+ window.recaptchaDialogCallback = null;
+ },
+
methods: {
appendRecaptchaScript() {
this.removeRecaptchaScript();
diff --git a/app/assets/javascripts/vue_shared/directives/autofocusonshow.js b/app/assets/javascripts/vue_shared/directives/autofocusonshow.js
new file mode 100644
index 00000000000..4659ec20ceb
--- /dev/null
+++ b/app/assets/javascripts/vue_shared/directives/autofocusonshow.js
@@ -0,0 +1,39 @@
+/**
+ * Input/Textarea Autofocus Directive for Vue
+ */
+export default {
+ /**
+ * Set focus when element is rendered, but
+ * is not visible, using IntersectionObserver
+ *
+ * @param {Element} el Target element
+ */
+ inserted(el) {
+ if ('IntersectionObserver' in window) {
+ // Element visibility is dynamic, so we attach observer
+ el.visibilityObserver = new IntersectionObserver(entries => {
+ entries.forEach(entry => {
+ // Combining `intersectionRatio > 0` and
+ // element's `offsetParent` presence will
+ // deteremine if element is truely visible
+ if (entry.intersectionRatio > 0 && entry.target.offsetParent) {
+ entry.target.focus();
+ }
+ });
+ });
+
+ // Bind the observer.
+ el.visibilityObserver.observe(el, { root: document.documentElement });
+ }
+ },
+ /**
+ * Detach observer on unbind hook.
+ *
+ * @param {Element} el Target element
+ */
+ unbind(el) {
+ if (el.visibilityObserver) {
+ el.visibilityObserver.disconnect();
+ }
+ },
+};
diff --git a/app/assets/stylesheets/errors.scss b/app/assets/stylesheets/errors.scss
index d287215096e..89029a58d1e 100644
--- a/app/assets/stylesheets/errors.scss
+++ b/app/assets/stylesheets/errors.scss
@@ -96,7 +96,7 @@ a {
}
.error-nav {
- padding: 0;
+ padding: $gl-padding 0 0;
text-align: center;
li {
diff --git a/app/assets/stylesheets/framework/badges.scss b/app/assets/stylesheets/framework/badges.scss
index c6060161dec..c036267a7c8 100644
--- a/app/assets/stylesheets/framework/badges.scss
+++ b/app/assets/stylesheets/framework/badges.scss
@@ -1,6 +1,6 @@
.badge.badge-pill {
font-weight: $gl-font-weight-normal;
background-color: $badge-bg;
- color: $gl-text-color-secondary;
+ color: $gray-800;
vertical-align: baseline;
}
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index f384a49e0ae..e9218dcec67 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -438,6 +438,7 @@ img.emoji {
.w-3rem { width: 3rem; }
.h-12em { height: 12em; }
+.h-32-px { height: 32px;}
.mw-460 { max-width: 460px; }
.mw-6em { max-width: 6em; }
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index e3dd127366d..96f6d02a68f 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -43,6 +43,7 @@
@extend .alert;
background-color: $orange-100;
color: $orange-900;
+ cursor: default;
margin: 0;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 1bc597bd4ae..ca737c53318 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -131,7 +131,6 @@
> li:not(.d-none) a {
@include media-breakpoint-down(xs) {
margin-left: 0;
- min-width: 100%;
}
}
}
@@ -233,7 +232,6 @@
.impersonation-btn,
.impersonation-btn:hover {
background-color: $white-light;
- margin-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
diff --git a/app/assets/stylesheets/highlight/common.scss b/app/assets/stylesheets/highlight/common.scss
index ac3214a07d9..bdeac7e97c0 100644
--- a/app/assets/stylesheets/highlight/common.scss
+++ b/app/assets/stylesheets/highlight/common.scss
@@ -16,3 +16,16 @@
color: $dark-diff-match-bg;
background: $dark-diff-match-color;
}
+
+@mixin diff-expansion($background, $border, $link) {
+ background-color: $background;
+
+ td {
+ border-top: 1px solid $border;
+ border-bottom: 1px solid $border;
+ }
+
+ a {
+ color: $link;
+ }
+}
diff --git a/app/assets/stylesheets/highlight/themes/dark.scss b/app/assets/stylesheets/highlight/themes/dark.scss
index 16893dd047e..cbce0ba3f1e 100644
--- a/app/assets/stylesheets/highlight/themes/dark.scss
+++ b/app/assets/stylesheets/highlight/themes/dark.scss
@@ -111,6 +111,10 @@ $dark-il: #de935f;
color: $dark-line-color;
}
+ .line_expansion {
+ @include diff-expansion($dark-main-bg, $dark-border, $dark-na);
+ }
+
// Diff line
.line_holder {
&.match .line_content,
diff --git a/app/assets/stylesheets/highlight/themes/monokai.scss b/app/assets/stylesheets/highlight/themes/monokai.scss
index 37fe61b925c..1b61ffa37e3 100644
--- a/app/assets/stylesheets/highlight/themes/monokai.scss
+++ b/app/assets/stylesheets/highlight/themes/monokai.scss
@@ -111,6 +111,10 @@ $monokai-gi: #a6e22e;
color: $monokai-text-color;
}
+ .line_expansion {
+ @include diff-expansion($monokai-bg, $monokai-border, $monokai-k);
+ }
+
// Diff line
.line_holder {
&.match .line_content,
diff --git a/app/assets/stylesheets/highlight/themes/none.scss b/app/assets/stylesheets/highlight/themes/none.scss
index b4217aac37a..a7ede266fb5 100644
--- a/app/assets/stylesheets/highlight/themes/none.scss
+++ b/app/assets/stylesheets/highlight/themes/none.scss
@@ -34,8 +34,11 @@
color: $gl-text-color;
}
-// Diff line
+ .line_expansion {
+ @include diff-expansion($gray-light, $white-normal, $gl-text-color);
+ }
+ // Diff line
$none-over-bg: #ded7fc;
$none-expanded-border: #e0e0e0;
$none-expanded-bg: #e0e0e0;
diff --git a/app/assets/stylesheets/highlight/themes/solarized-dark.scss b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
index a4e9eda22c9..6569f3abc8b 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-dark.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-dark.scss
@@ -115,6 +115,10 @@ $solarized-dark-il: #2aa198;
color: $solarized-dark-pre-color;
}
+ .line_expansion {
+ @include diff-expansion($solarized-dark-line-bg, $solarized-dark-border, $solarized-dark-kd);
+ }
+
// Diff line
.line_holder {
&.match .line_content,
diff --git a/app/assets/stylesheets/highlight/themes/solarized-light.scss b/app/assets/stylesheets/highlight/themes/solarized-light.scss
index b604d1ccb6c..4e74a9ea50a 100644
--- a/app/assets/stylesheets/highlight/themes/solarized-light.scss
+++ b/app/assets/stylesheets/highlight/themes/solarized-light.scss
@@ -122,6 +122,10 @@ $solarized-light-il: #2aa198;
color: $solarized-light-pre-color;
}
+ .line_expansion {
+ @include diff-expansion($solarized-light-line-bg, $solarized-light-border, $solarized-light-kd);
+ }
+
// Diff line
.line_holder {
&.match .line_content,
diff --git a/app/assets/stylesheets/highlight/white_base.scss b/app/assets/stylesheets/highlight/white_base.scss
index b3974df8639..973f94c63aa 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -101,24 +101,8 @@ pre.code,
color: $white-code-color;
}
-// Expansion line
.line_expansion {
- background-color: $gray-light;
-
- td {
- border-top: 1px solid $border-color;
- border-bottom: 1px solid $border-color;
- text-align: center;
- }
-
- a {
- color: $blue-600;
- }
-
- .unfold-icon {
- display: inline-block;
- padding: 8px 0;
- }
+ @include diff-expansion($gray-light, $border-color, $blue-600);
}
// Diff line
diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss
index 2b932d164a5..d80155a416d 100644
--- a/app/assets/stylesheets/pages/cycle_analytics.scss
+++ b/app/assets/stylesheets/pages/cycle_analytics.scss
@@ -51,27 +51,19 @@
}
.stage-header {
- width: 26%;
- padding-left: $gl-padding;
+ width: 18.5%;
}
.median-header {
- width: 14%;
+ width: 21.5%;
}
.event-header {
width: 45%;
- padding-left: $gl-padding;
}
.total-time-header {
width: 15%;
- text-align: right;
- padding-right: $gl-padding;
- }
-
- .stage-name {
- font-weight: $gl-font-weight-bold;
}
}
@@ -153,23 +145,13 @@
}
.stage-nav-item {
- display: flex;
line-height: 65px;
- border-top: 1px solid transparent;
- border-bottom: 1px solid transparent;
- border-right: 1px solid $border-color;
- background-color: $gray-light;
+ border: 1px solid $border-color;
&.active {
- background-color: transparent;
- border-right-color: transparent;
- border-top-color: $border-color;
- border-bottom-color: $border-color;
- box-shadow: inset 2px 0 0 0 $blue-500;
-
- .stage-name {
- font-weight: $gl-font-weight-bold;
- }
+ background: $blue-50;
+ border-color: $blue-300;
+ box-shadow: inset 4px 0 0 0 $blue-500;
}
&:hover:not(.active) {
@@ -178,24 +160,12 @@
cursor: pointer;
}
- &:first-child {
- border-top: 0;
- }
-
- &:last-child {
- border-bottom: 0;
- }
-
- .stage-nav-item-cell {
- &.stage-median {
- margin-left: auto;
- margin-right: $gl-padding;
- min-width: calc(35% - #{$gl-padding});
- }
+ .stage-nav-item-cell.stage-name {
+ width: 44.5%;
}
- .stage-name {
- padding-left: 16px;
+ .stage-nav-item-cell.stage-median {
+ min-width: 43%;
}
.stage-empty,
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index ffb27e54f34..77a2fd6b876 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -1032,7 +1032,6 @@ table.code {
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
max-height: calc(100vh - #{$top-pos});
- padding-right: $gl-padding;
z-index: 202;
.with-performance-bar & {
@@ -1043,7 +1042,7 @@ table.code {
.drag-handle {
bottom: 16px;
- transform: translateX(-6px);
+ transform: translateX(10px);
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 66b4f3bad2b..fa52ce6402d 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -522,6 +522,7 @@
}
.multiple-users {
+ position: relative;
height: 24px;
margin-bottom: 17px;
margin-top: 4px;
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index 85e9f303dde..0fbf7033aa5 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -48,11 +48,6 @@
padding: $gl-padding-top $gl-padding;
border-top: 1px solid $border-color;
}
-
- .report-block-list-icon .loading-container {
- position: relative;
- left: -2px;
- }
}
.report-block-container {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 5e65084a110..af6644b8fcc 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
include SessionlessAuthentication
+ include ConfirmEmailWarning
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
new file mode 100644
index 00000000000..5a4b5897a4f
--- /dev/null
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module ConfirmEmailWarning
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) }
+ end
+
+ protected
+
+ def set_confirm_warning
+ return unless current_user
+ return if current_user.confirmed?
+ return if peek_request? || json_request? || !request.get?
+
+ email = current_user.unconfirmed_email || current_user.email
+
+ flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % {
+ email: email,
+ resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
+ update_link: view_context.link_to(_('Update it'), profile_path)
+ }
+ end
+end
diff --git a/app/controllers/concerns/invisible_captcha.rb b/app/controllers/concerns/invisible_captcha.rb
index c9f66e5c194..45c0a5c58ef 100644
--- a/app/controllers/concerns/invisible_captcha.rb
+++ b/app/controllers/concerns/invisible_captcha.rb
@@ -41,9 +41,9 @@ module InvisibleCaptcha
request_information = {
message: message,
env: :invisible_captcha_signup_bot_detected,
- ip: request.ip,
+ remote_ip: request.ip,
request_method: request.request_method,
- fullpath: request.fullpath
+ path: request.fullpath
}
Gitlab::AuthLogger.error(request_information)
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 2ae500a2fdf..b192189ba3c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController
protected
def after_resending_confirmation_instructions_path_for(resource)
- users_almost_there_path
+ Feature.enabled?(:soft_email_confirmation) ? stored_location_for(resource) || dashboard_projects_path : users_almost_there_path
end
def after_confirmation_path_for(resource_name, resource)
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index b04ffe80db4..4125f44d00a 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -92,7 +92,7 @@ class Projects::BlobController < Projects::ApplicationController
def diff
apply_diff_view_cookie!
- @form = Blobs::UnfoldPresenter.new(blob, params.to_unsafe_h)
+ @form = Blobs::UnfoldPresenter.new(blob, diff_params)
# keep only json rendering when
# https://gitlab.com/gitlab-org/gitlab-ce/issues/44988 is done
@@ -239,4 +239,8 @@ class Projects::BlobController < Projects::ApplicationController
def tree_path
@path.rpartition('/').first
end
+
+ def diff_params
+ params.permit(:full, :since, :to, :bottom, :unfold, :offset, :indent)
+ end
end
diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb
index c8facea1d70..4efe956e973 100644
--- a/app/controllers/projects/starrers_controller.rb
+++ b/app/controllers/projects/starrers_controller.rb
@@ -5,25 +5,11 @@ class Projects::StarrersController < Projects::ApplicationController
def index
@starrers = UsersStarProjectsFinder.new(@project, params, current_user: @current_user).execute
-
- # Normally the number of public starrers is equal to the number of visible
- # starrers. We need to fix the counts in two cases: when the current user
- # is an admin (and can see everything) and when the current user has a
- # private profile and has starred the project (and can see itself).
- @public_count =
- if @current_user&.admin?
- @starrers.with_public_profile.count
- elsif @current_user&.private_profile && has_starred_project?(@starrers)
- @starrers.size - 1
- else
- @starrers.size
- end
-
- @total_count = @project.starrers.size
- @private_count = @total_count - @public_count
-
@sort = params[:sort].presence || sort_value_name
- @starrers = @starrers.sort_by_attribute(@sort).page(params[:page])
+ @starrers = @starrers.preload_users.sort_by_attribute(@sort).page(params[:page])
+ @public_count = @project.starrers.with_public_profile.size
+ @total_count = @project.starrers.size
+ @private_count = @total_count - @public_count
end
private
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index d1914c35bd3..b187fdb2723 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -16,6 +16,10 @@ class Projects::WikisController < Projects::ApplicationController
redirect_to(project_wiki_path(@project, @page))
end
+ def new
+ redirect_to project_wiki_path(@project, SecureRandom.uuid, random_title: true)
+ end
+
def pages
@wiki_pages = Kaminari.paginate_array(
@project_wiki.list_pages(sort: params[:sort], direction: params[:direction])
@@ -24,17 +28,25 @@ class Projects::WikisController < Projects::ApplicationController
@wiki_entries = WikiPage.group_by_directory(@wiki_pages)
end
+ # `#show` handles a number of scenarios:
+ #
+ # - If `id` matches a WikiPage, then show the wiki page.
+ # - If `id` is a file in the wiki repository, then send the file.
+ # - If we know the user wants to create a new page with the given `id`,
+ # then display a create form.
+ # - Otherwise show the empty wiki page and invite the user to create a page.
def show
- view_param = @project_wiki.empty? ? params[:view] : 'create'
-
if @page
set_encoding_error unless valid_encoding?
render 'show'
elsif file_blob
send_blob(@project_wiki.repository, file_blob)
- elsif can?(current_user, :create_wiki, @project) && view_param == 'create'
- @page = build_page(title: params[:id])
+ elsif show_create_form?
+ # Assign a title to the WikiPage unless `id` is a randomly generated slug from #new
+ title = params[:id] unless params[:random_title].present?
+
+ @page = build_page(title: title)
render 'edit'
else
@@ -110,6 +122,15 @@ class Projects::WikisController < Projects::ApplicationController
private
+ def show_create_form?
+ can?(current_user, :create_wiki, @project) &&
+ @page.nil? &&
+ # Always show the create form when the wiki has had at least one page created.
+ # Otherwise, we only show the form when the user has navigated from
+ # the 'empty wiki' page
+ (@project_wiki.exists? || params[:view] == 'create')
+ end
+
def load_project_wiki
@project_wiki = load_wiki
@@ -135,7 +156,7 @@ class Projects::WikisController < Projects::ApplicationController
params.require(:wiki).permit(:title, :content, :format, :message, :last_commit_sha)
end
- def build_page(args)
+ def build_page(args = {})
WikiPage.new(@project_wiki).tap do |page|
page.update_attributes(args) # rubocop:disable Rails/ActiveRecordAliases
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index db10515c0b4..e773ec09924 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -69,12 +69,12 @@ class RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(user)
Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?))
- user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path
+ confirmed_or_unconfirmed_access_allowed(user) ? stored_location_or_dashboard(user) : users_almost_there_path
end
def after_inactive_sign_up_path_for(resource)
Gitlab::AppLogger.info(user_created_message)
- users_almost_there_path
+ Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path
end
private
@@ -135,4 +135,12 @@ class RegistrationsController < Devise::RegistrationsController
def terms_accepted?
Gitlab::Utils.to_boolean(params[:terms_opt_in])
end
+
+ def confirmed_or_unconfirmed_access_allowed(user)
+ user.confirmed? || Feature.enabled?(:soft_email_confirmation)
+ end
+
+ def stored_location_or_dashboard(user)
+ stored_location_for(user) || dashboard_projects_path
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 160f9ac4793..bd26bd01313 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -31,6 +31,11 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group)
end
+ def can_disable_group_emails?(group)
+ Feature.enabled?(:emails_disabled, group, default_enabled: true) &&
+ can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
+ end
+
def group_issues_count(state:)
IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 11b9cf22142..5678304ffcf 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -5,7 +5,7 @@ module NotificationsHelper
def notification_icon_class(level)
case level.to_sym
- when :disabled
+ when :disabled, :owner_disabled
'microphone-slash'
when :participating
'volume-up'
@@ -18,6 +18,16 @@ module NotificationsHelper
end
end
+ def notification_icon_level(notification_setting, emails_disabled = false)
+ if emails_disabled
+ 'owner_disabled'
+ elsif notification_setting.global?
+ current_user.global_notification_setting.level
+ else
+ notification_setting.level
+ end
+ end
+
def notification_icon(level, text = nil)
icon("#{notification_icon_class(level)} fw", text: text)
end
@@ -53,6 +63,8 @@ module NotificationsHelper
_('Use your global notification setting')
when :custom
_('You will only receive notifications for the events you choose')
+ when :owner_disabled
+ _('Notifications have been disabled by the project or group owner')
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 71c9c121e48..33bf2d57fae 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -155,6 +155,12 @@ module ProjectsHelper
end
end
+ def can_disable_emails?(project, current_user)
+ return false if project.group&.emails_disabled?
+
+ can?(current_user, :set_emails_disabled, project) && Feature.enabled?(:emails_disabled, project, default_enabled: true)
+ end
+
def last_push_event
current_user&.recent_push(@project)
end
@@ -541,13 +547,15 @@ module ProjectsHelper
snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
- lfsEnabled: !!project.lfs_enabled
+ lfsEnabled: !!project.lfs_enabled,
+ emailsDisabled: project.emails_disabled?
}
end
def project_permissions_panel_data(project)
{
currentSettings: project_permissions_settings(project),
+ canDisableEmails: can_disable_emails?(project, current_user),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'),
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index f705e67121f..3c0efca31db 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -716,7 +716,7 @@ module Ci
depended_jobs = depends_on_builds
# find all jobs that are needed
- if Feature.enabled?(:ci_dag_support, project) && needs.exists?
+ if Feature.enabled?(:ci_dag_support, project, default_enabled: true) && needs.exists?
depended_jobs = depended_jobs.where(name: needs.select(:name))
end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 3b28eb246db..0a943a33bbb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -328,6 +328,10 @@ module Ci
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end
+ def self.bridgeable_statuses
+ ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created preparing pending]
+ end
+
def stages_count
statuses.select(:stage).distinct.count
end
diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb
index dab034b7234..5556fc8d3f0 100644
--- a/app/models/clusters/clusters_hierarchy.rb
+++ b/app/models/clusters/clusters_hierarchy.rb
@@ -46,7 +46,7 @@ module Clusters
def group_clusters_base_query
group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id')
- join_sources = ::Group.left_joins(:clusters).join_sources
+ join_sources = ::Group.left_joins(:clusters).arel.join_sources
model
.unscoped
@@ -59,7 +59,7 @@ module Clusters
def project_clusters_base_query
projects = ::Project.arel_table
project_parent_id_alias = alias_as_column(projects[:namespace_id], 'group_parent_id')
- join_sources = ::Project.left_joins(:clusters).join_sources
+ join_sources = ::Project.left_joins(:clusters).arel.join_sources
model
.unscoped
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index f6b19317c50..3d6f397e599 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -15,8 +15,8 @@ class GroupMember < Member
default_scope { where(source_type: SOURCE_TYPE) }
scope :of_groups, ->(groups) { where(source_id: groups.select(:id)) }
-
scope :count_users_by_group_id, -> { joins(:user).group(:source_id).count }
+ scope :of_ldap_type, -> { where(ldap: true) }
after_create :update_two_factor_requirement, unless: :invite?
after_destroy :update_two_factor_requirement, unless: :invite?
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index c91add6439f..4a19e05bf76 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -85,6 +85,10 @@ class ProjectWiki
list_pages(limit: 1).empty?
end
+ def exists?
+ !empty?
+ end
+
# Lists wiki pages of the repository.
#
# limit - max number of pages returned by the method.
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 9d45a12fa6e..b957b9b0bdd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -239,13 +239,13 @@ class Repository
def branch_exists?(branch_name)
return false unless raw_repository
- branch_names.include?(branch_name)
+ branch_names_include?(branch_name)
end
def tag_exists?(tag_name)
return false unless raw_repository
- tag_names.include?(tag_name)
+ tag_names_include?(tag_name)
end
def ref_exists?(ref)
@@ -389,11 +389,15 @@ class Repository
expire_statistics_caches
end
- # Runs code after a repository has been created.
- def after_create
+ def expire_status_cache
expire_exists_cache
expire_root_ref_cache
expire_emptiness_caches
+ end
+
+ # Runs code after a repository has been created.
+ def after_create
+ expire_status_cache
repository_event(:create_repository)
end
@@ -561,10 +565,10 @@ class Repository
end
delegate :branch_names, to: :raw_repository
- cache_method :branch_names, fallback: []
+ cache_method_as_redis_set :branch_names, fallback: []
delegate :tag_names, to: :raw_repository
- cache_method :tag_names, fallback: []
+ cache_method_as_redis_set :tag_names, fallback: []
delegate :branch_count, :tag_count, :has_visible_content?, to: :raw_repository
cache_method :branch_count, fallback: 0
@@ -1126,6 +1130,10 @@ class Repository
@cache ||= Gitlab::RepositoryCache.new(self)
end
+ def redis_set_cache
+ @redis_set_cache ||= Gitlab::RepositorySetCache.new(self)
+ end
+
def request_store_cache
@request_store_cache ||= Gitlab::RepositoryCache.new(self, backend: Gitlab::SafeRequestStore)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 374e00987c5..6131a8dc710 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1507,6 +1507,13 @@ class User < ApplicationRecord
super
end
+ # override from Devise::Confirmable
+ def confirmation_period_valid?
+ return false if Feature.disabled?(:soft_email_confirmation)
+
+ super
+ end
+
private
def default_private_profile_to_false
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 3c7a805cc5c..c633e2d8b3d 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -17,6 +17,7 @@ class UsersStarProject < ApplicationRecord
scope :by_project, -> (project) { where(project_id: project.id) }
scope :with_visible_profile, -> (user) { joins(:user).merge(User.with_visible_profile(user)) }
scope :with_public_profile, -> { joins(:user).merge(User.with_public_profile) }
+ scope :preload_users, -> { preload(:user) }
class << self
def sort_by_attribute(method)
diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb
index 4c6dd6895cf..a256dd05a4d 100644
--- a/app/presenters/blobs/unfold_presenter.rb
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -1,30 +1,34 @@
# frozen_string_literal: true
-require 'gt_one_coercion'
-
module Blobs
class UnfoldPresenter < BlobPresenter
- include Virtus.model
+ include ActiveModel::Attributes
+ include ActiveModel::AttributeAssignment
include Gitlab::Utils::StrongMemoize
- attribute :full, Boolean, default: false
- attribute :since, GtOneCoercion
- attribute :to, Integer
- attribute :bottom, Boolean
- attribute :unfold, Boolean, default: true
- attribute :offset, Integer
- attribute :indent, Integer, default: 0
+ attribute :full, :boolean, default: false
+ attribute :since, :integer, default: 1
+ attribute :to, :integer, default: 1
+ attribute :bottom, :boolean, default: false
+ attribute :unfold, :boolean, default: true
+ attribute :offset, :integer, default: 0
+ attribute :indent, :integer, default: 0
+
+ alias_method :full?, :full
+ alias_method :bottom?, :bottom
+ alias_method :unfold?, :unfold
def initialize(blob, params)
+ super(blob)
+ self.attributes = params
+
# Load all blob data first as we need to ensure they're all loaded first
# so we can accurately show the rest of the diff when unfolding.
load_all_blob_data
- @subject = blob
@all_lines = blob.data.lines
- super(params)
- self.attributes = prepare_attributes
+ handle_full_or_end!
end
# Returns an array of Gitlab::Diff::Line with match line added
@@ -56,21 +60,18 @@ module Blobs
private
- def prepare_attributes
- return attributes unless full? || to == -1
+ def handle_full_or_end!
+ return unless full? || to == -1
- full_opts = {
- since: 1,
+ self.since = 1 if full?
+
+ self.attributes = {
to: all_lines_size,
bottom: false,
unfold: false,
offset: 0,
indent: 0
}
-
- return full_opts if full?
-
- full_opts.merge(attributes.slice(:since))
end
def all_lines_size
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 943c707218d..6e91317eb20 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -18,6 +18,7 @@ class DeploymentEntity < Grape::Entity
end
expose :created_at
+ expose :finished_at
expose :tag
expose :last?
expose :user, using: UserEntity
diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb
index 04db6b88489..3fd3e1b9cc8 100644
--- a/app/serializers/deployment_serializer.rb
+++ b/app/serializers/deployment_serializer.rb
@@ -4,7 +4,7 @@ class DeploymentSerializer < BaseSerializer
entity DeploymentEntity
def represent_concise(resource, opts = {})
- opts[:only] = [:iid, :id, :sha, :created_at, :tag, :last?, :id, ref: [:name]]
+ opts[:only] = [:iid, :id, :sha, :created_at, :finished_at, :tag, :last?, :id, ref: [:name]]
represent(resource, opts)
end
end
diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb
index 61de3c93337..c02fd024345 100644
--- a/app/serializers/issuable_sidebar_basic_entity.rb
+++ b/app/serializers/issuable_sidebar_basic_entity.rb
@@ -98,6 +98,10 @@ class IssuableSidebarBasicEntity < Grape::Entity
autocomplete_projects_path(project_id: issuable.project.id)
end
+ expose :project_emails_disabled do |issuable|
+ issuable.project.emails_disabled?
+ end
+
private
def current_user
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index f4bd457ebc6..3b145a65d79 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -40,7 +40,7 @@ module Ci
def process_builds_with_needs(trigger_build_ids)
return false unless trigger_build_ids.present?
- return false unless Feature.enabled?(:ci_dag_support, project)
+ return false unless Feature.enabled?(:ci_dag_support, project, default_enabled: true)
# we find processables that are dependent:
# 1. because of current dependency,
@@ -96,7 +96,7 @@ module Ci
end
def created_processables_without_needs
- if Feature.enabled?(:ci_dag_support, project)
+ if Feature.enabled?(:ci_dag_support, project, default_enabled: true)
pipeline.processables.created.without_needs
else
pipeline.processables.created
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index 1db18fcf401..47c308c8280 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -8,8 +8,6 @@ module Git
PROCESS_COMMIT_LIMIT = 100
def execute
- project.repository.after_create if project.empty_repo?
-
create_events
create_pipelines
execute_project_hooks
@@ -58,7 +56,7 @@ module Git
return unless params.fetch(:create_pipelines, true)
Ci::CreatePipelineService
- .new(project, current_user, base_params)
+ .new(project, current_user, pipeline_params)
.execute(:push, pipeline_options)
end
@@ -70,31 +68,36 @@ module Git
end
def enqueue_invalidate_cache
- ProjectCacheWorker.perform_async(
- project.id,
- invalidated_file_types,
- [:commit_count, :repository_size]
- )
+ file_types = invalidated_file_types
+
+ return unless file_types.present?
+
+ ProjectCacheWorker.perform_async(project.id, file_types, [], false)
end
- def base_params
+ def pipeline_params
{
- oldrev: params[:oldrev],
- newrev: params[:newrev],
+ before: params[:oldrev],
+ after: params[:newrev],
ref: params[:ref],
- push_options: params[:push_options] || {}
+ push_options: params[:push_options] || {},
+ checkout_sha: Gitlab::DataBuilder::Push.checkout_sha(
+ project.repository, params[:newrev], params[:ref])
}
end
def push_data_params(commits:, with_changed_files: true)
- base_params.merge(
+ {
+ oldrev: params[:oldrev],
+ newrev: params[:newrev],
+ ref: params[:ref],
project: project,
user: current_user,
commits: commits,
message: event_message,
commits_count: commits_count,
with_changed_files: with_changed_files
- )
+ }
end
def event_push_data
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index 431a5aedf2e..d2b037a680c 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -83,8 +83,16 @@ module Git
# Schedules processing of commit messages
def enqueue_process_commit_messages
- limited_commits.each do |commit|
- next unless commit.matches_cross_reference_regex?
+ referencing_commits = limited_commits.select(&:matches_cross_reference_regex?)
+
+ upstream_commit_ids = upstream_commit_ids(referencing_commits)
+
+ referencing_commits.each do |commit|
+ # Avoid reprocessing commits that already exist upstream if the project
+ # is a fork. This will prevent duplicated/superfluous system notes on
+ # mentionables referenced by a commit that is pushed to the upstream,
+ # that is then also pushed to forks when these get synced by users.
+ next if upstream_commit_ids.include?(commit.id)
ProcessCommitWorker.perform_async(
project.id,
@@ -142,5 +150,18 @@ module Git
def branch_name
strong_memoize(:branch_name) { Gitlab::Git.ref_name(params[:ref]) }
end
+
+ def upstream_commit_ids(commits)
+ set = Set.new
+
+ upstream_project = project.fork_source
+ if upstream_project
+ upstream_project
+ .commits_by(oids: commits.map(&:id))
+ .each { |commit| set << commit.id }
+ end
+
+ set
+ end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e30debbbe75..ee7223d6349 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -598,11 +598,11 @@ module SystemNoteService
end
def zoom_link_added(issue, project, author)
- create_note(NoteSummary.new(issue, project, author, _('a Zoom call was added to this issue'), action: 'pinned_embed'))
+ create_note(NoteSummary.new(issue, project, author, _('added a Zoom call to this issue'), action: 'pinned_embed'))
end
def zoom_link_removed(issue, project, author)
- create_note(NoteSummary.new(issue, project, author, _('a Zoom call was removed from this issue'), action: 'pinned_embed'))
+ create_note(NoteSummary.new(issue, project, author, _('removed a Zoom call from this issue'), action: 'pinned_embed'))
end
private
diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml
index 82781f6716d..86f09bf1cb0 100644
--- a/app/views/admin/applications/_delete_form.html.haml
+++ b/app/views/admin/applications/_delete_form.html.haml
@@ -1,4 +1,4 @@
- submit_btn_css ||= 'btn btn-link btn-remove btn-sm'
= form_tag admin_application_path(application) do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
- = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css
+ = submit_tag 'Destroy', class: submit_btn_css, data: { confirm: _('Are you sure?') }
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
index 69cc510e9c1..9bc5e2ee42f 100644
--- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -5,4 +5,4 @@
= form_tag path do
%input{ :name => "_method", :type => "hidden", :value => "delete" }/
- = submit_tag _('Revoke'), onclick: "return confirm('#{_('Are you sure?')}')", class: 'btn btn-remove btn-sm'
+ = submit_tag _('Revoke'), class: 'btn btn-remove btn-sm', data: { confirm: _('Are you sure?') }
diff --git a/app/views/errors/access_denied.html.haml b/app/views/errors/access_denied.html.haml
index 46931b5932d..1ed7b56db1d 100644
--- a/app/views/errors/access_denied.html.haml
+++ b/app/views/errors/access_denied.html.haml
@@ -10,7 +10,7 @@
= message
%p
= s_('403|Please contact your GitLab administrator to get permission.')
- .action-container.js-go-back{ style: 'display: none' }
- %a{ href: 'javascript:history.back()', class: 'btn btn-success' }
+ .action-container.js-go-back{ hidden: true }
+ %button{ type: 'button', class: 'btn btn-success' }
= s_('Go Back')
= render "errors/footer"
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 4daf3683eaf..e50d2b8e994 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -1,4 +1,5 @@
- can_create_subgroups = can?(current_user, :create_subgroup, @group)
+- emails_disabled = @group.emails_disabled?
.group-home-panel
.row.mb-3
@@ -21,7 +22,7 @@
.home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
.group-buttons
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn'
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn', emails_disabled: emails_disabled
- if can? current_user, :create_projects, @group
- new_project_label = _("New project")
- new_subgroup_label = _("New subgroup")
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index d3375e00bad..94a938021f9 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -11,13 +11,20 @@
.form-check
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
= f.label :share_with_group_lock, class: 'form-check-label' do
- %span
+ %span.d-block
- group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %br
%span.descr.text-muted= share_with_group_lock_help_text(@group)
+ .form-group.append-bottom-default
+ .form-check
+ = f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input'
+ = f.label :emails_disabled, class: 'form-check-label' do
+ %span.d-block= s_('GroupSettings|Disable email notifications')
+ %span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
+
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
+ = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f
= render 'groups/settings/project_creation_level', f: f, group: @group
= render 'groups/settings/subgroup_creation_level', f: f, group: @group
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 74484005b48..dc924a0e25d 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -14,6 +14,10 @@
var goBackElement = document.querySelector('.js-go-back');
if (goBackElement && history.length > 1) {
- goBackElement.style.display = 'block';
+ goBackElement.removeAttribute('hidden');
+
+ goBackElement.querySelector('button').addEventListener('click', function() {
+ history.back();
+ });
}
}());
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 89f99472270..14c7b2428b2 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -4,6 +4,7 @@
- search_path_url = search_path(group_id: group.id)
- else
- search_path_url = search_path
+- has_impersonation_link = header_link?(:admin_impersonation)
%header.navbar.navbar-gitlab.navbar-expand-sm.js-navbar{ data: { qa_selector: 'navbar' } }
%a.sr-only.gl-accessibility{ href: "#content-body", tabindex: "1" } Skip to content
@@ -64,14 +65,14 @@
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/help_dropdown'
- if header_link?(:user_dropdown)
- %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", track_value: "", qa_selector: 'user_menu' } }
+ %li.nav-item.header-user.dropdown{ data: { track_label: "profile_dropdown", track_event: "click_dropdown", track_value: "", qa_selector: 'user_menu' }, class: ('mr-0' if has_impersonation_link) }
= link_to current_user, class: user_dropdown_class, data: { toggle: "dropdown" } do
= image_tag avatar_icon_for_user(current_user, 23), width: 23, height: 23, class: "header-user-avatar qa-user-avatar"
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.dropdown-menu-right
= render 'layouts/header/current_user_dropdown'
- - if header_link?(:admin_impersonation)
- %li.nav-item.impersonation
+ - if has_impersonation_link
+ %li.nav-item.impersonation.ml-0
= link_to admin_impersonation_path, class: 'nav-link impersonation-btn', method: :delete, title: _('Stop impersonation'), aria: { label: _('Stop impersonation') }, data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
= icon('user-secret')
- if header_link?(:sign_in)
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index cf17ee44145..1776d260e19 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -1,16 +1,15 @@
+- emails_disabled = group.emails_disabled?
+
.gl-responsive-table-row.notification-list-item
.table-section.section-40
%span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+ = notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
= link_to group.name, group_path(group)
.table-section.section-30.text-right
- = render 'shared/notifications/button', notification_setting: setting
+ = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
.table-section.section-30
= form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml
index 823fec3fab4..63a77b335b6 100644
--- a/app/views/profiles/notifications/_project_settings.html.haml
+++ b/app/views/profiles/notifications/_project_settings.html.haml
@@ -1,12 +1,11 @@
+- emails_disabled = project.emails_disabled?
+
%li.notification-list-item
%span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+ = notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
= link_to_project(project)
.float-right
- = render 'shared/notifications/button', notification_setting: setting
+ = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 65ef9690062..08a39fc4f58 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -31,7 +31,7 @@
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.feed-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: "label-bold"
- = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ = text_field_tag :feed_token, current_user.feed_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link }
@@ -49,7 +49,7 @@
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.incoming-email-token-reset
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: "label-bold"
- = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link }
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 824fe3c791d..4783b10cf6d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,6 +1,8 @@
- empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15
+- emails_disabled = @project.emails_disabled?
+
.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
@@ -41,7 +43,7 @@
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
.d-inline-flex
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs'
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', emails_disabled: emails_disabled
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
diff --git a/app/views/projects/commit/_ajax_signature.html.haml b/app/views/projects/commit/_ajax_signature.html.haml
index ae9aef5a9b0..e1bf0940f59 100644
--- a/app/views/projects/commit/_ajax_signature.html.haml
+++ b/app/views/projects/commit/_ajax_signature.html.haml
@@ -1,2 +1,2 @@
- if commit.has_signature?
- %a{ href: 'javascript:void(0)', tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'top', title: _('GPG signature (loading...)'), 'commit-sha' => commit.sha } }
+ %button{ tabindex: 0, class: commit_signature_badge_classes('js-loading-gpg-badge'), data: { toggle: 'tooltip', placement: 'top', title: _('GPG signature (loading...)'), 'commit-sha' => commit.sha } }
diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml
index 1331fa179fc..cbd998c60ef 100644
--- a/app/views/projects/commit/_signature_badge.html.haml
+++ b/app/views/projects/commit/_signature_badge.html.haml
@@ -24,5 +24,5 @@
= link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link')
-%a{ href: 'javascript:void(0)', tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
+%button{ tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } }
= label
diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml
index 59f0afd59e6..2b594c125f4 100644
--- a/app/views/projects/cycle_analytics/show.html.haml
+++ b/app/views/projects/cycle_analytics/show.html.haml
@@ -34,40 +34,29 @@
{{ n__('Last %d day', 'Last %d days', 90) }}
.stage-panel-container
.card.stage-panel
- .card-header
+ .card-header.border-bottom-0
%nav.col-headers
%ul
- %li.stage-header
- %span.stage-name
+ %li.stage-header.pl-5
+ %span.stage-name.font-weight-bold
{{ s__('ProjectLifecycle|Stage') }}
%i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The phase of the development lifecycle."), "aria-hidden" => "true" }
%li.median-header
- %span.stage-name
+ %span.stage-name.font-weight-bold
{{ __('Median') }}
%i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6."), "aria-hidden" => "true" }
- %li.event-header
- %span.stage-name
+ %li.event-header.pl-3
+ %span.stage-name.font-weight-bold
{{ currentStage ? __(currentStage.legend) : __('Related Issues') }}
%i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The collection of events added to the data gathered for that stage."), "aria-hidden" => "true" }
- %li.total-time-header
- %span.stage-name
+ %li.total-time-header.pr-5.text-right
+ %span.stage-name.font-weight-bold
{{ __('Total Time') }}
%i.has-tooltip.fa.fa-question-circle{ "data-placement" => "top", title: _("The time taken by each data entry gathered by that stage."), "aria-hidden" => "true" }
.stage-panel-body
%nav.stage-nav
%ul
- %li.stage-nav-item{ ':class' => '{ active: stage.active }', '@click' => 'selectStage(stage)', "v-for" => "stage in state.stages" }
- .stage-nav-item-cell.stage-name
- {{ stage.title }}
- .stage-nav-item-cell.stage-median
- %template{ "v-if" => "stage.isUserAllowed" }
- %span{ "v-if" => "stage.value" }
- {{ stage.value }}
- %span.stage-empty{ "v-else" => true }
- {{ __('Not enough data') }}
- %template{ "v-else" => true }
- %span.not-available
- {{ __('Not available') }}
+ %stage-nav-item{ "v-for" => "stage in state.stages", ":key" => '`ca-stage-title-${stage.title}`', '@select' => 'selectStage(stage)', ":title" => "stage.title", ":is-user-allowed" => "stage.isUserAllowed", ":value" => "stage.value", ":is-active" => "stage.active" }
.section.stage-events
%template{ "v-if" => "isLoadingStage" }
= icon("spinner spin")
diff --git a/app/views/projects/pages_domains/_certificate.html.haml b/app/views/projects/pages_domains/_certificate.html.haml
new file mode 100644
index 00000000000..42631fca5e8
--- /dev/null
+++ b/app/views/projects/pages_domains/_certificate.html.haml
@@ -0,0 +1,18 @@
+- if @domain.auto_ssl_enabled?
+ - if @domain.enabled?
+ - if @domain.certificate_text
+ %pre
+ = @domain.certificate_text
+ - else
+ .bs-callout.bs-callout-info
+ = _("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.")
+ - else
+ .bs-callout.bs-callout-warning
+ = _("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.")
+- else
+ - if @domain.certificate_text
+ %pre
+ = @domain.certificate_text
+ - else
+ .light
+ = _("missing")
diff --git a/app/views/projects/pages_domains/show.html.haml b/app/views/projects/pages_domains/show.html.haml
index e9019175219..d0b54946f7e 100644
--- a/app/views/projects/pages_domains/show.html.haml
+++ b/app/views/projects/pages_domains/show.html.haml
@@ -60,9 +60,4 @@
%td
= _("Certificate")
%td
- - if @domain.certificate_text
- %pre
- = @domain.certificate_text
- - else
- .light
- = _("missing")
+ = render 'certificate'
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 2b56ada8b73..9614f33fe2f 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -43,7 +43,7 @@
} }
Auto DevOps
- if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "The code of a detached pipeline is tested against the source branch instead of merged results" }
+ %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: _('Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results.') }
detached
- if @pipeline.stuck?
%span.js-pipeline-url-stuck.badge.badge-warning
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 858731b2dda..a153f527ee0 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -1,8 +1,8 @@
-- commit_message = @page.persisted? ? s_("WikiPageEdit|Update %{page_title}") : s_("WikiPageCreate|Create %{page_title}")
-- commit_message = commit_message % { page_title: @page.title }
+- form_classes = 'wiki-form common-note-form prepend-top-default js-quick-submit'
+- form_classes += ' js-new-wiki-page' unless @page.persisted?
= form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post,
- html: { class: 'wiki-form common-note-form prepend-top-default js-quick-submit' },
+ html: { class: form_classes },
data: { uploads_path: uploads_path } do |f|
= form_errors(@page)
@@ -12,12 +12,14 @@
.form-group.row
.col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12
- = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title
- - if @page.persisted?
- %span.d-inline-block.mw-100.prepend-top-5
- = icon('lightbulb-o')
+ = f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title, required: true, autofocus: !@page.persisted?, placeholder: _('Wiki|Page title')
+ %span.d-inline-block.mw-100.prepend-top-5
+ = icon('lightbulb-o')
+ - if @page.persisted?
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
+ - else
+ = s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
.form-group.row
.col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12
@@ -43,7 +45,7 @@
.form-group.row
.col-sm-12= f.label :commit_message, class: 'control-label-full-width'
- .col-sm-12= f.text_field :message, class: 'form-control qa-wiki-message-textbox', rows: 18, value: commit_message
+ .col-sm-12= f.text_field :message, class: 'form-control qa-wiki-message-textbox', rows: 18, value: nil
.form-actions
- if @page && @page.persisted?
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 643b51e01d1..2e1e176c42a 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -1,9 +1,9 @@
- if (@page && @page.persisted?)
- if can?(current_user, :create_wiki, @project)
- = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-success", "data-toggle" => "modal" do
+ = link_to project_wikis_new_path(@project), class: "add-new-wiki btn btn-success", role: "button" do
= s_("Wiki|New page")
- = link_to project_wiki_history_path(@project, @page), class: "btn" do
+ = link_to project_wiki_history_path(@project, @page), class: "btn", role: "button" do
= s_("Wiki|Page history")
- if can?(current_user, :create_wiki, @project) && @page.latest? && @valid_encoding
- = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit" do
+ = link_to project_wiki_edit_path(@project, @page), class: "btn js-wiki-edit", role: "button" do
= _("Edit")
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
deleted file mode 100644
index 2c675c0de9c..00000000000
--- a/app/views/projects/wikis/_new.html.haml
+++ /dev/null
@@ -1,18 +0,0 @@
-#modal-new-wiki.modal
- .modal-dialog
- .modal-content
- .modal-header
- %h3.page-title= s_("WikiNewPageTitle|New Wiki Page")
- %button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
- %span{ "aria-hidden": true } &times;
- .modal-body
- %form.new-wiki-page
- .form-group
- = label_tag :new_wiki_path do
- %span= s_("WikiPage|Page slug")
- = text_field_tag :new_wiki_path, nil, placeholder: s_("WikiNewPagePlaceholder|how-to-setup"), class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
- %span.d-inline-block.mw-100.prepend-top-5
- = icon('lightbulb-o')
- = s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
- .form-actions
- = button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-success"
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index 28353927135..a9d21470944 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -19,5 +19,3 @@
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
= s_("Wiki|More Pages")
-
-= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index e8b59a3b8c4..815b4a51261 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -13,14 +13,11 @@
%h2.wiki-page-title
- if @page.persisted?
= link_to @page.human_title, project_wiki_path(@project, @page)
- - else
- = @page.human_title
- %span.light
- &middot;
- - if @page.persisted?
+ %span.light
+ &middot;
= s_("Wiki|Edit Page")
- - else
- = s_("Wiki|Create Page")
+ - else
+ = s_("Wiki|Create New Page")
.nav-controls
- if @page.persisted?
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index b4f8377c008..825088a58e7 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -137,7 +137,11 @@
.js-sidebar-participants-entry-point
- if signed_in
- .js-sidebar-subscriptions-entry-point
+ - if issuable_sidebar[:project_emails_disabled]
+ .block.js-emails-disabled
+ = notification_description(:owner_disabled)
+ - else
+ .js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.project-reference
diff --git a/app/views/shared/members/_group.html.haml b/app/views/shared/members/_group.html.haml
index e83ca5eaab8..42a823e3a8d 100644
--- a/app/views/shared/members/_group.html.haml
+++ b/app/views/shared/members/_group.html.haml
@@ -32,7 +32,7 @@
%ul
- Gitlab::Access.options.each do |role, role_id|
%li
- = link_to role, "javascript:void(0)",
+ = link_to role, '#',
class: ("is-active" if group_link.group_access == role_id),
data: { id: role_id, el_id: dom_id }
.clearable-input.member-form-control.d-sm-inline-block
diff --git a/app/views/shared/members/_member.html.haml b/app/views/shared/members/_member.html.haml
index 331283f7eec..6762f211a80 100644
--- a/app/views/shared/members/_member.html.haml
+++ b/app/views/shared/members/_member.html.haml
@@ -82,7 +82,7 @@
%ul
- member.valid_level_roles.each do |role, role_id|
%li
- = link_to role, "javascript:void(0)",
+ = link_to role, '#',
class: ("is-active" if member.access_level == role_id),
data: { id: role_id, el_id: dom_id(member) }
= render_if_exists 'shared/members/ee/revert_ldap_group_sync_option',
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 749aa258af6..b4266937a4e 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,6 +1,15 @@
-- btn_class = local_assigns.fetch(:btn_class, nil)
+- btn_class = local_assigns.fetch(:btn_class, '')
+- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting
+ - if emails_disabled
+ - button_title = notification_description(:owner_disabled)
+ - aria_label = button_title
+ - btn_class << " disabled"
+ - else
+ - button_title = _("Notification setting")
+ - aria_label = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
+
.js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
@@ -8,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 052e6da5bae..3c8cc023848 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -1,6 +1,13 @@
-- btn_class = local_assigns.fetch(:btn_class, nil)
+- btn_class = local_assigns.fetch(:btn_class, '')
+- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting
+ - if emails_disabled
+ - button_title = notification_description(:owner_disabled)
+ - btn_class << " disabled"
+ - else
+ - button_title = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
+
.js-notification-dropdown.notification-dropdown.home-panel-action-button.prepend-top-default.append-right-8.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting)
@@ -9,14 +16,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
= sprite_icon("arrow-down", css_class: "icon")
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 622bd6f1f48..61d34981458 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -42,10 +42,8 @@ class PostReceive
user = identify_user(post_received)
return false unless user
- # Expire the branches cache so we have updated data for this push
- post_received.project.repository.expire_branches_cache if post_received.includes_branches?
- # We only need to expire tags once per push
- post_received.project.repository.expire_caches_for_tags if post_received.includes_tags?
+ # We only need to expire certain caches once per push
+ expire_caches(post_received)
post_received.enum_for(:changes_refs).with_index do |(oldrev, newrev, ref), index|
service_klass =
@@ -74,6 +72,30 @@ class PostReceive
after_project_changes_hooks(post_received, user, refs.to_a, changes)
end
+ # Expire the project, branch, and tag cache once per push. Schedule an
+ # update for the repository size and commit count if necessary.
+ def expire_caches(post_received)
+ project = post_received.project
+
+ project.repository.expire_status_cache if project.empty_repo?
+ project.repository.expire_branches_cache if post_received.includes_branches?
+ project.repository.expire_caches_for_tags if post_received.includes_tags?
+
+ enqueue_repository_cache_update(post_received)
+ end
+
+ def enqueue_repository_cache_update(post_received)
+ stats_to_invalidate = [:repository_size]
+ stats_to_invalidate << :commit_count if post_received.includes_default_branch?
+
+ ProjectCacheWorker.perform_async(
+ post_received.project.id,
+ [],
+ stats_to_invalidate,
+ true
+ )
+ end
+
def after_project_changes_hooks(post_received, user, refs, changes)
hook_data = Gitlab::DataBuilder::Repository.update(post_received.project, user, changes, refs)
SystemHooksService.new.execute_hooks(hook_data, :repository_update_hooks)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 3efb5343a96..f6ebe4ab006 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -2,7 +2,8 @@
# Worker for processing individual commit messages pushed to a repository.
#
-# Jobs for this worker are scheduled for every commit that is being pushed. As a
+# Jobs for this worker are scheduled for every commit that contains mentionable
+# references in its message and does not exist in the upstream project. As a
# result of this the workload of this worker should be kept to a bare minimum.
# Consider using an extra worker if you need to add any extra (and potentially
# slow) processing of commits.
@@ -19,7 +20,6 @@ class ProcessCommitWorker
project = Project.find_by(id: project_id)
return unless project
- return if commit_exists_in_upstream?(project, commit_hash)
user = User.find_by(id: user_id)
@@ -77,17 +77,4 @@ class ProcessCommitWorker
Commit.from_hash(hash, project)
end
-
- private
-
- # Avoid reprocessing commits that already exist in the upstream
- # when project is forked. This will also prevent duplicated system notes.
- def commit_exists_in_upstream?(project, commit_hash)
- upstream_project = project.fork_source
-
- return false unless upstream_project
-
- commit_id = commit_hash.with_indifferent_access[:id]
- upstream_project.commit(commit_id).present?
- end
end
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 4e8ea903139..5ac860c93e0 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -12,13 +12,15 @@ class ProjectCacheWorker
# CHANGELOG.
# statistics - An Array containing columns from ProjectStatistics to
# refresh, if empty all columns will be refreshed
+ # refresh_statistics - A boolean that determines whether project statistics should
+ # be updated.
# rubocop: disable CodeReuse/ActiveRecord
- def perform(project_id, files = [], statistics = [])
+ def perform(project_id, files = [], statistics = [], refresh_statistics = true)
project = Project.find_by(id: project_id)
return unless project
- update_statistics(project, statistics)
+ update_statistics(project, statistics) if refresh_statistics
return unless project.repository.exists?
diff --git a/changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml b/changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml
new file mode 100644
index 00000000000..d93e7634ae5
--- /dev/null
+++ b/changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Add new table to store email domain per group
+merge_request: 31071
+author:
+type: added
diff --git a/changelogs/unreleased/46299-wiki-page-creation.yml b/changelogs/unreleased/46299-wiki-page-creation.yml
new file mode 100644
index 00000000000..2e8f2accf45
--- /dev/null
+++ b/changelogs/unreleased/46299-wiki-page-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Remove wiki page slug dialog step when creating wiki page
+merge_request: 31362
+author:
+type: changed
diff --git a/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml b/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
new file mode 100644
index 00000000000..a5fe7b1d18e
--- /dev/null
+++ b/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
@@ -0,0 +1,5 @@
+---
+title: UI for disabling group/project email notifications
+merge_request: 30961
+author: Dustin Spicuzza
+type: added
diff --git a/changelogs/unreleased/50070-legacy-attachments.yml b/changelogs/unreleased/50070-legacy-attachments.yml
new file mode 100644
index 00000000000..03f1cec0f67
--- /dev/null
+++ b/changelogs/unreleased/50070-legacy-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Create rake tasks for migrating legacy uploads out of deprecated paths
+merge_request: 29409
+author:
+type: other
diff --git a/changelogs/unreleased/56130-deployment-date.yml b/changelogs/unreleased/56130-deployment-date.yml
new file mode 100644
index 00000000000..7d1e84bbaa4
--- /dev/null
+++ b/changelogs/unreleased/56130-deployment-date.yml
@@ -0,0 +1,5 @@
+---
+title: Add finished_at to the internal API Deployment entity
+merge_request: 31808
+author:
+type: other
diff --git a/changelogs/unreleased/59786-show-renamed-file-in-mr.yml b/changelogs/unreleased/59786-show-renamed-file-in-mr.yml
new file mode 100644
index 00000000000..e8c52b592d2
--- /dev/null
+++ b/changelogs/unreleased/59786-show-renamed-file-in-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Fix to show renamed file in mr
+merge_request: 31888
+author:
+type: changed
diff --git a/changelogs/unreleased/61335-fix-file-icon-status.yml b/changelogs/unreleased/61335-fix-file-icon-status.yml
new file mode 100644
index 00000000000..d524d91b246
--- /dev/null
+++ b/changelogs/unreleased/61335-fix-file-icon-status.yml
@@ -0,0 +1,5 @@
+---
+title: Fix IDE new files icon in tree
+merge_request: 31560
+author:
+type: fixed
diff --git a/changelogs/unreleased/62373-badge-counter-very-low-contrast-between-foreground-and-background-c.yml b/changelogs/unreleased/62373-badge-counter-very-low-contrast-between-foreground-and-background-c.yml
new file mode 100644
index 00000000000..12b19da1868
--- /dev/null
+++ b/changelogs/unreleased/62373-badge-counter-very-low-contrast-between-foreground-and-background-c.yml
@@ -0,0 +1,6 @@
+---
+title: 'Resolve Badge counter: Very low contrast between foreground and background
+ colors'
+merge_request: 31922
+author:
+type: other
diff --git a/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml b/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml
new file mode 100644
index 00000000000..b6bc03f4003
--- /dev/null
+++ b/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Embed specific metrics chart in issue
+merge_request: 31644
+author:
+type: added
diff --git a/changelogs/unreleased/63905-discussion-expand-collapse-button-is-only-clickable-on-one-side.yml b/changelogs/unreleased/63905-discussion-expand-collapse-button-is-only-clickable-on-one-side.yml
new file mode 100644
index 00000000000..61cd69e88bf
--- /dev/null
+++ b/changelogs/unreleased/63905-discussion-expand-collapse-button-is-only-clickable-on-one-side.yml
@@ -0,0 +1,5 @@
+---
+title: All of discussion expand/collapse button is clickable
+merge_request: 31730
+author:
+type: fixed
diff --git a/changelogs/unreleased/64251-branch-name-set-cache.yml b/changelogs/unreleased/64251-branch-name-set-cache.yml
new file mode 100644
index 00000000000..6ce4bdf5e43
--- /dev/null
+++ b/changelogs/unreleased/64251-branch-name-set-cache.yml
@@ -0,0 +1,5 @@
+---
+title: Cache branch and tag names as Redis sets
+merge_request: 30476
+author:
+type: performance
diff --git a/changelogs/unreleased/64630-add-warning-to-pages-domains-that-obtaining-deploying-ssl-certifica.yml b/changelogs/unreleased/64630-add-warning-to-pages-domains-that-obtaining-deploying-ssl-certifica.yml
new file mode 100644
index 00000000000..bd2c9a3e2dc
--- /dev/null
+++ b/changelogs/unreleased/64630-add-warning-to-pages-domains-that-obtaining-deploying-ssl-certifica.yml
@@ -0,0 +1,6 @@
+---
+title: Add warning to pages domains that obtaining/deploying SSL certificates through
+ Let's Encrypt can take some time
+merge_request: 31765
+author:
+type: other
diff --git a/changelogs/unreleased/64677-delete-directory-webide.yml b/changelogs/unreleased/64677-delete-directory-webide.yml
new file mode 100644
index 00000000000..27d596b6b19
--- /dev/null
+++ b/changelogs/unreleased/64677-delete-directory-webide.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed removing directories in Web IDE
+merge_request: 31727
+author:
+type: fixed
diff --git a/changelogs/unreleased/65427-improve-system-notes-for-zoom-links.yml b/changelogs/unreleased/65427-improve-system-notes-for-zoom-links.yml
new file mode 100644
index 00000000000..d081620c710
--- /dev/null
+++ b/changelogs/unreleased/65427-improve-system-notes-for-zoom-links.yml
@@ -0,0 +1,5 @@
+---
+title: Improve system notes for Zoom links
+merge_request: 31410
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml b/changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml
new file mode 100644
index 00000000000..1caa5fa84ce
--- /dev/null
+++ b/changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml
@@ -0,0 +1,5 @@
+---
+title: Fix starrers counts after searching
+merge_request: 31823
+author:
+type: fixed
diff --git a/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml b/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml
new file mode 100644
index 00000000000..826d5548999
--- /dev/null
+++ b/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml
@@ -0,0 +1,5 @@
+---
+title: Updates tooltip of 'detached' label/state
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml b/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
new file mode 100644
index 00000000000..13607ae938a
--- /dev/null
+++ b/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
@@ -0,0 +1,5 @@
+---
+title: Add syntax highlighting for line expansion
+merge_request: 31821
+author:
+type: fixed
diff --git a/changelogs/unreleased/66161-replace-expand-icons.yml b/changelogs/unreleased/66161-replace-expand-icons.yml
new file mode 100644
index 00000000000..e28e01108a8
--- /dev/null
+++ b/changelogs/unreleased/66161-replace-expand-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Replaced expand diff icons
+merge_request: 31907
+author:
+type: changed
diff --git a/changelogs/unreleased/an-fix-sidekiq-histogram-buckets.yml b/changelogs/unreleased/an-fix-sidekiq-histogram-buckets.yml
new file mode 100644
index 00000000000..696b8f3987e
--- /dev/null
+++ b/changelogs/unreleased/an-fix-sidekiq-histogram-buckets.yml
@@ -0,0 +1,5 @@
+---
+title: Allow latency measurements of sidekiq jobs taking > 2.5s
+merge_request: 32001
+author:
+type: fixed
diff --git a/changelogs/unreleased/bvl-mr-commit-note-counter.yml b/changelogs/unreleased/bvl-mr-commit-note-counter.yml
new file mode 100644
index 00000000000..0e9becbd31e
--- /dev/null
+++ b/changelogs/unreleased/bvl-mr-commit-note-counter.yml
@@ -0,0 +1,5 @@
+---
+title: Count comments on commits and merge requests
+merge_request: 31912
+author:
+type: other
diff --git a/changelogs/unreleased/ci-config-on-policy.yml b/changelogs/unreleased/ci-config-on-policy.yml
new file mode 100644
index 00000000000..d9804f0323a
--- /dev/null
+++ b/changelogs/unreleased/ci-config-on-policy.yml
@@ -0,0 +1,5 @@
+---
+title: Introduced Build::Rules configuration for Ci::Build
+merge_request: 29011
+author:
+type: added
diff --git a/changelogs/unreleased/clean-obsolete-css.yml b/changelogs/unreleased/clean-obsolete-css.yml
new file mode 100644
index 00000000000..d9716d16762
--- /dev/null
+++ b/changelogs/unreleased/clean-obsolete-css.yml
@@ -0,0 +1,5 @@
+---
+title: Fix MR reports section loading icon alignment
+merge_request: 31897
+author:
+type: fixed
diff --git a/changelogs/unreleased/dm-process-commit-worker-n-1.yml b/changelogs/unreleased/dm-process-commit-worker-n-1.yml
new file mode 100644
index 00000000000..0bd7de6730a
--- /dev/null
+++ b/changelogs/unreleased/dm-process-commit-worker-n-1.yml
@@ -0,0 +1,5 @@
+---
+title: Look up upstream commits once before queuing ProcessCommitWorkers
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml b/changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml
new file mode 100644
index 00000000000..7ca8ae0a8f6
--- /dev/null
+++ b/changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml
@@ -0,0 +1,5 @@
+---
+title: Fix search preserving space when change branch
+merge_request: 31973
+author: minghuan lei
+type: fixed
diff --git a/changelogs/unreleased/fix-commits-api-empty-refname.yml b/changelogs/unreleased/fix-commits-api-empty-refname.yml
new file mode 100644
index 00000000000..efdb950e45d
--- /dev/null
+++ b/changelogs/unreleased/fix-commits-api-empty-refname.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors in commits api caused by empty ref_name parameter
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-file-row-styling.yml b/changelogs/unreleased/fix-file-row-styling.yml
new file mode 100644
index 00000000000..a4427fdd48f
--- /dev/null
+++ b/changelogs/unreleased/fix-file-row-styling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix loading icon causing text to jump in file row of Web IDE
+merge_request: 31884
+author:
+type: fixed
diff --git a/changelogs/unreleased/optimize-note-indexes.yml b/changelogs/unreleased/optimize-note-indexes.yml
new file mode 100644
index 00000000000..bfb84779abf
--- /dev/null
+++ b/changelogs/unreleased/optimize-note-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize DB indexes for ES indexing of notes
+merge_request: 31846
+author:
+type: performance
diff --git a/changelogs/unreleased/pb-update-gitlab-shell-9-4-0.yml b/changelogs/unreleased/pb-update-gitlab-shell-9-4-0.yml
new file mode 100644
index 00000000000..81db7bb4900
--- /dev/null
+++ b/changelogs/unreleased/pb-update-gitlab-shell-9-4-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update to GitLab Shell v9.4.0
+merge_request: 32009
+author:
+type: other
diff --git a/changelogs/unreleased/remove-margin-from-user-header.yml b/changelogs/unreleased/remove-margin-from-user-header.yml
new file mode 100644
index 00000000000..31200d13c93
--- /dev/null
+++ b/changelogs/unreleased/remove-margin-from-user-header.yml
@@ -0,0 +1,5 @@
+---
+title: Remove margin from user header
+merge_request: 30878
+author: lucyfox
+type: fixed
diff --git a/changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml b/changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml
new file mode 100644
index 00000000000..f412ba11b91
--- /dev/null
+++ b/changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Security Dashboard feature flag
+merge_request: 31820
+author:
+type: other
diff --git a/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml b/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
new file mode 100644
index 00000000000..3177cb8d18c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly N+1 calls with listing issues/MRs via API
+merge_request: 31938
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml b/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml
new file mode 100644
index 00000000000..a6937eae588
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipelines not always being created after a push
+merge_request: 31927
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-post-receive-cache-clear-once.yml b/changelogs/unreleased/sh-post-receive-cache-clear-once.yml
new file mode 100644
index 00000000000..b677adf78d9
--- /dev/null
+++ b/changelogs/unreleased/sh-post-receive-cache-clear-once.yml
@@ -0,0 +1,5 @@
+---
+title: Expire project caches once per push instead of once per ref
+merge_request: 31876
+author:
+type: performance
diff --git a/changelogs/unreleased/tr-embed-metric-links.yml b/changelogs/unreleased/tr-embed-metric-links.yml
new file mode 100644
index 00000000000..6918114a4ae
--- /dev/null
+++ b/changelogs/unreleased/tr-embed-metric-links.yml
@@ -0,0 +1,5 @@
+---
+title: Generate shareable link for specific metric charts
+merge_request: 31339
+author:
+type: added
diff --git a/changelogs/unreleased/tr-param-undefined-fix.yml b/changelogs/unreleased/tr-param-undefined-fix.yml
new file mode 100644
index 00000000000..0a9051485bd
--- /dev/null
+++ b/changelogs/unreleased/tr-param-undefined-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for embedded metrics undefined params
+merge_request: 31975
+author:
+type: fixed
diff --git a/changelogs/unreleased/update-babel-to-7-5-5.yml b/changelogs/unreleased/update-babel-to-7-5-5.yml
new file mode 100644
index 00000000000..c498e2adfe8
--- /dev/null
+++ b/changelogs/unreleased/update-babel-to-7-5-5.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade babel to 7.5.5
+merge_request: 31819
+author: Takuya Noguchi
+type: other
diff --git a/config.ru b/config.ru
index f6a7dca0542..750c84f7642 100644
--- a/config.ru
+++ b/config.ru
@@ -17,24 +17,16 @@ end
require ::File.expand_path('../config/environment', __FILE__)
-# The following is necessary to ensure stale Prometheus metrics don't accumulate over time.
-# It needs to be done as early as here to ensure metrics files aren't deleted.
-# After we hit our app in `warmup`, first metrics and corresponding files already being created,
-# for example in `lib/gitlab/metrics/requests_rack_middleware.rb`.
-def cleanup_prometheus_multiproc_dir
- if dir = ::Prometheus::Client.configuration.multiprocess_files_dir
- old_metrics = Dir[File.join(dir, '*.db')]
-
- FileUtils.rm_rf(old_metrics)
- end
-end
-
def master_process?
Prometheus::PidProvider.worker_id.in? %w(unicorn_master puma_master)
end
warmup do |app|
- cleanup_prometheus_multiproc_dir if master_process?
+ # The following is necessary to ensure stale Prometheus metrics don't accumulate over time.
+ # It needs to be done as early as here to ensure metrics files aren't deleted.
+ # After we hit our app in `warmup`, first metrics and corresponding files already being created,
+ # for example in `lib/gitlab/metrics/requests_rack_middleware.rb`.
+ Prometheus::CleanupMultiprocDirService.new.execute if master_process?
client = Rack::MockRequest.new(app)
client.get('/')
diff --git a/config/initializers/0_inject_enterprise_edition_module.rb b/config/initializers/0_inject_enterprise_edition_module.rb
index 4b21732e179..39595e23abe 100644
--- a/config/initializers/0_inject_enterprise_edition_module.rb
+++ b/config/initializers/0_inject_enterprise_edition_module.rb
@@ -1,5 +1,7 @@
# frozen_string_literal: true
+require 'active_support/inflector'
+
module InjectEnterpriseEditionModule
def prepend_if_ee(constant)
prepend(constant.constantize) if Gitlab.ee?
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 70e5dcd042e..143b34b5368 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -32,6 +32,9 @@ end
Sidekiq.configure_server do |config|
config.on(:startup) do
+ # webserver metrics are cleaned up in config.ru: `warmup` block
+ Prometheus::CleanupMultiprocDirService.new.execute
+
Gitlab::Metrics::SidekiqMetricsExporter.instance.start
end
end
diff --git a/config/initializers/8_devise.rb b/config/initializers/8_devise.rb
index 3dd12c7e64d..8ef9ff6b7fc 100644
--- a/config/initializers/8_devise.rb
+++ b/config/initializers/8_devise.rb
@@ -81,7 +81,7 @@ Devise.setup do |config|
# You can use this to let your user access some features of your application
# without confirming the account, but blocking it after a certain period
# (ie 2 days).
- # config.allow_unconfirmed_access_for = 2.days
+ config.allow_unconfirmed_access_for = 30.days
# Defines which key will be used when confirming an account
# config.confirmation_keys = [ :email ]
diff --git a/config/initializers/rack_attack_logging.rb b/config/initializers/rack_attack_logging.rb
index 7eb34bd69e5..b43fff24bb0 100644
--- a/config/initializers/rack_attack_logging.rb
+++ b/config/initializers/rack_attack_logging.rb
@@ -7,9 +7,9 @@ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, r
rack_attack_info = {
message: 'Rack_Attack',
env: req.env['rack.attack.match_type'],
- ip: req.ip,
+ remote_ip: req.ip,
request_method: req.request_method,
- fullpath: req.fullpath
+ path: req.fullpath
}
if %w(throttle_authenticated_api throttle_authenticated_web).include? req.env['rack.attack.matched']
diff --git a/config/routes.rb b/config/routes.rb
index fdef31429f3..d633228a495 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -109,6 +109,7 @@ Rails.application.routes.draw do
Gitlab.ee do
draw :smartcard
draw :jira_connect
+ draw :username
end
Gitlab.ee do
diff --git a/config/routes/wiki.rb b/config/routes/wiki.rb
index 2ca52e55fca..d439c99270e 100644
--- a/config/routes/wiki.rb
+++ b/config/routes/wiki.rb
@@ -2,6 +2,7 @@ scope(controller: :wikis) do
scope(path: 'wikis', as: :wikis) do
get :git_access
get :pages
+ get :new
get '/', to: redirect('%{namespace_id}/%{project_id}/wikis/home')
post '/', to: 'wikis#create'
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index c7586aa1e38..ea165508d29 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -113,3 +113,4 @@
- [elastic_namespace_indexer, 1]
- [export_csv, 1]
- [incident_management, 2]
+ - [jira_connect, 1]
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index 05bda7d3672..5c8b681fa92 100644
--- a/db/fixtures/development/14_pipelines.rb
+++ b/db/fixtures/development/14_pipelines.rb
@@ -1,7 +1,7 @@
require './spec/support/sidekiq'
class Gitlab::Seeder::Pipelines
- STAGES = %w[build test security deploy notify]
+ STAGES = %w[build test deploy notify]
BUILDS = [
# build stage
{ name: 'build:linux', stage: 'build', status: :success,
@@ -31,16 +31,6 @@ class Gitlab::Seeder::Pipelines
{ name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true,
queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
- # security stage
- { name: 'dast', stage: 'security', status: :success,
- queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
- { name: 'sast', stage: 'security', status: :success,
- queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
- { name: 'dependency_scanning', stage: 'security', status: :success,
- queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
- { name: 'container_scanning', stage: 'security', status: :success,
- queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago },
-
# deploy stage
{ name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success,
options: { environment: { action: 'start', on_stop: 'stop staging' } },
@@ -127,11 +117,6 @@ class Gitlab::Seeder::Pipelines
setup_artifacts(build)
setup_test_reports(build)
- if build.ref == build.project.default_branch
- setup_security_reports_file(build)
- else
- setup_security_reports_legacy_archive(build)
- end
setup_build_log(build)
build.project.environments.
@@ -167,55 +152,6 @@ class Gitlab::Seeder::Pipelines
end
end
- def setup_security_reports_file(build)
- return unless build.stage == "security"
-
- # we have two sources: master and feature-branch
- branch_name = build.ref == build.project.default_branch ?
- 'master' : 'feature-branch'
-
- artifacts_cache_file(security_reports_path(branch_name, build.name)) do |file|
- build.job_artifacts.build(
- project: build.project,
- file_type: build.name,
- file_format: :raw,
- file: file)
- end
- end
-
- def setup_security_reports_legacy_archive(build)
- return unless build.stage == "security"
-
- # we have two sources: master and feature-branch
- branch_name = build.ref == build.project.default_branch ?
- 'master' : 'feature-branch'
-
- artifacts_cache_file(security_reports_archive_path(branch_name)) do |file|
- build.job_artifacts.build(
- project: build.project,
- file_type: :archive,
- file_format: :zip,
- file: file)
- end
-
- # assign dummy metadata
- artifacts_cache_file(artifacts_metadata_path) do |file|
- build.job_artifacts.build(
- project: build.project,
- file_type: :metadata,
- file_format: :gzip,
- file: file)
- end
-
- build.options = {
- artifacts: {
- paths: [
- Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(build.name.to_sym)
- ]
- }
- }
- end
-
def setup_build_log(build)
if %w(running success failed).include?(build.status)
build.trace.set(FFaker::Lorem.paragraphs(6).join("\n\n"))
@@ -267,15 +203,6 @@ class Gitlab::Seeder::Pipelines
Rails.root + 'spec/fixtures/junit/junit.xml.gz'
end
- def security_reports_archive_path(branch)
- Rails.root.join('spec', 'fixtures', 'security-reports', branch + '.zip')
- end
-
- def security_reports_path(branch, name)
- file_name = Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(name.to_sym)
- Rails.root.join('spec', 'fixtures', 'security-reports', branch, file_name)
- end
-
def artifacts_cache_file(file_path)
file = Tempfile.new("artifacts")
file.close
diff --git a/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb b/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb
new file mode 100644
index 00000000000..c6c5b56ed8b
--- /dev/null
+++ b/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateAllowedEmailDomainsForGroups < ActiveRecord::Migration[5.2]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :allowed_email_domains do |t|
+ t.timestamps_with_timezone null: false
+ t.references :group, references: :namespace,
+ column: :group_id,
+ type: :integer,
+ null: false,
+ index: true
+ t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
+ t.string :domain, null: false, limit: 255
+ end
+ end
+end
diff --git a/db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb b/db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb
new file mode 100644
index 00000000000..cbbece35901
--- /dev/null
+++ b/db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class AddIndexNotesOnProjectIdAndIdAndSystemFalse < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(*index_arguments)
+ end
+
+ def down
+ remove_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :notes,
+ [:project_id, :id],
+ {
+ name: 'index_notes_on_project_id_and_id_and_system_false',
+ where: 'NOT system'
+ }
+ ]
+ end
+end
diff --git a/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb b/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb
new file mode 100644
index 00000000000..158c88e6258
--- /dev/null
+++ b/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class RemoveIndexNotesOnNoteableType < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index(*index_arguments)
+ end
+
+ def down
+ add_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :notes,
+ [:noteable_type],
+ {
+ name: 'index_notes_on_noteable_type'
+ }
+ ]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index cf7f0fc0a3d..ce5fd38129a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_08_12_070645) do
+ActiveRecord::Schema.define(version: 2019_08_15_093949) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -26,6 +26,14 @@ ActiveRecord::Schema.define(version: 2019_08_12_070645) do
t.integer "cached_markdown_version"
end
+ create_table "allowed_email_domains", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "group_id", null: false
+ t.string "domain", limit: 255, null: false
+ t.index ["group_id"], name: "index_allowed_email_domains_on_group_id"
+ end
+
create_table "analytics_cycle_analytics_group_stages", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
@@ -2279,7 +2287,7 @@ ActiveRecord::Schema.define(version: 2019_08_12_070645) do
t.index ["line_code"], name: "index_notes_on_line_code"
t.index ["note"], name: "index_notes_on_note_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type"
- t.index ["noteable_type"], name: "index_notes_on_noteable_type"
+ t.index ["project_id", "id"], name: "index_notes_on_project_id_and_id_and_system_false", where: "(NOT system)"
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type"
t.index ["review_id"], name: "index_notes_on_review_id"
end
@@ -3670,6 +3678,7 @@ ActiveRecord::Schema.define(version: 2019_08_12_070645) do
t.index ["type"], name: "index_web_hooks_on_type"
end
+ add_foreign_key "allowed_email_domains", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "end_event_label_id", on_delete: :cascade
add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "start_event_label_id", on_delete: :cascade
add_foreign_key "analytics_cycle_analytics_group_stages", "namespaces", column: "group_id", on_delete: :cascade
diff --git a/doc/administration/geo/disaster_recovery/index.md b/doc/administration/geo/disaster_recovery/index.md
index d44e141b66b..ba95843b0b0 100644
--- a/doc/administration/geo/disaster_recovery/index.md
+++ b/doc/administration/geo/disaster_recovery/index.md
@@ -1,4 +1,4 @@
-# Disaster Recovery **(PREMIUM ONLY)**
+# Disaster Recovery (Geo) **(PREMIUM ONLY)**
Geo replicates your database, your Git repositories, and few other assets.
We will support and replicate more data in the future, that will enable you to
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index 5aedb665935..dbd466b906d 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -1,4 +1,4 @@
-# Geo Replication **(PREMIUM ONLY)**
+# Replication (Geo) **(PREMIUM ONLY)**
> - Introduced in GitLab Enterprise Edition 8.9.
> - Using Geo in combination with
@@ -6,7 +6,7 @@
> is considered **Generally Available** (GA) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
-Geo is the solution for widely distributed development teams.
+Replication with Geo is the solution for widely distributed development teams.
## Overview
diff --git a/doc/administration/integration/plantuml.md b/doc/administration/integration/plantuml.md
index c2ac063ce37..16a193550a1 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -72,12 +72,12 @@ our AsciiDoc snippets, wikis and repos using delimited blocks:
- **Markdown**
- ````markdown
+ ~~~markdown
```plantuml
Bob -> Alice : hello
Alice -> Bob : Go Away
```
- ````
+ ~~~
- **AsciiDoc**
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 47abbc512e0..9030c941cad 100644
--- a/doc/administration/logs.md
+++ b/doc/administration/logs.md
@@ -88,7 +88,7 @@ Introduced in GitLab 10.0, this file lives in
It helps you see requests made directly to the API. For example:
```json
-{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36}
+{"time":"2018-10-29T12:49:42.123Z","severity":"INFO","duration":709.08,"db":14.59,"view":694.49,"status":200,"method":"GET","path":"/api/v4/projects","params":[{"key":"action","value":"git-upload-pack"},{"key":"changes","value":"_any"},{"key":"key_id","value":"secret"},{"key":"secret_token","value":"[FILTERED]"}],"host":"localhost","remote_ip":"::1","ua":"Ruby","route":"/api/:version/projects","user_id":1,"username":"root","queue_duration":100.31,"gitaly_calls":30,"gitaly_duration":5.36}
```
This entry above shows an access to an internal endpoint to check whether an
@@ -284,13 +284,16 @@ Introduced in GitLab 11.3. This file lives in `/var/log/gitlab/gitlab-rails/impo
Omnibus GitLab packages or in `/home/git/gitlab/log/importer.log` for
installations from source.
-## `auth.log`
+## `auth.log`
Introduced in GitLab 12.0. This file lives in `/var/log/gitlab/gitlab-rails/auth.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/auth.log` for
installations from source.
-It logs information whenever [Rack Attack] registers an abusive request.
+This log records:
+
+- Information whenever [Rack Attack] registers an abusive request.
+- Requests over the [Rate Limit] on raw endpoints.
NOTE: **Note:**
From [%12.1](https://gitlab.com/gitlab-org/gitlab-ce/issues/62756), user id and username are available on this log.
@@ -309,6 +312,12 @@ GraphQL queries are recorded in that file. For example:
{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7}
```
+## `migrations.log`
+
+Introduced in GitLab 12.3. This file lives in `/var/log/gitlab/gitlab-rails/migrations.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/migrations.log` for
+installations from source.
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
@@ -328,3 +337,4 @@ installations from source.
[repocheck]: repository_checks.md
[Rack Attack]: ../security/rack_attack.md
+[Rate Limit]: ../user/admin_area/settings/rate_limits_on_raw_endpoints.md
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index fd8ea8d3162..86e8b763f51 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -103,3 +103,13 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, S
sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
```
+
+## Migrate legacy uploads out of deprecated paths
+
+> Introduced in GitLab 12.3.
+
+To migrate all uploads created by legacy uploaders, run:
+
+```shell
+bundle exec rake gitlab:uploads:legacy:migrate
+```
diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
index 90af8711ca2..238c522a0ee 100644
--- a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
+++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
@@ -29,7 +29,7 @@ and they will assist you with any issues you are having.
```bash
# for minikube:
minikube dashboard —url
- # for non-local installations if access via kubectl is configured:
+ # for non-local installations if access via Kubectl is configured:
kubectl proxy
```
@@ -49,7 +49,7 @@ and they will assist you with any issues you are having.
- What to do with pods in `CrashLoopBackoff` status:
- Check logs via Kubernetes dashboard.
- - Check logs via `kubectl`:
+ - Check logs via Kubectl:
```bash
kubectl logs <unicorn pod> -c dependencies
@@ -83,7 +83,7 @@ and they will assist you with any issues you are having.
kubectl logs gitlab-unicorn-7656fdd6bf-jqzfs -c unicorn
```
-- It is not possible to get all the logs via `kubectl` at once, like with `gitlab-ctl tail`,
+- It is not possible to get all the logs via Kubectl at once, like with `gitlab-ctl tail`,
but a number of third-party tools can be used to do it:
- [Kubetail](https://github.com/johanhaleby/kubetail)
@@ -131,7 +131,7 @@ and they will assist you with any issues you are having.
- Check the output of `kubectl get events -w --all-namespaces`.
- Check the logs of pods within `gitlab-managed-apps` namespace.
- On the side of GitLab check sidekiq log and kubernetes log. When GitLab is installed
- via helm chart, kubernetes.log can be found inside the sidekiq pod.
+ via Helm Chart, `kubernetes.log` can be found inside the sidekiq pod.
- How to get your initial admin password <https://docs.gitlab.com/charts/installation/deployment.html#initial-login>:
@@ -142,19 +142,19 @@ and they will assist you with any issues you are having.
kubectl get secret <secret-name> -ojsonpath={.data.password} | base64 --decode ; echo
```
-- How to connect to a GitLab postgres database:
+- How to connect to a GitLab Postgres database:
```bash
kubectl exec -it <task-runner-pod-name> -- /srv/gitlab/bin/rails dbconsole -p
```
-- How to get info about helm installation status:
+- How to get info about Helm installation status:
```bash
helm status name-of-installation
```
-- How to update GitLab installed using helm chart:
+- How to update GitLab installed using Helm Chart:
```bash
helm repo upgrade
@@ -179,25 +179,25 @@ and they will assist you with any issues you are having.
helm upgrade <release name> <chart path> -f gitlab.yaml
```
-## Installation of minimal GitLab config via minukube on macOS
+## Installation of minimal GitLab config via Minukube on macOS
This section is based on [Developing for Kubernetes with Minikube](https://gitlab.com/charts/gitlab/blob/master/doc/minikube/index.md)
and [Helm](https://gitlab.com/charts/gitlab/blob/master/doc/helm/index.md). Refer
to those documents for details.
-- Install kubectl via Homebrew:
+- Install Kubectl via Homebrew:
```bash
brew install kubernetes-cli
```
-- Install minikube via Homebrew:
+- Install Minikube via Homebrew:
```bash
brew cask install minikube
```
-- Start minikube and configure it. If minikube cannot start, try running `minikube delete && minikube start`
+- Start Minikube and configure it. If Minikube cannot start, try running `minikube delete && minikube start`
and repeat the steps:
```bash
@@ -206,7 +206,7 @@ to those documents for details.
minikube addons enable kube-dns
```
-- Install helm via Homebrew and initialize it:
+- Install Helm via Homebrew and initialize it:
```bash
brew install kubernetes-helm
@@ -219,7 +219,7 @@ to those documents for details.
- Find the IP address in the output of `minikube ip` and update the yaml file with
this IP address.
-- Install the GitLab helm chart:
+- Install the GitLab Helm Chart:
```bash
helm repo add gitlab https://charts.gitlab.io
diff --git a/doc/api/README.md b/doc/api/README.md
index b7ee710b87a..9156d719e11 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -77,11 +77,12 @@ authentication is not provided. For
those cases where it is not required, this will be mentioned in the documentation
for each individual endpoint. For example, the [`/projects/:id` endpoint](projects.md).
-There are three ways to authenticate with the GitLab API:
+There are four ways to authenticate with the GitLab API:
1. [OAuth2 tokens](#oauth2-tokens)
1. [Personal access tokens](#personal-access-tokens)
1. [Session cookie](#session-cookie)
+1. [GitLab CI job token](#gitlab-ci-job-token-premium) **(PREMIUM)**
For admins who want to authenticate with the API as a specific user, or who want to build applications or scripts that do so, two options are available:
@@ -151,6 +152,14 @@ The primary user of this authentication method is the web frontend of GitLab its
which can use the API as the authenticated user to get a list of their projects,
for example, without needing to explicitly pass an access token.
+### GitLab CI job token **(PREMIUM)**
+
+With a few API endpoints you can use a [GitLab CI job token](../user/project/new_ci_build_permissions_model.md#job-token)
+to authenticate with the API:
+
+* [Get job artifacts](jobs.md#get-job-artifacts)
+* [Pipeline triggers](pipeline_triggers.md)
+
### Impersonation tokens
> [Introduced][ce-9099] in GitLab 9.0. Needs admin permissions.
diff --git a/doc/api/boards.md b/doc/api/boards.md
index 08ec1d832df..b848d7788cd 100644
--- a/doc/api/boards.md
+++ b/doc/api/boards.md
@@ -466,7 +466,7 @@ Example response:
## Delete a board list
-Only for admins and project owners. Soft deletes the board list in question.
+Only for admins and project owners. Deletes the board list in question.
```
DELETE /projects/:id/boards/:board_id/lists/:list_id
diff --git a/doc/api/group_boards.md b/doc/api/group_boards.md
index 4d10f83720b..99b522a7ae9 100644
--- a/doc/api/group_boards.md
+++ b/doc/api/group_boards.md
@@ -536,7 +536,7 @@ Example response:
## Delete a group issue board list
-Only for admins and group owners. Soft deletes the board list in question.
+Only for admins and group owners. Deletes the board list in question.
```
DELETE /groups/:id/boards/:board_id/lists/:list_id
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 96a547551f1..4f2b4a966c9 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -136,7 +136,6 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
},
- "subscribed": false,
"task_completion_status":{
"count":0,
"completed_count":0
@@ -441,7 +440,6 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false,
"task_completion_status":{
"count":0,
"completed_count":0
@@ -790,7 +788,7 @@ the `weight` parameter:
## Delete an issue
-Only for admins and project owners. Soft deletes the issue in question.
+Only for admins and project owners. Deletes the issue in question.
```
DELETE /projects/:id/issues/:issue_iid
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index cc95689a65f..bf83bfd7e6a 100644
--- a/doc/api/merge_request_approvals.md
+++ b/doc/api/merge_request_approvals.md
@@ -23,36 +23,6 @@ GET /projects/:id/approvals
```json
{
- "approvers": [
- {
- "user": {
- "id": 5,
- "name": "John Doe6",
- "username": "user5",
- "state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
- }
- }
- ],
- "approver_groups": [
- {
- "group": {
- "id": 1,
- "name": "group1",
- "path": "group1",
- "description": "",
- "visibility": "public",
- "lfs_enabled": false,
- "avatar_url": null,
- "web_url": "http://localhost/groups/group1",
- "request_access_enabled": false,
- "full_name": "group1",
- "full_path": "group1",
- "parent_id": null,
- "ldap_cn": null,
- "ldap_access": null
- }
- }
- ],
"approvals_before_merge": 2,
"reset_approvals_on_push": true,
"disable_overriding_approvers_per_merge_request": false
@@ -75,7 +45,7 @@ POST /projects/:id/approvals
| Attribute | Type | Required | Description |
| ------------------------------------------------ | ------- | -------- | --------------------------------------------------------------------------------------------------- |
| `id` | integer | yes | The ID of a project |
-| `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged |
+| `approvals_before_merge` | integer | no | How many approvals are required before an MR can be merged. Deprecated in 12.0 in favor of Approval Rules API. |
| `reset_approvals_on_push` | boolean | no | Reset approvals on a new push |
| `disable_overriding_approvers_per_merge_request` | boolean | no | Allow/Disallow overriding approvers per MR |
| `merge_requests_author_approval` | boolean | no | Allow/Disallow authors from self approving merge requests; `true` means authors cannot self approve |
@@ -83,20 +53,68 @@ POST /projects/:id/approvals
```json
{
- "approvers": [
- {
- "user": {
+ "approvals_before_merge": 2,
+ "reset_approvals_on_push": true,
+ "disable_overriding_approvers_per_merge_request": false,
+ "merge_requests_author_approval": false,
+ "merge_requests_disable_committers_approval": false
+}
+```
+
+### Get project-level rules
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can request information about a project's approval rules using the following endpoint:
+
+```
+GET /projects/:id/approval_rules
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|-----------------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+
+```json
+[
+ {
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
"id": 5,
- "name": "John Doe6",
- "username": "user5",
- "state":"active","avatar_url":"https://www.gravatar.com/avatar/4aea8cf834ed91844a2da4ff7ae6b491?s=80\u0026d=identicon","web_url":"http://localhost/user5"
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
}
- }
- ],
- "approver_groups": [
- {
- "group": {
- "id": 1,
+ ],
+ "approvals_required": 3,
+ "users": [
+ {
+ "id": 5,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
"name": "group1",
"path": "group1",
"description": "",
@@ -111,18 +129,187 @@ POST /projects/:id/approvals
"ldap_cn": null,
"ldap_access": null
}
+ ],
+ "contains_hidden_groups": false
+ }
+]
+```
+
+### Create project-level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can create project approval rules using the following endpoint:
+
+```
+POST /projects/:id/approval_rules
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|-----------------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `name` | string | yes | The name of the approval rule |
+| `approvals_required` | integer | yes | The number of required approvals for this rule |
+| `users` | integer | no | The ids of users as approvers |
+| `groups` | integer | no | The ids of groups as approvers |
+
+```json
+{
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
}
],
- "approvals_before_merge": 2,
- "reset_approvals_on_push": true,
- "disable_overriding_approvers_per_merge_request": false,
- "merge_requests_author_approval": false,
- "merge_requests_disable_committers_approval": false
+ "approvals_required": 1,
+ "users": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ ],
+ "contains_hidden_groups": false
+}
+```
+
+### Update project-level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can update project approval rules using the following endpoint:
+
+```
+PUT /projects/:id/approval_rules/:approval_rule_id
+```
+
+**Important:** Approvers and groups not in the `users`/`groups` param will be **removed**
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|-----------------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `approval_rule_id` | integer | yes | The ID of a approval rule |
+| `name` | string | yes | The name of the approval rule |
+| `approvals_required` | integer | yes | The number of required approvals for this rule |
+| `users` | integer | no | The ids of users as approvers |
+| `groups` | integer | no | The ids of groups as approvers |
+
+```json
+{
+ "id": 1,
+ "name": "security",
+ "rule_type": "regular",
+ "eligible_approvers": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ },
+ {
+ "id": 50,
+ "name": "Group Member 1",
+ "username": "group_member_1",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/group_member_1"
+ }
+ ],
+ "approvals_required": 1,
+ "users": [
+ {
+ "id": 2,
+ "name": "John Doe",
+ "username": "jdoe",
+ "state": "active",
+ "avatar_url": "https://www.gravatar.com/avatar/0?s=80&d=identicon",
+ "web_url": "http://localhost/jdoe"
+ }
+ ],
+ "groups": [
+ {
+ "id": 5,
+ "name": "group1",
+ "path": "group1",
+ "description": "",
+ "visibility": "public",
+ "lfs_enabled": false,
+ "avatar_url": null,
+ "web_url": "http://localhost/groups/group1",
+ "request_access_enabled": false,
+ "full_name": "group1",
+ "full_path": "group1",
+ "parent_id": null,
+ "ldap_cn": null,
+ "ldap_access": null
+ }
+ ],
+ "contains_hidden_groups": false
}
```
+### Delete project-level rule
+
+>**Note:** This API endpoint is only available on 12.3 Starter and above.
+
+You can delete project approval rules using the following endpoint:
+
+```
+DELETE /projects/:id/approval_rules/:approval_rule_id
+```
+
+**Parameters:**
+
+| Attribute | Type | Required | Description |
+|----------------------|---------|----------|-----------------------------------------------------------|
+| `id` | integer | yes | The ID of a project |
+| `approval_rule_id` | integer | yes | The ID of a approval rule
+
### Change allowed approvers
+>**Note:** This API endpoint has been deprecated. Please use Approval Rule API instead.
>**Note:** This API endpoint is only available on 10.6 Starter and above.
If you are allowed to, you can change approvers and approver groups using
@@ -227,8 +414,6 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals
}
}
],
- "approvers": [],
- "approver_groups": []
}
```
@@ -249,7 +434,7 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals
|----------------------|---------|----------|--------------------------------------------|
| `id` | integer | yes | The ID of a project |
| `merge_request_iid` | integer | yes | The IID of MR |
-| `approvals_required` | integer | yes | Approvals required before MR can be merged |
+| `approvals_required` | integer | yes | Approvals required before MR can be merged. Deprecated in 12.0 in favor of Approval Rules API. |
```json
{
@@ -264,14 +449,13 @@ POST /projects/:id/merge_requests/:merge_request_iid/approvals
"merge_status": "cannot_be_merged",
"approvals_required": 2,
"approvals_left": 2,
- "approved_by": [],
- "approvers": [],
- "approver_groups": []
+ "approved_by": []
}
```
### Change allowed approvers for Merge Request
+>**Note:** This API endpoint has been deprecated. Please use Approval Rule API instead.
>**Note:** This API endpoint is only available on 10.6 Starter and above.
If you are allowed to, you can change approvers and approver groups using
@@ -401,8 +585,6 @@ does not match, the response code will be `409`.
}
}
],
- "approvers": [],
- "approver_groups": []
}
```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 1ade46efb1c..49ed4968b0d 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -1127,7 +1127,7 @@ the `approvals_before_merge` parameter:
## Delete a merge request
-Only for admins and project owners. Soft deletes the merge request in question.
+Only for admins and project owners. Deletes the merge request in question.
```
DELETE /projects/:id/merge_requests/:merge_request_iid
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index 2f000719c04..1c38c08b7cb 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -65,12 +65,12 @@ as quickly as possible.
Relationships are defined between jobs using the [`needs:` keyword](../yaml/README.md#needs).
Note that `needs:` also works with the [parallel](../yaml/README.md#parallel) keyword,
-giving your powerful options for parallelization within your pipeline.
+giving you powerful options for parallelization within your pipeline.
## Limitations
A directed acyclic graph is a complicated feature, and as of the initial MVC there
are certain use cases that you may need to work around. For more information:
- - [`needs` requirements and limitations](../yaml/README.md#requirements-and-limitations).
- - Related epic [gitlab-org#1716](https://gitlab.com/groups/gitlab-org/-/epics/1716).
+- [`needs` requirements and limitations](../yaml/README.md#requirements-and-limitations).
+- Related epic [gitlab-org#1716](https://gitlab.com/groups/gitlab-org/-/epics/1716).
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index d5056568dff..489791141ed 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -495,14 +495,6 @@ that runner.
## Define an image from a private Container Registry
-> **Notes:**
->
-> - This feature requires GitLab Runner **1.8** or higher
-> - For GitLab Runner versions **>= 0.6, <1.8** there was a partial
-> support for using private registries, which required manual configuration
-> of credentials on runner's host. We recommend to upgrade your Runner to
-> at least version **1.8** if you want to use private registries.
-
To access private container registries, the GitLab Runner process can use:
- [Statically defined credentials](#using-statically-defined-credentials). That is, a username and password for a specific registry.
@@ -525,6 +517,17 @@ it's provided as an environment variable. This is because GitLab Runnner uses **
`config.toml` configuration and doesn't interpolate **ANY** environment variables at
runtime.
+### Requirements and limitations
+
+- This feature requires GitLab Runner **1.8** or higher.
+- For GitLab Runner versions **>= 0.6, <1.8** there was a partial
+ support for using private registries, which required manual configuration
+ of credentials on runner's host. We recommend to upgrade your Runner to
+ at least version **1.8** if you want to use private registries.
+- Not available for [Kubernetes executor](https://docs.gitlab.com/runner/executors/kubernetes.html),
+ follow <https://gitlab.com/gitlab-org/gitlab-runner/issues/2673> for
+ details.
+
### Using statically-defined credentials
There are two approaches that you can take in order to access a
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
index ad07c662965..126e12e460f 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md
@@ -35,10 +35,9 @@ get out of WIP status or resolve merge conflicts as soon as possible.
## Requirements and limitations
-Pipelines for merged results require:
+Pipelines for merged results require a [GitLab Runner][runner] 11.9 or newer.
-- [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner) 11.9 or newer.
-- [Gitaly](https://gitlab.com/gitlab-org/gitaly) 1.21.0 or newer.
+[runner]: https://gitlab.com/gitlab-org/gitlab-runner
In addition, pipelines for merged results have the following limitations:
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index ced4344a0b0..cb8d383f7d9 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -176,6 +176,21 @@ Upstream pipelines take precedence over downstream ones. If there are two
variables with the same name defined in both upstream and downstream projects,
the ones defined in the upstream project will take precedence.
+### Mirroring status from upstream pipeline
+
+You can mirror the pipeline status from an upstream pipeline to a bridge job by
+using the `needs:pipeline` keyword. The latest pipeline status from master is
+replicated to the bridge job.
+
+Example:
+
+```yaml
+upstream_bridge:
+ stage: test
+ needs:
+ pipeline: other/project
+```
+
### Limitations
Because bridge jobs are a little different to regular jobs, it is not
diff --git a/doc/ci/quick_start/img/build_log.png b/doc/ci/quick_start/img/build_log.png
index 2bf0992c50e..16698629edc 100644
--- a/doc/ci/quick_start/img/build_log.png
+++ b/doc/ci/quick_start/img/build_log.png
Binary files differ
diff --git a/doc/ci/quick_start/img/builds_status.png b/doc/ci/quick_start/img/builds_status.png
index 58978e23978..b4aeeb988d2 100644
--- a/doc/ci/quick_start/img/builds_status.png
+++ b/doc/ci/quick_start/img/builds_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/pipelines_status.png b/doc/ci/quick_start/img/pipelines_status.png
index 06d1559f5d2..39a77a26b25 100644
--- a/doc/ci/quick_start/img/pipelines_status.png
+++ b/doc/ci/quick_start/img/pipelines_status.png
Binary files differ
diff --git a/doc/ci/quick_start/img/runners_activated.png b/doc/ci/quick_start/img/runners_activated.png
index cd83c1a7e4c..ac09e1d0137 100644
--- a/doc/ci/quick_start/img/runners_activated.png
+++ b/doc/ci/quick_start/img/runners_activated.png
Binary files differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 8474d4ef66e..269bd5c3428 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -88,7 +88,7 @@ visit the project you want to make the Runner work for in GitLab:
## Registering a group Runner
-Creating a group Runner requires Maintainer permissions for the group. To create a
+Creating a group Runner requires Owner permissions for the group. To create a
group Runner visit the group you want to make the Runner work for in GitLab:
1. Go to **Settings > CI/CD** to obtain the token
@@ -124,9 +124,9 @@ To lock/unlock a Runner:
## Assigning a Runner to another project
-If you are Maintainer on a project where a specific Runner is assigned to, and the
+If you are an Owner on a project where a specific Runner is assigned to, and the
Runner is not [locked only to that project](#locking-a-specific-runner-from-being-enabled-for-other-projects),
-you can enable the Runner also on any other project where you have Maintainer permissions.
+you can enable the Runner also on any other project where you have Owner permissions.
To enable/disable a Runner in your project:
@@ -250,7 +250,7 @@ When you [register a Runner][register], its default behavior is to **only pick**
[tagged jobs](../yaml/README.md#tags).
NOTE: **Note:**
-Maintainer [permissions](../../user/permissions.md) are required to change the
+Owner [permissions](../../user/permissions.md) are required to change the
Runner settings.
To make a Runner pick untagged jobs:
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index d9f022a7125..b6aebd3bd78 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -76,7 +76,7 @@ to access it. This is where an SSH key pair comes in handy.
## without extra base64 encoding.
## https://gitlab.com/gitlab-examples/ssh-private-key/issues/1#note_48526556
##
- - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
+ - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
##
## Create the SSH directory and give it the right permissions
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index d741482b662..5a15b907da0 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -94,7 +94,10 @@ This means that the value of the variable will be hidden in job logs,
though it must match certain requirements to do so:
- The value must be in a single line.
-- The value must only consist of characters from the Base64 alphabet ([RFC4648](https://tools.ietf.org/html/rfc4648)) with the addition of `@` and `:`.
+- The value must only consist of characters from the Base64 alphabet (RFC4648).
+
+ [In GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-ce/issues/63043)
+ and newer, `@` and `:` are also valid values.
- The value must be at least 8 characters long.
- The value must not use variables.
@@ -509,7 +512,7 @@ Below you can find supported syntax reference:
1. Checking for an empty variable
Examples:
-
+
- `$VARIABLE == ""`
- `$VARIABLE != ""` (introduced in GitLab 11.11)
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 2be93433b36..c957ed2cc6b 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -518,10 +518,24 @@ Four keys are available:
- `changes`
- `kubernetes`
-If you use multiple keys under `only` or `except`, they act as an AND. The logic is:
+If you use multiple keys under `only` or `except`, the keys will be evaluated as a
+single conjoined expression. That is:
+
+- `only:` means "include this job if all of the conditions match".
+- `except:` means "exclude this job if any of the conditions match".
+
+The the individual keys are logically joined by an AND:
> (any of refs) AND (any of variables) AND (any of changes) AND (if kubernetes is active)
+`except` is implemented as a negation of this complete expression:
+
+> NOT((any of refs) AND (any of variables) AND (any of changes) AND (if kubernetes is active))
+
+This, more intuitively, means the keys join by an OR. A functionally equivalent expression:
+
+> (any of refs) OR (any of variables) OR (any of changes) OR (if kubernetes is active)
+
#### `only:refs`/`except:refs`
> `refs` policy introduced in GitLab 10.0.
@@ -1721,7 +1735,7 @@ This example creates three paths of execution:
1. If `needs:` is set to point to a job that is not instantiated
because of `only/except` rules or otherwise does not exist, the
job will fail.
-1. Note that one day one of the launch, we are temporarily limiting the
+1. Note that on day one of the launch, we are temporarily limiting the
maximum number of jobs that a single job can need in the `needs:` array. Track
our [infrastructure issue](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/7541)
for details on the current limit.
@@ -1734,8 +1748,8 @@ This example creates three paths of execution:
in the first stage (see [gitlab-ce#65504](https://gitlab.com/gitlab-org/gitlab-ce/issues/65504)).
1. If `needs:` refers to a job that is marked as `parallel:`.
the current job will depend on all parallel jobs created.
-1. `needs:` is similar to `dependencies:` in that needs to use jobs from
- prior stages, this means that it is impossible to create circular
+1. `needs:` is similar to `dependencies:` in that it needs to use jobs from
+ prior stages, meaning it is impossible to create circular
dependencies or depend on jobs in the current stage (see [gitlab-ce#65505](https://gitlab.com/gitlab-org/gitlab-ce/issues/65505)).
1. Related to the above, stages must be explicitly defined for all jobs
that have the keyword `needs:` or are referred to by one.
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 87405bc2fec..5cb2ddf6e52 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -20,7 +20,7 @@ A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a w
We also support deploying GitLab on Kubernetes using our [gitlab Helm chart](https://docs.gitlab.com/charts/).
-The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
+The GitLab web app uses PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository.
When serving repositories over HTTP/HTTPS GitLab utilizes the GitLab API to resolve authorization and access as well as serving git objects.
@@ -511,7 +511,15 @@ To summarize here's the [directory structure of the `git` user home directory](.
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).
+GitLab has several components to operate. It requires a persistent database
+(PostgreSQL) and redis database, and uses Apache httpd or Nginx to proxypass
+Unicorn. All these components should run as different system users to GitLab
+(e.g., `postgres`, `redis` and `www-data`, instead of `git`).
+
+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).
### Repository access
@@ -554,12 +562,9 @@ $ /etc/init.d/nginx
Usage: nginx {start|stop|restart|reload|force-reload|status|configtest}
```
-Persistent database (one of the following)
+Persistent database
```
-/etc/init.d/mysqld
-Usage: /etc/init.d/mysqld {start|stop|status|restart|condrestart|try-restart|reload|force-reload}
-
$ /etc/init.d/postgresql
Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [version ..]
```
@@ -597,11 +602,6 @@ PostgreSQL
- `/var/log/postgresql/*`
-MySQL
-
-- `/var/log/mysql/*`
-- `/var/log/mysql.*`
-
### GitLab specific config files
GitLab has configuration files located in `/home/git/gitlab/config/*`. Commonly referenced config files include:
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 98b8a48abf4..001a92790e1 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -200,7 +200,7 @@ code.
### Why merge automatically?
As we work towards continuous deployments and a single repository for both CE
-and EE, we need to first make sure that all CE changes make their way into CE as
+and EE, we need to first make sure that all CE changes make their way into EE as
fast as possible. Past experiences and data have shown that periodic CE to EE
merge requests do not scale, and often take a very long time to complete. For
example, [in this
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index a38794c49af..b2e3ef7bf63 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -60,37 +60,18 @@ Subject labels are always all-lowercase.
## Team labels
-**Important**: Most of the team labels will be soon deprecated in favor of [Group labels](#group-labels).
+**Important**: Most of the historical team labels (e.g. Manage, Plan etc.) are
+now deprecated in favor of [Group labels](#group-labels) and [Stage labels](#stage-labels).
Team labels specify what team is responsible for this issue.
Assigning a team label makes sure issues get the attention of the appropriate
people.
-The team labels planned for deprecation are:
-
-- ~Configure
-- ~Create
-- ~Defend
-- ~Distribution
-- ~Ecosystem
-- ~Geo
-- ~Gitaly
-- ~Growth
-- ~Manage
-- ~Memory
-- ~Monitor
-- ~Plan
-- ~Release
-- ~Secure
-- ~Verify
-
-The following team labels are **true** teams per our [organization structure](https://about.gitlab.com/company/team/structure/#organizational-structure) which will remain post deprecation.
+The current team labels are:
- ~Delivery
- ~Documentation
-
-The descriptions on the [labels page](https://gitlab.com/gitlab-org/gitlab-ce/-/labels) explain what falls under the
-responsibility of each team.
+- ~Quality
Team labels are always capitalized so that they show up as the first label for
any issue.
@@ -120,13 +101,13 @@ and thus are mutually exclusive.
You can find the groups listed in the [Product Stages, Groups, and Categories][product-categories] page.
-We use the term group to map down product requirements from our product stages.
+We use the term group to map down product requirements from our product stages.
As a team needs some way to collect the work their members are planning to be assigned to, we use the `~group::` labels to do so.
Normally there is a 1:1 relationship between Stage labels and Group labels. In the spirit of "Everyone can contribute",
any issue can be picked up by any group, depending on current priorities. For example, an issue labeled ~"devops::create" may be picked up by the ~"group::access" group.
-We also use stage and group labels to help quantify our [throughput](https://about.gitlab.com/handbook/engineering/management/throughput).
+We also use stage and group labels to help quantify our [throughput](https://about.gitlab.com/handbook/engineering/management/throughput).
Please read [Stage and Group labels in Throughtput](https://about.gitlab.com/handbook/engineering/management/throughput/#stage-and-group-labels-in-throughput) for more information on how the labels are used in this context.
[structure-groups]: https://about.gitlab.com/company/team/structure/#groups
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 680f2cd13c2..c1e3eb9680b 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -655,15 +655,16 @@ nicely on different mobile devices.
## Code blocks
-- Always wrap code added to a sentence in inline code blocks (``` ` ```).
+- Always wrap code added to a sentence in inline code blocks (`` ` ``).
E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: master`.
File names, commands, entries, and anything that refers to code should be added to code blocks.
To make things easier for the user, always add a full code block for things that can be
useful to copy and paste, as they can easily do it with the button on code blocks.
+- Add a blank line above and below code blocks.
- For regular code blocks, always use a highlighting class corresponding to the
language for better readability. Examples:
- ````md
+ ~~~md
```ruby
Ruby code
```
@@ -673,16 +674,17 @@ nicely on different mobile devices.
```
```md
- Markdown code
+ [Markdown code example](example.md)
```
```text
- Code for which no specific highlighting class is available.
+ Code or text for which no specific highlighting class is available.
```
- ````
+ ~~~
-- To display raw markdown instead of rendered markdown, use four backticks on their own lines around the
- markdown to display. See [example](https://gitlab.com/gitlab-org/gitlab-ce/blob/8c1991b9bb7e3b8d606481fdea316d633cfa5eb7/doc/development/documentation/styleguide.md#L275-287).
+- To display raw markdown instead of rendered markdown, you can use triple backticks
+ with `md`, like the `Markdown code` example above, unless you want to include triple
+ backticks in the code block as well. In that case, use triple tildes (`~~~`) instead.
- For a complete reference on code blocks, check the [Kramdown guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/#code-blocks).
## Alert boxes
@@ -1024,7 +1026,7 @@ on this document. Further explanation is given below.
The following can be used as a template to get started:
-````md
+~~~md
## Descriptive title
One or two sentence description of what endpoint does.
@@ -1052,7 +1054,7 @@ Example response:
}
]
```
-````
+~~~
### Fake tokens
@@ -1080,7 +1082,7 @@ You can use the following fake tokens as examples.
### Method description
Use the following table headers to describe the methods. Attributes should
-always be in code blocks using backticks (``` ` ```).
+always be in code blocks using backticks (`` ` ``).
```md
| Attribute | Type | Required | Description |
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 09b4a3c3d96..6e7cf523f36 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -38,7 +38,7 @@ Advantages over [`spyOn()`]:
- no need to create response objects
- does not allow call through (which we want to avoid)
-- simple API to test error cases
+- simple API to test error cases
- provides `replyOnce()` to allow for different responses
We have also decided against using [axios interceptors] because they are not suitable for mocking.
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index d3fa350b847..125b11afcd0 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -21,31 +21,31 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. **Never Ever EVER** disable eslint globally for a file
```javascript
- // bad
- /* eslint-disable */
+ // bad
+ /* eslint-disable */
- // better
- /* eslint-disable some-rule, some-other-rule */
+ // better
+ /* eslint-disable some-rule, some-other-rule */
- // best
- // nothing :)
+ // best
+ // nothing :)
```
1. If you do need to disable a rule for a single violation, try to do it as locally as possible
```javascript
- // bad
- /* eslint-disable no-new */
+ // bad
+ /* eslint-disable no-new */
- import Foo from 'foo';
+ import Foo from 'foo';
- new Foo();
+ new Foo();
- // better
- import Foo from 'foo';
+ // better
+ import Foo from 'foo';
- // eslint-disable-next-line no-new
- new Foo();
+ // eslint-disable-next-line no-new
+ new Foo();
```
1. There are few rules that we need to disable due to technical debt. Which are:
@@ -56,16 +56,16 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
followed by any global declarations, then a blank newline prior to any imports or code.
```javascript
- // bad
- /* global Foo */
- /* eslint-disable no-new */
- import Bar from './bar';
+ // bad
+ /* global Foo */
+ /* eslint-disable no-new */
+ import Bar from './bar';
- // good
- /* eslint-disable no-new */
- /* global Foo */
+ // good
+ /* eslint-disable no-new */
+ /* global Foo */
- import Bar from './bar';
+ import Bar from './bar';
```
1. **Never** disable the `no-undef` rule. Declare globals with `/* global Foo */` instead.
@@ -73,23 +73,23 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. When declaring multiple globals, always use one `/* global [name] */` line per variable.
```javascript
- // bad
- /* globals Flash, Cookies, jQuery */
+ // bad
+ /* globals Flash, Cookies, jQuery */
- // good
- /* global Flash */
- /* global Cookies */
- /* global jQuery */
+ // good
+ /* global Flash */
+ /* global Cookies */
+ /* global jQuery */
```
1. Use up to 3 parameters for a function or class. If you need more accept an Object instead.
```javascript
- // bad
- fn(p1, p2, p3, p4) {}
+ // bad
+ fn(p1, p2, p3, p4) {}
- // good
- fn(options) {}
+ // good
+ fn(options) {}
```
#### Modules, Imports, and Exports
@@ -97,32 +97,32 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. Use ES module syntax to import modules
```javascript
- // bad
- const SomeClass = require('some_class');
+ // bad
+ const SomeClass = require('some_class');
- // good
- import SomeClass from 'some_class';
+ // good
+ import SomeClass from 'some_class';
- // bad
- module.exports = SomeClass;
+ // bad
+ module.exports = SomeClass;
- // good
- export default SomeClass;
+ // good
+ export default SomeClass;
```
Import statements are following usual naming guidelines, for example object literals use camel case:
```javascript
- // some_object file
- export default {
- key: 'value',
- };
+ // some_object file
+ export default {
+ key: 'value',
+ };
- // bad
- import ObjectLiteral from 'some_object';
+ // bad
+ import ObjectLiteral from 'some_object';
- // good
- import objectLiteral from 'some_object';
+ // good
+ import objectLiteral from 'some_object';
```
1. Relative paths: when importing a module in the same directory, a child
@@ -171,22 +171,22 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. Avoid adding to the global namespace.
```javascript
- // bad
- window.MyClass = class { /* ... */ };
+ // bad
+ window.MyClass = class { /* ... */ };
- // good
- export default class MyClass { /* ... */ }
+ // good
+ export default class MyClass { /* ... */ }
```
1. Side effects are forbidden in any script which contains export
```javascript
- // bad
- export default class MyClass { /* ... */ }
+ // bad
+ export default class MyClass { /* ... */ }
- document.addEventListener("DOMContentLoaded", function(event) {
- new MyClass();
- }
+ document.addEventListener("DOMContentLoaded", function(event) {
+ new MyClass();
+ }
```
#### Data Mutation and Pure functions
@@ -257,17 +257,17 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
`.reduce` or `.filter`
```javascript
- const users = [ { name: 'Foo' }, { name: 'Bar' } ];
+ const users = [ { name: 'Foo' }, { name: 'Bar' } ];
- // bad
- users.forEach((user, index) => {
- user.id = index;
- });
+ // bad
+ users.forEach((user, index) => {
+ user.id = index;
+ });
- // good
- const usersWithId = users.map((user, index) => {
- return Object.assign({}, user, { id: index });
- });
+ // good
+ const usersWithId = users.map((user, index) => {
+ return Object.assign({}, user, { id: index });
+ });
```
#### Parse Strings into Numbers
@@ -275,14 +275,14 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. `parseInt()` is preferable over `Number()` or `+`
```javascript
- // bad
- +'10' // 10
+ // bad
+ +'10' // 10
- // good
- Number('10') // 10
+ // good
+ Number('10') // 10
- // better
- parseInt('10', 10);
+ // better
+ parseInt('10', 10);
```
#### CSS classes used for JavaScript
@@ -290,15 +290,15 @@ See [our current .eslintrc](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/
1. If the class is being used in Javascript it needs to be prepend with `js-`
```html
- // bad
- <button class="add-user">
- Add User
- </button>
+ // bad
+ <button class="add-user">
+ Add User
+ </button>
- // good
- <button class="js-add-user">
- Add User
- </button>
+ // good
+ <button class="js-add-user">
+ Add User
+ </button>
```
### Vue.js
@@ -315,41 +315,41 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. Use a function in the bundle file to instantiate the Vue component:
```javascript
- // bad
- class {
- init() {
- new Component({})
- }
+ // bad
+ class {
+ init() {
+ new Component({})
}
+ }
- // good
- document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#element',
- components: {
- componentName
- },
- render: createElement => createElement('component-name'),
- }));
+ // good
+ document.addEventListener('DOMContentLoaded', () => new Vue({
+ el: '#element',
+ components: {
+ componentName
+ },
+ render: createElement => createElement('component-name'),
+ }));
```
1. Do not use a singleton for the service or the store
```javascript
- // bad
- class Store {
- constructor() {
- if (!this.prototype.singleton) {
- // do something
- }
+ // bad
+ class Store {
+ constructor() {
+ if (!this.prototype.singleton) {
+ // do something
}
}
+ }
- // good
- class Store {
- constructor() {
- // do something
- }
+ // good
+ class Store {
+ constructor() {
+ // do something
}
+ }
```
1. Use `.vue` for Vue templates. Do not use `%template` in HAML.
@@ -360,36 +360,36 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. **Reference Naming**: Use PascalCase for their instances:
```javascript
- // bad
- import cardBoard from 'cardBoard.vue'
+ // bad
+ import cardBoard from 'cardBoard.vue'
- components: {
- cardBoard,
- };
+ components: {
+ cardBoard,
+ };
- // good
- import CardBoard from 'cardBoard.vue'
+ // good
+ import CardBoard from 'cardBoard.vue'
- components: {
- CardBoard,
- };
+ components: {
+ CardBoard,
+ };
```
1. **Props Naming:** Avoid using DOM component prop names.
1. **Props Naming:** Use kebab-case instead of camelCase to provide props in templates.
```javascript
- // bad
- <component class="btn">
+ // bad
+ <component class="btn">
- // good
- <component css-class="btn">
+ // good
+ <component css-class="btn">
- // bad
- <component myProp="prop" />
+ // bad
+ <component myProp="prop" />
- // good
- <component my-prop="prop" />
+ // good
+ <component my-prop="prop" />
```
[#34371]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34371
@@ -401,37 +401,37 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. With more than one attribute, all attributes should be on a new line:
```javascript
- // bad
- <component v-if="bar"
- param="baz" />
+ // bad
+ <component v-if="bar"
+ param="baz" />
- <button class="btn">Click me</button>
+ <button class="btn">Click me</button>
- // good
- <component
- v-if="bar"
- param="baz"
- />
+ // good
+ <component
+ v-if="bar"
+ param="baz"
+ />
- <button class="btn">
- Click me
- </button>
+ <button class="btn">
+ Click me
+ </button>
```
1. The tag can be inline if there is only one attribute:
```javascript
- // good
- <component bar="bar" />
+ // good
+ <component bar="bar" />
- // good
- <component
- bar="bar"
- />
+ // good
+ <component
+ bar="bar"
+ />
- // bad
- <component
- bar="bar" />
+ // bad
+ <component
+ bar="bar" />
```
#### Quotes
@@ -439,15 +439,15 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
```javascript
- // bad
- template: `
- <button :class='style'>Button</button>
- `
+ // bad
+ template: `
+ <button :class='style'>Button</button>
+ `
- // good
- template: `
- <button :class="style">Button</button>
- `
+ // good
+ template: `
+ <button :class="style">Button</button>
+ `
```
#### Props
@@ -455,37 +455,37 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. Props should be declared as an object
```javascript
- // bad
- props: ['foo']
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
+ // bad
+ props: ['foo']
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
}
+ }
```
1. Required key should always be provided when declaring a prop
```javascript
- // bad
- props: {
- foo: {
- type: String,
- }
+ // bad
+ props: {
+ foo: {
+ type: String,
}
+ }
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
}
+ }
```
1. Default key should be provided if the prop is not required.
@@ -493,30 +493,30 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
On those a default key should not be provided.
```javascript
- // good
- props: {
- foo: {
- type: String,
- required: false,
- }
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
}
+ }
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
}
+ }
- // good
- props: {
- foo: {
- type: String,
- required: true
- }
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: true
}
+ }
```
#### Data
@@ -524,17 +524,17 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. `data` method should always be a function
```javascript
- // bad
- data: {
- foo: 'foo'
- }
+ // bad
+ data: {
+ foo: 'foo'
+ }
- // good
- data() {
- return {
- foo: 'foo'
- };
- }
+ // good
+ data() {
+ return {
+ foo: 'foo'
+ };
+ }
```
#### Directives
@@ -542,31 +542,31 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. Shorthand `@` is preferable over `v-on`
```javascript
- // bad
- <component v-on:click="eventHandler"/>
+ // bad
+ <component v-on:click="eventHandler"/>
- // good
- <component @click="eventHandler"/>
+ // good
+ <component @click="eventHandler"/>
```
1. Shorthand `:` is preferable over `v-bind`
```javascript
- // bad
- <component v-bind:class="btn"/>
+ // bad
+ <component v-bind:class="btn"/>
- // good
- <component :class="btn"/>
+ // good
+ <component :class="btn"/>
```
1. Shorthand `#` is preferable over `v-slot`
```javascript
- // bad
- <template v-slot:header></template>
+ // bad
+ <template v-slot:header></template>
- // good
- <template #header></template>
+ // good
+ <template #header></template>
```
#### Closing tags
@@ -574,11 +574,11 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. Prefer self closing component tags
```javascript
- // bad
- <component></component>
+ // bad
+ <component></component>
- // good
- <component />
+ // good
+ <component />
```
#### Ordering
@@ -610,48 +610,48 @@ When using `v-for` you need to provide a *unique* `:key` attribute for each item
1. If the elements of the array being iterated have an unique `id` it is advised to use it:
```html
- <div
- v-for="item in items"
- :key="item.id"
- >
- <!-- content -->
- </div>
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <!-- content -->
+ </div>
```
1. When the elements being iterated don't have a unique id, you can use the array index as the `:key` attribute
```html
- <div
- v-for="(item, index) in items"
- :key="index"
- >
- <!-- content -->
- </div>
+ <div
+ v-for="(item, index) in items"
+ :key="index"
+ >
+ <!-- content -->
+ </div>
```
1. When using `v-for` with `template` and there is more than one child element, the `:key` values must be unique. It's advised to use `kebab-case` namespaces.
```html
- <template v-for="(item, index) in items">
- <span :key="`span-${index}`"></span>
- <button :key="`button-${index}`"></button>
- </template>
+ <template v-for="(item, index) in items">
+ <span :key="`span-${index}`"></span>
+ <button :key="`button-${index}`"></button>
+ </template>
```
1. When dealing with nested `v-for` use the same guidelines as above.
```html
- <div
- v-for="item in items"
- :key="item.id"
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <span
+ v-for="element in array"
+ :key="element.id"
>
- <span
- v-for="element in array"
- :key="element.id"
- >
- <!-- content -->
- </span>
- </div>
+ <!-- content -->
+ </span>
+ </div>
```
Useful links:
@@ -664,19 +664,19 @@ Useful links:
1. Tooltips: Do not rely on `has-tooltip` class name for Vue components
```javascript
- // bad
- <span
- class="has-tooltip"
- title="Some tooltip text">
- Text
- </span>
+ // bad
+ <span
+ class="has-tooltip"
+ title="Some tooltip text">
+ Text
+ </span>
- // good
- <span
- v-tooltip
- title="Some tooltip text">
- Text
- </span>
+ // good
+ <span
+ v-tooltip
+ title="Some tooltip text">
+ Text
+ </span>
```
1. Tooltips: When using a tooltip, include the tooltip directive, `./app/assets/javascripts/vue_shared/directives/tooltip.js`
@@ -684,13 +684,13 @@ Useful links:
1. Don't change `data-original-title`.
```javascript
- // bad
- <span data-original-title="tooltip text">Foo</span>
+ // bad
+ <span data-original-title="tooltip text">Foo</span>
- // good
- <span title="tooltip text">Foo</span>
+ // good
+ <span title="tooltip text">Foo</span>
- $('span').tooltip('_fixTitle');
+ $('span').tooltip('_fixTitle');
```
### The Javascript/Vue Accord
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 9eeaee4482f..557d3132d71 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -313,7 +313,7 @@ export default {
1. Do not call a mutation directly. Always use an action to commit a mutation. Doing so will keep consistency throughout the application. From Vuex docs:
- > why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
+ > Why don't we just call store.commit('action') directly? Well, remember that mutations must be synchronous? Actions aren't. We can perform asynchronous operations inside an action.
```javascript
// component.vue
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index 9f0ac8cc753..83444093f9c 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -107,6 +107,32 @@ Modules](https://github.com/golang/go/wiki/Modules). It provides a way to
define and lock dependencies for reproducible builds. It should be used
whenever possible.
+When Go Modules are in use, there should not be a `vendor/` directory. Instead,
+Go will automatically download dependencies when they are needed to build the
+project. This is in line with how dependencies are handled with Bundler in Ruby
+projects, and makes merge requests easier to review.
+
+In some cases, such as building a Go project for it to act as a dependency of a
+CI run for another project, removing the `vendor/` directory means the code must
+be downloaded repeatedly, which can lead to intermittent problems due to rate
+limiting or network failures. In these circumstances, you should cache the
+downloaded code between runs with a `.gitlab-ci.yml` snippet like this:
+
+```yaml
+.go-cache:
+ variables:
+ GOPATH: $CI_PROJECT_DIR/.go
+ before_script:
+ - mkdir -p .go
+ cache:
+ paths:
+ - .go/pkg/mod/
+
+test:
+ extends: .go-cache
+ # ...
+```
+
There was a [bug on modules
checksums](https://github.com/golang/go/issues/29278) in Go < v1.11.4, so make
sure to use at least this version to avoid `checksum mismatch` errors.
diff --git a/doc/development/hash_indexes.md b/doc/development/hash_indexes.md
index e6c1b3590b1..417ea18e22f 100644
--- a/doc/development/hash_indexes.md
+++ b/doc/development/hash_indexes.md
@@ -1,6 +1,6 @@
# Hash Indexes
-Both PostgreSQL and MySQL support hash indexes besides the regular btree
+PostgreSQL supports hash indexes besides the regular btree
indexes. Hash indexes however are to be avoided at all costs. While they may
_sometimes_ provide better performance the cost of rehashing can be very high.
More importantly: at least until PostgreSQL 10.0 hash indexes are not
diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md
index 5f95cf3707c..777d372ec60 100644
--- a/doc/development/instrumentation.md
+++ b/doc/development/instrumentation.md
@@ -1,6 +1,6 @@
# Instrumenting Ruby Code
-GitLab Performance Monitoring allows instrumenting of both methods and custom
+[GitLab Performance Monitoring](../administration/monitoring/performance/index.md) allows instrumenting of both methods and custom
blocks of Ruby code. Method instrumentation is the primary form of
instrumentation with block-based instrumentation only being used when we want to
drill down to specific regions of code within a method.
diff --git a/doc/development/interacting_components.md b/doc/development/interacting_components.md
index 74d52d808e2..5e6dc8d460a 100644
--- a/doc/development/interacting_components.md
+++ b/doc/development/interacting_components.md
@@ -9,8 +9,8 @@ when making _backend_ changes that might involve multiple features or [component
## Uploads
-GitLab supports uploads to [object storage]. That means every feature and
-change that affects uploads should also be tested against [object storage],
+GitLab supports uploads to [object storage]. That means every feature and
+change that affects uploads should also be tested against [object storage],
which is _not_ enabled by default in [GDK](https://gitlab.com/gitlab-org/gitlab-development-kit).
When working on a related feature, make sure to enable and test it
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index f7ea496d935..e0d413b748b 100644
--- a/doc/development/new_fe_guide/development/testing.md
+++ b/doc/development/new_fe_guide/development/testing.md
@@ -1,361 +1,6 @@
-# Overview of Frontend Testing
+---
+redirect_to: '../../testing_guide/frontend_testing.md'
+---
-Tests relevant for frontend development can be found at the following places:
+This document was moved to [another location](../../testing_guide/frontend_testing.md).
-- `spec/javascripts/` which are run by Karma (command: `yarn karma`) and contain
- - [frontend unit tests](#frontend-unit-tests)
- - [frontend component tests](#frontend-component-tests)
- - [frontend integration tests](#frontend-integration-tests)
-- `spec/frontend/` which are run by Jest (command: `yarn jest`) and contain
- - [frontend unit tests](#frontend-unit-tests)
- - [frontend component tests](#frontend-component-tests)
- - [frontend integration tests](#frontend-integration-tests)
-- `spec/features/` which are run by RSpec and contain
- - [feature tests](#feature-tests)
-
-All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#52483](https://gitlab.com/gitlab-org/gitlab-ce/issues/52483)).
-
-In addition there were feature tests in `features/` run by Spinach in the past.
-These have been removed from our codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)).
-
-See also:
-
-- [Old testing guide](../../testing_guide/frontend_testing.html).
-- [Notes on testing Vue components](../../fe_guide/vue.html#testing-vue-components).
-
-## Frontend unit tests
-
-Unit tests are on the lowest abstraction level and typically test functionality that is not directly perceivable by a user.
-
-### When to use unit tests
-
-<details>
- <summary>exported functions and classes</summary>
- Anything that is exported can be reused at various places in a way you have no control over.
- Therefore it is necessary to document the expected behavior of the public interface with tests.
-</details>
-
-<details>
- <summary>Vuex actions</summary>
- Any Vuex action needs to work in a consistent way independent of the component it is triggered from.
-</details>
-
-<details>
- <summary>Vuex mutations</summary>
- For complex Vuex mutations it helps to identify the source of a problem by separating the tests from other parts of the Vuex store.
-</details>
-
-### When *not* to use unit tests
-
-<details>
- <summary>non-exported functions or classes</summary>
- Anything that is not exported from a module can be considered private or an implementation detail and doesn't need to be tested.
-</details>
-
-<details>
- <summary>constants</summary>
- Testing the value of a constant would mean to copy it.
- This results in extra effort without additional confidence that the value is correct.
-</details>
-
-<details>
- <summary>Vue components</summary>
- Computed properties, methods, and lifecycle hooks can be considered an implementation detail of components and don't need to be tested.
- They are implicitly covered by component tests.
- The <a href="https://vue-test-utils.vuejs.org/guides/#getting-started">official Vue guidelines</a> suggest the same.
-</details>
-
-### What to mock in unit tests
-
-<details>
- <summary>state of the class under test</summary>
- Modifying the state of the class under test directly rather than using methods of the class avoids side-effects in test setup.
-</details>
-
-<details>
- <summary>other exported classes</summary>
- Every class needs to be tested in isolation to prevent test scenarios from growing exponentially.
-</details>
-
-<details>
- <summary>single DOM elements if passed as parameters</summary>
- For tests that only operate on single DOM elements rather than a whole page, creating these elements is cheaper than loading a whole HTML fixture.
-</details>
-
-<details>
- <summary>all server requests</summary>
- When running frontend unit tests, the backend may not be reachable.
- Therefore all outgoing requests need to be mocked.
-</details>
-
-<details>
- <summary>asynchronous background operations</summary>
- Background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
-</details>
-
-### What *not* to mock in unit tests
-
-<details>
- <summary>non-exported functions or classes</summary>
- Everything that is not exported can be considered private to the module and will be implicitly tested via the exported classes / functions.
-</details>
-
-<details>
- <summary>methods of the class under test</summary>
- By mocking methods of the class under test, the mocks will be tested and not the real methods.
-</details>
-
-<details>
- <summary>utility functions (pure functions, or those that only modify parameters)</summary>
- If a function has no side effects because it has no state, it is safe to not mock it in tests.
-</details>
-
-<details>
- <summary>full HTML pages</summary>
- Loading the HTML of a full page slows down tests, so it should be avoided in unit tests.
-</details>
-
-## Frontend component tests
-
-Component tests cover the state of a single component that is perceivable by a user depending on external signals such as user input, events fired from other components, or application state.
-
-### When to use component tests
-
-- Vue components
-
-### When *not* to use component tests
-
-<details>
- <summary>Vue applications</summary>
- Vue applications may contain many components.
- Testing them on a component level requires too much effort.
- Therefore they are tested on frontend integration level.
-</details>
-
-<details>
- <summary>HAML templates</summary>
- HAML templates contain only Markup and no frontend-side logic.
- Therefore they are not complete components.
-</details>
-
-### What to mock in component tests
-
-<details>
- <summary>DOM</summary>
- Operating on the real DOM is significantly slower than on the virtual DOM.
-</details>
-
-<details>
- <summary>properties and state of the component under test</summary>
- Similarly to testing classes, modifying the properties directly (rather than relying on methods of the component) avoids side-effects.
-</details>
-
-<details>
- <summary>Vuex store</summary>
- To avoid side effects and keep component tests simple, Vuex stores are replaced with mocks.
-</details>
-
-<details>
- <summary>all server requests</summary>
- Similar to unit tests, when running component tests, the backend may not be reachable.
- Therefore all outgoing requests need to be mocked.
-</details>
-
-<details>
- <summary>asynchronous background operations</summary>
- Similar to unit tests, background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
-</details>
-
-<details>
- <summary>child components</summary>
- Every component is tested individually, so child components are mocked.
- See also <a href="https://vue-test-utils.vuejs.org/api/#shallowmount">shallowMount()</a>
-</details>
-
-### What *not* to mock in component tests
-
-<details>
- <summary>methods or computed properties of the component under test</summary>
- By mocking part of the component under test, the mocks will be tested and not the real component.
-</details>
-
-<details>
- <summary>functions and classes independent from Vue</summary>
- All plain JavaScript code is already covered by unit tests and needs not to be mocked in component tests.
-</details>
-
-## Frontend integration tests
-
-Integration tests cover the interaction between all components on a single page.
-Their abstraction level is comparable to how a user would interact with the UI.
-
-### When to use integration tests
-
-<details>
- <summary>page bundles (<code>index.js</code> files in <code>app/assets/javascripts/pages/</code>)</summary>
- Testing the page bundles ensures the corresponding frontend components integrate well.
-</details>
-
-<details>
- <summary>Vue applications outside of page bundles</summary>
- Testing Vue applications as a whole ensures the corresponding frontend components integrate well.
-</details>
-
-### What to mock in integration tests
-
-<details>
- <summary>HAML views (use fixtures instead)</summary>
- Rendering HAML views requires a Rails environment including a running database which we cannot rely on in frontend tests.
-</details>
-
-<details>
- <summary>all server requests</summary>
- Similar to unit and component tests, when running component tests, the backend may not be reachable.
- Therefore all outgoing requests need to be mocked.
-</details>
-
-<details>
- <summary>asynchronous background operations that are not perceivable on the page</summary>
- Background operations that affect the page need to be tested on this level.
- All other background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
-</details>
-
-### What *not* to mock in integration tests
-
-<details>
- <summary>DOM</summary>
- Testing on the real DOM ensures our components work in the environment they are meant for.
- Part of this will be delegated to <a href="https://gitlab.com/gitlab-org/quality/team-tasks/issues/45">cross-browser testing</a>.
-</details>
-
-<details>
- <summary>properties or state of components</summary>
- On this level, all tests can only perform actions a user would do.
- For example to change the state of a component, a click event would be fired.
-</details>
-
-<details>
- <summary>Vuex stores</summary>
- When testing the frontend code of a page as a whole, the interaction between Vue components and Vuex stores is covered as well.
-</details>
-
-## Feature tests
-
-In contrast to [frontend integration tests](#frontend-integration-tests), feature tests make requests against the real backend instead of using fixtures.
-This also implies that database queries are executed which makes this category significantly slower.
-
-See also the [RSpec testing guidelines](../../testing_guide/best_practices.md#rspec).
-
-### When to use feature tests
-
-- use cases that require a backend and cannot be tested using fixtures
-- behavior that is not part of a page bundle but defined globally
-
-### Relevant notes
-
-A `:js` flag is added to the test to make sure the full environment is loaded.
-
-```
-scenario 'successfully', :js do
- sign_in(create(:admin))
-end
-```
-
-The steps of each test are written using capybara methods ([documentation](https://www.rubydoc.info/gems/capybara)).
-
-Bear in mind <abbr title="XMLHttpRequest">XHR</abbr> calls might require you to use `wait_for_requests` in between steps, like so:
-
-```rspec
-find('.form-control').native.send_keys(:enter)
-
-wait_for_requests
-
-expect(page).not_to have_selector('.card')
-```
-
-## Test helpers
-
-### Vuex Helper: `testAction`
-
-We have a helper available to make testing actions easier, as per [official documentation](https://vuex.vuejs.org/guide/testing.html):
-
-```
-testAction(
- actions.actionName, // action
- { }, // params to be passed to action
- state, // state
- [
- { type: types.MUTATION},
- { type: types.MUTATION_1, payload: {}},
- ], // mutations committed
- [
- { type: 'actionName', payload: {}},
- { type: 'actionName1', payload: {}},
- ] // actions dispatched
- done,
-);
-```
-
-Check an example in [spec/javascripts/ide/stores/actions_spec.jsspec/javascripts/ide/stores/actions_spec.js](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/javascripts/ide/stores/actions_spec.js).
-
-### Vue Helper: `mountComponent`
-
-To make mounting a Vue component easier and more readable, we have a few helpers available in `spec/helpers/vue_mount_component_helper`.
-
-- `createComponentWithStore`
-- `mountComponentWithStore`
-
-Examples of usage:
-
-```
-beforeEach(() => {
- vm = createComponentWithStore(Component, store);
-
- vm.$store.state.currentBranchId = 'master';
-
- vm.$mount();
-},
-```
-
-```
-beforeEach(() => {
- vm = mountComponentWithStore(Component, {
- el: '#dummy-element',
- store,
- props: { badge },
- });
-},
-```
-
-Don't forget to clean up:
-
-```
-afterEach(() => {
- vm.$destroy();
-});
-```
-
-## Testing with older browsers
-
-Some regressions only affect a specific browser version. We can install and test in particular browsers with either Firefox or Browserstack using the following steps:
-
-### Browserstack
-
-[Browserstack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
-You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
-You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
-
-### Firefox
-
-#### macOS
-
-You can download any older version of Firefox from the releases FTP server, <https://ftp.mozilla.org/pub/firefox/releases/>
-
-1. From the website, select a version, in this case `50.0.1`.
-1. Go to the mac folder.
-1. Select your preferred language, you will find the dmg package inside, download it.
-1. Drag and drop the application to any other folder but the `Applications` folder.
-1. Rename the application to something like `Firefox_Old`.
-1. Move the application to the `Applications` folder.
-1. Open up a terminal and run `/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager` to create a new profile specific to that Firefox version.
-1. Once the profile has been created, quit the app, and run it again like normal. You now have a working older Firefox version.
diff --git a/doc/development/new_fe_guide/modules/dirty_submit.md b/doc/development/new_fe_guide/modules/dirty_submit.md
index 6c03958b463..217743ea395 100644
--- a/doc/development/new_fe_guide/modules/dirty_submit.md
+++ b/doc/development/new_fe_guide/modules/dirty_submit.md
@@ -1,7 +1,6 @@
# Dirty Submit
-> [Introduced][ce-21115] in GitLab 11.3.
-> [dirty_submit][dirty-submit]
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21115) in GitLab 11.3.
## Summary
@@ -9,6 +8,9 @@ Prevent submitting forms with no changes.
Currently handles `input`, `textarea` and `select` elements.
+Also, see [the code](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/dirty_submit/)
+within the GitLab project.
+
## Usage
```js
@@ -18,6 +20,3 @@ new DirtySubmitForm(document.querySelector('form'));
// or
new DirtySubmitForm(document.querySelectorAll('form'));
```
-
-[ce-21115]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21115
-[dirty-submit]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/dirty_submit/ \ No newline at end of file
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 802ebd12d92..b742d567f41 100644
--- a/doc/development/new_fe_guide/style/javascript.md
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -192,4 +192,4 @@ rules only if you are invoking/instantiating existing code modules.
- [class-method-use-this](http://eslint.org/docs/rules/class-methods-use-this)
> Note: Disable these rules on a per line basis. This makes it easier to refactor
- in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`.
+> in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`.
diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md
index a6b60149ea4..3787e2ef187 100644
--- a/doc/development/query_recorder.md
+++ b/doc/development/query_recorder.md
@@ -36,6 +36,13 @@ it "avoids N+1 database queries" do
end
```
+## Use request specs instead of controller specs
+
+Use a [request spec](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/spec/requests) when writing a N+1 test on the controller level.
+
+Controller specs should not be used to write N+1 tests as the controller is only initialized once per example.
+This could lead to false successes where subsequent "requests" could have queries reduced (e.g. because of memoization).
+
## Finding the source of the query
It may be useful to identify the source of the queries by looking at the call backtrace.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index c97e179910b..fc96820c555 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -9,7 +9,7 @@ bundle exec rake setup
```
The `setup` task is an alias for `gitlab:setup`.
-This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database.
+This tasks calls `db:reset` to create the database, and calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
### Seeding issues for all or a given project
@@ -216,3 +216,4 @@ bundle exec rake routes
Since these take some time to create, it's often helpful to save the output to
a file for quick reference.
+
diff --git a/doc/development/sha1_as_binary.md b/doc/development/sha1_as_binary.md
index 3151cc29bbc..6c4252ec634 100644
--- a/doc/development/sha1_as_binary.md
+++ b/doc/development/sha1_as_binary.md
@@ -2,7 +2,7 @@
Storing SHA1 hashes as strings is not very space efficient. A SHA1 as a string
requires at least 40 bytes, an additional byte to store the encoding, and
-perhaps more space depending on the internals of PostgreSQL and MySQL.
+perhaps more space depending on the internals of PostgreSQL.
On the other hand, if one were to store a SHA1 as binary one would only need 20
bytes for the actual SHA1, and 1 or 4 bytes of additional space (again depending
diff --git a/doc/development/shell_scripting_guide/index.md b/doc/development/shell_scripting_guide/index.md
index ae7f2154682..0809f8b1a0a 100644
--- a/doc/development/shell_scripting_guide/index.md
+++ b/doc/development/shell_scripting_guide/index.md
@@ -24,7 +24,8 @@ Having said all of the above, we recommend staying away from shell scripts
as much as possible. A language like Ruby or Python (if required for
consistency with codebases that we leverage) is almost always a better choice.
The high-level interpreted languages have more readable syntax, offer much more
-mature capabilities for unit-testing, linting, and error reporting.
+mature capabilities for unit-testing, linting, and error reporting.
+
Use shell scripts only if there's a strong restriction on project's
dependencies size or any other requirements that are more important
in a particular case.
@@ -48,12 +49,12 @@ that is:
This section describes the tools that should be made a mandatory part of
a project's CI pipeline if it contains shell scripts. These tools
-automate shell code formatting, checking for errors or vulnerabilities, etc.
+automate shell code formatting, checking for errors or vulnerabilities, etc.
### Linting
We're using the [ShellCheck](https://www.shellcheck.net/) utility in its default configuration to lint our
-shell scripts.
+shell scripts.
All projects with shell scripts should use this GitLab CI/CD job:
@@ -98,7 +99,7 @@ NOTE: **Note:**
This is a work in progress.
It is an [ongoing effort](https://gitlab.com/gitlab-org/gitlab-ce/issues/64016) to evaluate different tools for the
-automated testing of shell scripts (like [BATS](https://github.com/sstephenson/bats)).
+automated testing of shell scripts (like [BATS](https://github.com/sstephenson/bats)).
## Code Review
diff --git a/doc/development/sql.md b/doc/development/sql.md
index a256fd46c09..2584dcfb4ca 100644
--- a/doc/development/sql.md
+++ b/doc/development/sql.md
@@ -15,14 +15,11 @@ FROM issues
WHERE title LIKE 'WIP:%';
```
-On PostgreSQL the `LIKE` statement is case-sensitive. On MySQL this depends on
-the case-sensitivity of the collation, which is usually case-insensitive. To
-perform a case-insensitive `LIKE` on PostgreSQL you have to use `ILIKE` instead.
-This statement in turn isn't supported on MySQL.
+On PostgreSQL the `LIKE` statement is case-sensitive. To perform a case-insensitive
+`LIKE` you have to use `ILIKE` instead.
-To work around this problem you should write `LIKE` queries using Arel instead
-of raw SQL fragments as Arel automatically uses `ILIKE` on PostgreSQL and `LIKE`
-on MySQL. This means that instead of this:
+To handle this automatically you should use `LIKE` queries using Arel instead
+of raw SQL fragments, as Arel automatically uses `ILIKE` on PostgreSQL.
```ruby
Issue.where('title LIKE ?', 'WIP:%')
@@ -45,7 +42,7 @@ table = Issue.arel_table
Issue.where(table[:title].matches('WIP:%').or(table[:foo].matches('WIP:%')))
```
-For PostgreSQL this produces:
+On PostgreSQL, this produces:
```sql
SELECT *
@@ -53,18 +50,10 @@ FROM issues
WHERE (title ILIKE 'WIP:%' OR foo ILIKE 'WIP:%')
```
-In turn for MySQL this produces:
-
-```sql
-SELECT *
-FROM issues
-WHERE (title LIKE 'WIP:%' OR foo LIKE 'WIP:%')
-```
-
## LIKE & Indexes
-Neither PostgreSQL nor MySQL use any indexes when using `LIKE` / `ILIKE` with a
-wildcard at the start. For example, this will not use any indexes:
+PostgreSQL won't use any indexes when using `LIKE` / `ILIKE` with a wildcard at
+the start. For example, this will not use any indexes:
```sql
SELECT *
@@ -75,9 +64,8 @@ WHERE title ILIKE '%WIP:%';
Because the value for `ILIKE` starts with a wildcard the database is not able to
use an index as it doesn't know where to start scanning the indexes.
-MySQL provides no known solution to this problem. Luckily PostgreSQL _does_
-provide a solution: trigram GIN indexes. These indexes can be created as
-follows:
+Luckily, PostgreSQL _does_ provide a solution: trigram GIN indexes. These
+indexes can be created as follows:
```sql
CREATE INDEX [CONCURRENTLY] index_name_here
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 9d6792e9139..f30a83a4c71 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -15,16 +15,6 @@ manifest themselves within our code. When designing our tests, take time to revi
our test design. We can find some helpful heuristics documented in the Handbook in the
[Test Design](https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/test-design/) section.
-## Run tests against MySQL
-
-By default, tests are only run against PostgreSQL, but you can run them on
-demand against MySQL by following one of the following conventions:
-
-| Convention | Valid example |
-|:----------------------|:-----------------------------|
-| Include `mysql` in your branch name | `enhance-mysql-support` |
-| Include `[run mysql]` in your commit message | `Fix MySQL support<br><br>[run mysql]` |
-
## Test speed
GitLab has a massive test suite that, without [parallelization], can take hours
@@ -455,6 +445,19 @@ complexity of RSpec expectations.They should be placed under
a certain type of specs only (e.g. features, requests etc.) but shouldn't be if
they apply to multiple type of specs.
+#### `be_like_time`
+
+Time returned from a database can differ in precision from time objects
+in Ruby, so we need flexible tolerances when comparing in specs. We can
+use `be_like_time` to compare that times are within one second of each
+other.
+
+Example:
+
+```ruby
+expect(metrics.merged_at).to be_like_time(time)
+```
+
#### `have_gitlab_http_status`
Prefer `have_gitlab_http_status` over `have_http_status` because the former
diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md
index 87d48726268..d9f66a827de 100644
--- a/doc/development/testing_guide/ci.md
+++ b/doc/development/testing_guide/ci.md
@@ -39,7 +39,6 @@ slowest test files and try to improve them.
## CI setup
-- On CE and EE, the test suite runs both PostgreSQL and MySQL.
- Rails logging to `log/test.log` is disabled by default in CI [for
performance reasons][logging]. To override this setting, provide the
`RAILS_ENABLE_TEST_LOG` environment variable.
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index 931cbc51cae..eb0bf6fc563 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -35,8 +35,8 @@ Once a test is in quarantine, there are 3 choices:
Quarantined tests are run on the CI in dedicated jobs that are allowed to fail:
-- `rspec-pg-quarantine` and `rspec-mysql-quarantine` (CE & EE)
-- `rspec-pg-quarantine-ee` and `rspec-mysql-quarantine-ee` (EE only)
+- `rspec-pg-quarantine` (CE & EE)
+- `rspec-pg-quarantine-ee` (EE only)
## Automatic retries and flaky tests detection
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 2985278cc92..ed867c41f59 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -232,7 +232,7 @@ module. GitLab has a custom `spyOnDependency` method which utilizes
[babel-plugin-rewire](https://github.com/speedskater/babel-plugin-rewire) to
achieve this. It can be used like so:
-```js
+```javascript
// my_module.js
import { visitUrl } from '~/lib/utils/url_utility';
@@ -241,7 +241,7 @@ export default function doSomething() {
}
```
-```js
+```javascript
// my_module_spec.js
import doSomething from '~/my_module';
@@ -593,6 +593,365 @@ end
[capybara]: https://github.com/teamcapybara/capybara
[jasmine]: https://jasmine.github.io/
+## Overview of Frontend Testing Levels
+
+Tests relevant for frontend development can be found at the following places:
+
+- `spec/javascripts/` which are run by Karma (command: `yarn karma`) and contain
+ - [frontend unit tests](#frontend-unit-tests)
+ - [frontend component tests](#frontend-component-tests)
+ - [frontend integration tests](#frontend-integration-tests)
+- `spec/frontend/` which are run by Jest (command: `yarn jest`) and contain
+ - [frontend unit tests](#frontend-unit-tests)
+ - [frontend component tests](#frontend-component-tests)
+ - [frontend integration tests](#frontend-integration-tests)
+- `spec/features/` which are run by RSpec and contain
+ - [feature tests](#feature-tests)
+
+All tests in `spec/javascripts/` will eventually be migrated to `spec/frontend/` (see also [#52483](https://gitlab.com/gitlab-org/gitlab-ce/issues/52483)).
+
+In addition, there used to be feature tests in `features/`, run by Spinach.
+These were removed from the codebase in May 2018 ([#23036](https://gitlab.com/gitlab-org/gitlab-ce/issues/23036)).
+
+See also [Notes on testing Vue components](../fe_guide/vue.html#testing-vue-components).
+
+### Frontend unit tests
+
+Unit tests are on the lowest abstraction level and typically test functionality that is not directly perceivable by a user.
+
+#### When to use unit tests
+
+<details>
+ <summary>exported functions and classes</summary>
+ Anything that is exported can be reused at various places in a way you have no control over.
+ Therefore it is necessary to document the expected behavior of the public interface with tests.
+</details>
+
+<details>
+ <summary>Vuex actions</summary>
+ Any Vuex action needs to work in a consistent way independent of the component it is triggered from.
+</details>
+
+<details>
+ <summary>Vuex mutations</summary>
+ For complex Vuex mutations it helps to identify the source of a problem by separating the tests from other parts of the Vuex store.
+</details>
+
+#### When *not* to use unit tests
+
+<details>
+ <summary>non-exported functions or classes</summary>
+ Anything that is not exported from a module can be considered private or an implementation detail and doesn't need to be tested.
+</details>
+
+<details>
+ <summary>constants</summary>
+ Testing the value of a constant would mean to copy it.
+ This results in extra effort without additional confidence that the value is correct.
+</details>
+
+<details>
+ <summary>Vue components</summary>
+ Computed properties, methods, and lifecycle hooks can be considered an implementation detail of components and don't need to be tested.
+ They are implicitly covered by component tests.
+ The <a href="https://vue-test-utils.vuejs.org/guides/#getting-started">official Vue guidelines</a> suggest the same.
+</details>
+
+#### What to mock in unit tests
+
+<details>
+ <summary>state of the class under test</summary>
+ Modifying the state of the class under test directly rather than using methods of the class avoids side-effects in test setup.
+</details>
+
+<details>
+ <summary>other exported classes</summary>
+ Every class needs to be tested in isolation to prevent test scenarios from growing exponentially.
+</details>
+
+<details>
+ <summary>single DOM elements if passed as parameters</summary>
+ For tests that only operate on single DOM elements rather than a whole page, creating these elements is cheaper than loading a whole HTML fixture.
+</details>
+
+<details>
+ <summary>all server requests</summary>
+ When running frontend unit tests, the backend may not be reachable.
+ Therefore all outgoing requests need to be mocked.
+</details>
+
+<details>
+ <summary>asynchronous background operations</summary>
+ Background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
+</details>
+
+#### What *not* to mock in unit tests
+
+<details>
+ <summary>non-exported functions or classes</summary>
+ Everything that is not exported can be considered private to the module and will be implicitly tested via the exported classes / functions.
+</details>
+
+<details>
+ <summary>methods of the class under test</summary>
+ By mocking methods of the class under test, the mocks will be tested and not the real methods.
+</details>
+
+<details>
+ <summary>utility functions (pure functions, or those that only modify parameters)</summary>
+ If a function has no side effects because it has no state, it is safe to not mock it in tests.
+</details>
+
+<details>
+ <summary>full HTML pages</summary>
+ Loading the HTML of a full page slows down tests, so it should be avoided in unit tests.
+</details>
+
+### Frontend component tests
+
+Component tests cover the state of a single component that is perceivable by a user depending on external signals such as user input, events fired from other components, or application state.
+
+#### When to use component tests
+
+- Vue components
+
+#### When *not* to use component tests
+
+<details>
+ <summary>Vue applications</summary>
+ Vue applications may contain many components.
+ Testing them on a component level requires too much effort.
+ Therefore they are tested on frontend integration level.
+</details>
+
+<details>
+ <summary>HAML templates</summary>
+ HAML templates contain only Markup and no frontend-side logic.
+ Therefore they are not complete components.
+</details>
+
+#### What to mock in component tests
+
+<details>
+ <summary>DOM</summary>
+ Operating on the real DOM is significantly slower than on the virtual DOM.
+</details>
+
+<details>
+ <summary>properties and state of the component under test</summary>
+ Similarly to testing classes, modifying the properties directly (rather than relying on methods of the component) avoids side-effects.
+</details>
+
+<details>
+ <summary>Vuex store</summary>
+ To avoid side effects and keep component tests simple, Vuex stores are replaced with mocks.
+</details>
+
+<details>
+ <summary>all server requests</summary>
+ Similar to unit tests, when running component tests, the backend may not be reachable.
+ Therefore all outgoing requests need to be mocked.
+</details>
+
+<details>
+ <summary>asynchronous background operations</summary>
+ Similar to unit tests, background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
+</details>
+
+<details>
+ <summary>child components</summary>
+ Every component is tested individually, so child components are mocked.
+ See also <a href="https://vue-test-utils.vuejs.org/api/#shallowmount">shallowMount()</a>
+</details>
+
+#### What *not* to mock in component tests
+
+<details>
+ <summary>methods or computed properties of the component under test</summary>
+ By mocking part of the component under test, the mocks will be tested and not the real component.
+</details>
+
+<details>
+ <summary>functions and classes independent from Vue</summary>
+ All plain JavaScript code is already covered by unit tests and needs not to be mocked in component tests.
+</details>
+
+### Frontend integration tests
+
+Integration tests cover the interaction between all components on a single page.
+Their abstraction level is comparable to how a user would interact with the UI.
+
+#### When to use integration tests
+
+<details>
+ <summary>page bundles (<code>index.js</code> files in <code>app/assets/javascripts/pages/</code>)</summary>
+ Testing the page bundles ensures the corresponding frontend components integrate well.
+</details>
+
+<details>
+ <summary>Vue applications outside of page bundles</summary>
+ Testing Vue applications as a whole ensures the corresponding frontend components integrate well.
+</details>
+
+#### What to mock in integration tests
+
+<details>
+ <summary>HAML views (use fixtures instead)</summary>
+ Rendering HAML views requires a Rails environment including a running database which we cannot rely on in frontend tests.
+</details>
+
+<details>
+ <summary>all server requests</summary>
+ Similar to unit and component tests, when running component tests, the backend may not be reachable.
+ Therefore all outgoing requests need to be mocked.
+</details>
+
+<details>
+ <summary>asynchronous background operations that are not perceivable on the page</summary>
+ Background operations that affect the page need to be tested on this level.
+ All other background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects.
+</details>
+
+#### What *not* to mock in integration tests
+
+<details>
+ <summary>DOM</summary>
+ Testing on the real DOM ensures our components work in the environment they are meant for.
+ Part of this will be delegated to <a href="https://gitlab.com/gitlab-org/quality/team-tasks/issues/45">cross-browser testing</a>.
+</details>
+
+<details>
+ <summary>properties or state of components</summary>
+ On this level, all tests can only perform actions a user would do.
+ For example to change the state of a component, a click event would be fired.
+</details>
+
+<details>
+ <summary>Vuex stores</summary>
+ When testing the frontend code of a page as a whole, the interaction between Vue components and Vuex stores is covered as well.
+</details>
+
+### Feature tests
+
+In contrast to [frontend integration tests](#frontend-integration-tests), feature tests make requests against the real backend instead of using fixtures.
+This also implies that database queries are executed which makes this category significantly slower.
+
+See also the [RSpec testing guidelines](../testing_guide/best_practices.md#rspec).
+
+#### When to use feature tests
+
+- Use cases that require a backend and cannot be tested using fixtures.
+- Behavior that is not part of a page bundle but defined globally.
+
+#### Relevant notes
+
+A `:js` flag is added to the test to make sure the full environment is loaded.
+
+```ruby
+scenario 'successfully', :js do
+ sign_in(create(:admin))
+end
+```
+
+The steps of each test are written using capybara methods ([documentation](https://www.rubydoc.info/gems/capybara)).
+
+Bear in mind <abbr title="XMLHttpRequest">XHR</abbr> calls might require you to use `wait_for_requests` in between steps, like so:
+
+```ruby
+find('.form-control').native.send_keys(:enter)
+
+wait_for_requests
+
+expect(page).not_to have_selector('.card')
+```
+
+## Test helpers
+
+### Vuex Helper: `testAction`
+
+We have a helper available to make testing actions easier, as per [official documentation](https://vuex.vuejs.org/guide/testing.html):
+
+```javascript
+testAction(
+ actions.actionName, // action
+ { }, // params to be passed to action
+ state, // state
+ [
+ { type: types.MUTATION},
+ { type: types.MUTATION_1, payload: {}},
+ ], // mutations committed
+ [
+ { type: 'actionName', payload: {}},
+ { type: 'actionName1', payload: {}},
+ ] // actions dispatched
+ done,
+);
+```
+
+Check an example in [spec/javascripts/ide/stores/actions_spec.jsspec/javascripts/ide/stores/actions_spec.js](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/javascripts/ide/stores/actions_spec.js).
+
+### Vue Helper: `mountComponent`
+
+To make mounting a Vue component easier and more readable, we have a few helpers available in `spec/helpers/vue_mount_component_helper`:
+
+- `createComponentWithStore`
+- `mountComponentWithStore`
+
+Examples of usage:
+
+```javascript
+beforeEach(() => {
+ vm = createComponentWithStore(Component, store);
+
+ vm.$store.state.currentBranchId = 'master';
+
+ vm.$mount();
+});
+```
+
+```javascript
+beforeEach(() => {
+ vm = mountComponentWithStore(Component, {
+ el: '#dummy-element',
+ store,
+ props: { badge },
+ });
+});
+```
+
+Don't forget to clean up:
+
+```javascript
+afterEach(() => {
+ vm.$destroy();
+});
+```
+
+## Testing with older browsers
+
+Some regressions only affect a specific browser version. We can install and test in particular browsers with either Firefox or Browserstack using the following steps:
+
+### Browserstack
+
+[Browserstack](https://www.browserstack.com/) allows you to test more than 1200 mobile devices and browsers.
+You can use it directly through the [live app](https://www.browserstack.com/live) or you can install the [chrome extension](https://chrome.google.com/webstore/detail/browserstack/nkihdmlheodkdfojglpcjjmioefjahjb) for easy access.
+You can find the credentials on 1Password, under `frontendteam@gitlab.com`.
+
+### Firefox
+
+#### macOS
+
+You can download any older version of Firefox from the releases FTP server, <https://ftp.mozilla.org/pub/firefox/releases/>:
+
+1. From the website, select a version, in this case `50.0.1`.
+1. Go to the mac folder.
+1. Select your preferred language, you will find the dmg package inside, download it.
+1. Drag and drop the application to any other folder but the `Applications` folder.
+1. Rename the application to something like `Firefox_Old`.
+1. Move the application to the `Applications` folder.
+1. Open up a terminal and run `/Applications/Firefox_Old.app/Contents/MacOS/firefox-bin -profilemanager` to create a new profile specific to that Firefox version.
+1. Once the profile has been created, quit the app, and run it again like normal. You now have a working older Firefox version.
+
---
[Return to Testing documentation](index.md)
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index 7843fc4c874..11449712a04 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -255,8 +255,8 @@ that a machine will hit the "too many mount points" problem in the future.
thousands of unused Docker images.**
> We have to start somewhere and improve later. Also, we're using the
- CNG-mirror project to store these Docker images so that we can just wipe out
- the registry at some point, and use a new fresh, empty one.
+ > CNG-mirror project to store these Docker images so that we can just wipe out
+ > the registry at some point, and use a new fresh, empty one.
**How do we secure this from abuse? Apps are open to the world so we need to
find a way to limit it to only us.**
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index e1ce4d3b7d1..0090c84cbf0 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -63,10 +63,9 @@ They're useful to test permissions, redirections, what view is rendered etc.
| Code path | Tests path | Testing engine | Notes |
| --------- | ---------- | -------------- | ----- |
-| `app/controllers/` | `spec/controllers/` | RSpec | |
+| `app/controllers/` | `spec/controllers/` | RSpec | For N+1 tests, use [request specs](../query_recorder.md#use-request-specs-instead-of-controller-specs) |
| `app/mailers/` | `spec/mailers/` | RSpec | |
| `lib/api/` | `spec/requests/api/` | RSpec | |
-| `lib/ci/api/` | `spec/requests/ci/api/` | RSpec | |
| `app/assets/javascripts/` | `spec/javascripts/`, `spec/frontend/` | Karma & Jest | More details in the [Frontend Testing guide](frontend_testing.md) section. |
### About controller tests
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
index ccec6f7d719..6b4995aebe2 100644
--- a/doc/development/verifying_database_capabilities.md
+++ b/doc/development/verifying_database_capabilities.md
@@ -1,15 +1,15 @@
# Verifying Database Capabilities
-Sometimes certain bits of code may only work on a certain database and/or
+Sometimes certain bits of code may only work on a certain database
version. While we try to avoid such code as much as possible sometimes it is
necessary to add database (version) specific behaviour.
To facilitate this we have the following methods that you can use:
-- `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used
-- `Gitlab::Database.mysql?`: returns `true` if MySQL is being used
+- `Gitlab::Database.postgresql?`: returns `true` if PostgreSQL is being used.
+ You can normally just assume this is the case.
- `Gitlab::Database.version`: returns the PostgreSQL version number as a string
- in the format `X.Y.Z`. This method does not work for MySQL
+ in the format `X.Y.Z`.
This allows you to write code such as:
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index f0da1cc2ddc..944bf5900c5 100644
--- a/doc/development/what_requires_downtime.md
+++ b/doc/development/what_requires_downtime.md
@@ -7,9 +7,8 @@ downtime.
## Adding Columns
-On PostgreSQL you can safely add a new column to an existing table as long as it
-does **not** have a default value. For example, this query would not require
-downtime:
+You can safely add a new column to an existing table as long as it does **not**
+have a default value. For example, this query would not require downtime:
```sql
ALTER TABLE projects ADD COLUMN random_value int;
@@ -27,11 +26,6 @@ This requires updating every single row in the `projects` table so that
indexes in a table. This in turn acquires enough locks on the table for it to
effectively block any other queries.
-As of MySQL 5.6 adding a column to a table is still quite an expensive
-operation, even when using `ALGORITHM=INPLACE` and `LOCK=NONE`. This means
-downtime _may_ be required when modifying large tables as otherwise the
-operation could potentially take hours to complete.
-
Adding a column with a default value _can_ be done without requiring downtime
when using the migration helper method
`Gitlab::Database::MigrationHelpers#add_column_with_default`. This method works
@@ -311,8 +305,7 @@ migrations](background_migrations.md#cleaning-up).
## Adding Indexes
Adding indexes is an expensive process that blocks INSERT and UPDATE queries for
-the duration. When using PostgreSQL one can work around this by using the
-`CONCURRENTLY` option:
+the duration. You can work around this by using the `CONCURRENTLY` option:
```sql
CREATE INDEX CONCURRENTLY index_name ON projects (column_name);
@@ -336,17 +329,9 @@ end
Note that `add_concurrent_index` can not be reversed automatically, thus you
need to manually define `up` and `down`.
-When running this on PostgreSQL the `CONCURRENTLY` option mentioned above is
-used. On MySQL this method produces a regular `CREATE INDEX` query.
-
-MySQL doesn't really have a workaround for this. Supposedly it _can_ create
-indexes without the need for downtime but only for variable width columns. The
-details on this are a bit sketchy. Since it's better to be safe than sorry one
-should assume that adding indexes requires downtime on MySQL.
-
## Dropping Indexes
-Dropping an index does not require downtime on both PostgreSQL and MySQL.
+Dropping an index does not require downtime.
## Adding Tables
@@ -370,7 +355,7 @@ transaction this means this approach would require downtime.
GitLab allows you to work around this by using
`Gitlab::Database::MigrationHelpers#add_concurrent_foreign_key`. This method
-ensures that when PostgreSQL is used no downtime is needed.
+ensures that no downtime is needed.
## Removing Foreign Keys
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 1c80fc543af..eee05eaef02 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -333,6 +333,10 @@ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
Enable Elasticsearch search in **Admin > Settings > Integrations**. That's it. Enjoy it!
+### Index limit
+
+Currently for repository and snippet files, GitLab would only index up to 1 MB of content, in order to avoid indexing timeout.
+
## GitLab Elasticsearch Rake Tasks
There are several rake tasks available to you via the command line:
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index 0e5bdcd9c79..c80f2f264b2 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -22,11 +22,12 @@ similarly mitigated by a rate limit.
## Admin Area settings
-See
-[User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
+- [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
+- [Rate limits on raw endpoints](../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
## Rack Attack initializer
This method of rate limiting is cumbersome, but has some advantages. It allows
throttling of specific paths, and is also integrated into Git and container
registry requests. See [Rack Attack initializer](rack_attack.md).
+
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 95220d6364c..4bfcd4aad96 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -584,6 +584,52 @@ Unless you have a `Dockerfile` in your repo, your image is built with
Herokuish, and you must prefix commands run in these images with `/bin/herokuish
procfile exec` to replicate the environment where your application will run.
+#### Workers
+
+Some web applications need to run extra deployments for "worker processes". For
+example it is common in a Rails application to have a separate worker process
+to run background tasks like sending emails.
+
+The [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app)
+used in Auto Deploy [has support for running worker
+processes](https://gitlab.com/gitlab-org/charts/auto-deploy-app/merge_requests/9).
+
+In order to run a worker you'll need to ensure that it is able to respond to
+the standard health checks which expect a successful HTTP response on port
+`5000`. For sidekiq you could make use of the
+[sidekiq_alive gem](https://rubygems.org/gems/sidekiq_alive) to do this.
+
+In order to work with sidekiq you'll also need to ensure your deployments have
+access to a redis instance. Auto DevOps won't deploy this for you so you'll
+need to manage this separately and then set a CI variable
+`K8S_SECRET_REDIS_URL` which the URL of this instance to ensure it's passed
+into your deployments.
+
+Once you have configured your worker to respond to health checks you you will
+need to configure a CI variable `HELM_UPGRADE_EXTRA_ARGS` with the value
+`--values helm-values.yaml`. Then you can, for example, run a
+[sidekiq](https://github.com/mperham/sidekiq) worker for your rails application
+by adding a file named `helm-values.yaml` to your repo with the following
+content:
+
+```yml
+workers:
+ sidekiq:
+ replicaCount: 1
+ command:
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiq
+ preStopCommand:
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiqctl
+ - quiet
+ terminationGracePeriodSeconds: 60
+```
+
### Auto Monitoring
See the [requirements](#requirements) for Auto Monitoring to enable this stage.
@@ -816,7 +862,7 @@ To configure your application variables:
1. Create a CI Variable, ensuring the key is prefixed with
`K8S_SECRET_`. For example, you can create a variable with key
-`K8S_SECRET_RAILS_MASTER_KEY`.
+ `K8S_SECRET_RAILS_MASTER_KEY`.
1. Run an Auto Devops pipeline either by manually creating a new
pipeline or by pushing a code change to GitLab.
@@ -1017,10 +1063,10 @@ Everything behaves the same way, except:
- It's enabled by setting the `INCREMENTAL_ROLLOUT_MODE` variable to `timed`.
- Instead of the standard `production` job, the following jobs with a 5 minute delay between each are created:
- 1. `timed rollout 10%`
- 1. `timed rollout 25%`
- 1. `timed rollout 50%`
- 1. `timed rollout 100%`
+ 1. `timed rollout 10%`
+ 1. `timed rollout 25%`
+ 1. `timed rollout 50%`
+ 1. `timed rollout 100%`
## Currently supported languages
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 423ba1cfbd7..1218465c87a 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -9,12 +9,8 @@ which can be found at [End User Slides](https://gitlab-org.gitlab.io/end-user-tr
through it's [RevealJS](https://gitlab.com/gitlab-org/end-user-training-slides)
project.
----
-
## Git Intro
----
-
### What is a Version Control System (VCS)
- Records changes to a file
@@ -22,8 +18,6 @@ project.
- Disaster Recovery
- Types of VCS: Local, Centralized and Distributed
----
-
### Short Story of Git
- 1991-2002: The Linux kernel was being maintained by sharing archived files
@@ -31,8 +25,6 @@ project.
- 2002: The Linux kernel project began using a DVCS called BitKeeper
- 2005: BitKeeper revoked the free-of-charge status and Git was created
----
-
### What is Git
- Distributed Version Control System
@@ -42,8 +34,6 @@ project.
- Disaster recovery friendly
- Open Source
----
-
### Getting Help
- Use the tools at your disposal when you get stuck.
@@ -51,14 +41,10 @@ project.
- Use Google (i.e. StackOverflow, Google groups)
- Read documentation at <https://git-scm.com>
----
-
## Git Setup
Workshop Time!
----
-
### Setup
- Windows: Install 'Git for Windows'
@@ -69,8 +55,6 @@ Workshop Time!
- Debian: `sudo apt-get install git-all`
- Red Hat `sudo yum install git-all`
----
-
### Configure
- One-time configuration of the Git client:
@@ -91,16 +75,12 @@ git config --global --list
- You might want or be required to use an SSH key.
- Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html)
----
-
### Workspace
- Choose a directory on you machine easy to access
- Create a workspace or development directory
- This is where we'll be working and adding content
----
-
```bash
mkdir ~/development
cd ~/development
@@ -111,12 +91,8 @@ mkdir ~/workspace
cd ~/workspace
```
----
-
## Git Basics
----
-
### Git Workflow
- Untracked files
@@ -128,8 +104,6 @@ cd ~/workspace
- Upstream
- Hosted repository on a shared server
----
-
### GitLab
- GitLab is an application to code, test and deploy.
@@ -137,8 +111,6 @@ cd ~/workspace
issue tracking, Merge Requests, and other features.
- The hosted version of GitLab is gitlab.com
----
-
### New Project
- Sign in into your gitlab.com account
@@ -146,8 +118,6 @@ cd ~/workspace
- Choose to import from 'Any Repo by URL' and use <https://gitlab.com/gitlab-org/training-examples.git>
- On your machine clone the `training-examples` project
----
-
### Git and GitLab basics
1. Edit `edit_this_file.rb` in `training-examples`
@@ -158,8 +128,6 @@ cd ~/workspace
1. Push the commit to the remote
1. View the git log
----
-
```shell
# Edit `edit_this_file.rb`
git status
@@ -170,8 +138,6 @@ git push origin master
git log
```
----
-
### Feature Branching
1. Create a new feature branch called `squash_some_bugs`
@@ -179,8 +145,6 @@ git log
1. Commit
1. Push
----
-
```shell
git checkout -b squash_some_bugs
# Edit `bugs.rb`
@@ -190,14 +154,8 @@ git commit -m 'Fix some buggy code'
git push origin squash_some_bugs
```
----
-
## Merge Request
----
-
-### Merge requests
-
- When you want feedback create a merge request
- Target is the ‘default’ branch (usually master)
- Assign or mention the person you would like to review
@@ -206,8 +164,6 @@ git push origin squash_some_bugs
- Anyone can comment, not just the assignee
- Push corrections to the same branch
----
-
### Merge request example
- Create your first merge request
@@ -216,8 +172,6 @@ git push origin squash_some_bugs
- Push a new commit to the same branch
- Review the changes again and notice the update
----
-
### Feedback and Collaboration
- Merge requests are a time for feedback and collaboration
@@ -230,24 +184,17 @@ git push origin squash_some_bugs
---
-- Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:[Thoughtbot](https://github.com/thoughtbot/guides/tree/master/code-review)
+- Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
+ [Thoughtbot](https://github.com/thoughtbot/guides/tree/master/code-review)
- See GitLab merge requests for examples: [Merge Requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests)
----
-
## Merge Conflicts
----
-
-### Merge Conflicts
-
- Happen often
- Learning to fix conflicts is hard
- Practice makes perfect
- Force push after fixing conflicts. Be careful!
----
-
### Example Plan
1. Checkout a new branch and edit conflicts.rb. Add 'Line4' and 'Line5'.
@@ -261,8 +208,6 @@ git push origin squash_some_bugs
1. Force push the changes
1. Finally continue with the Merge Request
----
-
### Example 1/2
```sh
@@ -282,8 +227,6 @@ git commit -am "add line6 and line7"
git push origin master
```
----
-
### Example 2/2
Create a merge request on the GitLab web UI. You'll see a conflict warning.
@@ -305,8 +248,6 @@ git rebase --continue
git push origin conflicts_branch -f
```
----
-
### Notes
- When to use `git merge` and when to use `git rebase`
@@ -314,12 +255,8 @@ git push origin conflicts_branch -f
- Merge when bringing changes from feature to master
- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
----
-
## Revert and Unstage
----
-
### Unstage
To remove files from stage use reset HEAD. Where HEAD is the last commit of the current branch:
@@ -347,8 +284,6 @@ If we want to remove a file from the repository but keep it on disk, say we forg
git rm <filename> --cache
```
----
-
### Undo Commits
Undo last commit putting everything back into the staging area:
@@ -377,8 +312,6 @@ git reset --hard HEAD^^
Don't reset after pushing
----
-
### Reset Workflow
1. Edit file again 'edit_this_file.rb'
@@ -392,8 +325,6 @@ Don't reset after pushing
1. Pull for updates
1. Push changes
----
-
```sh
# Change file edit_this_file.rb
git status
@@ -407,8 +338,6 @@ git pull origin master
git push origin master
```
----
-
### git revert vs git reset
Reset removes the commit while revert removes the changes but leaves the commit
@@ -425,16 +354,10 @@ git revert <rev commit hash>
# reverted commit is back (new commit created again)
```
----
-
## Questions
----
-
## Instructor Notes
----
-
### Version Control
- Local VCS was used with a filesystem or a simple db.
diff --git a/doc/university/training/topics/bisect.md b/doc/university/training/topics/bisect.md
index 4178afa2086..24dc670d9d5 100644
--- a/doc/university/training/topics/bisect.md
+++ b/doc/university/training/topics/bisect.md
@@ -4,13 +4,11 @@ comments: false
# Bisect
-## Bisect
-
- Find a commit that introduced a bug
- Works through a process of elimination
- Specify a known good and bad revision to begin
-## Bisect
+## Bisect sample workflow
1. Start the bisect process
1. Enter the bad revision (usually latest commit)
diff --git a/doc/university/training/topics/cherry_picking.md b/doc/university/training/topics/cherry_picking.md
index fa0cb5fe6a4..f5bcdfcbf12 100644
--- a/doc/university/training/topics/cherry_picking.md
+++ b/doc/university/training/topics/cherry_picking.md
@@ -4,13 +4,11 @@ comments: false
# Cherry Pick
-## Cherry Pick
-
- Given an existing commit on one branch, apply the change to another branch
- Useful for backporting bug fixes to previous release branches
- Make the commit on the master branch and pick in to stable
-## Cherry Pick
+## Cherry Pick sample workflow
1. Check out a new 'stable' branch from 'master'
1. Change back to 'master'
@@ -19,8 +17,6 @@ comments: false
1. Check out the 'stable' branch
1. Cherry pick the commit using the SHA obtained earlier
-## Commands
-
```bash
git checkout master
git checkout -b stable
diff --git a/doc/university/training/topics/feature_branching.md b/doc/university/training/topics/feature_branching.md
index d2efe634533..f530389d4da 100644
--- a/doc/university/training/topics/feature_branching.md
+++ b/doc/university/training/topics/feature_branching.md
@@ -11,15 +11,13 @@ comments: false
- Push branches to the server frequently
- Hint: This is a cheap backup for your work-in-progress code
-## Feature branching
+## Feature branching sample workflow
1. Create a new feature branch called 'squash_some_bugs'
1. Edit '`bugs.rb`' and remove all the bugs.
1. Commit
1. Push
-## Commands
-
```sh
git checkout -b squash_some_bugs
# Edit `bugs.rb`
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index e8ff7916590..3fadb58e804 100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
@@ -35,8 +35,6 @@ comments: false
1. Create a '`Workspace`' directory in your home directory.
1. Clone the '`training-examples`' project.
-## Commands
-
```sh
mkdir ~/workspace
cd ~/workspace
@@ -69,8 +67,6 @@ Modified files that have been marked to go in the next commit.
1. Push the commit to the remote
1. View the git log
-## Commands
-
```sh
# Edit `edit_this_file.rb`
git status
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index 7152fc2030b..0c9a50bb5e1 100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
@@ -4,8 +4,6 @@ comments: false
# Git Add
-## Git Add
-
Adds content to the index or staging area.
- Adds a list of file:
@@ -20,8 +18,6 @@ Adds content to the index or staging area.
git add -A
```
-## Git add continued
-
- Add all text files in current dir:
```bash
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index dd235fe3a81..97bb038f405 100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
@@ -9,7 +9,7 @@ comments: false
- Practice makes perfect
- Force push after fixing conflicts. Be careful!
-## Merge conflicts
+## Merge conflicts sample workflow
1. Checkout a new branch and edit `conflicts.rb`. Add 'Line4' and 'Line5'.
1. Commit and push.
@@ -22,8 +22,6 @@ comments: false
1. Force push the changes.
1. Finally continue with the Merge Request.
-## Commands
-
```sh
git checkout -b conflicts_branch
diff --git a/doc/university/training/topics/merge_requests.md b/doc/university/training/topics/merge_requests.md
index b5bbe7b2e1e..656871ae5b2 100644
--- a/doc/university/training/topics/merge_requests.md
+++ b/doc/university/training/topics/merge_requests.md
@@ -30,8 +30,6 @@ comments: false
- Be as receptive as possible
- Feedback is about the best code, not the person. You are not your code
-## Feedback and Collaboration
-
Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
[https://github.com/thoughtbot/guides/tree/master/code-review](https://github.com/thoughtbot/guides/tree/master/code-review)
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index 21abad88cfa..d3e63db0c6a 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -25,7 +25,7 @@ and we need to change to a different branch.
git stash apply stash@{3}
```
-- Every time we save a stash it gets stacked so by using list we can see all our
+- Every time we save a stash it gets stacked so by using `list` we can see all our
stashes.
```sh
@@ -54,7 +54,7 @@ and we need to change to a different branch.
- If we meet conflicts we need to either reset or commit our changes.
- Conflicts through `pop` will not drop a stash afterwards.
-## Git Stash
+## Git Stash sample workflow
1. Modify a file
1. Stage file
@@ -64,8 +64,6 @@ and we need to change to a different branch.
1. Apply with pop
1. View list to confirm changes
-## Commands
-
```sh
# Modify edit_this_file.rb file
git add .
diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md
index cdbb8a2da7c..631b93cc384 100644
--- a/doc/university/training/topics/tags.md
+++ b/doc/university/training/topics/tags.md
@@ -11,18 +11,12 @@ type: reference
- Many projects combine an annotated release tag with a stable branch
- Consider setting deployment/release tags automatically
-# Tags
+## Tags sample workflow
- Create a lightweight tag
- Create an annotated tag
- Push the tags to the remote repository
-**Additional resources**
-
-<https://git-scm.com/book/en/Git-Basics-Tagging>
-
-# Commands
-
```sh
git checkout master
@@ -36,6 +30,10 @@ git tag
git push origin --tags
```
+**Additional resources**
+
+<https://git-scm.com/book/en/Git-Basics-Tagging>
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index fa1f63f9ec4..d7482bf2bd5 100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
@@ -4,8 +4,6 @@ comments: false
# Unstage
-## Unstage
-
- To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This will unstage the file but maintain the modifications.
```bash
diff --git a/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png b/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png
new file mode 100644
index 00000000000..c32eb93c8a8
--- /dev/null
+++ b/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png
Binary files differ
diff --git a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
new file mode 100644
index 00000000000..8e53a6995fb
--- /dev/null
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -0,0 +1,22 @@
+---
+type: reference
+---
+
+# Rate limits on raw endpoints **(CORE ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30829) in GitLab 12.2.
+
+This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
+It can be modified in **Admin Area > Network > Performance Optimization**.
+
+For example, requests over `300` per minute to `https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/controllers/application_controller.rb` will be blocked. Access to the raw file will be released after 1 minute.
+
+![Rate limits on raw endpoints](img/rate_limits_on_raw_endpoints.png)
+
+This limit is:
+
+- Applied independently per project, per commit and per file path.
+- Not applied per IP address.
+- Active by default. To disable, set the option to `0`.
+
+Requests over the rate limit are logged into `auth.log`.
diff --git a/doc/user/analytics/cycle_analytics.md b/doc/user/analytics/cycle_analytics.md
new file mode 100644
index 00000000000..b7389c8689d
--- /dev/null
+++ b/doc/user/analytics/cycle_analytics.md
@@ -0,0 +1,182 @@
+# Cycle Analytics
+
+> - Introduced prior to GitLab 12.2 at the project level.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12077) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2 at the group level (enabled by feature flag `analytics`).
+
+Cycle Analytics measures the time spent to go from an [idea to production] - also known
+as cycle time - for each of your projects. Cycle Analytics displays the median time for an idea to
+reach production, along with the time typically spent in each DevOps stage along the way.
+
+Cycle Analytics is useful in order to quickly determine the velocity of a given
+project. It points to bottlenecks in the development process, enabling management
+to uncover, triage, and identify the root cause of slowdowns in the software development life cycle.
+
+Cycle Analytics is tightly coupled with the [GitLab flow] and
+calculates a separate median for each stage.
+
+## Overview
+
+Cycle Analytics is available:
+
+- From GitLab 12.2, at the group level in the analytics workspace at
+ **Analytics > Cycle Analytics**. **(PREMIUM)**
+
+ In the future, multiple groups will be selectable which will effectively make this an
+ instance-level feature.
+
+ NOTE: **Note:**
+ Requires the [analytics workspace](index.md) to be enabled.
+
+- At the project level via **Project > Cycle Analytics**.
+
+There are seven stages that are tracked as part of the Cycle Analytics calculations.
+
+- **Issue** (Tracker)
+ - Time to schedule an issue (by milestone or by adding it to an issue board)
+- **Plan** (Board)
+ - Time to first commit
+- **Code** (IDE)
+ - Time to create a merge request
+- **Test** (CI)
+ - Time it takes GitLab CI/CD to test your code
+- **Review** (Merge Request/MR)
+ - Time spent on code review
+- **Staging** (Continuous Deployment)
+ - Time between merging and deploying to production
+- **Production** (Total)
+ - Total lifecycle time; i.e. the velocity of the project or team
+
+## How the data is measured
+
+Cycle Analytics records cycle time and data based on the project issues with the
+exception of the staging and production stages, where only data deployed to
+production are measured.
+
+Specifically, if your CI is not set up and you have not defined a `production`
+or `production/*` [environment], then you will not have any data for those stages.
+
+Each stage of Cycle Analytics is further described in the table below.
+
+| **Stage** | **Description** |
+| --------- | --------------- |
+| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list](../project/issue_board.md#creating-a-new-list) created for it. |
+| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
+| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically) to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. |
+| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |
+| Review | Measures the median time taken to review the merge request that has closing issue pattern, between its creation and until it's merged. |
+| Staging | Measures the median time between merging the merge request with closing issue pattern until the very first deployment to production. It's tracked by the [environment] set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
+| Production| The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. |
+
+---
+
+How this works, behind the scenes:
+
+1. Issues and merge requests are grouped together in pairs, such that for each
+ `<issue, merge request>` pair, the merge request has the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
+ for the corresponding issue. All other issues and merge requests are **not**
+ considered.
+1. Then the `<issue, merge request>` pairs are filtered out by last XX days (specified
+ by the UI - default is 90 days). So it prohibits these pairs from being considered.
+1. For the remaining `<issue, merge request>` pairs, we check the information that
+ we need for the stages, like issue creation date, merge request merge time,
+ etc.
+
+To sum up, anything that doesn't follow [GitLab flow] will not be tracked and the
+Cycle Analytics dashboard will not present any data for:
+
+- merge requests that do not close an issue.
+- issues not labeled with a label present in the Issue Board or for issues not assigned a milestone.
+- staging and production stages, if the project has no `production` or `production/*`
+ environment.
+
+## Example workflow
+
+Below is a simple fictional workflow of a single cycle that happens in a
+single day passing through all seven stages. Note that if a stage does not have
+a start and a stop mark, it is not measured and hence not calculated in the median
+time. It is assumed that milestones are created and CI for testing and setting
+environments is configured.
+
+1. Issue is created at 09:00 (start of **Issue** stage).
+1. Issue is added to a milestone at 11:00 (stop of **Issue** stage / start of
+ **Plan** stage).
+1. Start working on the issue, create a branch locally and make one commit at
+ 12:00.
+1. Make a second commit to the branch which mentions the issue number at 12.30
+ (stop of **Plan** stage / start of **Code** stage).
+1. Push branch and create a merge request that contains the [issue closing pattern](../project/issues/managing_issues.md#closing-issues-automatically)
+ in its description at 14:00 (stop of **Code** stage / start of **Test** and
+ **Review** stages).
+1. The CI starts running your scripts defined in [`.gitlab-ci.yml`][yml] and
+ takes 5min (stop of **Test** stage).
+1. Review merge request, ensure that everything is OK and merge the merge
+ request at 19:00. (stop of **Review** stage / start of **Staging** stage).
+1. Now that the merge request is merged, a deployment to the `production`
+ environment starts and finishes at 19:30 (stop of **Staging** stage).
+1. The cycle completes and the sum of the median times of the previous stages
+ is recorded to the **Production** stage. That is the time between creating an
+ issue and deploying its relevant merge request to production.
+
+From the above example you can conclude the time it took each stage to complete
+as long as their total time:
+
+- **Issue**: 2h (11:00 - 09:00)
+- **Plan**: 1h (12:00 - 11:00)
+- **Code**: 2h (14:00 - 12:00)
+- **Test**: 5min
+- **Review**: 5h (19:00 - 14:00)
+- **Staging**: 30min (19:30 - 19:00)
+- **Production**: Since this stage measures the sum of median time off all
+ previous stages, we cannot calculate it if we don't know the status of the
+ stages before. In case this is the very first cycle that is run in the project,
+ then the **Production** time is 10h 30min (19:30 - 09:00)
+
+A few notes:
+
+- In the above example we demonstrated that it doesn't matter if your first
+ commit doesn't mention the issue number, you can do this later in any commit
+ of the branch you are working on.
+- You can see that the **Test** stage is not calculated to the overall time of
+ the cycle since it is included in the **Review** process (every MR should be
+ tested).
+- The example above was just **one cycle** of the seven stages. Add multiple
+ cycles, calculate their median time and the result is what the dashboard of
+ Cycle Analytics is showing.
+
+## Permissions
+
+The current permissions on the Project Cycle Analytics dashboard are:
+
+- Public projects - anyone can access
+- Internal projects - any authenticated user can access
+- Private projects - any member Guest and above can access
+
+You can [read more about permissions][permissions] in general.
+
+NOTE: **Note:**
+As of GitLab 12.2, the project-level page is deprecated. You should access
+project-level Cycle Analytics from **Analytics > Cycle Analytics** in the top
+navigation bar. We will ensure that the same project-level functionality is available
+to CE users in the new analytics space.
+
+For Cycle Analytics functionality introduced in GitLab 12.2 and later:
+
+- Users must have Reporter access or above.
+- Features are available only on
+ [Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
+
+## More resources
+
+Learn more about Cycle Analytics in the following resources:
+
+- [Cycle Analytics feature page](https://about.gitlab.com/features/cycle-analytics/)
+- [Cycle Analytics feature preview](https://about.gitlab.com/2016/09/16/feature-preview-introducing-cycle-analytics/)
+- [Cycle Analytics feature highlight](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/)
+
+[ce-5986]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5986
+[ce-20975]: https://gitlab.com/gitlab-org/gitlab-ce/issues/20975
+[environment]: ../../ci/yaml/README.md#environment
+[GitLab flow]: ../../workflow/gitlab_flow.md
+[idea to production]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab
+[permissions]: ../permissions.md
+[yml]: ../../ci/yaml/README.md
diff --git a/doc/user/analytics/index.md b/doc/user/analytics/index.md
new file mode 100644
index 00000000000..ec719c0b4a1
--- /dev/null
+++ b/doc/user/analytics/index.md
@@ -0,0 +1,22 @@
+# Analytics workspace
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12077) in GitLab 12.2 (enabled using `analytics` feature flag).
+
+The Analytics workspace will make it possible to aggregate analytics across
+GitLab, so that users can view information across multiple projects and groups
+in one place.
+
+To access the centralized analytics workspace:
+
+1. Ensure it's enabled. Requires a GitLab administrator to enable it with the `analytics` feature
+ flag.
+1. Once enabled, click on **Analytics** from the top navigation bar.
+
+## Available analytics
+
+From the centralized analytics workspace, the following analytics are available:
+
+- [Cycle Analytics](cycle_analytics.md).
+
+NOTE: **Note:**
+Project-level Cycle Analytics are still available at a project's **Project > Cycle Analytics**.
diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md
deleted file mode 100644
index db3e37bd0fb..00000000000
--- a/doc/user/analytics/productivity_analytics.md
+++ /dev/null
@@ -1,69 +0,0 @@
-# Productivity Analytics **(PREMIUM)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2 (enabled by feature flags `analytics` and `productivity_analytics`).
-
-Track development velocity with Productivity Analytics.
-
-For many companies, the development cycle is a blackbox and getting an estimate of how
-long, on average, it takes to deliver features is an enormous endeavor.
-
-While [Cycle Analytics](../project/cycle_analytics.md) focuses on the entire
-SDLC process, Productivity Analytics provides a way for Engineering Management to
-drill down in a systematic way to uncover patterns and causes for success or failure at
-an individual, project or group level.
-
-Productivity can slow down for many reasons ranging from degrading code base to quickly
-growing teams. In order to investigate, department or team leaders can start by visualizing the time
-it takes for merge requests to be merged.
-
-## Supported features
-
-Productivity Analytics allows GitLab users to:
-
-- Visualize typical Merge Request lifetime and statistics. Use a histogram
- that shows the distribution of the time elapsed between creating and merging
- Merge Requests.
-- Drill down into the most time consuming Merge Requests, select a number of outliers,
- and filter down all subsequent charts to investigate potential causes.
-- Filter by group, project, author, label, milestone, or a specific date range.
- Filter down, for example, to the Merge Requests of a specific author in a group
- or project during a milestone or specific date range.
-
-## Accessing metrics and visualizations
-
-To access the **Productivity Analytics** page, go to **Analytics > Productivity Analytics**.
-
-The following metrics and visualizations are available on a project or group level:
-
-- Histogram showing the number of Merge Request that took a specified number of days to
- merge after creation. Select a specific column to filter down subsequent charts.
-- Histogram showing a breakdown of the time taken (in hours) to merge a Merge Request.
- The following intervals are available:
- - Time from first commit to first comment.
- - Time from first comment until last commit.
- - Time from last commit to merge.
-- Histogram showing the size or complexity of a Merge Request, using the following:
- - Number of commits per Merge Request.
- - Number of lines of code per commit.
- - Number of files touched.
-- Table showing list of Merge Requests with their respective times and size metrics.
- Can be sorted by the above metrics.
- - Users can sort by any of the above metrics
-
-## Retrieving data
-
-Users can retrieve three months of data when they deploy Productivity Analytics for the first time.
-
-To retrieve data for a different time span, run the following in the GitLab directory:
-
-```sh
-MERGED_AT_AFTER = <your_date> rake gitlab:productivity_analytics:recalc
-```
-
-## Permissions
-
-The **Productivity Analytics** dashboard can be accessed only:
-
-- On GitLab instances and namespaces on
- [Premium or Silver tier](https://about.gitlab.com/pricing/) and above.
-- By users with [Reporter access](../permissions.md) and above.
diff --git a/doc/user/asciidoc.md b/doc/user/asciidoc.md
index df86b2a1cbe..862316b57da 100644
--- a/doc/user/asciidoc.md
+++ b/doc/user/asciidoc.md
@@ -277,11 +277,11 @@ source - a listing that is embellished with (colorized) syntax highlighting
----
```
-````asciidoc
+~~~asciidoc
\```language
fenced code - a shorthand syntax for the source block
\```
-````
+~~~
```asciidoc
[,attribution,citetitle]
diff --git a/doc/user/discussions/img/make_suggestion.png b/doc/user/discussions/img/make_suggestion.png
index 20acc1417da..a24e29770aa 100644
--- a/doc/user/discussions/img/make_suggestion.png
+++ b/doc/user/discussions/img/make_suggestion.png
Binary files differ
diff --git a/doc/user/discussions/img/suggestion.png b/doc/user/discussions/img/suggestion.png
index 68a67e6ae5e..f7962305a15 100644
--- a/doc/user/discussions/img/suggestion.png
+++ b/doc/user/discussions/img/suggestion.png
Binary files differ
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index c9fbd7effa0..d21a325d401 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -112,57 +112,6 @@ Below are the shared Runners settings.
The full contents of our `config.toml` are:
-**DigitalOcean**
-
-```toml
-concurrent = X
-check_interval = 1
-metrics_server = "X"
-sentry_dsn = "X"
-
-[[runners]]
- name = "docker-auto-scale"
- request_concurrency = X
- url = "https://gitlab.com/"
- token = "SHARED_RUNNER_TOKEN"
- executor = "docker+machine"
- environment = [
- "DOCKER_DRIVER=overlay2"
- ]
- limit = X
- [runners.docker]
- image = "ruby:2.5"
- privileged = true
- [runners.machine]
- IdleCount = 20
- IdleTime = 1800
- OffPeakPeriods = ["* * * * * sat,sun *"]
- OffPeakTimezone = "UTC"
- OffPeakIdleCount = 5
- OffPeakIdleTime = 1800
- MaxBuilds = 1
- MachineName = "srm-%s"
- MachineDriver = "digitalocean"
- MachineOptions = [
- "digitalocean-image=X",
- "digitalocean-ssh-user=core",
- "digitalocean-region=nyc1",
- "digitalocean-size=s-2vcpu-2gb",
- "digitalocean-private-networking",
- "digitalocean-tags=shared_runners,gitlab_com",
- "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR",
- "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN",
- ]
- [runners.cache]
- Type = "s3"
- BucketName = "runner"
- Insecure = true
- Shared = true
- ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
- AccessKey = "ACCESS_KEY"
- SecretKey = "ACCESS_SECRET_KEY"
-```
-
**Google Cloud Platform**
```toml
@@ -178,20 +127,25 @@ sentry_dsn = "X"
token = "SHARED_RUNNER_TOKEN"
executor = "docker+machine"
environment = [
- "DOCKER_DRIVER=overlay2"
+ "DOCKER_DRIVER=overlay2",
+ "DOCKER_TLS_CERTDIR="
]
limit = X
[runners.docker]
image = "ruby:2.5"
privileged = true
+ volumes = [
+ "/certs/client",
+ "/dummy-sys-class-dmi-id:/sys/class/dmi/id:ro" # Make kaniko builds work on GCP.
+ ]
[runners.machine]
- IdleCount = 20
- IdleTime = 1800
+ IdleCount = 50
+ IdleTime = 3600
OffPeakPeriods = ["* * * * * sat,sun *"]
OffPeakTimezone = "UTC"
- OffPeakIdleCount = 5
- OffPeakIdleTime = 1800
- MaxBuilds = 1
+ OffPeakIdleCount = 15
+ OffPeakIdleTime = 3600
+ MaxBuilds = 1 # For security reasons we delete the VM after job has finished so it's not reused.
MachineName = "srm-%s"
MachineDriver = "google"
MachineOptions = [
@@ -202,17 +156,18 @@ sentry_dsn = "X"
"google-tags=gitlab-com,srm",
"google-use-internal-ip",
"google-zone=us-east1-d",
+ "engine-opt=mtu=1460", # Set MTU for container interface, for more information check https://gitlab.com/gitlab-org/gitlab-runner/issues/3214#note_82892928
"google-machine-image=PROJECT/global/images/IMAGE",
- "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR"
+ "engine-opt=ipv6", # This will create IPv6 interfaces in the containers.
+ "engine-opt=fixed-cidr-v6=fc00::/7",
+ "google-operation-backoff-initial-interval=2" # Custom flag from forked docker-machine, for more information check https://github.com/docker/machine/pull/4600
]
[runners.cache]
- Type = "s3"
- BucketName = "runner"
- Insecure = true
+ Type = "gcs"
Shared = true
- ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
- AccessKey = "ACCESS_KEY"
- SecretKey = "ACCESS_SECRET_KEY"
+ [runners.cache.gcs]
+ CredentialsFile = "/path/to/file"
+ BucketName = "bucket-name"
```
## Sidekiq
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 43fd0bfd45a..d1d4f3740b0 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -350,6 +350,38 @@ Restriction currently applies to UI, API access is not restricted.
To avoid accidental lock-out, admins and group owners are are able to access
the group regardless of the IP restriction.
+#### Allowed domain restriction **(PREMIUM ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7297) in
+[GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+
+You can restrict access to groups and their underlying projects by
+allowing only users with email addresses in particular domains to be added to the group.
+
+Add email domains you want to whitelist and users with emails from different
+domains won't be allowed to be added to this group.
+
+Some domains cannot be restricted. These are the most popular public email domains, such as:
+
+- `gmail.com`
+- `yahoo.com`
+- `hotmail.com`
+- `aol.com`
+- `msn.com`
+- `hotmail.co.uk`
+- `hotmail.fr`
+- `live.com`
+- `outlook.com`
+- `icloud.com`
+
+To enable this feature:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and enter domain name into **Restrict membership by email** field.
+1. Click **Save changes**.
+
+This will enable the domain-checking for all new users added to the group from this moment on.
+
#### Group file templates **(PREMIUM)**
Group file templates allow you to share a set of templates for common file
@@ -379,6 +411,17 @@ To enable this feature, navigate to the group settings page, expand the
Define project templates at a group level by setting a group as the template source.
[Learn more about group-level project templates](custom_project_templates.md).
+#### Disabling email notifications
+
+You can disable all email notifications related to the group, which also includes
+it's subgroups and projects.
+
+To enable this feature:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
+1. Click **Save changes**.
+
### Advanced settings
- **Projects**: View all projects within that group, add members to each project,
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index f8bef8b8a6a..5d136ad62da 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -59,15 +59,14 @@ Once [Single sign-on](index.md) has been configured, we can:
### Azure
-First, double check the [Single sign-on](index.md) configuration for your group and ensure that **Name identifier value** (NameID) points to `user.objectid` or another unique identifier. This will match the `extern_uid` used on GitLab.
+The SAML application that was created during [Single sign-on](index.md) setup now needs to be set up for SCIM.
-![Name identifier value mapping](img/scim_name_identifier_mapping.png)
+1. Check the configuration for your GitLab SAML app and ensure that **Name identifier value** (NameID) points to `user.objectid` or another unique identifier. This will match the `extern_uid` used on GitLab.
-#### Set up admin credentials
+ ![Name identifier value mapping](img/scim_name_identifier_mapping.png)
-Next, configure your GitLab application in Azure by following the
-[Provisioning users and groups to applications that support SCIM](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#provisioning-users-and-groups-to-applications-that-support-scim)
-section in Azure's SCIM setup documentation.
+1. Set up automatic provisioning and administrative credentials by following the
+ [Provisioning users and groups to applications that support SCIM](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#provisioning-users-and-groups-to-applications-that-support-scim) section in Azure's SCIM setup documentation.
During this configuration, note the following:
@@ -97,6 +96,7 @@ You can then test the connection by clicking on **Test Connection**. If the conn
NOTE: **Note:** If you used a unique identifier **other than** `objectId`, be sure to map it instead to both `id` and `externalId`.
1. Below the mapping list click on **Show advanced options > Edit attribute list for AppName**.
+
1. Leave the `id` as the primary and only required field.
NOTE: **Note:**
@@ -129,8 +129,7 @@ When testing the connection, you may encounter an error: **You appear to have en
When checking the Audit Logs for the Provisioning, you can sometimes see the
error `Namespace can't be blank, Name can't be blank, and User can't be blank.`
-This is likely caused because not all required fields (such as first name and
-last name) are present for all users being mapped.
+This is likely caused because not all required fields (such as first name and last name) are present for all users being mapped.
As a workaround, try an alternate mapping:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 16684b9f72b..9ecc8a80b3a 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -126,6 +126,7 @@ The following table depicts the various user permission levels in a project.
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Delete issues | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | |
@@ -161,7 +162,7 @@ to learn more.
### Cycle Analytics permissions
Find the current permissions on the Cycle Analytics dashboard on
-the [documentation on Cycle Analytics permissions](project/cycle_analytics.md#permissions).
+the [documentation on Cycle Analytics permissions](analytics/cycle_analytics.md#permissions).
### Issue Board permissions
@@ -220,6 +221,7 @@ group.
| Remove group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ |
| View group Audit Events | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
- (1): Groups can be set to [allow either Owners or Owners and
Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
@@ -235,13 +237,16 @@ To learn more, read through the documentation on
## Guest User
-Create a user and assign to a project with a role as `Guest` user, this user
-will be considered as guest user by GitLab and will not take up the license.
-There is no specific `Guest` role for newly created users. If this user will
-be assigned a higher role to any of the projects and groups then this user will
-take a license seat. If a user creates a project this user becomes a maintainer,
-therefore, takes up a license seat as well, in order to prevent this you have
-to go and edit user profile and mark the user as External.
+When a user is given `Guest` permissions on a project and/or group, and holds no
+higher permission level on any other project or group on the instance, the user
+is considered a guest user by GitLab and will not consume a license seat.
+There is no other specific "guest" designation for newly created users.
+
+If the user is assigned a higher role on any projects or groups, the user will
+take a license seat. If a user creates a project, the user becomes a `Maintainer`
+on the project, resulting in the use of a license seat. To prevent a guest user
+from creating projects, you can edit the user profile to mark the user as
+[External](#external-users-permissions).
## External users permissions
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index 5bd4b263a58..82a6d2b3703 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -87,6 +87,16 @@ You have 8 options here that you can use for your default dashboard view:
- Assigned Merge Requests
- Operations Dashboard **(PREMIUM)**
+### Group overview content
+
+The **Group overview content** dropdown allows you to choose what information is
+displayed on a group’s home page.
+
+You can choose between 2 options:
+
+- Details (default)
+- [Security dashboard](../application_security/security_dashboard/index.md) **(ULTIMATE)**
+
### Project overview content
The project overview content setting allows you to choose what content you want to
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index ffaa07cb3a4..cf3a3fef79f 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -173,6 +173,9 @@ Starting from [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/issues/55902
### Add existing Kubernetes cluster
+NOTE: **Note:**
+Kubernetes integration is not supported for arm64 clusters. See the issue [Helm Tiller fails to install on arm64 cluster](https://gitlab.com/gitlab-org/gitlab-ce/issues/64044) for details.
+
To add an existing Kubernetes cluster to your project:
1. Navigate to your project's **Operations > Kubernetes** page.
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 424bee6e9f1..87577c9ec88 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -1,181 +1,5 @@
-# Cycle Analytics
-
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12077) at a group level in [GitLab Premium and Silver](https://about.gitlab.com/pricing/) 12.2 (enabled by feature flag `analytics`).
-
-Cycle Analytics measures the time spent to go from an [idea to production] - also known
-as cycle time - for each of your projects. Cycle Analytics displays the median time for an idea to
-reach production, along with the time typically spent in each DevOps stage along the way.
-
-Cycle Analytics is useful in order to quickly determine the velocity of a given
-project. It points to bottlenecks in the development process, enabling management
-to uncover, triage, and root-cause slowdowns in the software development life cycle.
-
-Cycle Analytics is tightly coupled with the [GitLab flow] and
-calculates a separate median for each stage.
-
-## Overview
-
-Cycle Analytics are available at a:
-
-- Group level from the top navigation bar **Analytics > Cycle Analytics**. **(PREMIUM)**
-
- In the future, multiple groups will be selectable which will effectively make this an
- instance-level feature.
-
-- Project level from a project's **Project > Cycle Analytics**.
-
- ![Cycle Analytics landing page](img/cycle_analytics_landing_page.png)
-
-There are seven stages that are tracked as part of the Cycle Analytics calculations.
-
-- **Issue** (Tracker)
- - Time to schedule an issue (by milestone or by adding it to an issue board)
-- **Plan** (Board)
- - Time to first commit
-- **Code** (IDE)
- - Time to create a merge request
-- **Test** (CI)
- - Time it takes GitLab CI/CD to test your code
-- **Review** (Merge Request/MR)
- - Time spent on code review
-- **Staging** (Continuous Deployment)
- - Time between merging and deploying to production
-- **Production** (Total)
- - Total lifecycle time; i.e. the velocity of the project or team
-
-## How the data is measured
-
-Cycle Analytics records cycle time and data based on the project issues with the
-exception of the staging and production stages, where only data deployed to
-production are measured.
-
-Specifically, if your CI is not set up and you have not defined a `production`
-or `production/*` [environment], then you will not have any data for those stages.
-
-Below you can see in more detail what the various stages of Cycle Analytics mean.
-
-| **Stage** | **Description** |
-| --------- | --------------- |
-| Issue | Measures the median time between creating an issue and taking action to solve it, by either labeling it or adding it to a milestone, whatever comes first. The label will be tracked only if it already has an [Issue Board list][board] created for it. |
-| Plan | Measures the median time between the action you took for the previous stage, and pushing the first commit to the branch. The very first commit of the branch is the one that triggers the separation between **Plan** and **Code**, and at least one of the commits in the branch needs to contain the related issue number (e.g., `#42`). If none of the commits in the branch mention the related issue number, it is not considered to the measurement time of the stage. |
-| Code | Measures the median time between pushing a first commit (previous stage) and creating a merge request (MR) related to that commit. The key to keep the process tracked is to include the [issue closing pattern] to the description of the merge request (for example, `Closes #xxx`, where `xxx` is the number of the issue related to this merge request). If the issue closing pattern is not present in the merge request description, the MR is not considered to the measurement time of the stage. |
-| Test | Measures the median time to run the entire pipeline for that project. It's related to the time GitLab CI takes to run every job for the commits pushed to that merge request defined in the previous stage. It is basically the start->finish time for all pipelines. |
-| Review | Measures the median time taken to review the merge request that has closing issue pattern, between its creation and until it's merged. |
-| Staging | Measures the median time between merging the merge request with closing issue pattern until the very first deployment to production. It's tracked by the [environment] set to `production` or matching `production/*` (case-sensitive, `Production` won't work) in your GitLab CI configuration. If there isn't a production environment, this is not tracked. |
-| Production| The sum of all time (medians) taken to run the entire process, from issue creation to deploying the code to production. |
-
+---
+redirect_to: '../analytics/cycle_analytics.md'
---
-Here's a little explanation of how this works behind the scenes:
-
-1. Issues and merge requests are grouped together in pairs, such that for each
- `<issue, merge request>` pair, the merge request has the [issue closing pattern]
- for the corresponding issue. All other issues and merge requests are **not**
- considered.
-1. Then the `<issue, merge request>` pairs are filtered out by last XX days (specified
- by the UI - default is 90 days). So it prohibits these pairs from being considered.
-1. For the remaining `<issue, merge request>` pairs, we check the information that
- we need for the stages, like issue creation date, merge request merge time,
- etc.
-
-To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all.
-So, the Cycle Analytics dashboard won't present any data:
-
-- For merge requests that do not close an issue.
-- For issues not labeled with a label present in the Issue Board or for issues not assigned a milestone.
-- For staging and production stages, if the project has no `production` or `production/*`
- environment.
-
-## Example workflow
-
-Below is a simple fictional workflow of a single cycle that happens in a
-single day passing through all seven stages. Note that if a stage does not have
-a start and a stop mark, it is not measured and hence not calculated in the median
-time. It is assumed that milestones are created and CI for testing and setting
-environments is configured.
-
-1. Issue is created at 09:00 (start of **Issue** stage).
-1. Issue is added to a milestone at 11:00 (stop of **Issue** stage / start of
- **Plan** stage).
-1. Start working on the issue, create a branch locally and make one commit at
- 12:00.
-1. Make a second commit to the branch which mentions the issue number at 12.30
- (stop of **Plan** stage / start of **Code** stage).
-1. Push branch and create a merge request that contains the [issue closing pattern]
- in its description at 14:00 (stop of **Code** stage / start of **Test** and
- **Review** stages).
-1. The CI starts running your scripts defined in [`.gitlab-ci.yml`][yml] and
- takes 5min (stop of **Test** stage).
-1. Review merge request, ensure that everything is OK and merge the merge
- request at 19:00. (stop of **Review** stage / start of **Staging** stage).
-1. Now that the merge request is merged, a deployment to the `production`
- environment starts and finishes at 19:30 (stop of **Staging** stage).
-1. The cycle completes and the sum of the median times of the previous stages
- is recorded to the **Production** stage. That is the time between creating an
- issue and deploying its relevant merge request to production.
-
-From the above example you can conclude the time it took each stage to complete
-as long as their total time:
-
-- **Issue**: 2h (11:00 - 09:00)
-- **Plan**: 1h (12:00 - 11:00)
-- **Code**: 2h (14:00 - 12:00)
-- **Test**: 5min
-- **Review**: 5h (19:00 - 14:00)
-- **Staging**: 30min (19:30 - 19:00)
-- **Production**: Since this stage measures the sum of median time off all
- previous stages, we cannot calculate it if we don't know the status of the
- stages before. In case this is the very first cycle that is run in the project,
- then the **Production** time is 10h 30min (19:30 - 09:00)
-
-A few notes:
-
-- In the above example we demonstrated that it doesn't matter if your first
- commit doesn't mention the issue number, you can do this later in any commit
- of the branch you are working on.
-- You can see that the **Test** stage is not calculated to the overall time of
- the cycle since it is included in the **Review** process (every MR should be
- tested).
-- The example above was just **one cycle** of the seven stages. Add multiple
- cycles, calculate their median time and the result is what the dashboard of
- Cycle Analytics is showing.
-
-## Permissions
-
-The current permissions on the Project Cycle Analytics dashboard are:
-
-- Public projects - anyone can access
-- Internal projects - any authenticated user can access
-- Private projects - any member Guest and above can access
-
-You can [read more about permissions][permissions] in general.
-
-NOTE: **Note:**
-As of GitLab 12.2, the project-level page is deprecated. You should access
-project-level Cycle Analytics from **Analytics > Cycle Analytics** in the top
-navigation bar. We will ensure that the same project-level functionality is available
-to CE users in the new analytics space.
-
-For Cycle Analytics functionality introduced in GitLab 12.2 and later:
-
-- Users must have Reporter access or above.
-- Features are available only on
- [Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
-
-## More resources
-
-Learn more about Cycle Analytics in the following resources:
-
-- [Cycle Analytics feature page](https://about.gitlab.com/features/cycle-analytics/)
-- [Cycle Analytics feature preview](https://about.gitlab.com/2016/09/16/feature-preview-introducing-cycle-analytics/)
-- [Cycle Analytics feature highlight](https://about.gitlab.com/2016/09/21/cycle-analytics-feature-highlight/)
-
-[board]: issue_board.md#creating-a-new-list
-[ce-5986]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5986
-[ce-20975]: https://gitlab.com/gitlab-org/gitlab-ce/issues/20975
-[environment]: ../../ci/yaml/README.md#environment
-[GitLab flow]: ../../workflow/gitlab_flow.md
-[idea to production]: https://about.gitlab.com/2016/08/05/continuous-integration-delivery-and-deployment-with-gitlab/#from-idea-to-production-with-gitlab
-[issue closing pattern]: issues/managing_issues.md#closing-issues-automatically
-[permissions]: ../permissions.md
-[yml]: ../../ci/yaml/README.md
+This document was moved to [another location](../analytics/cycle_analytics.md)
diff --git a/doc/user/project/img/cycle_analytics_landing_page.png b/doc/user/project/img/cycle_analytics_landing_page.png
deleted file mode 100644
index c0c07e84a82..00000000000
--- a/doc/user/project/img/cycle_analytics_landing_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/gitlab_com.md b/doc/user/project/import/gitlab_com.md
index f48a158e2d3..2f87f257754 100644
--- a/doc/user/project/import/gitlab_com.md
+++ b/doc/user/project/import/gitlab_com.md
@@ -1,21 +1,21 @@
# Project importing from GitLab.com to your private GitLab instance
-You can import your existing GitLab.com projects to your GitLab instance. But keep in mind that it is possible only if
-GitLab.com integration is enabled on your GitLab instance.
+You can import your existing GitLab.com projects to your GitLab instance, but keep in
+mind that it is possible only if GitLab.com integration is enabled on your GitLab instance.
[Read more about GitLab.com integration for self-managed GitLab instances](../../../integration/gitlab.md).
To get to the importer page you need to go to "New project" page.
>**Note:**
-If you are interested in importing Wiki and Merge Request data to your new
-instance, you'll need to follow the instructions for [project export](../settings/import_export.md)
+If you are interested in importing Wiki and Merge Request data to your new instance,
+you'll need to follow the instructions for [exporting a project](../settings/import_export.md#exporting-a-project-and-its-data)
-![New project page](img/gitlab_new_project_page.png)
+![New project page](img/gitlab_new_project_page_v12_2.png)
-Click on the "Import projects from GitLab.com" link and you will be redirected to GitLab.com
+Go to the **Import Projects** tab, then click on **GitLab.com**, and you will be redirected to GitLab.com
for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
![Importer page](img/gitlab_importer.png)
-To import a project, you can simple click "Import". The importer will import your repository and issues.
+To import a project, click "Import". The importer will import your repository and issues.
Once the importer is done, a new GitLab project will be created with your imported data.
diff --git a/doc/user/project/import/img/gitlab_new_project_page.png b/doc/user/project/import/img/gitlab_new_project_page.png
deleted file mode 100644
index c673724f436..00000000000
--- a/doc/user/project/import/img/gitlab_new_project_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/img/gitlab_new_project_page_v12_2.png b/doc/user/project/import/img/gitlab_new_project_page_v12_2.png
new file mode 100644
index 00000000000..e79c27f32c0
--- /dev/null
+++ b/doc/user/project/import/img/gitlab_new_project_page_v12_2.png
Binary files differ
diff --git a/doc/user/project/integrations/img/generate_link_to_chart.png b/doc/user/project/integrations/img/generate_link_to_chart.png
new file mode 100644
index 00000000000..03e018969b1
--- /dev/null
+++ b/doc/user/project/integrations/img/generate_link_to_chart.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service_page.png b/doc/user/project/integrations/img/jira_service_page.png
deleted file mode 100644
index 76fd5f4641c..00000000000
--- a/doc/user/project/integrations/img/jira_service_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_service_page_v12_2.png b/doc/user/project/integrations/img/jira_service_page_v12_2.png
new file mode 100644
index 00000000000..ba7dad9b438
--- /dev/null
+++ b/doc/user/project/integrations/img/jira_service_page_v12_2.png
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index ca990ee6c32..61f6f6c9412 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -93,7 +93,7 @@ even if the status you are changing to is the same.
After saving the configuration, your GitLab project will be able to interact
with all Jira projects in your Jira instance and you'll see the Jira link on the GitLab project pages that takes you to the appropriate Jira project.
-![Jira service page](img/jira_service_page.png)
+![Jira service page](img/jira_service_page_v12_2.png)
## Jira issues
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index aa7db97c413..3ddda802796 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -363,6 +363,10 @@ It is possible to display metrics charts within [GitLab Flavored Markdown](../..
To display a metric chart, include a link of the form `https://<root_url>/<project>/environments/<environment_id>/metrics`.
+A single chart may also be embedded. You can generate a link to the chart via the dropdown located on the right side of the chart:
+
+![Generate Link To Chart](img/generate_link_to_chart.png)
+
The following requirements must be met for the metric to unfurl:
- The `<environment_id>` must correspond to a real environment.
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
index bffbcb544e3..1324a90e00b 100644
--- a/doc/user/project/issues/design_management.md
+++ b/doc/user/project/issues/design_management.md
@@ -35,9 +35,19 @@ to be enabled:
## Limitations
-- Files uploaded must have a file extension of either `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff` or `ico`. The [`svg` extension is not yet supported](https://gitlab.com/gitlab-org/gitlab-ee/issues/12771).
+- Files uploaded must have a file extension of either `png`, `jpg`, `jpeg`, `gif`, `bmp`, `tiff` or `ico`.
+ The [`svg` extension is not yet supported](https://gitlab.com/gitlab-org/gitlab-ee/issues/12771).
+- Design uploads are limited to 10 files at a time.
- [Designs cannot yet be deleted](https://gitlab.com/gitlab-org/gitlab-ee/issues/11089).
-- Design Management is [not yet supported in the project export](https://gitlab.com/gitlab-org/gitlab-ee/issues/11090).
+- Design Management is
+ [not yet supported in the project export](https://gitlab.com/gitlab-org/gitlab-ee/issues/11090).
+- Design Management data
+ [isn't deleted when a project is destroyed](https://gitlab.com/gitlab-org/gitlab-ee/issues/13429) yet.
+- Design Management data [won't be moved](https://gitlab.com/gitlab-org/gitlab-ee/issues/13426)
+ when an issue is moved, nor [deleted](https://gitlab.com/gitlab-org/gitlab-ee/issues/13427)
+ when an issue is deleted.
+- Design Management
+ [isn't supported by Geo](https://gitlab.com/groups/gitlab-org/-/epics/1633) yet.
## The Design Management page
diff --git a/doc/user/project/merge_requests/fast_forward_merge.md b/doc/user/project/merge_requests/fast_forward_merge.md
index 4f3c68090e4..2d59e535ce1 100644
--- a/doc/user/project/merge_requests/fast_forward_merge.md
+++ b/doc/user/project/merge_requests/fast_forward_merge.md
@@ -15,7 +15,7 @@ to accept merge requests without creating merge commits.
When the fast-forward merge
([`--ff-only`](https://git-scm.com/docs/git-merge#git-merge---ff-only)) setting
is enabled, no merge commits will be created and all merges are fast-forwarded,
-which means that merging is only allowed if the branch could be fast-forwarded.
+which means that merging is only allowed if the branch can be fast-forwarded.
When a fast-forward merge is not possible, the user is given the option to rebase.
@@ -28,9 +28,15 @@ When a fast-forward merge is not possible, the user is given the option to rebas
Now, when you visit the merge request page, you will be able to accept it
**only if a fast-forward merge is possible**.
+![Fast forward merge request](img/ff_merge_mr.png)
+
+If a fast-forward merge is not possible but a conflict free rebase is possible,
+a rebase button will be offered.
+
![Fast forward merge request](img/ff_merge_rebase.png)
-If the target branch is ahead of the source branch, you need to rebase the
+If the target branch is ahead of the source branch and a conflict free rebase is
+not possible, you need to rebase the
source branch locally before you will be able to do a fast-forward merge.
![Fast forward merge rebase locally](img/ff_merge_rebase_locally.png)
diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.png
index 2dc02634fd8..2dc02634fd8 100644
--- a/doc/user/project/merge_requests/img/cross-project-dependencies-edit-inaccessible.png
+++ b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-edit.png b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_v12_2.png
index 362e7e0ead2..362e7e0ead2 100644
--- a/doc/user/project/merge_requests/img/cross-project-dependencies-edit.png
+++ b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_v12_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/cross-project-dependencies-view.png b/doc/user/project/merge_requests/img/cross_project_dependencies_view_v12_2.png
index e00231c839b..e00231c839b 100644
--- a/doc/user/project/merge_requests/img/cross-project-dependencies-view.png
+++ b/doc/user/project/merge_requests/img/cross_project_dependencies_view_v12_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/ff_merge_mr.png b/doc/user/project/merge_requests/img/ff_merge_mr.png
new file mode 100644
index 00000000000..241cc990343
--- /dev/null
+++ b/doc/user/project/merge_requests/img/ff_merge_mr.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/incrementally_expand_merge_request_diffs_v12_2.png b/doc/user/project/merge_requests/img/incrementally_expand_merge_request_diffs_v12_2.png
new file mode 100644
index 00000000000..ee94dbdea5c
--- /dev/null
+++ b/doc/user/project/merge_requests/img/incrementally_expand_merge_request_diffs_v12_2.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 4c8bd230b3f..2794cbc0f39 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -488,15 +488,6 @@ without having to check the entire job log.
[Read more about JUnit test reports](../../../ci/junit_test_reports.md).
-## Live preview with Review Apps
-
-If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
-you can preview the changes submitted to a feature-branch through a merge request
-in a per-branch basis. No need to checkout the branch, install and preview locally;
-all your changes will be available to preview by anyone with the Review Apps link.
-
-[Read more about Review Apps.](../../../ci/review_apps/index.md)
-
## Merge request diff file navigation
When reviewing changes in the **Changes** tab the diff can be navigated using
@@ -506,6 +497,15 @@ list.
![Merge request diff file navigation](img/merge_request_diff_file_navigation.png)
+### Incrementally expand merge request diffs
+
+By default, the diff shows only the parts of a file which are changed.
+To view more unchanged lines above or below a change click on the
+**Expand up** or **Expand down** icons. You can also click on **Show all lines**
+to expand the entire file.
+
+![Incrementally expand merge request diffs](img/incrementally_expand_merge_request_diffs_v12_2.png)
+
## Ignore whitespace changes in Merge Request diff view
If you click the **Hide whitespace changes** button, you can see the diff
diff --git a/doc/user/project/merge_requests/merge_request_dependencies.md b/doc/user/project/merge_requests/merge_request_dependencies.md
index e046b3466c4..b30e24b2386 100644
--- a/doc/user/project/merge_requests/merge_request_dependencies.md
+++ b/doc/user/project/merge_requests/merge_request_dependencies.md
@@ -2,9 +2,9 @@
type: reference, concepts
---
-# Cross-project merge request dependencies **(PREMIUM)**
+# Cross-project Merge Request dependencies **(PREMIUM)**
-> Introduced in GitLab Premium 12.2
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9688) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
Cross-project merge request dependencies allows a required order of merging
between merge requests in different projects to be expressed. If a
@@ -24,11 +24,11 @@ merge requests in the same project cannot depend on each other.
## Use cases
- Ensure changes to a library are merged before changes to a project that
- imports the library
+ imports the library.
- Prevent a documentation-only merge request from being merged before the merge request
- implementing the feature to be documented
+ implementing the feature to be documented.
- Require an merge request updating a permissions matrix to be merged before merging an
- merge request from someone who hasn't yet been granted permissions
+ merge request from someone who hasn't yet been granted permissions.
It is common for a single logical change to span several merge requests, spread
out across multiple projects, and the order in which they are merged can be
@@ -60,33 +60,33 @@ new merge request in `awesome-project` (or by editing it, if it already exists).
The dependency needs to be configured on the **dependent** merge
request. There is a "Cross-project dependencies" section in the form:
-![Cross-project dependencies form control](img/cross-project-dependencies-edit.png)
+![Cross-project dependencies form control](img/cross_project_dependencies_edit_v12_2.png)
Anyone who can edit a merge request can change the list of dependencies.
New dependencies can be added by reference, or by URL. To remove a dependency,
-press the "X" by its reference.
+press the **X** by its reference.
As dependencies are specified across projects, it's possible that someone else
has added a dependency for a merge request in a project you don't have access to.
These are shown as a simple count:
-![Cross-project dependencies form control with inaccessible merge requests](img/cross-project-dependencies-edit-inaccessible.png)
+![Cross-project dependencies form control with inaccessible merge requests](img/cross_project_dependencies_edit_inaccessible_v12_2.png)
-If necessary, you can remove all the dependencies like this by pressing the "X",
-just as you would for a single, visible dependency.
+If necessary, you can remove all the dependencies like this by pressing the
+**X**, just as you would for a single, visible dependency.
-Once you're finished, press the "Save changes" button to submit the request, or
-"Cancel" to return without making any changes.
+Once you're finished, press the **Save changes** button to submit the request,
+or **Cancel** to return without making any changes.
The list of configured dependencies, and the status of each one, is shown in the
merge request widget:
-![Cross-project dependencies in merge request widget](img/cross-project-dependencies-view.png)
+![Cross-project dependencies in merge request widget](img/cross_project_dependencies_view_v12_2.png)
-Until all dependencies have, themselves, been merged, the "Merge"
+Until all dependencies have, themselves, been merged, the **Merge**
button will be disabled for the dependent merge request. In
-particular, note that **closed** merge request still prevent their
+particular, note that **closed merge requests** still prevent their
dependents from being merged - it is impossible to automatically
determine whether the dependency expressed by a closed merge request
has been satisfied in some other way or not.
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 03ae24242e3..8606d92f20c 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -28,13 +28,13 @@ The reasons to do it like that are:
With the new behavior, any job that is triggered by the user, is also marked
with their read permissions. When a user does a `git push` or changes files through
the web UI, a new pipeline will be usually created. This pipeline will be marked
-as created be the pusher (local push or via the UI) and any job created in this
+as created by the pusher (local push or via the UI) and any job created in this
pipeline will have the read permissions of the pusher but not write permissions.
This allows us to make it really easy to evaluate the access for all projects
that have [Git submodules][gitsub] or are using container images that the pusher
-would have access too. **The permission is granted only for time that job is
-running. The access is revoked after the job is finished.**
+would have access too. **The permission is granted only for the time that the job
+is running. The access is revoked after the job is finished.**
## Types of users
@@ -74,7 +74,7 @@ We try to make sure that this token doesn't leak by:
1. Securing all API endpoints to not expose the job token.
1. Masking the job token from job logs.
-1. Allowing to use the job token **only** when job is running.
+1. Granting permissions to the job token **only** when the job is running.
However, this brings a question about the Runners security. To make sure that
this token doesn't leak, you should also make sure that you configure
@@ -86,12 +86,6 @@ your Runners in the most possible secure way, by avoiding the following:
By using an insecure GitLab Runner configuration, you allow the rogue developers
to steal the tokens of other jobs.
-## Pipeline triggers
-
-Since 9.0 [pipeline triggers][triggers] do support the new permission model.
-The new triggers do impersonate their associated user including their access
-to projects and their project permissions.
-
## Before GitLab 8.12
In versions before GitLab 8.12, all CI jobs would use the CI Runner's token
@@ -203,7 +197,7 @@ Container Registries for private projects.
> **Notes:**
>
> - GitLab Runner versions prior to 1.8 don't incorporate the introduced changes
-> for permissions. This makes the `image:` directive to not work with private
+> for permissions. This makes the `image:` directive not work with private
> projects automatically and it needs to be configured manually on Runner's host
> with a predefined account (for example administrator's personal account with
> access token created explicitly for this purpose). This issue is resolved with
@@ -227,11 +221,22 @@ test:
- docker run $CI_REGISTRY/group/other-project:latest
```
+### Pipeline triggers
+
+Since 9.0 [pipeline triggers][triggers] do support the new permission model.
+The new triggers do impersonate their associated user including their access
+to projects and their project permissions.
+
+### API
+
+GitLab API cannot be used via `CI_JOB_TOKEN` but there is a [proposal](https://gitlab.com/gitlab-org/gitlab-ce/issues/29566)
+to support it.
+
[job permissions]: ../permissions.md#job-permissions
[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302
[gitsub]: ../../ci/git_submodules.md
[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
-[triggers]: ../../ci/triggers/README.md
+[triggers]: ../../ci/triggers/README.md#ci-job-token
[update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update
[workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse
[jobenv]: ../../ci/variables/README.md#predefined-environment-variables
diff --git a/doc/user/project/operations/feature_flags.md b/doc/user/project/operations/feature_flags.md
index 19ccde6e16a..75b0623e6b0 100644
--- a/doc/user/project/operations/feature_flags.md
+++ b/doc/user/project/operations/feature_flags.md
@@ -112,6 +112,19 @@ If this strategy is selected, then the Unleash client **must** be given a user i
**Percent rollout (logged in users)** is implemented using the Unleash [gradualRolloutUserId](https://unleash.github.io/docs/activation_strategy#gradualrolloutuserid) activation strategy.
+## Target Users
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
+
+A feature flag may be enabled for a list of target users.
+
+![Feature flag target users](img/target_users_v12_2.png)
+
+CAUTION: **Caution:**
+The Unleash client **must** be given a user id for the feature to be enabled for target users. See the [Ruby example](#ruby-application-example) below.
+
+**Target users** is implemented using the Unleash [userWithId](https://unleash.github.io/docs/activation_strategy#userwithid) activation strategy.
+
## Integrating with your application
In order to use Feature Flags, you need to first
@@ -207,7 +220,7 @@ func main() {
Here's an example of how to integrate the feature flags in a Ruby application.
-The Unleash client is given a user id for use with a **Percent rollout (logged in users)** rollout strategy.
+The Unleash client is given a user id for use with a **Percent rollout (logged in users)** rollout strategy or a list of **Target Users**.
```ruby
#!/usr/bin/env ruby
diff --git a/doc/user/project/operations/img/target_users_v12_2.png b/doc/user/project/operations/img/target_users_v12_2.png
new file mode 100644
index 00000000000..c88d2b7be97
--- /dev/null
+++ b/doc/user/project/operations/img/target_users_v12_2.png
Binary files differ
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 6758adf2b43..647250bd02a 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -40,18 +40,20 @@ discussions, and descriptions:
| `/label ~label1 ~label2` | Add label(s). Label names can also start without ~ but mixed syntax is not supported. | ✓ | ✓ |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s)| ✓ | ✓ |
| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified | ✓ | ✓ |
-| `/copy_metadata <#issue | !merge_request>` | Copy labels and milestone from other issue or merge request in the project | ✓ | ✓ |
+| `/copy_metadata <#issue>` | Copy labels and milestone from another issue in the project | ✓ | ✓ |
+| `/copy_metadata <!merge_request>` | Copy labels and milestone from another merge request in the project | ✓ | ✓ |
| `/estimate <1w 3d 2h 14m>` | Set time estimate | ✓ | ✓ |
| `/remove_estimate` | Remove time estimate | ✓ | ✓ |
-| `/spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)>` | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
+| `/spend <time(1h 30m)> <date(YYYY-MM-DD)>` | Add spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
+| `/spend <time(-1h 5m)> <date(YYYY-MM-DD)>` | Subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ |
| `/remove_time_spent` | Remove time spent | ✓ | ✓ |
| `/lock` | Lock the thread | ✓ | ✓ |
| `/unlock` | Unlock the thread | ✓ | ✓ |
-| `/due <in 2 days | this Friday | December 31st>`| Set due date | ✓ | |
+| `/due <date>` | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. | ✓ | |
| `/remove_due_date` | Remove due date | ✓ | |
-| `/weight <0 | 1 | 2 | ...>` | Set weight **(STARTER)** | ✓ | |
+| `/weight <value>` | Set weight. Valid options for `<value>` include `0`, `1`, `2`, etc. **(STARTER)** | ✓ | |
| `/clear_weight` | Clears weight **(STARTER)** | ✓ | |
-| `/epic <&epic | group&epic | Epic URL>` | Add to epic **(ULTIMATE)** | ✓ | |
+| `/epic <epic>` | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. **(ULTIMATE)** | ✓ | |
| `/remove_epic` | Removes from epic **(ULTIMATE)** | ✓ | |
| `/promote` | Promote issue to epic **(ULTIMATE)** | ✓ | |
| `/confidential` | Make confidential | ✓ | |
@@ -110,9 +112,9 @@ The following quick actions are applicable for epics threads and description:
| `/label ~label1 ~label2` | Add label(s) |
| `/unlabel ~label1 ~label2` | Remove all or specific label(s) |
| `/relabel ~label1 ~label2` | Replace existing label(s) with those specified |
-| `/child_epic <&epic | group&epic | Epic URL>` | Adds child epic to epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| `/remove_child_epic <&epic | group&epic | Epic URL>` | Removes child epic from epic ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) |
-| `/parent_epic <&epic | group&epic | Epic URL>` | Sets parent epic to epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
+| `/child_epic <epic>` | Adds child epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)**|
+| `/remove_child_epic <epic>` | Removes child epic from `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([Introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ee/issues/7330)) **(ULTIMATE)** |
+| `/parent_epic <epic>` | Sets parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic` or `epic-URL`. ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) **(ULTIMATE)** |
| `/remove_parent_epic` | Removes parent epic from epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ee/issues/10556)) |
<!-- ## Troubleshooting
diff --git a/doc/user/project/repository/img/repository_languages.png b/doc/user/project/repository/img/repository_languages.png
deleted file mode 100644
index 5977ad7faae..00000000000
--- a/doc/user/project/repository/img/repository_languages.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/repository/img/repository_languages_v12_2.gif b/doc/user/project/repository/img/repository_languages_v12_2.gif
new file mode 100644
index 00000000000..d0a0e57c919
--- /dev/null
+++ b/doc/user/project/repository/img/repository_languages_v12_2.gif
Binary files differ
diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md
index 84d63d29929..bd966185c94 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -182,7 +182,7 @@ were used and display this on the projects pages. If this information is missing
be added after updating the default branch on the project. This process can take up to 5
minutes.
-![Repository Languages bar](img/repository_languages.png)
+![Repository Languages bar](img/repository_languages_v12_2.gif)
Not all files are detected, among others; documentation,
vendored code, and most markup languages are excluded. This behaviour can be
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 17ec9ecb5d1..4e3db95b6d6 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -32,6 +32,12 @@ links will be missing from the sidebar UI.
You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see
Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages.
+#### Disabling email notifications
+
+You can disable all email notifications related to the project by selecting the
+**Disable email notifications** checkbox. Only the project owner is allowed to change
+this setting.
+
### Issue settings
Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template.
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index e3c6cd6d6ff..d1bab47de25 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -28,11 +28,12 @@ NOTE: **Note:**
Requires Developer [permissions](../../permissions.md).
Create a new page by clicking the **New page** button that can be found
-in all wiki pages. You will be asked to fill in the page name from which GitLab
-will create the path to the page. You can specify a full path for the new file
-and any missing directories will be created automatically.
+in all wiki pages.
-![New page modal](img/wiki_create_new_page_modal.png)
+You will be asked to fill in a title for your new wiki page. Wiki titles
+also determine the path to the wiki page. You can specify a full path
+(using "`/`" for subdirectories) for the new title and any missing
+directories will be created automatically.
Once you enter the page name, it's time to fill in its content. GitLab wikis
support Markdown, RDoc and AsciiDoc. For Markdown based pages, all the
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 3160b0c4deb..7e880b3009d 100644
--- a/doc/workflow/lfs/lfs_administration.md
+++ b/doc/workflow/lfs/lfs_administration.md
@@ -46,8 +46,7 @@ In `config/gitlab.yml`:
## Storing LFS objects in remote object storage
-> [Introduced][ee-2760] in [GitLab Premium][eep] 10.0. Brought to GitLab Core
-in 10.7.
+> [Introduced][ee-2760] in [GitLab Premium][eep] 10.0. Brought to GitLab Core in 10.7.
It is possible to store LFS objects in remote object storage which allows you
to offload local hard disk R/W operations, and free up disk space significantly.
@@ -91,7 +90,7 @@ Here is a configuration example with S3.
| `aws_access_key_id` | AWS credentials, or compatible | `ABC123DEF456` |
| `aws_secret_access_key` | AWS credentials, or compatible | `ABC123DEF456ABC123DEF456ABC123DEF456` |
| `aws_signature_version` | AWS signature version to use. 2 or 4 are valid options. Digital Ocean Spaces and other providers may need 2. | 4 |
-| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with AWS v4 signatures (https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true
+| `enable_signature_v4_streaming` | Set to true to enable HTTP chunked transfers with [AWS v4 signatures](https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-streaming.html). Oracle Cloud S3 needs this to be false | true |
| `region` | AWS region | us-east-1 |
| `host` | S3 compatible host for when not using AWS, e.g. `localhost` or `storage.example.com` | s3.amazonaws.com |
| `endpoint` | Can be used when configuring an S3 compatible service such as [Minio](https://www.minio.io), by entering a URL such as `http://127.0.0.1:9000` | (optional) |
@@ -107,7 +106,9 @@ Here is a configuration example with GCS.
| `google_client_email` | The email address of the service account | `foo@gcp-project-12345.iam.gserviceaccount.com` |
| `google_json_key_location` | The json key path | `/path/to/gcp-project-12345-abcde.json` |
-_NOTE: The service account must have permission to access the bucket. [See more](https://cloud.google.com/storage/docs/authentication)_
+NOTE: **Note:**
+The service account must have permission to access the bucket.
+[See more](https://cloud.google.com/storage/docs/authentication)
Here is a configuration example with Rackspace Cloud Files.
@@ -119,7 +120,13 @@ Here is a configuration example with Rackspace Cloud Files.
| `rackspace_region` | The Rackspace storage region to use, a three letter code from the [list of service access endpoints](https://developer.rackspace.com/docs/cloud-files/v1/general-api-info/service-access/) | `iad` |
| `rackspace_temp_url_key` | The private key you have set in the Rackspace API for temporary URLs. Read more [here](https://developer.rackspace.com/docs/cloud-files/v1/use-cases/public-access-to-your-cloud-files-account/#tempurl) | `ABC123DEF456ABC123DEF456ABC123DE` |
-_NOTES: Regardless of whether the container has public access enabled or disabled, Fog will use the TempURL method to grant access to LFS objects. If you see errors in logs referencing instantiating storage with a temp-url-key, ensure that you have set they key properly on the Rackspace API and in gitlab.rb. You can verify the value of the key Rackspace has set by sending a GET request with token header to the service access endpoint URL and comparing the output of the returned headers._
+NOTE: **Note:**
+Regardless of whether the container has public access enabled or disabled, Fog will
+use the TempURL method to grant access to LFS objects. If you see errors in logs referencing
+instantiating storage with a temp-url-key, ensure that you have set they key properly
+on the Rackspace API and in gitlab.rb. You can verify the value of the key Rackspace
+has set by sending a GET request with token header to the service access endpoint URL
+and comparing the output of the returned headers.
### Manual uploading to an object storage
@@ -167,13 +174,13 @@ On Omnibus installations, the settings are prefixed by `lfs_object_store_`:
1. Save the file and [reconfigure GitLab]s for the changes to take effect.
1. Migrate any existing local LFS objects to the object storage:
- ```bash
- gitlab-rake gitlab:lfs:migrate
- ```
+ ```bash
+ gitlab-rake gitlab:lfs:migrate
+ ```
- This will migrate existing LFS objects to object storage. New LFS objects
- will be forwarded to object storage unless
- `gitlab_rails['lfs_object_store_background_upload']` is set to false.
+ This will migrate existing LFS objects to object storage. New LFS objects
+ will be forwarded to object storage unless
+ `gitlab_rails['lfs_object_store_background_upload']` is set to false.
### S3 for installations from source
@@ -203,13 +210,13 @@ For source installations the settings are nested under `lfs:` and then
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local LFS objects to the object storage:
- ```bash
- sudo -u git -H bundle exec rake gitlab:lfs:migrate RAILS_ENV=production
- ```
+ ```bash
+ sudo -u git -H bundle exec rake gitlab:lfs:migrate RAILS_ENV=production
+ ```
- This will migrate existing LFS objects to object storage. New LFS objects
- will be forwarded to object storage unless `background_upload` is set to
- false.
+ This will migrate existing LFS objects to object storage. New LFS objects
+ will be forwarded to object storage unless `background_upload` is set to
+ false.
## Storage statistics
diff --git a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
index 264372a512d..f5593927cc2 100644
--- a/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
+++ b/doc/workflow/lfs/manage_large_binaries_with_git_lfs.md
@@ -35,9 +35,10 @@ Documentation for GitLab instance administrators is under [LFS administration do
- Git LFS always assumes HTTPS so if you have GitLab server on HTTP you will have
to add the URL to Git config manually (see [troubleshooting](#troubleshooting))
->**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
- still goes over HTTP, but now the SSH client passes the correct credentials
- to the Git LFS client, so no action is required by the user.
+NOTE: **Note:**
+With 8.12 GitLab added LFS support to SSH. The Git LFS communication
+still goes over HTTP, but now the SSH client passes the correct credentials
+to the Git LFS client, so no action is required by the user.
## Using Git LFS
@@ -61,12 +62,13 @@ git commit -am "Added Debian iso" # commit the file meta data
git push origin master # sync the git repo and large file to the GitLab server
```
-> **Note**: Make sure that `.gitattributes` is tracked by git. Otherwise Git
-> LFS will not be working properly for people cloning the project.
->
-> ```bash
-> git add .gitattributes
-> ```
+NOTE: **Note:**
+**Make sure** that `.gitattributes` is tracked by Git. Otherwise Git
+LFS will not be working properly for people cloning the project.
+
+```bash
+git add .gitattributes
+```
Cloning the repository works the same as before. Git automatically detects the
LFS-tracked files and clones them via HTTP. If you performed the git clone
@@ -216,9 +218,10 @@ git config --add lfs.url "http://gitlab.example.com/group/project.git/info/lfs"
### Credentials are always required when pushing an object
->**Note**: With 8.12 GitLab added LFS support to SSH. The Git LFS communication
- still goes over HTTP, but now the SSH client passes the correct credentials
- to the Git LFS client, so no action is required by the user.
+NOTE: **Note:**
+With 8.12 GitLab added LFS support to SSH. The Git LFS communication
+still goes over HTTP, but now the SSH client passes the correct credentials
+to the Git LFS client, so no action is required by the user.
Given that Git LFS uses HTTP Basic Authentication to authenticate the user pushing
the LFS object on every push for every object, user HTTPS credentials are required.
diff --git a/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
index 75673d23799..5ceeb3253f1 100644
--- a/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
+++ b/doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md
@@ -76,7 +76,7 @@ Here you'll find a guide on
Since Annex files are stored as objects with symlinks and cannot be directly
modified, we need to first remove those symlinks.
->**Note:**
+NOTE: **Note:**
Make sure the you read about the [`direct` mode][annex-direct] as it contains
useful information that may fit in your use case. Note that `annex direct` is
deprecated in Git Annex version 6, so you may need to upgrade your repository
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index d82f7c6fdc7..ccb8844aea3 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -51,6 +51,10 @@ Organization like this is suitable for users that belong to different groups but
same need for being notified for every group they are member of.
These settings can be configured on group page under the name of the group. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
+The group owner can disable email notifications for a group, which also includes
+it's subgroups and projects. If this is the case, you will not receive any corresponding notifications,
+and the notification button will be disabled with an explanatory tooltip.
+
### Project Settings
![notification settings](img/notification_project_settings.png)
@@ -60,6 +64,10 @@ other setting.
This is suitable for users that have different needs for notifications per project basis.
These settings can be configured on project page under the name of the project. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
+The project owner (or it's group owner) can disable email notifications for the project.
+If this is the case, you will not receive any corresponding notifications, and the notification
+button will be disabled with an explanatory tooltip.
+
## Notification events
Below is the table of events users can be notified of:
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 0b8e7d851b0..753518d0424 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -37,8 +37,7 @@ The following are some possible use cases for repository mirroring:
## Pushing to a remote repository **(CORE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise
-> Edition 8.7. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/249) in GitLab Enterprise Edition 8.7. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
For an existing project, you can set up push mirroring as follows:
@@ -67,8 +66,7 @@ section.
### Push only protected branches **(CORE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in
-> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3350) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3. [Moved to GitLab Core](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18715) in 10.8.
You can choose to only push your protected branches from GitLab to your remote repository.
@@ -98,8 +96,7 @@ The repository will push soon. To force a push, click the appropriate button.
## Pulling from a remote repository **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
-> [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2. [Added Git LFS support](https://gitlab.com/gitlab-org/gitlab-ee/issues/10871) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.11.
NOTE: **Note:** This feature [is available for free](https://gitlab.com/gitlab-org/gitlab-ee/issues/10361) to
GitLab.com users until September 22nd, 2019.
@@ -157,8 +154,7 @@ Repository mirrors are updated as Sidekiq becomes available to process them. If
### SSH authentication
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Pull mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5.
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Push mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2551) for Pull mirroring in [GitLab Starter](https://about.gitlab.com/pricing/) 9.5. [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22982) for Push mirroring in [GitLab Core](https://about.gitlab.com/pricing/) 11.6
SSH authentication is mutual:
@@ -245,8 +241,7 @@ key to keep the mirror running.
### Overwrite diverged branches **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559) in
-> [GitLab Starter](https://about.gitlab.com/pricing/) 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4559) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.6.
You can choose to always update your local branches with remote versions, even if they have
diverged from the remote.
@@ -258,8 +253,7 @@ To use this option, check the **Overwrite diverged branches** box when creating
### Only mirror protected branches **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326) in
-> [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3326) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
You can choose to pull mirror only the protected branches from your remote repository to GitLab.
Non-protected branches are not mirrored and can diverge.
@@ -268,8 +262,7 @@ To use this option, check the **Only mirror protected branches** box when creati
### Hard failure **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117) in
-> [GitLab Starter](https://about.gitlab.com/pricing/) 10.2.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3117) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.2.
Once the mirroring process is unsuccessfully retried 14 times in a row, it will get marked as hard
failed. This will become visible in either the:
@@ -282,8 +275,7 @@ project mirroring again by [Forcing an update](#forcing-an-update-core).
### Trigger update using API **(STARTER)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453) in
-[GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3453) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
Pull mirroring uses polling to detect new branches and commits added upstream, often minutes
afterwards. If you notify GitLab by [API](../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
@@ -325,9 +317,10 @@ protected branches.
### Preventing conflicts using a `pre-receive` hook
-> **Warning:** The solution proposed will negatively impact the performance of
-> Git push operations because they will be proxied to the upstream Git
-> repository.
+CAUTION: **Warning:**
+The solution proposed will negatively impact the performance of
+Git push operations because they will be proxied to the upstream Git
+repository.
A server-side `pre-receive` hook can be used to prevent the race condition
described above by only accepting the push after first pushing the commit to
diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md
index 0432c406f31..211d242e2d3 100644
--- a/doc/workflow/todos.md
+++ b/doc/workflow/todos.md
@@ -35,8 +35,8 @@ A To Do displays on your To-Do List when:
- You are `@mentioned` in a comment on a commit
- A job in the CI pipeline running for your merge request failed, but this
job is not allowed to fail
-- An open merge request becomes unmergeable due to conflict, and you are either:
- - The author
+- An open merge request becomes unmergeable due to conflict, and you are either:
+ - The author
- Have set it to automatically merge once the pipeline succeeds
To-do triggers are not affected by [GitLab Notification Email settings](notifications.md).
diff --git a/lib/api/api.rb b/lib/api/api.rb
index e500a93b31e..219ed45eff6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -18,7 +18,7 @@ module API
formatter: Gitlab::GrapeLogging::Formatters::LogrageWithTimestamp.new,
include: [
GrapeLogging::Loggers::FilterParameters.new(LOG_FILTERS),
- GrapeLogging::Loggers::ClientEnv.new,
+ Gitlab::GrapeLogging::Loggers::ClientEnvLogger.new,
Gitlab::GrapeLogging::Loggers::RouteLogger.new,
Gitlab::GrapeLogging::Loggers::UserLogger.new,
Gitlab::GrapeLogging::Loggers::QueueDurationLogger.new,
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index e4f4e79cd46..a2f3e87ebd2 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -43,7 +43,7 @@ module API
path = params[:path]
before = params[:until]
after = params[:since]
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
+ ref = params[:ref_name].presence || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 09253ab6b0e..5e66b4e76a5 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -645,7 +645,10 @@ module API
end
end
- expose :subscribed do |issue, options|
+ # Calculating the value of subscribed field triggers Markdown
+ # processing. We can't do that for multiple issues / merge
+ # requests in a single API request.
+ expose :subscribed, if: -> (_, options) { options.fetch(:include_subscribed, true) } do |issue, options|
issue.subscribed?(options[:current_user], options[:project] || issue.project)
end
end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index d687acf3423..7819c2de515 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -96,7 +96,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
@@ -122,7 +123,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
diff --git a/lib/gitlab/action_rate_limiter.rb b/lib/gitlab/action_rate_limiter.rb
index fdb06d00548..0e8707af631 100644
--- a/lib/gitlab/action_rate_limiter.rb
+++ b/lib/gitlab/action_rate_limiter.rb
@@ -49,9 +49,9 @@ module Gitlab
request_information = {
message: 'Action_Rate_Limiter_Request',
env: type,
- ip: request.ip,
+ remote_ip: request.ip,
request_method: request.request_method,
- fullpath: request.fullpath
+ path: request.fullpath
}
if current_user
diff --git a/lib/gitlab/background_migration/legacy_upload_mover.rb b/lib/gitlab/background_migration/legacy_upload_mover.rb
new file mode 100644
index 00000000000..051c1176edb
--- /dev/null
+++ b/lib/gitlab/background_migration/legacy_upload_mover.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class takes a legacy upload and migrates it to the correct location
+ class LegacyUploadMover
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :upload, :project, :note
+ attr_accessor :logger
+
+ def initialize(upload)
+ @upload = upload
+ @note = Note.find_by(id: upload.model_id)
+ @project = note&.project
+ @logger = Gitlab::BackgroundMigration::Logger.build
+ end
+
+ def execute
+ return unless upload
+
+ if !project
+ # if we don't have models associated with the upload we can not move it
+ warn('Deleting upload due to model not found.')
+
+ destroy_legacy_upload
+ elsif note.is_a?(LegacyDiffNote)
+ return unless move_legacy_diff_file
+
+ migrate_upload
+ elsif !legacy_file_exists?
+ warn('Deleting upload due to file not found.')
+ destroy_legacy_upload
+ else
+ migrate_upload
+ end
+ end
+
+ private
+
+ def migrate_upload
+ return unless copy_upload_to_project
+
+ add_upload_link_to_note_text
+ destroy_legacy_file
+ destroy_legacy_upload
+ end
+
+ # we should proceed and log whenever one upload copy fails, no matter the reasons
+ # rubocop: disable Lint/RescueException
+ def copy_upload_to_project
+ @uploader = FileUploader.copy_to(legacy_file_uploader, project)
+
+ logger.info(
+ message: 'MigrateLegacyUploads: File copied successfully',
+ old_path: legacy_file_uploader.file.path, new_path: @uploader.file.path
+ )
+ true
+ rescue Exception => e
+ warn(
+ 'File could not be copied to project uploads',
+ file_path: legacy_file_uploader.file.path, error: e.message
+ )
+ false
+ end
+ # rubocop: enable Lint/RescueException
+
+ def destroy_legacy_upload
+ if note
+ note.remove_attachment = true
+ note.save
+ end
+
+ if upload.destroy
+ logger.info(message: 'MigrateLegacyUploads: Upload was destroyed.', upload: upload.inspect)
+ else
+ warn('MigrateLegacyUploads: Upload destroy failed.')
+ end
+ end
+
+ def destroy_legacy_file
+ legacy_file_uploader.file.delete
+ end
+
+ def add_upload_link_to_note_text
+ new_text = "#{note.note} \n #{@uploader.markdown_link}"
+ # Bypass validations because old data may have invalid
+ # noteable values. If we fail hard here, we may kill the
+ # entire background migration, which affects a range of notes.
+ note.update_attribute(:note, new_text)
+ end
+
+ def legacy_file_uploader
+ strong_memoize(:legacy_file_uploader) do
+ uploader = upload.build_uploader
+ uploader.retrieve_from_store!(File.basename(upload.path))
+ uploader
+ end
+ end
+
+ def legacy_file_exists?
+ legacy_file_uploader.file.exists?
+ end
+
+ # we should proceed and log whenever one upload copy fails, no matter the reasons
+ # rubocop: disable Lint/RescueException
+ def move_legacy_diff_file
+ old_path = upload.absolute_path
+ old_path_sub = '-/system/note/attachment'
+
+ if !File.exist?(old_path) || !old_path.include?(old_path_sub)
+ log_legacy_diff_note_problem(old_path)
+ return false
+ end
+
+ new_path = upload.absolute_path.sub(old_path_sub, '-/system/legacy_diff_note/attachment')
+ new_dir = File.dirname(new_path)
+ FileUtils.mkdir_p(new_dir)
+
+ FileUtils.mv(old_path, new_path)
+ rescue Exception => e
+ log_legacy_diff_note_problem(old_path, new_path, e)
+ false
+ end
+
+ def warn(message, params = {})
+ logger.warn(
+ params.merge(message: "MigrateLegacyUploads: #{message}", upload: upload.inspect)
+ )
+ end
+
+ def log_legacy_diff_note_problem(old_path, new_path = nil, error = nil)
+ warn('LegacyDiffNote upload could not be moved to a new path',
+ old_path: old_path, new_path: new_path, error: error&.message
+ )
+ end
+ # rubocop: enable Lint/RescueException
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/legacy_uploads_migrator.rb b/lib/gitlab/background_migration/legacy_uploads_migrator.rb
new file mode 100644
index 00000000000..a9d38a27e0c
--- /dev/null
+++ b/lib/gitlab/background_migration/legacy_uploads_migrator.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This migration takes all legacy uploads (that were uploaded using AttachmentUploader)
+ # and migrate them to the new (FileUploader) location (=under projects).
+ #
+ # We have dependencies (uploaders) in this migration because extracting code would add a lot of complexity
+ # and possible errors could appear as the logic in the uploaders is not trivial.
+ #
+ # This migration will be removed in 13.0 in order to get rid of a migration that depends on
+ # the application code.
+ class LegacyUploadsMigrator
+ include Database::MigrationHelpers
+
+ def perform(start_id, end_id)
+ Upload.where(id: start_id..end_id, uploader: 'AttachmentUploader').find_each do |upload|
+ LegacyUploadMover.new(upload).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/logger.rb b/lib/gitlab/background_migration/logger.rb
new file mode 100644
index 00000000000..4ea89771eff
--- /dev/null
+++ b/lib/gitlab/background_migration/logger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Logger that can be used for migrations logging
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'migrations'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/policy/variables.rb b/lib/gitlab/ci/build/policy/variables.rb
index 0698136166a..e9c8864123f 100644
--- a/lib/gitlab/ci/build/policy/variables.rb
+++ b/lib/gitlab/ci/build/policy/variables.rb
@@ -10,7 +10,7 @@ module Gitlab
end
def satisfied_by?(pipeline, seed)
- variables = seed.to_resource.scoped_variables_hash
+ variables = seed.scoped_variables_hash
statements = @expressions.map do |statement|
::Gitlab::Ci::Pipeline::Expression::Statement
diff --git a/lib/gitlab/ci/build/rules.rb b/lib/gitlab/ci/build/rules.rb
new file mode 100644
index 00000000000..89623a809c9
--- /dev/null
+++ b/lib/gitlab/ci/build/rules.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Rules
+ include ::Gitlab::Utils::StrongMemoize
+
+ Result = Struct.new(:when, :start_in)
+
+ def initialize(rule_hashes, default_when = 'on_success')
+ @rule_list = Rule.fabricate_list(rule_hashes)
+ @default_when = default_when
+ end
+
+ def evaluate(pipeline, build)
+ if @rule_list.nil?
+ Result.new(@default_when)
+ elsif matched_rule = match_rule(pipeline, build)
+ Result.new(
+ matched_rule.attributes[:when] || @default_when,
+ matched_rule.attributes[:start_in]
+ )
+ else
+ Result.new('never')
+ end
+ end
+
+ private
+
+ def match_rule(pipeline, build)
+ @rule_list.find { |rule| rule.matches?(pipeline, build) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/rules/rule.rb b/lib/gitlab/ci/build/rules/rule.rb
new file mode 100644
index 00000000000..8d52158c8d2
--- /dev/null
+++ b/lib/gitlab/ci/build/rules/rule.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Rules::Rule
+ attr_accessor :attributes
+
+ def self.fabricate_list(list)
+ list.map(&method(:new)) if list
+ end
+
+ def initialize(spec)
+ @clauses = []
+ @attributes = {}
+
+ spec.each do |type, value|
+ if clause = Clause.fabricate(type, value)
+ @clauses << clause
+ else
+ @attributes.merge!(type => value)
+ end
+ end
+ end
+
+ def matches?(pipeline, build)
+ @clauses.all? { |clause| clause.satisfied_by?(pipeline, build) }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/rules/rule/clause.rb b/lib/gitlab/ci/build/rules/rule/clause.rb
new file mode 100644
index 00000000000..ff0baf3348c
--- /dev/null
+++ b/lib/gitlab/ci/build/rules/rule/clause.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Rules::Rule::Clause
+ ##
+ # Abstract class that defines an interface of a single
+ # job rule specification.
+ #
+ # Used for job's inclusion rules configuration.
+ #
+ UnknownClauseError = Class.new(StandardError)
+
+ def self.fabricate(type, value)
+ type = type.to_s.camelize
+
+ self.const_get(type).new(value) if self.const_defined?(type)
+ end
+
+ def initialize(spec)
+ @spec = spec
+ end
+
+ def satisfied_by?(pipeline, seed = nil)
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/rules/rule/clause/changes.rb b/lib/gitlab/ci/build/rules/rule/clause/changes.rb
new file mode 100644
index 00000000000..81d2ee6c24c
--- /dev/null
+++ b/lib/gitlab/ci/build/rules/rule/clause/changes.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Rules::Rule::Clause::Changes < Rules::Rule::Clause
+ def initialize(globs)
+ @globs = Array(globs)
+ end
+
+ def satisfied_by?(pipeline, seed)
+ return true if pipeline.modified_paths.nil?
+
+ pipeline.modified_paths.any? do |path|
+ @globs.any? do |glob|
+ File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH | File::FNM_EXTGLOB)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/rules/rule/clause/if.rb b/lib/gitlab/ci/build/rules/rule/clause/if.rb
new file mode 100644
index 00000000000..18c3b450f95
--- /dev/null
+++ b/lib/gitlab/ci/build/rules/rule/clause/if.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ class Rules::Rule::Clause::If < Rules::Rule::Clause
+ def initialize(expression)
+ @expression = expression
+ end
+
+ def satisfied_by?(pipeline, seed)
+ variables = seed.scoped_variables_hash
+
+ ::Gitlab::Ci::Pipeline::Expression::Statement.new(@expression, variables).truthful?
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 29a52b9da17..6e11c582750 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,7 +11,8 @@ module Gitlab
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Attributable
- ALLOWED_KEYS = %i[tags script only except type image services
+ ALLOWED_WHEN = %w[on_success on_failure always manual delayed].freeze
+ ALLOWED_KEYS = %i[tags script only except rules type image services
allow_failure type stage when start_in artifacts cache
dependencies needs before_script after_script variables
environment coverage retry parallel extends].freeze
@@ -19,12 +20,19 @@ module Gitlab
REQUIRED_BY_NEEDS = %i[stage].freeze
validations do
+ validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, required_keys: REQUIRED_BY_NEEDS, if: :has_needs?
validates :config, presence: true
validates :script, presence: true
validates :name, presence: true
validates :name, type: Symbol
+ validates :config,
+ disallowed_keys: {
+ in: %i[only except when start_in],
+ message: 'key may not be used with `rules`'
+ },
+ if: :has_rules?
with_options allow_nil: true do
validates :tags, array_of_strings: true
@@ -32,17 +40,19 @@ module Gitlab
validates :parallel, numericality: { only_integer: true,
greater_than_or_equal_to: 2,
less_than_or_equal_to: 50 }
- validates :when,
- inclusion: { in: %w[on_success on_failure always manual delayed],
- message: 'should be on_success, on_failure, ' \
- 'always, manual or delayed' }
+ validates :when, inclusion: {
+ in: ALLOWED_WHEN,
+ message: "should be one of: #{ALLOWED_WHEN.join(', ')}"
+ }
+
validates :dependencies, array_of_strings: true
validates :needs, array_of_strings: true
validates :extends, array_of_strings_or_string: true
+ validates :rules, array_of_hashes: true
end
validates :start_in, duration: { limit: '1 day' }, if: :delayed?
- validates :start_in, absence: true, unless: :delayed?
+ validates :start_in, absence: true, if: -> { has_rules? || !delayed? }
validate do
next unless dependencies.present?
@@ -91,6 +101,9 @@ module Gitlab
entry :except, Entry::Policy,
description: 'Refs policy this job will be executed for.'
+ entry :rules, Entry::Rules,
+ description: 'List of evaluable Rules to determine job inclusion.'
+
entry :variables, Entry::Variables,
description: 'Environment variables available for this job.'
@@ -112,7 +125,7 @@ module Gitlab
:parallel, :needs
attributes :script, :tags, :allow_failure, :when, :dependencies,
- :needs, :retry, :parallel, :extends, :start_in
+ :needs, :retry, :parallel, :extends, :start_in, :rules
def self.matching?(name, config)
!name.to_s.start_with?('.') &&
@@ -151,6 +164,10 @@ module Gitlab
self.when == 'delayed'
end
+ def has_rules?
+ @config.try(:key?, :rules)
+ end
+
def ignored?
allow_failure.nil? ? manual_action? : allow_failure
end
diff --git a/lib/gitlab/ci/config/entry/rules.rb b/lib/gitlab/ci/config/entry/rules.rb
new file mode 100644
index 00000000000..65cad0880f5
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/rules.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ class Rules < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+
+ validations do
+ validates :config, presence: true
+ validates :config, type: Array
+ end
+
+ def compose!(deps = nil)
+ super(deps) do
+ @config.each_with_index do |rule, index|
+ @entries[index] = ::Gitlab::Config::Entry::Factory.new(Entry::Rules::Rule)
+ .value(rule)
+ .with(key: "rule", parent: self, description: "rule definition.") # rubocop:disable CodeReuse/ActiveRecord
+ .create!
+ end
+
+ @entries.each_value do |entry|
+ entry.compose!(deps)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/config/entry/rules/rule.rb b/lib/gitlab/ci/config/entry/rules/rule.rb
new file mode 100644
index 00000000000..1f2a34ec90e
--- /dev/null
+++ b/lib/gitlab/ci/config/entry/rules/rule.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ class Config
+ module Entry
+ class Rules::Rule < ::Gitlab::Config::Entry::Node
+ include ::Gitlab::Config::Entry::Validatable
+ include ::Gitlab::Config::Entry::Attributable
+
+ CLAUSES = %i[if changes].freeze
+ ALLOWED_KEYS = %i[if changes when start_in].freeze
+ ALLOWED_WHEN = %w[on_success on_failure always never manual delayed].freeze
+
+ attributes :if, :changes, :when, :start_in
+
+ validations do
+ validates :config, presence: true
+ validates :config, type: { with: Hash }
+ validates :config, allowed_keys: ALLOWED_KEYS
+ validates :config, disallowed_keys: %i[start_in], unless: :specifies_delay?
+ validates :start_in, presence: true, if: :specifies_delay?
+ validates :start_in, duration: { limit: '1 day' }, if: :specifies_delay?
+
+ with_options allow_nil: true do
+ validates :if, expression: true
+ validates :changes, array_of_strings: true
+ validates :when, allowed_values: { in: ALLOWED_WHEN }
+ end
+ end
+
+ def specifies_delay?
+ self.when == 'delayed'
+ end
+
+ def default
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index b0ce7457926..1066331062b 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -7,7 +7,7 @@ module Gitlab
class Build < Seed::Base
include Gitlab::Utils::StrongMemoize
- delegate :dig, to: :@attributes
+ delegate :dig, to: :@seed_attributes
# When the `ci_dag_limit_needs` is enabled it uses the lower limit
LOW_NEEDS_LIMIT = 5
@@ -15,14 +15,20 @@ module Gitlab
def initialize(pipeline, attributes, previous_stages)
@pipeline = pipeline
- @attributes = attributes
+ @seed_attributes = attributes
@previous_stages = previous_stages
@needs_attributes = dig(:needs_attributes)
+ @using_rules = attributes.key?(:rules)
+ @using_only = attributes.key?(:only)
+ @using_except = attributes.key?(:except)
+
@only = Gitlab::Ci::Build::Policy
.fabricate(attributes.delete(:only))
@except = Gitlab::Ci::Build::Policy
.fabricate(attributes.delete(:except))
+ @rules = Gitlab::Ci::Build::Rules
+ .new(attributes.delete(:rules))
end
def name
@@ -31,8 +37,13 @@ module Gitlab
def included?
strong_memoize(:inclusion) do
- all_of_only? &&
- none_of_except?
+ if @using_rules
+ included_by_rules?
+ elsif @using_only || @using_except
+ all_of_only? && none_of_except?
+ else
+ true
+ end
end
end
@@ -45,19 +56,16 @@ module Gitlab
end
def attributes
- @attributes.merge(
- pipeline: @pipeline,
- project: @pipeline.project,
- user: @pipeline.user,
- ref: @pipeline.ref,
- tag: @pipeline.tag,
- trigger_request: @pipeline.legacy_trigger,
- protected: @pipeline.protected_ref?
- )
+ @seed_attributes
+ .deep_merge(pipeline_attributes)
+ .deep_merge(rules_attributes)
end
def bridge?
- @attributes.to_h.dig(:options, :trigger).present?
+ attributes_hash = @seed_attributes.to_h
+ attributes_hash.dig(:options, :trigger).present? ||
+ (attributes_hash.dig(:options, :bridge_needs).instance_of?(Hash) &&
+ attributes_hash.dig(:options, :bridge_needs, :pipeline).present?)
end
def to_resource
@@ -70,6 +78,18 @@ module Gitlab
end
end
+ def scoped_variables_hash
+ strong_memoize(:scoped_variables_hash) do
+ # This is a temporary piece of technical debt to allow us access
+ # to the CI variables to evaluate rules before we persist a Build
+ # with the result. We should refactor away the extra Build.new,
+ # but be able to get CI Variables directly from the Seed::Build.
+ ::Ci::Build.new(
+ @seed_attributes.merge(pipeline_attributes)
+ ).scoped_variables_hash
+ end
+ end
+
private
def all_of_only?
@@ -106,6 +126,28 @@ module Gitlab
HARD_NEEDS_LIMIT
end
end
+
+ def pipeline_attributes
+ {
+ pipeline: @pipeline,
+ project: @pipeline.project,
+ user: @pipeline.user,
+ ref: @pipeline.ref,
+ tag: @pipeline.tag,
+ trigger_request: @pipeline.legacy_trigger,
+ protected: @pipeline.protected_ref?
+ }
+ end
+
+ def included_by_rules?
+ rules_attributes[:when] != 'never'
+ end
+
+ def rules_attributes
+ strong_memoize(:rules_attributes) do
+ @using_rules ? @rules.evaluate(@pipeline, self).to_h.compact : {}
+ end
+ end
end
end
end
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 4190de73e1f..90278122361 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -46,11 +46,14 @@ sast:
SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
SAST_RUN_ANALYZER_TIMEOUT \
+ SAST_JAVA_VERSION \
ANT_HOME \
ANT_PATH \
GRADLE_PATH \
JAVA_OPTS \
JAVA_PATH \
+ JAVA_8_VERSION \
+ JAVA_11_VERSION \
MAVEN_CLI_OPTS \
MAVEN_PATH \
MAVEN_REPO_PATH \
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index 998130e5bd0..2e1eab270ff 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -55,7 +55,8 @@ module Gitlab
parallel: job[:parallel],
instance: job[:instance],
start_in: job[:start_in],
- trigger: job[:trigger]
+ trigger: job[:trigger],
+ bridge_needs: job[:needs]
}.compact }.compact
end
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index 0289e675c6b..374f929878e 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -20,8 +20,10 @@ module Gitlab
present_keys = value.try(:keys).to_a & options[:in]
if present_keys.any?
- record.errors.add(attribute, "contains disallowed keys: " +
- present_keys.join(', '))
+ message = options[:message] || "contains disallowed keys"
+ message += ": #{present_keys.join(', ')}"
+
+ record.errors.add(attribute, message)
end
end
end
@@ -65,6 +67,16 @@ module Gitlab
end
end
+ class ArrayOfHashesValidator < ActiveModel::EachValidator
+ include LegacyValidationHelpers
+
+ def validate_each(record, attribute, value)
+ unless value.is_a?(Array) && value.map { |hsh| hsh.is_a?(Hash) }.all?
+ record.errors.add(attribute, 'should be an array of hashes')
+ end
+ end
+ end
+
class ArrayOrStringValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless value.is_a?(Array) || value.is_a?(String)
@@ -231,6 +243,14 @@ module Gitlab
end
end
+ class ExpressionValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ unless value.is_a?(String) && ::Gitlab::Ci::Pipeline::Expression::Statement.new(value).valid?
+ record.errors.add(attribute, 'Invalid expression syntax')
+ end
+ end
+ end
+
class PortNamePresentAndUniqueValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless value.is_a?(Array)
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index 37fadb47736..75d9a2d55b9 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -129,8 +129,6 @@ module Gitlab
SAMPLE_DATA
end
- private
-
def checkout_sha(repository, newrev, ref)
# Checkout sha is nil when we remove branch or tag
return if Gitlab::Git.blank_ref?(newrev)
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index 24d752b8a4b..2a8bcd015a8 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -39,6 +39,17 @@ module Gitlab
end
end
+ def includes_default_branch?
+ # If the branch doesn't have a default branch yet, we presume the
+ # first branch pushed will be the default.
+ return true unless project.default_branch.present?
+
+ enum_for(:changes_refs).any? do |_oldrev, _newrev, ref|
+ Gitlab::Git.branch_ref?(ref) &&
+ Gitlab::Git.branch_name(ref) == project.default_branch
+ end
+ end
+
private
def deserialize_changes(changes)
diff --git a/lib/gitlab/grape_logging/loggers/client_env_logger.rb b/lib/gitlab/grape_logging/loggers/client_env_logger.rb
new file mode 100644
index 00000000000..3acc6f6a2ef
--- /dev/null
+++ b/lib/gitlab/grape_logging/loggers/client_env_logger.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+# This is a fork of
+# https://github.com/aserafin/grape_logging/blob/master/lib/grape_logging/loggers/client_env.rb
+# to use remote_ip instead of ip.
+module Gitlab
+ module GrapeLogging
+ module Loggers
+ class ClientEnvLogger < ::GrapeLogging::Loggers::Base
+ def parameters(request, _)
+ { remote_ip: request.env["HTTP_X_FORWARDED_FOR"] || request.env["REMOTE_ADDR"], ua: request.env["HTTP_USER_AGENT"] }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/repository_cache_adapter.rb b/lib/gitlab/repository_cache_adapter.rb
index e40c366ed02..75503ee1789 100644
--- a/lib/gitlab/repository_cache_adapter.rb
+++ b/lib/gitlab/repository_cache_adapter.rb
@@ -23,6 +23,37 @@ module Gitlab
end
end
+ # Caches and strongly memoizes the method as a Redis Set.
+ #
+ # This only works for methods that do not take any arguments. The method
+ # should return an Array of Strings to be cached.
+ #
+ # In addition to overriding the named method, a "name_include?" method is
+ # defined. This uses the "SISMEMBER" query to efficiently check membership
+ # without needing to load the entire set into memory.
+ #
+ # name - The name of the method to be cached.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil.
+ def cache_method_as_redis_set(name, fallback: nil)
+ uncached_name = alias_uncached_method(name)
+
+ define_method(name) do
+ cache_method_output_as_redis_set(name, fallback: fallback) do
+ __send__(uncached_name) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
+ define_method("#{name}_include?") do |value|
+ # If the cache isn't populated, we can't rely on it
+ return redis_set_cache.include?(name, value) if redis_set_cache.exist?(name)
+
+ # Since we have to pull all branch names to populate the cache, use
+ # the data we already have to answer the query just this once
+ __send__(name).include?(value) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
# Caches truthy values from the method. All values are strongly memoized,
# and cached in RequestStore.
#
@@ -84,6 +115,11 @@ module Gitlab
raise NotImplementedError
end
+ # RepositorySetCache to be used. Should be overridden by the including class
+ def redis_set_cache
+ raise NotImplementedError
+ end
+
# List of cached methods. Should be overridden by the including class
def cached_methods
raise NotImplementedError
@@ -100,6 +136,18 @@ module Gitlab
end
end
+ # Caches and strongly memoizes the supplied block as a Redis Set. The result
+ # will be provided as a sorted array.
+ #
+ # name - The name of the method to be cached.
+ # fallback - A value to fall back to if the repository does not exist, or
+ # in case of a Git error. Defaults to nil.
+ def cache_method_output_as_redis_set(name, fallback: nil, &block)
+ memoize_method_output(name, fallback: fallback) do
+ redis_set_cache.fetch(name, &block).sort
+ end
+ end
+
# Caches truthy values from the supplied block. All values are strongly
# memoized, and cached in RequestStore.
#
@@ -154,6 +202,7 @@ module Gitlab
clear_memoization(memoizable_name(name))
end
+ expire_redis_set_method_caches(methods)
expire_request_store_method_caches(methods)
end
@@ -169,6 +218,10 @@ module Gitlab
end
end
+ def expire_redis_set_method_caches(methods)
+ methods.each { |name| redis_set_cache.expire(name) }
+ end
+
# All cached repository methods depend on the existence of a Git repository,
# so if the repository doesn't exist, we already know not to call it.
def fallback_early?(method_name)
diff --git a/lib/gitlab/repository_set_cache.rb b/lib/gitlab/repository_set_cache.rb
new file mode 100644
index 00000000000..fb634328a95
--- /dev/null
+++ b/lib/gitlab/repository_set_cache.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+# Interface to the Redis-backed cache store for keys that use a Redis set
+module Gitlab
+ class RepositorySetCache
+ attr_reader :repository, :namespace, :expires_in
+
+ def initialize(repository, extra_namespace: nil, expires_in: 2.weeks)
+ @repository = repository
+ @namespace = "#{repository.full_path}:#{repository.project.id}"
+ @namespace = "#{@namespace}:#{extra_namespace}" if extra_namespace
+ @expires_in = expires_in
+ end
+
+ def cache_key(type)
+ [type, namespace, 'set'].join(':')
+ end
+
+ def expire(key)
+ with { |redis| redis.del(cache_key(key)) }
+ end
+
+ def exist?(key)
+ with { |redis| redis.exists(cache_key(key)) }
+ end
+
+ def read(key)
+ with { |redis| redis.smembers(cache_key(key)) }
+ end
+
+ def write(key, value)
+ full_key = cache_key(key)
+
+ with do |redis|
+ redis.multi do
+ redis.del(full_key)
+
+ # Splitting into groups of 1000 prevents us from creating a too-long
+ # Redis command
+ value.in_groups_of(1000, false) { |subset| redis.sadd(full_key, subset) }
+
+ redis.expire(full_key, expires_in)
+ end
+ end
+
+ value
+ end
+
+ def fetch(key, &block)
+ if exist?(key)
+ read(key)
+ else
+ write(key, yield)
+ end
+ end
+
+ def include?(key, value)
+ with { |redis| redis.sismember(cache_key(key), value) }
+ end
+
+ private
+
+ def with(&blk)
+ Gitlab::Redis::Cache.with(&blk) # rubocop:disable CodeReuse/ActiveRecord
+ end
+ end
+end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 764db14d720..005cb3112b8 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -39,9 +39,14 @@ module Gitlab
# development and test. If you need development and test to behave
# just the same as production you can use this instead of
# track_exception.
+ #
+ # If the exception implements the method `sentry_extra_data` and that method
+ # returns a Hash, then the return value of that method will be merged into
+ # `extra`. Exceptions can use this mechanism to provide structured data
+ # to sentry in addition to their message and back-trace.
def self.track_acceptable_exception(exception, issue_url: nil, extra: {})
if enabled?
- extra[:issue_url] = issue_url if issue_url
+ extra = build_extra_data(exception, issue_url, extra)
context # Make sure we've set everything we know in the context
Raven.capture_exception(exception, tags: default_tags, extra: extra)
@@ -58,5 +63,15 @@ module Gitlab
locale: I18n.locale
}
end
+
+ def self.build_extra_data(exception, issue_url, extra)
+ exception.try(:sentry_extra_data)&.tap do |data|
+ extra.merge!(data) if data.is_a?(Hash)
+ end
+
+ extra.merge({ issue_url: issue_url }.compact)
+ end
+
+ private_class_method :build_extra_data
end
end
diff --git a/lib/gitlab/sidekiq_middleware/metrics.rb b/lib/gitlab/sidekiq_middleware/metrics.rb
index b06ffa9c121..3dc9521ee8b 100644
--- a/lib/gitlab/sidekiq_middleware/metrics.rb
+++ b/lib/gitlab/sidekiq_middleware/metrics.rb
@@ -3,6 +3,10 @@
module Gitlab
module SidekiqMiddleware
class Metrics
+ # SIDEKIQ_LATENCY_BUCKETS are latency histogram buckets better suited to Sidekiq
+ # timeframes than the DEFAULT_BUCKET definition. Defined in seconds.
+ SIDEKIQ_LATENCY_BUCKETS = [0.1, 0.25, 0.5, 1, 2.5, 5, 10, 60, 300, 600].freeze
+
def initialize
@metrics = init_metrics
end
@@ -31,7 +35,7 @@ module Gitlab
def init_metrics
{
- sidekiq_jobs_completion_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete sidekiq job'),
+ sidekiq_jobs_completion_seconds: ::Gitlab::Metrics.histogram(:sidekiq_jobs_completion_seconds, 'Seconds to complete sidekiq job', buckets: SIDEKIQ_LATENCY_BUCKETS),
sidekiq_jobs_failed_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_failed_total, 'Sidekiq jobs failed'),
sidekiq_jobs_retried_total: ::Gitlab::Metrics.counter(:sidekiq_jobs_retried_total, 'Sidekiq jobs retried'),
sidekiq_running_jobs: ::Gitlab::Metrics.gauge(:sidekiq_running_jobs, 'Number of Sidekiq jobs running', {}, :livesum)
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 038553c5dd7..353298e67b3 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -100,9 +100,7 @@ module Gitlab
.merge(services_usage)
.merge(approximate_counts)
}.tap do |data|
- if Feature.enabled?(:group_overview_security_dashboard)
- data[:counts][:user_preferences] = user_preferences_usage
- end
+ data[:counts][:user_preferences] = user_preferences_usage
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -190,8 +188,8 @@ module Gitlab
{} # augmented in EE
end
- def count(relation, fallback: -1)
- relation.count
+ def count(relation, count_by: nil, fallback: -1)
+ count_by ? relation.count(count_by) : relation.count
rescue ActiveRecord::StatementInvalid
fallback
end
diff --git a/lib/gitlab/usage_data_counters/note_counter.rb b/lib/gitlab/usage_data_counters/note_counter.rb
index e93a0bcfa27..672450ec82b 100644
--- a/lib/gitlab/usage_data_counters/note_counter.rb
+++ b/lib/gitlab/usage_data_counters/note_counter.rb
@@ -4,7 +4,7 @@ module Gitlab::UsageDataCounters
class NoteCounter < BaseCounter
KNOWN_EVENTS = %w[create].freeze
PREFIX = 'note'
- COUNTABLE_TYPES = %w[Snippet].freeze
+ COUNTABLE_TYPES = %w[Snippet Commit MergeRequest].freeze
class << self
def redis_key(event, noteable_type)
@@ -24,9 +24,9 @@ module Gitlab::UsageDataCounters
end
def totals
- {
- snippet_comment: read(:create, 'Snippet')
- }
+ COUNTABLE_TYPES.map do |countable_type|
+ [:"#{countable_type.underscore}_comment", read(:create, countable_type)]
+ end.to_h
end
private
diff --git a/lib/gt_one_coercion.rb b/lib/gt_one_coercion.rb
deleted file mode 100644
index 99be51bc8c6..00000000000
--- a/lib/gt_one_coercion.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class GtOneCoercion < Virtus::Attribute
- def coerce(value)
- [1, value.to_i].max
- end
-end
diff --git a/lib/prometheus/cleanup_multiproc_dir_service.rb b/lib/prometheus/cleanup_multiproc_dir_service.rb
new file mode 100644
index 00000000000..6418b4de166
--- /dev/null
+++ b/lib/prometheus/cleanup_multiproc_dir_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Prometheus
+ class CleanupMultiprocDirService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute
+ FileUtils.rm_rf(old_metrics) if old_metrics
+ end
+
+ private
+
+ def old_metrics
+ strong_memoize(:old_metrics) do
+ Dir[File.join(multiprocess_files_dir, '*.db')] if multiprocess_files_dir
+ end
+ end
+
+ def multiprocess_files_dir
+ ::Prometheus::Client.configuration.multiprocess_files_dir
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/uploads/legacy.rake b/lib/tasks/gitlab/uploads/legacy.rake
new file mode 100644
index 00000000000..18fb8afe455
--- /dev/null
+++ b/lib/tasks/gitlab/uploads/legacy.rake
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :uploads do
+ namespace :legacy do
+ desc "GitLab | Uploads | Migrate all legacy attachments"
+ task migrate: :environment do
+ class Upload < ApplicationRecord
+ self.table_name = 'uploads'
+
+ include ::EachBatch
+ end
+
+ migration = 'LegacyUploadsMigrator'.freeze
+ batch_size = 5000
+ delay_interval = 5.minutes.to_i
+
+ Upload.where(uploader: 'AttachmentUploader').each_batch(of: batch_size) do |relation, index|
+ start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+ delay = index * delay_interval
+
+ BackgroundMigrationWorker.perform_in(delay, migration, [start_id, end_id])
+ end
+ end
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 6227ca9afc3..6776bc3ea24 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -469,6 +469,9 @@ msgstr ""
msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
+msgid "A Let's Encrypt SSL certificate can not be obtained until your domain is verified."
+msgstr ""
+
msgid "A Let's Encrypt account will be configured for this GitLab installation using your email address. You will receive emails to warn of expiring certificates."
msgstr ""
@@ -3943,6 +3946,9 @@ msgstr ""
msgid "Disable"
msgstr ""
+msgid "Disable email notifications"
+msgstr ""
+
msgid "Disable for this project"
msgstr ""
@@ -4141,6 +4147,9 @@ msgstr ""
msgid "Edit public deploy key"
msgstr ""
+msgid "Edit stage"
+msgstr ""
+
msgid "Edit wiki page"
msgstr ""
@@ -5181,6 +5190,9 @@ msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate link to chart"
+msgstr ""
+
msgid "Generate new export"
msgstr ""
@@ -5238,6 +5250,9 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later."
+msgstr ""
+
msgid "GitLab member or Email address"
msgstr ""
@@ -5478,6 +5493,9 @@ msgstr ""
msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
msgstr ""
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
@@ -5505,6 +5523,9 @@ msgstr ""
msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
msgstr ""
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
msgstr ""
@@ -5646,6 +5667,9 @@ msgstr ""
msgid "Hide shared projects"
msgstr ""
+msgid "Hide stage"
+msgstr ""
+
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
@@ -6534,6 +6558,9 @@ msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
msgstr[1] ""
+msgid "Link copied to clipboard"
+msgstr ""
+
msgid "Linked emails (%{email_count})"
msgstr ""
@@ -7586,6 +7613,9 @@ msgstr ""
msgid "Notifications"
msgstr ""
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
msgid "Notifications off"
msgstr ""
@@ -7990,6 +8020,9 @@ msgstr ""
msgid "Pipelines for last year"
msgstr ""
+msgid "Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more on the documentation for Pipelines for Merged Results."
+msgstr ""
+
msgid "Pipelines settings for '%{project_name}' were successfully updated."
msgstr ""
@@ -8146,6 +8179,9 @@ msgstr ""
msgid "Please add a list to your board first"
msgstr ""
+msgid "Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
@@ -9336,6 +9372,9 @@ msgstr ""
msgid "Remove spent time"
msgstr ""
+msgid "Remove stage"
+msgstr ""
+
msgid "Remove time estimate"
msgstr ""
@@ -9528,6 +9567,9 @@ msgstr ""
msgid "Resend invite"
msgstr ""
+msgid "Resend it"
+msgstr ""
+
msgid "Reset health check access token"
msgstr ""
@@ -11121,9 +11163,6 @@ msgstr ""
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr ""
-msgid "The code of a detached pipeline is tested against the source branch instead of merged results"
-msgstr ""
-
msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request."
msgstr ""
@@ -11649,6 +11688,9 @@ msgstr ""
msgid "This setting can be overridden in each project."
msgstr ""
+msgid "This setting will override user notification preferences for all project members."
+msgstr ""
+
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
@@ -12304,6 +12346,9 @@ msgstr ""
msgid "Update failed"
msgstr ""
+msgid "Update it"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -12843,15 +12888,9 @@ msgstr ""
msgid "WikiMarkdownTip|To link to a (new) page, simply type %{link_example}"
msgstr ""
-msgid "WikiNewPagePlaceholder|how-to-setup"
-msgstr ""
-
msgid "WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories."
msgstr ""
-msgid "WikiNewPageTitle|New Wiki Page"
-msgstr ""
-
msgid "WikiPageConfirmDelete|Are you sure you want to delete this page?"
msgstr ""
@@ -12867,19 +12906,16 @@ msgstr ""
msgid "WikiPageConflictMessage|the page"
msgstr ""
-msgid "WikiPageCreate|Create %{page_title}"
+msgid "WikiPageCreate|Create %{pageTitle}"
msgstr ""
-msgid "WikiPageEdit|Update %{page_title}"
-msgstr ""
-
-msgid "WikiPage|Page slug"
+msgid "WikiPageEdit|Update %{pageTitle}"
msgstr ""
msgid "WikiPage|Write your content or drag files here…"
msgstr ""
-msgid "Wiki|Create Page"
+msgid "Wiki|Create New Page"
msgstr ""
msgid "Wiki|Create page"
@@ -12900,6 +12936,9 @@ msgstr ""
msgid "Wiki|Page history"
msgstr ""
+msgid "Wiki|Page title"
+msgstr ""
+
msgid "Wiki|Page version"
msgstr ""
@@ -13356,18 +13395,15 @@ msgstr ""
msgid "Your request for access has been queued for review."
msgstr ""
-msgid "a Zoom call was added to this issue"
-msgstr ""
-
-msgid "a Zoom call was removed from this issue"
-msgstr ""
-
msgid "a deleted user"
msgstr ""
msgid "added %{created_at_timeago}"
msgstr ""
+msgid "added a Zoom call to this issue"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -13904,6 +13940,9 @@ msgstr ""
msgid "remove due date"
msgstr ""
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
msgid "rendered diff"
msgstr ""
diff --git a/package.json b/package.json
index 2b9a00d1cbd..97ea872ee61 100644
--- a/package.json
+++ b/package.json
@@ -30,13 +30,13 @@
"webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
},
"dependencies": {
- "@babel/core": "^7.4.4",
- "@babel/plugin-proposal-class-properties": "^7.4.4",
+ "@babel/core": "^7.5.5",
+ "@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-json-strings": "^7.2.0",
"@babel/plugin-proposal-private-methods": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
- "@babel/preset-env": "^7.4.4",
+ "@babel/preset-env": "^7.5.5",
"@gitlab/csslab": "^1.9.0",
"@gitlab/svgs": "^1.68.0",
"@gitlab/ui": "5.15.0",
@@ -145,7 +145,7 @@
"xterm": "^3.5.0"
},
"devDependencies": {
- "@babel/plugin-transform-modules-commonjs": "^7.2.0",
+ "@babel/plugin-transform-modules-commonjs": "^7.5.0",
"@gitlab/eslint-config": "^1.6.0",
"@gitlab/eslint-plugin-i18n": "^1.1.0",
"@gitlab/eslint-plugin-vue-i18n": "^1.2.0",
diff --git a/qa/Dockerfile b/qa/Dockerfile
index 74be373b8e8..3309f5b6ce3 100644
--- a/qa/Dockerfile
+++ b/qa/Dockerfile
@@ -47,9 +47,13 @@ RUN export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)" && \
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - && \
apt-get update -y && apt-get install google-cloud-sdk kubectl -y
-WORKDIR /home/qa
-COPY ./Gemfile* ./
-RUN bundle install
-COPY ./ ./
+WORKDIR /home/gitlab/qa
+COPY ./qa/Gemfile* /home/gitlab/qa/
+COPY ./config/initializers/0_inject_enterprise_edition_module.rb /home/gitlab/config/initializers/
+COPY ./lib/gitlab.rb /home/gitlab/lib/
+COPY ./INSTALLATION_TYPE /home/gitlab/
+COPY ./VERSION /home/gitlab/
+RUN cd /home/gitlab/qa/ && bundle install
+COPY ./qa /home/gitlab/qa
ENTRYPOINT ["bin/test"]
diff --git a/qa/Gemfile b/qa/Gemfile
index 6abc0d622ad..f04ecb13879 100644
--- a/qa/Gemfile
+++ b/qa/Gemfile
@@ -1,6 +1,7 @@
source 'https://rubygems.org'
gem 'gitlab-qa'
+gem 'activesupport', '5.2.3' # This should stay in sync with the root's Gemfile
gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'capybara', '~> 2.16.1'
gem 'capybara-screenshot', '~> 1.0.18'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index bf051a115b5..d582d77c5cd 100644
--- a/qa/Gemfile.lock
+++ b/qa/Gemfile.lock
@@ -1,9 +1,9 @@
GEM
remote: https://rubygems.org/
specs:
- activesupport (5.1.4)
+ activesupport (5.2.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
- i18n (~> 0.7)
+ i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
addressable (2.5.2)
@@ -28,7 +28,7 @@ GEM
childprocess (0.9.0)
ffi (~> 1.0, >= 1.0.11)
coderay (1.1.2)
- concurrent-ruby (1.0.5)
+ concurrent-ruby (1.1.5)
diff-lcs (1.3)
domain_name (0.5.20170404)
unf (>= 0.0.5, < 1.0.0)
@@ -38,7 +38,7 @@ GEM
gitlab-qa (4.0.0)
http-cookie (1.0.3)
domain_name (~> 0.5)
- i18n (0.9.1)
+ i18n (1.6.0)
concurrent-ruby (~> 1.0)
knapsack (1.17.1)
rake
@@ -50,7 +50,7 @@ GEM
mime-types-data (3.2016.0521)
mini_mime (1.0.0)
mini_portile2 (2.4.0)
- minitest (5.11.1)
+ minitest (5.11.3)
netrc (0.11.0)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
@@ -94,7 +94,7 @@ GEM
childprocess (~> 0.5)
rubyzip (~> 1.2, >= 1.2.2)
thread_safe (0.3.6)
- tzinfo (1.2.4)
+ tzinfo (1.2.5)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
@@ -106,6 +106,7 @@ PLATFORMS
ruby
DEPENDENCIES
+ activesupport (= 5.2.3)
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
diff --git a/qa/README.md b/qa/README.md
index bab19665dac..97555f8d0c2 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -123,10 +123,11 @@ To set multiple cookies, separate them with the `;` character, for example: `QA_
Once you have made changes to the CE/EE repositories, you may want to build a
Docker image to test locally instead of waiting for the `gitlab-ce-qa` or
-`gitlab-ee-qa` nightly builds. To do that, you can run from this directory:
+`gitlab-ee-qa` nightly builds. To do that, you can run **from the top `gitlab`
+directory** (one level up from this directory):
```sh
-docker build -t gitlab/gitlab-ce-qa:nightly .
+docker build -t gitlab/gitlab-ce-qa:nightly --file ./qa/Dockerfile ./
```
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
diff --git a/qa/qa.rb b/qa/qa.rb
index 18fb4509dce..8be2a289422 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -4,6 +4,9 @@ $: << File.expand_path(File.dirname(__FILE__))
Encoding.default_external = 'UTF-8'
+require_relative '../lib/gitlab'
+require_relative '../config/initializers/0_inject_enterprise_edition_module'
+
module QA
##
# GitLab QA runtime classes, mostly singletons.
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
index 567c6a83ddf..458072b1507 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127
+ context 'Create', :quarantine do
describe 'File templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index c09c65a57a5..9ff7919f199 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ context 'Create', :quarantine do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127
describe 'Web IDE file templates' do
include Runtime::Fixtures
diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb
new file mode 100644
index 00000000000..0c598a360af
--- /dev/null
+++ b/spec/controllers/concerns/confirm_email_warning_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ConfirmEmailWarning do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ controller(ApplicationController) do
+ # `described_class` is not available in this context
+ include ConfirmEmailWarning # rubocop:disable RSpec/DescribedClass
+
+ def index
+ head :ok
+ end
+ end
+
+ RSpec::Matchers.define :set_confirm_warning_for do |email|
+ match do |response|
+ expect(response).to set_flash.now[:warning].to include("Please check your email (#{email}) to verify that you own this address.")
+ end
+ end
+
+ describe 'confirm email flash warning' do
+ context 'when not signed in' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'with a confirmed user' do
+ let(:user) { create(:user) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'with an unconfirmed user' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ context 'when executing a peek request' do
+ before do
+ request.path = '/-/peek'
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a json request' do
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a post request' do
+ before do
+ post :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a get request' do
+ before do
+ get :index
+ end
+
+ context 'with an unconfirmed email address present' do
+ let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'unconfirmed@gitlab.com') }
+
+ it { is_expected.to set_confirm_warning_for(user.unconfirmed_email) }
+ end
+
+ context 'without an unconfirmed email address present' do
+ it { is_expected.to set_confirm_warning_for(user.email) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 908c564e761..0c3dd971582 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -172,7 +172,7 @@ describe Groups::GroupMembersController do
it '[JS] removes user from members' do
delete :destroy, params: { group_id: group, id: member }, xhr: true
- expect(response).to be_success
+ expect(response).to be_successful
expect(group.members).not_to include member
end
end
diff --git a/spec/controllers/groups/milestones_controller_spec.rb b/spec/controllers/groups/milestones_controller_spec.rb
index bf164aeed38..41927907fd1 100644
--- a/spec/controllers/groups/milestones_controller_spec.rb
+++ b/spec/controllers/groups/milestones_controller_spec.rb
@@ -186,7 +186,7 @@ describe Groups::MilestonesController do
it "removes milestone" do
delete :destroy, params: { group_id: group.to_param, id: milestone.iid }, format: :js
- expect(response).to be_success
+ expect(response).to be_successful
expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
end
end
diff --git a/spec/controllers/health_check_controller_spec.rb b/spec/controllers/health_check_controller_spec.rb
index 92f005faf4a..b48b7dc86e0 100644
--- a/spec/controllers/health_check_controller_spec.rb
+++ b/spec/controllers/health_check_controller_spec.rb
@@ -33,14 +33,14 @@ describe HealthCheckController do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
it 'supports passing the token in query params' do
get :index, params: { token: token }
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
end
@@ -54,14 +54,14 @@ describe HealthCheckController do
it 'supports successful plaintext response' do
get :index
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'text/plain'
end
it 'supports successful json response' do
get :index, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
@@ -69,7 +69,7 @@ describe HealthCheckController do
it 'supports successful xml response' do
get :index, format: :xml
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/xml'
expect(xml_response['healthy']).to be true
end
@@ -77,7 +77,7 @@ describe HealthCheckController do
it 'supports successful responses for specific checks' do
get :index, params: { checks: 'email' }, format: :json
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'application/json'
expect(json_response['healthy']).to be true
end
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 43c910da7a5..03b6e85b653 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -114,7 +114,7 @@ describe HelpController do
path: 'user/project/img/labels_default_v12_1'
},
format: :png
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq 'image/png'
expect(response.headers['Content-Disposition']).to match(/^inline;/)
end
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 753eb432c5e..3bed117deb0 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -10,7 +10,7 @@ describe Profiles::KeysController do
it "does not generally work" do
get :get_keys, params: { username: 'not-existent' }
- expect(response).not_to be_success
+ expect(response).not_to be_successful
end
end
@@ -18,7 +18,7 @@ describe Profiles::KeysController do
it "does generally work" do
get :get_keys, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
it "renders all keys separated with a new line" do
@@ -41,7 +41,7 @@ describe Profiles::KeysController do
it "does generally work" do
get :get_keys, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
it "renders all non deploy keys separated with a new line" do
diff --git a/spec/controllers/projects/ci/lints_controller_spec.rb b/spec/controllers/projects/ci/lints_controller_spec.rb
index 96e82b7086c..14128fb5b0e 100644
--- a/spec/controllers/projects/ci/lints_controller_spec.rb
+++ b/spec/controllers/projects/ci/lints_controller_spec.rb
@@ -20,9 +20,7 @@ describe Projects::Ci::LintsController do
get :show, params: { namespace_id: project.namespace, project_id: project }
end
- it 'is success' do
- expect(response).to be_success
- end
+ it { expect(response).to be_successful }
it 'renders show page' do
expect(response).to render_template :show
@@ -78,9 +76,7 @@ describe Projects::Ci::LintsController do
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
end
- it 'is success' do
- expect(response).to be_success
- end
+ it { expect(response).to be_successful }
it 'render show page' do
expect(response).to render_template :show
diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb
index 58a1d96d010..afd5cb15e0f 100644
--- a/spec/controllers/projects/commit_controller_spec.rb
+++ b/spec/controllers/projects/commit_controller_spec.rb
@@ -45,14 +45,14 @@ describe Projects::CommitController do
it 'handles binary files' do
go(id: TestEnv::BRANCH_SHA['binary-encoding'], format: 'html')
- expect(response).to be_success
+ expect(response).to be_successful
end
shared_examples "export as" do |format|
it "does generally work" do
go(id: commit.id, format: format)
- expect(response).to be_success
+ expect(response).to be_successful
end
it "generates it" do
@@ -110,7 +110,7 @@ describe Projects::CommitController do
id: commit.id
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -177,7 +177,7 @@ describe Projects::CommitController do
id: commit.id
})
- expect(response).not_to be_success
+ expect(response).not_to be_successful
expect(response).to have_gitlab_http_status(404)
end
end
@@ -234,7 +234,7 @@ describe Projects::CommitController do
id: master_pickable_commit.id
})
- expect(response).not_to be_success
+ expect(response).not_to be_successful
expect(response).to have_gitlab_http_status(404)
end
end
diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb
index 9db1ac2a46c..9c4d6fdcb2a 100644
--- a/spec/controllers/projects/commits_controller_spec.rb
+++ b/spec/controllers/projects/commits_controller_spec.rb
@@ -79,7 +79,7 @@ describe Projects::CommitsController do
end
it "renders as atom" do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq('application/atom+xml')
end
@@ -104,7 +104,7 @@ describe Projects::CommitsController do
end
it "renders as HTML" do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.content_type).to eq('text/html')
end
end
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 48a92a772dc..9afc46c4be9 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -19,7 +19,7 @@ describe Projects::CompareController do
end
it 'returns successfully' do
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -49,7 +49,7 @@ describe Projects::CompareController do
it 'shows some diffs with ignore whitespace change option' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
diff_file = assigns(:diffs).diff_files.first
expect(diff_file).not_to be_nil
expect(assigns(:commits).length).to be >= 1
@@ -67,7 +67,7 @@ describe Projects::CompareController do
it 'sets the diffs and commits ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs).diff_files.first).not_to be_nil
expect(assigns(:commits).length).to be >= 1
end
@@ -81,7 +81,7 @@ describe Projects::CompareController do
it 'sets empty diff and commit ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs)).to eq([])
expect(assigns(:commits)).to eq([])
end
@@ -94,7 +94,7 @@ describe Projects::CompareController do
it 'sets empty diff and commit ivars' do
show_request
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:diffs)).to eq([])
expect(assigns(:commits)).to eq([])
end
diff --git a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
index 8fc3ae0aa32..b828c678d0c 100644
--- a/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
@@ -16,7 +16,7 @@ describe Projects::CycleAnalytics::EventsController do
it 'is empty' do
get_issue
- expect(response).to be_success
+ expect(response).to be_successful
expect(JSON.parse(response.body)['events']).to be_empty
end
end
@@ -32,7 +32,7 @@ describe Projects::CycleAnalytics::EventsController do
it 'is not empty' do
get_issue
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'contains event detais' do
@@ -49,7 +49,7 @@ describe Projects::CycleAnalytics::EventsController do
it 'is empty' do
get_issue(additional_params: { cycle_analytics: { start_date: 7 } })
- expect(response).to be_success
+ expect(response).to be_successful
expect(JSON.parse(response.body)['events']).to be_empty
end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 5e6ceef2517..65eee7b8ead 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -21,7 +21,7 @@ describe Projects::CycleAnalyticsController do
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -34,7 +34,7 @@ describe Projects::CycleAnalyticsController do
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:cycle_analytics_no_data)).to eq(true)
end
end
@@ -55,7 +55,7 @@ describe Projects::CycleAnalyticsController do
project_id: project
})
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns(:cycle_analytics_no_data)).to eq(false)
end
end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 3816e1c7a31..ce977f26ec6 100644
--- a/spec/controllers/projects/merge_requests/creations_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
@@ -28,7 +28,7 @@ describe Projects::MergeRequests::CreationsController do
it 'renders new merge request widget template' do
get :new, params: get_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -56,7 +56,7 @@ describe Projects::MergeRequests::CreationsController do
it 'limits total commits' do
get :new, params: large_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
total = assigns(:total_commit_count)
expect(assigns(:commits)).to be_an Array
@@ -70,7 +70,7 @@ describe Projects::MergeRequests::CreationsController do
it 'shows total commits' do
get :new, params: large_diff_params
- expect(response).to be_success
+ expect(response).to be_successful
total = assigns(:total_commit_count)
expect(assigns(:commits)).to be_an CommitCollection
@@ -89,7 +89,7 @@ describe Projects::MergeRequests::CreationsController do
get :diffs, params: get_diff_params.merge(format: 'json')
- expect(response).to be_success
+ expect(response).to be_successful
expect(assigns[:diffs]).to be_nil
end
end
diff --git a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
index d940d226176..ac3e9901123 100644
--- a/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/diffs_controller_spec.rb
@@ -66,7 +66,7 @@ describe Projects::MergeRequests::DiffsController do
end
it 'renders' do
- expect(response).to be_success
+ expect(response).to be_successful
expect(response.body).to have_content('Subproject commit')
end
end
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
index b1dc6a65dd4..11b1eaf11b7 100644
--- a/spec/controllers/projects/merge_requests_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -57,7 +57,7 @@ describe Projects::MergeRequestsController do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -66,7 +66,7 @@ describe Projects::MergeRequestsController do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
context "that is invalid" do
@@ -75,7 +75,7 @@ describe Projects::MergeRequestsController do
it "renders merge request page" do
go(format: :html)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
@@ -124,7 +124,7 @@ describe Projects::MergeRequestsController do
it "renders merge request page" do
go(format: :json)
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 9b2025b836c..cbf9d437909 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -139,7 +139,7 @@ describe Projects::MilestonesController do
expect(issue.milestone_id).to eq(milestone.id)
delete :destroy, params: { namespace_id: project.namespace.id, project_id: project.id, id: milestone.iid }, format: :js
- expect(response).to be_success
+ expect(response).to be_successful
expect(Event.recent.first.action).to eq(Event::DESTROYED)
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 9a50ea79f5e..089d06e11eb 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -177,18 +177,22 @@ describe Projects::PipelinesController do
end
it 'does not perform N + 1 queries' do
+ # Set up all required variables
+ get_pipeline_json
+
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- create_build('test', 1, 'rspec 1')
- create_build('test', 1, 'spinach 0')
- create_build('test', 1, 'spinach 1')
- create_build('test', 1, 'audit')
- create_build('post deploy', 3, 'pages 1')
- create_build('post deploy', 3, 'pages 2')
+ first_build = pipeline.builds.first
+ first_build.tag_list << [:hello, :world]
+ create(:deployment, deployable: first_build)
+
+ second_build = pipeline.builds.second
+ second_build.tag_list << [:docker, :ruby]
+ create(:deployment, deployable: second_build)
new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- expect(new_count).to be_within(12).of(control_count)
+ expect(new_count).to be_within(1).of(control_count)
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 4141e41c7a7..5130e26c928 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -158,7 +158,7 @@ describe Projects::ProjectMembersController do
id: member
}, xhr: true
- expect(response).to be_success
+ expect(response).to be_successful
expect(project.members).not_to include member
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index b958f419a19..8b43d1264b2 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -67,9 +67,9 @@ describe Projects::RawController do
attributes = {
message: 'Action_Rate_Limiter_Request',
env: :raw_blob_request_limit,
- ip: '0.0.0.0',
+ remote_ip: '0.0.0.0',
request_method: 'GET',
- fullpath: "/#{project.full_path}/raw/#{file_path}"
+ path: "/#{project.full_path}/raw/#{file_path}"
}
expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index 6db98f2428b..646c7a7db7c 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -49,7 +49,7 @@ describe Projects::RefsController do
expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
xhr_get(:js)
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'renders JSON' do
@@ -57,7 +57,7 @@ describe Projects::RefsController do
xhr_get(:json)
- expect(response).to be_success
+ expect(response).to be_successful
expect(json_response).to be_kind_of(Array)
end
end
diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb
index 68eabce8513..563b61962cf 100644
--- a/spec/controllers/projects/services_controller_spec.rb
+++ b/spec/controllers/projects/services_controller_spec.rb
@@ -159,7 +159,7 @@ describe Projects::ServicesController do
context 'with approved services' do
it 'renders edit page' do
- expect(response).to be_success
+ expect(response).to be_successful
end
end
end
diff --git a/spec/controllers/projects/starrers_controller_spec.rb b/spec/controllers/projects/starrers_controller_spec.rb
index 59d258e99ce..5774ff7c576 100644
--- a/spec/controllers/projects/starrers_controller_spec.rb
+++ b/spec/controllers/projects/starrers_controller_spec.rb
@@ -3,23 +3,47 @@
require 'spec_helper'
describe Projects::StarrersController do
- let(:user) { create(:user) }
- let(:private_user) { create(:user, private_profile: true) }
+ let(:user_1) { create(:user, name: 'John') }
+ let(:user_2) { create(:user, name: 'Michael') }
+ let(:private_user) { create(:user, name: 'Michael Douglas', private_profile: true) }
let(:admin) { create(:user, admin: true) }
- let(:project) { create(:project, :public, :repository) }
+ let(:project) { create(:project, :public) }
before do
- user.toggle_star(project)
+ user_1.toggle_star(project)
+ user_2.toggle_star(project)
private_user.toggle_star(project)
end
describe 'GET index' do
- def get_starrers
- get :index,
- params: {
- namespace_id: project.namespace,
- project_id: project
- }
+ def get_starrers(search: nil)
+ get :index, params: { namespace_id: project.namespace, project_id: project, search: search }
+ end
+
+ def user_ids
+ assigns[:starrers].map { |s| s['user_id'] }
+ end
+
+ shared_examples 'starrers counts' do
+ it 'starrers counts are correct' do
+ expect(assigns[:total_count]).to eq(3)
+ expect(assigns[:public_count]).to eq(2)
+ expect(assigns[:private_count]).to eq(1)
+ end
+ end
+
+ context 'N+1 queries' do
+ render_views
+
+ it 'avoids N+1s loading users', :request_store do
+ get_starrers
+
+ control_count = ActiveRecord::QueryRecorder.new { get_starrers }.count
+
+ create_list(:user, 5).each { |user| user.toggle_star(project) }
+
+ expect { get_starrers }.not_to exceed_query_limit(control_count)
+ end
end
context 'when project is public' do
@@ -28,55 +52,118 @@ describe Projects::StarrersController do
end
context 'when no user is logged in' do
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when public user is logged in' do
before do
- get_starrers
+ sign_in(user_1)
end
- it 'only public starrers are visible' do
- user_ids = assigns[:starrers].map { |s| s['user_id'] }
- expect(user_ids).to include(user.id)
- expect(user_ids).not_to include(private_user.id)
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
end
- it 'public/private starrers counts are correct' do
- expect(assigns[:public_count]).to eq(1)
- expect(assigns[:private_count]).to eq(1)
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
end
end
context 'when private user is logged in' do
before do
sign_in(private_user)
-
- get_starrers
end
- it 'their star is also visible' do
- user_ids = assigns[:starrers].map { |s| s['user_id'] }
- expect(user_ids).to include(user.id, private_user.id)
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
end
- it 'public/private starrers counts are correct' do
- expect(assigns[:public_count]).to eq(1)
- expect(assigns[:private_count]).to eq(1)
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
end
end
context 'when admin is logged in' do
before do
sign_in(admin)
-
- get_starrers
end
- it 'all stars are visible' do
- user_ids = assigns[:starrers].map { |s| s['user_id'] }
- expect(user_ids).to include(user.id, private_user.id)
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'all users are visible' do
+ expect(user_ids).to include(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
end
- it 'public/private starrers counts are correct' do
- expect(assigns[:public_count]).to eq(1)
- expect(assigns[:private_count]).to eq(1)
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'public and private starrers are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
end
end
end
@@ -95,15 +182,14 @@ describe Projects::StarrersController do
context 'when user is logged in' do
before do
sign_in(project.creator)
- end
-
- it 'only public starrers are visible' do
get_starrers
+ end
- user_ids = assigns[:starrers].map { |s| s['user_id'] }
- expect(user_ids).to include(user.id)
- expect(user_ids).not_to include(private_user.id)
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
end
+
+ include_examples 'starrers counts'
end
end
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index fbca1d5740f..6fea6bca4f2 100644
--- a/spec/controllers/projects/wikis_controller_spec.rb
+++ b/spec/controllers/projects/wikis_controller_spec.rb
@@ -3,11 +3,11 @@
require 'spec_helper'
describe Projects::WikisController do
- let(:project) { create(:project, :public, :repository) }
- let(:user) { project.owner }
+ set(:project) { create(:project, :public, :repository) }
+ set(:user) { project.owner }
let(:project_wiki) { ProjectWiki.new(project, user) }
let(:wiki) { project_wiki.wiki }
- let(:wiki_title) { 'page-title-test' }
+ let(:wiki_title) { 'page title test' }
before do
create_page(wiki_title, 'hello world')
@@ -19,6 +19,21 @@ describe Projects::WikisController do
destroy_page(wiki_title)
end
+ describe 'GET #new' do
+ subject { get :new, params: { namespace_id: project.namespace, project_id: project } }
+
+ it 'redirects to #show and appends a `random_title` param' do
+ subject
+
+ expect(response).to have_http_status(302)
+ expect(Rails.application.routes.recognize_path(response.redirect_url)).to include(
+ controller: 'projects/wikis',
+ action: 'show'
+ )
+ expect(response.redirect_url).to match(/\?random_title=true\Z/)
+ end
+ end
+
describe 'GET #pages' do
subject { get :pages, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
@@ -75,40 +90,62 @@ describe Projects::WikisController do
describe 'GET #show' do
render_views
- subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: wiki_title } }
+ let(:random_title) { nil }
- it 'limits the retrieved pages for the sidebar' do
- expect(controller).to receive(:load_wiki).and_return(project_wiki)
+ subject { get :show, params: { namespace_id: project.namespace, project_id: project, id: id, random_title: random_title } }
- # empty? call
- expect(project_wiki).to receive(:list_pages).with(limit: 1).and_call_original
- # Sidebar entries
- expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
+ context 'when page exists' do
+ let(:id) { wiki_title }
- subject
+ it 'limits the retrieved pages for the sidebar' do
+ expect(controller).to receive(:load_wiki).and_return(project_wiki)
+ expect(project_wiki).to receive(:list_pages).with(limit: 15).and_call_original
+
+ subject
+
+ expect(response).to have_http_status(:ok)
+ expect(assigns(:page).title).to eq(wiki_title)
+ end
+
+ context 'when page content encoding is invalid' do
+ it 'sets flash error' do
+ allow(controller).to receive(:valid_encoding?).and_return(false)
- expect(response).to have_http_status(:ok)
- expect(response.body).to include(wiki_title)
+ subject
+
+ expect(response).to have_http_status(:ok)
+ expect(flash[:notice]).to eq('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
+ end
+ end
end
- context 'when page content encoding is invalid' do
- it 'sets flash error' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
+ context 'when the page does not exist' do
+ let(:id) { 'does not exist' }
+ before do
subject
+ end
- expect(response).to have_http_status(:ok)
- expect(flash[:notice]).to eq 'The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
+
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
end
end
context 'when page is a file' do
include WikiHelpers
- let(:path) { upload_file_to_wiki(project, user, file_name) }
+ let(:id) { upload_file_to_wiki(project, user, file_name) }
before do
- get :show, params: { namespace_id: project.namespace, project_id: project, id: path }
+ subject
end
context 'when file is an image' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index d05482f095e..35487682462 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -30,13 +30,36 @@ describe RegistrationsController do
end
context 'when send_user_confirmation_email is true' do
- it 'does not authenticate user and sends confirmation email' do
+ before do
stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
+
+ it 'does not authenticate the user and sends a confirmation email' do
+ post(:create, params: user_params)
+
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(subject.current_user).to be_nil
+ end
+ end
- post(:create, params: user_params)
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
- expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
- expect(subject.current_user).to be_nil
+ it 'authenticates the user and sends a confirmation email' do
+ post(:create, params: user_params)
+
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(response).to redirect_to(dashboard_projects_path)
+ end
end
end
@@ -106,9 +129,9 @@ describe RegistrationsController do
{
message: auth_log_message,
env: :invisible_captcha_signup_bot_detected,
- ip: '0.0.0.0',
+ remote_ip: '0.0.0.0',
request_method: 'POST',
- fullpath: '/users'
+ path: '/users'
}
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 8b8d4c57000..5566df0c216 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -19,7 +19,7 @@ describe UsersController do
it 'renders the show template' do
get :show, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
expect(response).to render_template('show')
end
end
@@ -362,7 +362,7 @@ describe UsersController do
it 'responds with success' do
get :show, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
@@ -418,7 +418,7 @@ describe UsersController do
it 'responds with success' do
get :projects, params: { username: user.username }
- expect(response).to be_success
+ expect(response).to be_successful
end
end
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index 6491b9dca19..b1b714277e4 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -8,7 +8,7 @@ FactoryBot.define do
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
- status :success
+ status :created
pipeline factory: :ci_pipeline
@@ -17,6 +17,7 @@ FactoryBot.define do
end
transient { downstream nil }
+ transient { upstream nil }
after(:build) do |bridge, evaluator|
bridge.project ||= bridge.pipeline.project
@@ -26,6 +27,12 @@ FactoryBot.define do
trigger: { project: evaluator.downstream.full_path }
)
end
+
+ if evaluator.upstream.present?
+ bridge.options = bridge.options.to_h.merge(
+ bridge_needs: { pipeline: evaluator.upstream.full_path }
+ )
+ end
end
end
end
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 8dab6c71b06..4c875935d82 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -20,5 +20,9 @@ FactoryBot.define do
"email#{n}@email.com"
end
end
+
+ trait(:ldap) do
+ ldap true
+ end
end
end
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 2ede92c8af0..3f6326114c9 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -56,10 +56,7 @@ FactoryBot.define do
end
trait :attachment_upload do
- transient do
- mount_point :attachment
- end
-
+ mount_point :attachment
model { build(:note) }
uploader "AttachmentUploader"
end
diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb
new file mode 100644
index 00000000000..9a2b7a80498
--- /dev/null
+++ b/spec/features/boards/multiple_boards_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Multiple Issue Boards', :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ set(:planning) { create(:label, project: project, name: 'Planning') }
+ set(:board) { create(:board, name: 'board1', project: project) }
+ set(:board2) { create(:board, name: 'board2', project: project) }
+ let(:parent) { project }
+ let(:boards_path) { project_boards_path(project) }
+
+ it_behaves_like 'multiple issue boards'
+end
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 942a9889488..bcaed2a5f18 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -161,4 +161,27 @@ describe 'Group show page' do
expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
end
end
+
+ context 'notification button', :js do
+ let(:maintainer) { create(:user) }
+ let!(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(maintainer)
+ sign_in(maintainer)
+ end
+
+ it 'is enabled by default' do
+ visit path
+
+ expect(page).to have_selector('.notifications-btn:not(.disabled)', visible: true)
+ end
+
+ it 'is disabled if emails are disabled' do
+ group.update_attribute(:emails_disabled, true)
+ visit path
+
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index 832c4a57aa3..1e054a7b358 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -17,11 +17,10 @@ describe 'Invites' do
group_invite.generate_invite_token!
end
- def confirm_email_and_sign_in(new_user)
+ def confirm_email(new_user)
new_user_token = User.find_by_email(new_user.email).confirmation_token
visit user_confirmation_path(confirmation_token: new_user_token)
- fill_in_sign_in_form(new_user)
end
def fill_in_sign_up_form(new_user)
@@ -155,17 +154,41 @@ describe 'Invites' do
context 'email confirmation enabled' do
let(:send_email_confirmation) { true }
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ # stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(root_path)
- expect(page).to have_content(project.full_name)
- visit group_path(group)
- expect(page).to have_content(group.full_name)
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
end
- it "doesn't accept invitations until the user confirm his email" do
+ context 'when soft email confirmation is enabled' do
+ before do
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
+ end
+
+ it "doesn't accept invitations until the user confirms his email" do
fill_in_sign_up_form(new_user)
sign_in(owner)
@@ -176,11 +199,32 @@ describe 'Invites' do
context 'the user sign-up using a different email address' do
let(:invite_email) { build_stubbed(:user).email }
- it 'signs up and redirects to the invitation page' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
end
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index 7a721adc8dd..165d41950da 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -27,4 +27,14 @@ describe "User toggles subscription", :js do
# Check we're unsubscribed.
expect(subscription_button).to have_css("button:not(.is-checked)")
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project_empty_repo, :public, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ expect(page).not_to have_selector('.js-issuable-subscribe-button')
+ end
+ end
end
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index 1472cc882a7..d788c0574e2 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -20,4 +20,12 @@ describe 'User visits the notifications tab', :js do
expect(page).to have_selector('#notifications-button', text: 'On mention')
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, emails_disabled: true) }
+
+ it 'notification button is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
index c19e46da913..6bd569e5ee2 100644
--- a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
+++ b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
@@ -3,18 +3,15 @@
require 'spec_helper'
# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
-# Quarantine: https://gitlab.com/gitlab-org/gitlab-ce/issues/65329
-describe 'Projects > Files > User browses a tree with a folder containing only a folder', :quarantine do
+describe 'Projects > Files > User browses a tree with a folder containing only a folder', :js do
let(:project) { create(:project, :empty_repo) }
let(:user) { project.owner }
before do
- # We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
-
project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
sign_in(user)
visit(project_tree_path(project, project.repository.root_ref))
+ wait_for_requests
end
it 'shows the nested folder on a single row' do
diff --git a/spec/features/projects/pages_lets_encrypt_spec.rb b/spec/features/projects/pages_lets_encrypt_spec.rb
index a5f8702302c..8b5964b2eee 100644
--- a/spec/features/projects/pages_lets_encrypt_spec.rb
+++ b/spec/features/projects/pages_lets_encrypt_spec.rb
@@ -75,12 +75,10 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do
end
shared_examples 'user sees private keys only for user provided certificate' do
- before do
- visit edit_project_pages_domain_path(project, domain)
- end
-
shared_examples 'user do not see private key' do
it 'user do not see private key' do
+ visit edit_project_pages_domain_path(project, domain)
+
expect(find_field('Key (PEM)', visible: :all, disabled: :all).value).to be_blank
end
end
@@ -101,6 +99,8 @@ describe "Pages with Let's Encrypt", :https_pages_enabled do
let(:domain) { create(:pages_domain, project: project) }
it 'user sees private key' do
+ visit edit_project_pages_domain_path(project, domain)
+
expect(find_field('Key (PEM)').value).not_to be_blank
end
end
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 46fd676954d..0e757e647a0 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -59,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do
end
end
end
+
+ context 'disable email notifications' do
+ it 'is visible' do
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
context 'as maintainer' do
@@ -76,5 +82,11 @@ describe 'Projects > Settings > Visibility settings', :js do
expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
end
+
+ context 'disable email notifications' do
+ it 'is not available' do
+ expect(page).not_to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
end
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index 5e9c98428cf..851a09cf28a 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -65,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do
end
end
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, :public, :repository, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 7b511c4d3d5..5c6b04a7141 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe 'Projects > Wiki > User previews markdown changes', :js do
- let(:user) { create(:user) }
+ set(:user) { create(:user) }
let(:project) { create(:project, :wiki_repo, namespace: user.namespace) }
let(:wiki_page) { create(:wiki_page, wiki: project.wiki, attrs: { title: 'home', content: '[some link](other-page)' }) }
let(:wiki_content) do
@@ -20,23 +20,12 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
project.add_maintainer(user)
sign_in(user)
-
- visit project_wiki_path(project, wiki_page)
end
context "while creating a new wiki page" do
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- find('.add-new-wiki').click
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a/b/c/d'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a/b/c/d', content: wiki_content)
expect(page).to have_content("regular link")
@@ -50,16 +39,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are spaces in the page name" do
it "rewrites relative links as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a page/b page/c page/d page'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a page/b page/c page/d page', content: wiki_content)
expect(page).to have_content("regular link")
@@ -73,16 +53,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are hyphens in the page name" do
it "rewrites relative links as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'a-page/b-page/c-page/d-page'
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: wiki_content
- click_on "Preview"
- end
+ create_wiki_page('a-page/b-page/c-page/d-page', content: wiki_content)
expect(page).to have_content("regular link")
@@ -96,23 +67,9 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
end
context "while editing a wiki page" do
- def create_wiki_page(path)
- find('.add-new-wiki').click
-
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: path
- click_button 'Create page'
- end
-
- page.within '.wiki-form' do
- fill_in :wiki_content, with: 'content'
- click_on "Create page"
- end
- end
-
context "when there are no spaces or hyphens in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a/b/c/d'
+ create_wiki_page('a/b/c/d')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -130,7 +87,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are spaces in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a page/b page/c page/d page'
+ create_wiki_page('a page/b page/c page/d page')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -148,7 +105,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context "when there are hyphens in the page name" do
it "rewrites relative links as expected" do
- create_wiki_page 'a-page/b-page/c-page/d-page'
+ create_wiki_page('a-page/b-page/c-page/d-page')
click_link 'Edit'
fill_in :wiki_content, with: wiki_content
@@ -166,7 +123,7 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
context 'when rendering the preview' do
it 'renders content with CommonMark' do
- create_wiki_page 'a-page/b-page/c-page/common-mark'
+ create_wiki_page('a-page/b-page/c-page/common-mark')
click_link 'Edit'
fill_in :wiki_content, with: "1. one\n - sublist\n"
@@ -180,25 +137,31 @@ describe 'Projects > Wiki > User previews markdown changes', :js do
end
it "does not linkify double brackets inside code blocks as expected" do
- click_link 'New page'
- page.within '#modal-new-wiki' do
- fill_in :new_wiki_path, with: 'linkify_test'
- click_button 'Create page'
- end
+ wiki_content = <<-HEREDOC
+ `[[do_not_linkify]]`
+ ```
+ [[also_do_not_linkify]]
+ ```
+ HEREDOC
- page.within '.wiki-form' do
- fill_in :wiki_content, with: <<-HEREDOC
- `[[do_not_linkify]]`
- ```
- [[also_do_not_linkify]]
- ```
- HEREDOC
- click_on "Preview"
- end
+ create_wiki_page('linkify_test', wiki_content)
expect(page).to have_content("do_not_linkify")
expect(page.html).to include('[[do_not_linkify]]')
expect(page.html).to include('[[also_do_not_linkify]]')
end
+
+ private
+
+ def create_wiki_page(path, content = 'content')
+ visit project_wiki_path(project, wiki_page)
+
+ click_link 'New page'
+
+ fill_in :wiki_title, with: path
+ fill_in :wiki_content, with: content
+
+ click_button 'Create page'
+ end
end
diff --git a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
index cc6dbaa6eb8..56d0518015d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -42,10 +42,10 @@ describe "User creates wiki page" do
click_link("link test")
- expect(page).to have_content("Create Page")
+ expect(page).to have_content("Create New Page")
end
- it "shows non-escaped link in the pages list", :js, :quarantine do
+ it "shows non-escaped link in the pages list", :quarantine do
fill_in(:wiki_title, with: "one/two/three-test")
page.within(".wiki-form") do
@@ -58,7 +58,9 @@ describe "User creates wiki page" do
expect(page).to have_xpath("//a[@href='/#{project.full_path}/wikis/one/two/three-test']")
end
- it "has `Create home` as a commit message" do
+ it "has `Create home` as a commit message", :js do
+ wait_for_requests
+
expect(page).to have_field("wiki[message]", with: "Create home")
end
@@ -81,7 +83,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "test"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("test").and have_content("Create Page")
+ expect(page).to have_content("Create New Page")
end
click_link("Home")
@@ -93,7 +95,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "api"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("api")
+ expect(page).to have_content("Create")
end
click_link("Home")
@@ -105,7 +107,7 @@ describe "User creates wiki page" do
expect(current_path).to eq(project_wiki_path(project, "raketasks"))
page.within(:css, ".nav-text") do
- expect(page).to have_content("Create").and have_content("rake")
+ expect(page).to have_content("Create")
end
end
@@ -150,6 +152,8 @@ describe "User creates wiki page" do
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
it "has `Create home` as a commit message" do
+ wait_for_requests
+
expect(page).to have_field("wiki[message]", with: "Create home")
end
@@ -181,20 +185,15 @@ describe "User creates wiki page" do
it "creates a page with a single word" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "foo")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "foo")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create foo")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
@@ -204,20 +203,15 @@ describe "User creates wiki page" do
it "creates a page with spaces in the name" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "Spaces in the name")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "Spaces in the name")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create Spaces in the name")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("Spaces in the name")
.and have_content("Last edited by #{user.name}")
@@ -227,10 +221,9 @@ describe "User creates wiki page" do
it "creates a page with hyphens in the name" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "hyphens-in-the-name")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "hyphens-in-the-name")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
@@ -251,12 +244,6 @@ describe "User creates wiki page" do
it "shows the emoji autocompletion dropdown" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "test-autocomplete")
-
- click_button("Create page")
- end
-
page.within(".wiki-form") do
find("#wiki_content").native.send_keys("")
@@ -274,20 +261,15 @@ describe "User creates wiki page" do
it "creates a page" do
click_link("New page")
- page.within("#modal-new-wiki") do
- fill_in(:new_wiki_path, with: "foo")
-
- click_button("Create page")
+ page.within(".wiki-form") do
+ fill_in(:wiki_title, with: "foo")
+ fill_in(:wiki_content, with: "My awesome wiki!")
end
# Commit message field should have correct value.
expect(page).to have_field("wiki[message]", with: "Create foo")
- page.within(".wiki-form") do
- fill_in(:wiki_content, with: "My awesome wiki!")
-
- click_button("Create page")
- end
+ click_button("Create page")
expect(page).to have_content("foo")
.and have_content("Last edited by #{user.name}")
diff --git a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
index 2aab8fda62d..3f3711f9eb8 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -70,7 +70,7 @@ describe 'User updates wiki page' do
context 'in a user namespace' do
let(:project) { create(:project, :wiki_repo) }
- it 'updates a page' do
+ it 'updates a page', :js do
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
@@ -82,6 +82,18 @@ describe 'User updates wiki page' do
expect(page).to have_content('My awesome wiki!')
end
+ it 'updates the commit message as the title is changed', :js do
+ fill_in(:wiki_title, with: 'Wiki title')
+
+ expect(page).to have_field('wiki[message]', with: 'Update Wiki title')
+ end
+
+ it 'does not allow XSS', :js do
+ fill_in(:wiki_title, with: '<script>')
+
+ expect(page).to have_field('wiki[message]', with: 'Update &lt;script&gt;')
+ end
+
it 'shows a validation error message' do
fill_in(:wiki_content, with: '')
click_button('Save changes')
@@ -129,7 +141,7 @@ describe 'User updates wiki page' do
context 'in a group namespace' do
let(:project) { create(:project, :wiki_repo, namespace: create(:group, :public)) }
- it 'updates a page' do
+ it 'updates a page', :js do
# Commit message field should have correct value.
expect(page).to have_field('wiki[message]', with: 'Update home')
diff --git a/spec/features/projects/wiki/user_views_wiki_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
index 05742b63c43..77e725e7f11 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -101,8 +101,7 @@ describe 'User views a wiki page' do
click_on('image')
expect(current_path).to match("wikis/#{path}")
- expect(page).to have_content('New Wiki Page')
- expect(page).to have_content('Create page')
+ expect(page).to have_content('Create New Page')
end
end
@@ -156,6 +155,6 @@ describe 'User views a wiki page' do
find('.shortcuts-wiki').click
click_link "Create your first page"
- expect(page).to have_content('Home · Create Page')
+ expect(page).to have_content('Create New Page')
end
end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 5a60991c1bf..4ca79ccaea8 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -39,17 +39,16 @@ describe 'User searches for code' do
context 'when on a project page', :js do
before do
visit(search_path)
- end
-
- include_examples 'top right search form'
-
- it 'finds code' do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
click_link(project.full_name)
end
+ end
+ include_examples 'top right search form'
+
+ it 'finds code' do
fill_in('dashboard_search', with: 'rspec')
find('.btn-search').click
@@ -57,6 +56,27 @@ describe 'User searches for code' do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
end
end
+
+ it 'search mutiple words with refs switching' do
+ expected_result = 'Use `snake_case` for naming files'
+ search = 'for naming files'
+
+ fill_in('dashboard_search', with: search)
+ find('.btn-search').click
+
+ page.within('.results') do
+ expect(find('.search-results')).to have_content(expected_result)
+ end
+
+ find('.js-project-refs-dropdown').click
+ find('.dropdown-page-one .dropdown-content').click_link('v1.0.0')
+
+ page.within('.results') do
+ expect(find(:css, '.search-results')).to have_content(expected_result)
+ end
+
+ expect(find_field('dashboard_search').value).to eq(search)
+ end
end
context 'search code within refs', :js do
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index e2b3444272e..70e6978a7b6 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -15,8 +15,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user changes his email which makes the gpg key verified
perform_enqueued_jobs do
@@ -26,8 +26,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
it 'changes from unverified to verified when the user adds the missing gpg key' do
@@ -36,8 +36,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user adds the gpg key which makes the signature valid
perform_enqueued_jobs do
@@ -46,8 +46,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
context 'shows popover badges', :js do
@@ -136,7 +136,7 @@ describe 'GPG signed commits' do
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
# wait for the signature to get generated
- expect(page).to have_link 'Verified'
+ expect(page).to have_button 'Verified'
user_1.destroy!
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index 1d8c9e7e426..8e4db2ca840 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -781,4 +781,39 @@ describe 'Login' do
end
end
end
+
+ context 'when sending confirmation email and not yet confirmed' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ it 'allows login and shows a flash warning to confirm the email address' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq root_path
+ expect(page).to have_content("Please check your email (#{user.email}) to verify that you own this address.")
+ end
+
+ context "when not having confirmed within Devise's allow_unconfirmed_access_for time" do
+ it 'does not allow login and shows a flash alert to confirm the email address' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index cf57fafc4f5..fb927a9ca3b 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -166,24 +166,51 @@ describe 'Signup' do
end
context 'with no errors' do
- context "when sending confirmation email" do
+ context 'when sending confirmation email' do
before do
stub_application_setting(send_user_confirmation_email: true)
end
- it 'creates the user account and sends a confirmation email' do
- visit root_path
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ end
- fill_in 'new_user_name', with: new_user.name
- fill_in 'new_user_username', with: new_user.username
- fill_in 'new_user_email', with: new_user.email
- fill_in 'new_user_email_confirmation', with: new_user.email
- fill_in 'new_user_password', with: new_user.password
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+
+ expect { click_button 'Register' }.to change { User.count }.by(1)
+
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content('Please check your email to confirm your account')
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ end
+
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
- expect { click_button 'Register' }.to change { User.count }.by(1)
+ expect { click_button 'Register' }.to change { User.count }.by(1)
- expect(current_path).to eq users_almost_there_path
- expect(page).to have_content("Please check your email to confirm your account")
+ expect(current_path).to eq dashboard_projects_path
+ expect(page).to have_content("Please check your email (#{new_user.email}) to verify that you own this address.")
+ end
end
end
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 0828f113495..9216ad0060b 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -3,6 +3,7 @@
"required": [
"sha",
"created_at",
+ "finished_at",
"iid",
"tag",
"last?",
@@ -11,6 +12,7 @@
],
"properties": {
"created_at": { "type": "string" },
+ "finished_at": { "type": ["string", "null"] },
"id": { "type": "integer" },
"iid": { "type": "integer" },
"last?": { "type": "boolean" },
diff --git a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
deleted file mode 100644
index 8fb66f6652b..00000000000
--- a/spec/fixtures/security-reports/dependency_list/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,422 +0,0 @@
-{
- "version": "2.1",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "Vulnerabilities in libxml2",
- "message": "Vulnerabilities in libxml2 in nokogiri",
- "description": " The version of libxml2 packaged with Nokogiri contains several vulnerabilities.\r\n Nokogiri has mitigated these issues by upgrading to libxml 2.9.5.\r\n\r\n It was discovered that a type confusion error existed in libxml2. An\r\n attacker could use this to specially construct XML data that\r\n could cause a denial of service or possibly execute arbitrary\r\n code. (CVE-2017-0663)\r\n\r\n It was discovered that libxml2 did not properly validate parsed entity\r\n references. An attacker could use this to specially construct XML\r\n data that could expose sensitive information. (CVE-2017-7375)\r\n\r\n It was discovered that a buffer overflow existed in libxml2 when\r\n handling HTTP redirects. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-7376)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overflow in\r\n libxml2 when handling elements. An attacker could use this to specially\r\n construct XML data that could cause a denial of service or possibly\r\n execute arbitrary code. (CVE-2017-9047)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered a buffer overread\r\n in libxml2 when handling elements. An attacker could use this\r\n to specially construct XML data that could cause a denial of\r\n service. (CVE-2017-9048)\r\n\r\n Marcel Böhme and Van-Thuan Pham discovered multiple buffer overreads\r\n in libxml2 when handling parameter-entity references. An attacker\r\n could use these to specially construct XML data that could cause a\r\n denial of service. (CVE-2017-9049, CVE-2017-9050)",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:06565b64-486d-4326-b906-890d9915804d",
- "severity": "High",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Infinite recursion in parameter entities",
- "message": "Infinite recursion in parameter entities in nokogiri",
- "description": "libxml2 incorrectly handles certain parameter entities. An attacker can leverage this with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:6a0d56f6-2441-492a-9b14-edb95ac31919",
- "severity": "High",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6a0d56f6-2441-492a-9b14-edb95ac31919",
- "value": "6a0d56f6-2441-492a-9b14-edb95ac31919",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-16932",
- "value": "CVE-2017-16932",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-16932"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-16932.html"
- },
- {
- "url": "https://usn.ubuntu.com/usn/usn-3504-1/"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Denial of Service",
- "message": "Denial of Service in nokogiri",
- "description": "libxml2 incorrectly handles certain files. An attacker can use this issue with specially constructed XML data to cause libxml2 to consume resources, leading to a denial of service.\r\n\r\n",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:78658378-bd8f-4d79-81c8-07c419302426",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-78658378-bd8f-4d79-81c8-07c419302426",
- "value": "78658378-bd8f-4d79-81c8-07c419302426",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-15412",
- "value": "CVE-2017-15412",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15412"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1714"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/2017/CVE-2017-15412.html"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Bypass of a protection mechanism in libxslt",
- "message": "Bypass of a protection mechanism in libxslt in nokogiri",
- "description": "libxslt through 1.1.33 allows bypass of a protection mechanism because callers of xsltCheckRead and xsltCheckWrite permit access even upon receiving a -1 error code. xsltCheckRead can return -1 for a crafted URL that is not actually invalid and is subsequently loaded. Vendored version of libxslt has been patched to remediate this vulnerability. Note that this patch is not yet (as of 2019-04-22) in an upstream release of libxslt.",
- "cve": "rails/Gemfile.lock:nokogiri:gemnasium:1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "severity": "Unknown",
- "solution": "Upgrade to latest version if using vendored version of libxslt OR update the system library libxslt to a fixed version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "value": "1a2e2e6e-67ba-4142-bfa1-3391f5416e4c",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2019-11068",
- "value": "CVE-2019-11068",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
- }
- ],
- "links": [
- {
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-11068"
- },
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1892"
- },
- {
- "url": "https://people.canonical.com/~ubuntu-security/cve/CVE-2019-11068"
- },
- {
- "url": "https://security-tracker.debian.org/tracker/CVE-2019-11068"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "cve": "yarn/yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn/yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- },
- {
- "url": "https://nodesecurity.io/advisories/534"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "cve": "yarn/yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn/yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [],
- "dependency_files": [
- {
- "path": "rails/Gemfile.lock",
- "package_manager": "bundler",
- "dependencies": [
- {
- "package": {
- "name": "mini_portile2"
- },
- "version": "2.2.0"
- },
- {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- ]
- },
- {
- "path": "yarn/yarn.lock",
- "package_manager": "yarn",
- "dependencies": [
- {
- "package": {
- "name": "async"
- },
- "version": "0.2.10"
- },
- {
- "package": {
- "name": "async"
- },
- "version": "1.5.2"
- },
- {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- },
- {
- "package": {
- "name": "ejs"
- },
- "version": "0.8.8"
- },
- {
- "package": {
- "name": "ms"
- },
- "version": "2.0.0"
- },
- {
- "package": {
- "name": "node-forge"
- },
- "version": "0.2.24"
- },
- {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- },
- {
- "package": {
- "name": "sax"
- },
- "version": "1.2.4"
- },
- {
- "package": {
- "name": "underscore"
- },
- "version": "1.9.1"
- },
- {
- "package": {
- "name": "underscore"
- },
- "version": "1.6.0"
- },
- {
- "package": {
- "name": "xml-crypto"
- },
- "version": "0.8.5"
- },
- {
- "package": {
- "name": "xml-encryption"
- },
- "version": "0.7.4"
- },
- {
- "package": {
- "name": "xml2js"
- },
- "version": "0.4.19"
- },
- {
- "package": {
- "name": "xmlbuilder"
- },
- "version": "2.1.0"
- },
- {
- "package": {
- "name": "xmlbuilder"
- },
- "version": "9.0.7"
- },
- {
- "package": {
- "name": "xmldom"
- },
- "version": "0.1.19"
- },
- {
- "package": {
- "name": "xmldom"
- },
- "version": "0.1.27"
- },
- {
- "package": {
- "name": "xpath.js"
- },
- "version": "1.1.0"
- },
- {
- "package": {
- "name": "xpath"
- },
- "version": "0.0.5"
- }
- ]
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
deleted file mode 100644
index ce66f562175..00000000000
--- a/spec/fixtures/security-reports/deprecated/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,178 +0,0 @@
-[
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
-]
diff --git a/spec/fixtures/security-reports/deprecated/gl-sast-report.json b/spec/fixtures/security-reports/deprecated/gl-sast-report.json
deleted file mode 100644
index 2f7e47281e2..00000000000
--- a/spec/fixtures/security-reports/deprecated/gl-sast-report.json
+++ /dev/null
@@ -1,964 +0,0 @@
-[
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 1,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 47,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 41,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 11,
- "end_line": 11
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 11,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 12,
- "end_line": 12
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 12,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 13,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 14,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Pickle library appears to be in use, possible security issue.",
- "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 15,
- "end_line": 15
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 15,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "ECB mode is insecure",
- "message": "ECB mode is insecure",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-ECB_MODE",
- "value": "ECB_MODE",
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 14,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 10,
- "end_line": 10
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 10,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 22,
- "end_line": 22
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B106",
- "value": "B106",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 22,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'root'",
- "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 5,
- "end_line": 5
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 5,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: ''",
- "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 13,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 23,
- "end_line": 23
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 23,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 24,
- "end_line": 24
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 24,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 7,
- "end_line": 7
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
- "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B602",
- "value": "B602",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 1,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 7,
- "end_line": 8
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with loads module.",
- "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 4
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - char",
- "value": "char"
- },
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 4,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
- "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
- "confidence": "Low",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 8
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - fopen",
- "value": "fopen"
- },
- {
- "type": "cwe",
- "name": "CWE-362",
- "value": "362",
- "url": "https://cwe.mitre.org/data/definitions/362.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 8,
- "url": "https://cwe.mitre.org/data/definitions/362.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 6
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - char",
- "value": "char"
- },
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 6,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
- "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
- "confidence": "Low",
- "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 7
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - strcpy",
- "value": "strcpy"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 7,
- "url": "https://cwe.mitre.org/data/definitions/120.html",
- "tool": "flawfinder"
- }
-]
diff --git a/spec/fixtures/security-reports/feature-branch.zip b/spec/fixtures/security-reports/feature-branch.zip
deleted file mode 100644
index dd49f4e9e1d..00000000000
--- a/spec/fixtures/security-reports/feature-branch.zip
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
deleted file mode 100644
index 6f89d20d4bf..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-container-scanning-report.json
+++ /dev/null
@@ -1,16 +0,0 @@
-{
- "image": "registry.gitlab.com/bikebilly/auto-devops-10-6/feature-branch:e7315ba964febb11bac8f5cd6ec433db8a3a1583",
- "unapproved": ["CVE-2017-15650"],
- "vulnerabilities": [
- {
- "featurename": "musl",
- "featureversion": "1.1.14-r15",
- "vulnerability": "CVE-2017-15650",
- "namespace": "alpine:v3.4",
- "description": "",
- "link": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-15650",
- "severity": "Medium",
- "fixedby": "1.1.14-r16"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json b/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
deleted file mode 100644
index 3a308bf047e..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-dast-report.json
+++ /dev/null
@@ -1,40 +0,0 @@
-{
- "site": {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- "@generated": "Fri, 13 Apr 2018 09:22:01",
- "@version": "2.7.0"
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
deleted file mode 100644
index 8555be6618c..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "version": "1.3",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json b/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
deleted file mode 100644
index 5fd81fd69bd..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-license-management-report.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "licenses": [
- {
- "count": 1,
- "name": "WTFPL"
- },
- {
- "count": 1,
- "name": "MIT"
- }
- ],
- "dependencies": [
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actioncable",
- "url": "http://rubyonrails.org",
- "description": "WebSocket framework for Rails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "WTFPL",
- "url": "http://www.wtfpl.net/"
- },
- "dependency": {
- "name": "wtfpl_init",
- "url": "https://rubygems.org/gems/wtfpl_init",
- "description": "Download WTFPL license file and rename to LICENSE.md or something",
- "pathes": [
- "."
- ]
- }
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json b/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
deleted file mode 100644
index 4bef3d22f70..00000000000
--- a/spec/fixtures/security-reports/feature-branch/gl-sast-report.json
+++ /dev/null
@@ -1,947 +0,0 @@
-{
- "version": "1.2",
- "vulnerabilities": [
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 1,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 47,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 41,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 11,
- "end_line": 11
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 11,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 12,
- "end_line": 12
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 12,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 13,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 14,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Pickle library appears to be in use, possible security issue.",
- "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 15,
- "end_line": 15
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 15,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "ECB mode is insecure",
- "message": "ECB mode is insecure",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-ECB_MODE",
- "value": "ECB_MODE",
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 14,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 10,
- "end_line": 10
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 10,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 22,
- "end_line": 22
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B106",
- "value": "B106",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 22,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'root'",
- "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 5,
- "end_line": 5
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 5,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: ''",
- "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 13,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 23,
- "end_line": 23
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 23,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 24,
- "end_line": 24
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 24,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 7,
- "end_line": 7
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
- "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B602",
- "value": "B602",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 1,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 7,
- "end_line": 8
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with loads module.",
- "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 4
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 4,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
- "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
- "confidence": "Low",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 8
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-362",
- "value": "362",
- "url": "https://cwe.mitre.org/data/definitions/362.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 8,
- "url": "https://cwe.mitre.org/data/definitions/362.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 6
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 6,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
- "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
- "confidence": "Low",
- "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 7
- },
- "identifiers": [
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 7,
- "url": "https://cwe.mitre.org/data/definitions/120.html",
- "tool": "flawfinder"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master.zip b/spec/fixtures/security-reports/master.zip
deleted file mode 100644
index 2261b5a1674..00000000000
--- a/spec/fixtures/security-reports/master.zip
+++ /dev/null
Binary files differ
diff --git a/spec/fixtures/security-reports/master/gl-container-scanning-report.json b/spec/fixtures/security-reports/master/gl-container-scanning-report.json
deleted file mode 100644
index 03dfc647162..00000000000
--- a/spec/fixtures/security-reports/master/gl-container-scanning-report.json
+++ /dev/null
@@ -1,105 +0,0 @@
-{
- "image": "registry.gitlab.com/groulot/container-scanning-test/master:5f21de6956aee99ddb68ae49498662d9872f50ff",
- "unapproved": [
- "CVE-2017-18269",
- "CVE-2017-16997",
- "CVE-2018-1000001",
- "CVE-2016-10228",
- "CVE-2018-18520",
- "CVE-2010-4052",
- "CVE-2018-16869",
- "CVE-2018-18311"
- ],
- "vulnerabilities": [
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2017-18269",
- "namespace": "debian:9",
- "description": "SSE2-optimized memmove implementation problem.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2017-18269",
- "severity": "Defcon1",
- "fixedby": "2.24-11+deb9u4"
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2017-16997",
- "namespace": "debian:9",
- "description": "elf/dl-load.c in the GNU C Library (aka glibc or libc6) 2.19 through 2.26 mishandles RPATH and RUNPATH containing $ORIGIN for a privileged (setuid or AT_SECURE) program, which allows local users to gain privileges via a Trojan horse library in the current working directory, related to the fillin_rpath and decompose_rpath functions. This is associated with misinterpretion of an empty RPATH/RUNPATH token as the \"./\" directory. NOTE: this configuration of RPATH/RUNPATH for a privileged program is apparently very uncommon; most likely, no such program is shipped with any common Linux distribution.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2017-16997",
- "severity": "Critical",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2018-1000001",
- "namespace": "debian:9",
- "description": "In glibc 2.26 and earlier there is confusion in the usage of getcwd() by realpath() which can be used to write before the destination buffer leading to a buffer underflow and potential code execution.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-1000001",
- "severity": "High",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2016-10228",
- "namespace": "debian:9",
- "description": "The iconv program in the GNU C Library (aka glibc or libc6) 2.25 and earlier, when invoked with the -c option, enters an infinite loop when processing invalid multi-byte input sequences, leading to a denial of service.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2016-10228",
- "severity": "Medium",
- "fixedby": ""
- },
- {
- "featurename": "elfutils",
- "featureversion": "0.168-1",
- "vulnerability": "CVE-2018-18520",
- "namespace": "debian:9",
- "description": "An Invalid Memory Address Dereference exists in the function elf_end in libelf in elfutils through v0.174. Although eu-size is intended to support ar files inside ar files, handle_ar in size.c closes the outer ar file before handling all inner entries. The vulnerability allows attackers to cause a denial of service (application crash) with a crafted ELF file.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-18520",
- "severity": "Low",
- "fixedby": ""
- },
- {
- "featurename": "glibc",
- "featureversion": "2.24-11+deb9u3",
- "vulnerability": "CVE-2010-4052",
- "namespace": "debian:9",
- "description": "Stack consumption vulnerability in the regcomp implementation in the GNU C Library (aka glibc or libc6) through 2.11.3, and 2.12.x through 2.12.2, allows context-dependent attackers to cause a denial of service (resource exhaustion) via a regular expression containing adjacent repetition operators, as demonstrated by a {10,}{10,}{10,}{10,} sequence in the proftpd.gnu.c exploit for ProFTPD.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2010-4052",
- "severity": "Negligible",
- "fixedby": ""
- },
- {
- "featurename": "nettle",
- "featureversion": "3.3-1",
- "vulnerability": "CVE-2018-16869",
- "namespace": "debian:9",
- "description": "A Bleichenbacher type side-channel based padding oracle attack was found in the way nettle handles endian conversion of RSA decrypted PKCS#1 v1.5 data. An attacker who is able to run a process on the same physical core as the victim process, could use this flaw extract plaintext or in some cases downgrade any TLS connections to a vulnerable server.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-16869",
- "severity": "Unknown",
- "fixedby": ""
- },
- {
- "featurename": "perl",
- "featureversion": "5.24.1-3+deb9u4",
- "vulnerability": "CVE-2018-18311",
- "namespace": "debian:9",
- "description": "Perl before 5.26.3 and 5.28.x before 5.28.1 has a buffer overflow via a crafted regular expression that triggers invalid write operations.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-18311",
- "severity": "Unknown",
- "fixedby": "5.24.1-3+deb9u5"
- },
- {
- "featurename": "foo",
- "featureversion": "1.3",
- "vulnerability": "CVE-2018-666",
- "namespace": "debian:9",
- "description": "Foo has a vulnerability nobody cares about and whitelist.",
- "link": "https://security-tracker.debian.org/tracker/CVE-2018-666",
- "severity": "Unknown",
- "fixedby": "1.4"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-dast-report.json b/spec/fixtures/security-reports/master/gl-dast-report.json
deleted file mode 100644
index df459d9419d..00000000000
--- a/spec/fixtures/security-reports/master/gl-dast-report.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "site": [
- {
- "alerts": [
- {
- "sourceid": "3",
- "wascid": "15",
- "cweid": "16",
- "reference": "<p>http://msdn.microsoft.com/en-us/library/ie/gg622941%28v=vs.85%29.aspx</p><p>https://www.owasp.org/index.php/List_of_useful_HTTP_headers</p>",
- "otherinfo": "<p>This issue still applies to error type pages (401, 403, 500, etc) as those pages are often still affected by injection issues, in which case there is still concern for browsers sniffing pages away from their actual content type.</p><p>At \"High\" threshold this scanner will not alert on client or server error responses.</p>",
- "solution": "<p>Ensure that the application/web server sets the Content-Type header appropriately, and that it sets the X-Content-Type-Options header to 'nosniff' for all web pages.</p><p>If possible, ensure that the end user uses a standards-compliant and modern web browser that does not perform MIME-sniffing at all, or that can be directed by the web application/web server to not perform MIME-sniffing.</p>",
- "count": "2",
- "pluginid": "10021",
- "alert": "X-Content-Type-Options Header Missing",
- "name": "X-Content-Type-Options Header Missing",
- "riskcode": "1",
- "confidence": "2",
- "riskdesc": "Low (Medium)",
- "desc": "<p>The Anti-MIME-Sniffing header X-Content-Type-Options was not set to 'nosniff'. This allows older versions of Internet Explorer and Chrome to perform MIME-sniffing on the response body, potentially causing the response body to be interpreted and displayed as a content type other than the declared content type. Current (early 2014) and legacy versions of Firefox will use the declared content type (if one is set), rather than performing MIME-sniffing.</p>",
- "instances": [
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- },
- {
- "param": "X-Content-Type-Options",
- "method": "GET",
- "uri": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io/"
- }
- ]
- }
- ],
- "@ssl": "false",
- "@port": "80",
- "@host": "bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io",
- "@name": "http://bikebilly-spring-auto-devops-review-feature-br-3y2gpb.35.192.176.43.xip.io"
- }
- ],
- "@generated": "Fri, 13 Apr 2018 09:22:01",
- "@version": "2.7.0"
-}
diff --git a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
deleted file mode 100644
index 8555be6618c..00000000000
--- a/spec/fixtures/security-reports/master/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,181 +0,0 @@
-{
- "version": "1.3",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "io.netty/netty - CVE-2014-3488",
- "message": "DoS by CPU exhaustion when using malicious SSL packets",
- "cve": "app/pom.xml:io.netty/netty@3.9.1.Final:CVE-2014-3488",
- "severity": "Unknown",
- "solution": "Upgrade to the latest version",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/pom.xml",
- "dependency": {
- "package": {
- "name": "io.netty/netty"
- },
- "version": "3.9.1.Final"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "value": "d1bf36d9-9f07-46cd-9cfc-8675338ada8f",
- "url": "https://deps.sec.gitlab.com/packages/maven/io.netty/netty/versions/3.9.1.Final/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2014-3488",
- "value": "CVE-2014-3488",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2014-3488"
- }
- ],
- "links": [
- {
- "url": "https://bugzilla.redhat.com/CVE-2014-3488"
- },
- {
- "url": "http://netty.io/news/2014/06/11/3.html"
- },
- {
- "url": "https://github.com/netty/netty/issues/2562"
- }
- ],
- "priority": "Unknown",
- "file": "app/pom.xml",
- "url": "https://bugzilla.redhat.com/CVE-2014-3488",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "Django - CVE-2017-12794",
- "message": "Possible XSS in traceback section of technical 500 debug page",
- "cve": "app/requirements.txt:Django@1.11.3:CVE-2017-12794",
- "severity": "Unknown",
- "solution": "Upgrade to latest version or apply patch.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "app/requirements.txt",
- "dependency": {
- "package": {
- "name": "Django"
- },
- "version": "1.11.3"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-6162a015-8635-4a15-8d7c-dc9321db366f",
- "value": "6162a015-8635-4a15-8d7c-dc9321db366f",
- "url": "https://deps.sec.gitlab.com/packages/pypi/Django/versions/1.11.3/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-12794",
- "value": "CVE-2017-12794",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-12794"
- }
- ],
- "links": [
- {
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/"
- }
- ],
- "priority": "Unknown",
- "file": "app/requirements.txt",
- "url": "https://www.djangoproject.com/weblog/2017/sep/05/security-releases/",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "nokogiri - USN-3424-1",
- "message": "Vulnerabilities in libxml2",
- "cve": "rails/Gemfile.lock:nokogiri@1.8.0:USN-3424-1",
- "severity": "Unknown",
- "solution": "Upgrade to latest version.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "nokogiri"
- },
- "version": "1.8.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-06565b64-486d-4326-b906-890d9915804d",
- "value": "06565b64-486d-4326-b906-890d9915804d",
- "url": "https://deps.sec.gitlab.com/packages/gem/nokogiri/versions/1.8.0/advisories"
- },
- {
- "type": "usn",
- "name": "USN-3424-1",
- "value": "USN-3424-1",
- "url": "https://usn.ubuntu.com/3424-1/"
- }
- ],
- "links": [
- {
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673"
- }
- ],
- "priority": "Unknown",
- "file": "rails/Gemfile.lock",
- "url": "https://github.com/sparklemotion/nokogiri/issues/1673",
- "tool": "gemnasium"
- },
- {
- "category": "dependency_scanning",
- "name": "ffi - CVE-2018-1000201",
- "message": "ruby-ffi DDL loading issue on Windows OS",
- "cve": "ffi:1.9.18:CVE-2018-1000201",
- "severity": "High",
- "solution": "upgrade to \u003e= 1.9.24",
- "scanner": {
- "id": "bundler_audit",
- "name": "bundler-audit"
- },
- "location": {
- "file": "sast-sample-rails/Gemfile.lock",
- "dependency": {
- "package": {
- "name": "ffi"
- },
- "version": "1.9.18"
- }
- },
- "identifiers": [
- {
- "type": "cve",
- "name": "CVE-2018-1000201",
- "value": "CVE-2018-1000201",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-1000201"
- }
- ],
- "links": [
- {
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24"
- }
- ],
- "priority": "High",
- "file": "sast-sample-rails/Gemfile.lock",
- "url": "https://github.com/ffi/ffi/releases/tag/1.9.24",
- "tool": "bundler_audit"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-license-management-report.json b/spec/fixtures/security-reports/master/gl-license-management-report.json
deleted file mode 100644
index e0de6f58fdf..00000000000
--- a/spec/fixtures/security-reports/master/gl-license-management-report.json
+++ /dev/null
@@ -1,817 +0,0 @@
-{
- "licenses": [
- {
- "count": 52,
- "name": "MIT"
- },
- {
- "count": 3,
- "name": "New BSD"
- },
- {
- "count": 1,
- "name": "Apache 2.0"
- },
- {
- "count": 1,
- "name": "unknown"
- }
- ],
- "dependencies": [
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actioncable",
- "url": "http://rubyonrails.org",
- "description": "WebSocket framework for Rails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionmailer",
- "url": "http://rubyonrails.org",
- "description": "Email composition, delivery, and receiving framework (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionpack",
- "url": "http://rubyonrails.org",
- "description": "Web-flow and rendering framework putting the VC in MVC (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "actionview",
- "url": "http://rubyonrails.org",
- "description": "Rendering framework putting the V in MVC (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activejob",
- "url": "http://rubyonrails.org",
- "description": "Job framework with pluggable queues.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activemodel",
- "url": "http://rubyonrails.org",
- "description": "A toolkit for building modeling frameworks (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activerecord",
- "url": "http://rubyonrails.org",
- "description": "Object-relational mapper framework (part of Rails).",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "activesupport",
- "url": "http://rubyonrails.org",
- "description": "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "arel",
- "url": "https://github.com/rails/arel",
- "description": "Arel Really Exasperates Logicians Arel is a SQL AST manager for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "builder",
- "url": "http://onestepback.org",
- "description": "Builders for MarkUp.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "bundler",
- "url": "http://bundler.io",
- "description": "The best way to manage your application's dependencies",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-rails",
- "url": "https://github.com/rails/coffee-rails",
- "description": "CoffeeScript adapter for the Rails asset pipeline.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-script",
- "url": "http://github.com/josh/ruby-coffee-script",
- "description": "Ruby CoffeeScript Compiler",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "coffee-script-source",
- "url": "http://coffeescript.org",
- "description": "The CoffeeScript Compiler",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "concurrent-ruby",
- "url": "http://www.concurrent-ruby.com",
- "description": "Modern concurrency tools for Ruby. Inspired by Erlang, Clojure, Scala, Haskell, F#, C#, Java, and classic concurrency patterns.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "crass",
- "url": "https://github.com/rgrove/crass/",
- "description": "CSS parser based on the CSS Syntax Level 3 spec.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "erubis",
- "url": "http://www.kuwata-lab.com/erubis/",
- "description": "a fast and extensible eRuby implementation which supports multi-language",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "execjs",
- "url": "https://github.com/rails/execjs",
- "description": "Run JavaScript code from Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "ffi",
- "url": "http://wiki.github.com/ffi/ffi",
- "description": "Ruby FFI",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "globalid",
- "url": "http://www.rubyonrails.org",
- "description": "Refer to any model with a URI: gid://app/class/id",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "i18n",
- "url": "http://github.com/svenfuchs/i18n",
- "description": "New wave Internationalization support for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "jbuilder",
- "url": "https://github.com/rails/jbuilder",
- "description": "Create JSON structures via a Builder-style DSL",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "loofah",
- "description": "",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mail",
- "url": "https://github.com/mikel/mail",
- "description": "Mail provides a nice Ruby DSL for making, sending and reading emails.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "method_source",
- "url": "http://banisterfiend.wordpress.com",
- "description": "retrieve the sourcecode for a method",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mini_mime",
- "url": "https://github.com/discourse/mini_mime",
- "description": "A lightweight mime type lookup toy",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "mini_portile2",
- "url": "http://github.com/flavorjones/mini_portile",
- "description": "Simplistic port-like solution for developers",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "minitest",
- "url": "https://github.com/seattlerb/minitest",
- "description": "minitest provides a complete suite of testing facilities supporting TDD, BDD, mocking, and benchmarking",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "multi_json",
- "url": "http://github.com/intridea/multi_json",
- "description": "A common interface to multiple JSON libraries.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "nio4r",
- "url": "https://github.com/celluloid/nio4r",
- "description": "NIO provides a high performance selector API for monitoring IO objects",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "nokogiri",
- "url": "http://nokogiri.org",
- "description": "Nokogiri (鋸) is an HTML, XML, SAX, and Reader parser",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "puma",
- "url": "http://puma.io",
- "description": "Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server for Ruby/Rack applications",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack",
- "url": "https://rack.github.io/",
- "description": "a modular Ruby webserver interface",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rack-test",
- "url": "http://github.com/brynary/rack-test",
- "description": "Simple testing API built on Rack",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails",
- "url": "http://rubyonrails.org",
- "description": "Full-stack web application framework.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails-dom-testing",
- "url": "https://github.com/rails/rails-dom-testing",
- "description": "Dom and Selector assertions for Rails applications",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rails-html-sanitizer",
- "url": "https://github.com/rails/rails-html-sanitizer",
- "description": "This gem is responsible to sanitize HTML fragments in Rails applications.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "railties",
- "url": "http://rubyonrails.org",
- "description": "Tools for creating, working with, and running Rails applications.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rake",
- "url": "https://github.com/ruby/rake",
- "description": "Rake is a Make-like program implemented in Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rb-fsevent",
- "url": "http://rubygems.org/gems/rb-fsevent",
- "description": "Very simple & usable FSEvents API",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "rb-inotify",
- "url": "https://github.com/guard/rb-inotify",
- "description": "A Ruby wrapper for Linux inotify, using FFI",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "unknown"
- },
- "dependency": {
- "name": "ruby-bundler-rails",
- "description": "",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass",
- "url": "http://sass-lang.com/",
- "description": "A powerful but elegant CSS compiler that makes CSS fun again.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass-listen",
- "url": "https://github.com/sass/listen",
- "description": "Fork of guard/listen",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sass-rails",
- "url": "https://github.com/rails/sass-rails",
- "description": "Sass adapter for the Rails asset pipeline.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sprockets",
- "url": "https://github.com/rails/sprockets",
- "description": "Rack-based asset packaging system",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "sprockets-rails",
- "url": "https://github.com/rails/sprockets-rails",
- "description": "Sprockets Rails integration",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "New BSD",
- "url": "http://opensource.org/licenses/BSD-3-Clause"
- },
- "dependency": {
- "name": "sqlite3",
- "url": "https://github.com/sparklemotion/sqlite3-ruby",
- "description": "This module allows Ruby programs to interface with the SQLite3 database engine (http://www.sqlite.org)",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "thor",
- "url": "http://whatisthor.com/",
- "description": "Thor is a toolkit for building powerful command-line interfaces.",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "Apache 2.0",
- "url": "http://www.apache.org/licenses/LICENSE-2.0.txt"
- },
- "dependency": {
- "name": "thread_safe",
- "url": "https://github.com/ruby-concurrency/thread_safe",
- "description": "Thread-safe collections and utilities for Ruby",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "tilt",
- "url": "http://github.com/rtomayko/tilt/",
- "description": "Generic interface to multiple Ruby template engines",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "turbolinks",
- "url": "https://github.com/turbolinks/turbolinks",
- "description": "Turbolinks makes navigating your web application faster",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "turbolinks-source",
- "url": "https://github.com/turbolinks/turbolinks-source-gem",
- "description": "Turbolinks JavaScript assets",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "tzinfo",
- "url": "http://tzinfo.github.io",
- "description": "Daylight savings aware timezone library",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "uglifier",
- "url": "http://github.com/lautis/uglifier",
- "description": "Ruby wrapper for UglifyJS JavaScript compressor",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "websocket-driver",
- "url": "http://github.com/faye/websocket-driver-ruby",
- "description": "WebSocket protocol handler with pluggable I/O",
- "pathes": [
- "."
- ]
- }
- },
- {
- "license": {
- "name": "MIT",
- "url": "http://opensource.org/licenses/mit-license"
- },
- "dependency": {
- "name": "websocket-extensions",
- "url": "https://github.com/faye/websocket-extensions-ruby",
- "description": "Generic extension manager for WebSocket connections",
- "pathes": [
- "."
- ]
- }
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/master/gl-sast-report.json b/spec/fixtures/security-reports/master/gl-sast-report.json
deleted file mode 100644
index 345e1e9f83a..00000000000
--- a/spec/fixtures/security-reports/master/gl-sast-report.json
+++ /dev/null
@@ -1,967 +0,0 @@
-{
- "version": "1.2",
- "vulnerabilities": [
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:52865813c884a507be1f152d654245af34aba8a391626d01f1ab6d3f52ec8779:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 1,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:47:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 47,
- "end_line": 47,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken2"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 47,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Predictable pseudorandom number generator",
- "message": "Predictable pseudorandom number generator",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:41:PREDICTABLE_RANDOM",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 41,
- "end_line": 41,
- "class": "com.gitlab.security_products.tests.App",
- "method": "generateSecretToken1"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-PREDICTABLE_RANDOM",
- "value": "PREDICTABLE_RANDOM",
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 41,
- "url": "https://find-sec-bugs.github.io/bugs.htm#PREDICTABLE_RANDOM",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:cb203b465dffb0cb3a8e8bd8910b84b93b0a5995a938e4b903dbb0cd6ffa1254:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 11,
- "end_line": 11
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 11,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:a7173c43ae66bd07466632d819d450e0071e02dbf782763640d1092981f9631b:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 12,
- "end_line": 12
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 12,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:017017b77deb0b8369b6065947833eeea752a92ec8a700db590fece3e934cf0d:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 13,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Use of insecure MD2, MD4, or MD5 hash function.",
- "cve": "python/imports/imports-aliases.py:45fc8c53aea7b84f06bc4e590cc667678d6073c4c8a1d471177ca2146fb22db2:B303",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B303",
- "value": "B303"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 14,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Pickle library appears to be in use, possible security issue.",
- "cve": "python/imports/imports-aliases.py:5f200d47291e7bbd8352db23019b85453ca048dd98ea0c291260fa7d009963a4:B301",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 15,
- "end_line": 15
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B301",
- "value": "B301"
- }
- ],
- "priority": "Medium",
- "file": "python/imports/imports-aliases.py",
- "line": 15,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "name": "ECB mode is insecure",
- "message": "ECB mode is insecure",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:ECB_MODE",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-ECB_MODE",
- "value": "ECB_MODE",
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#ECB_MODE",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "name": "Cipher with no integrity",
- "message": "Cipher with no integrity",
- "cve": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy:29:CIPHER_INTEGRITY",
- "severity": "Medium",
- "confidence": "High",
- "scanner": {
- "id": "find_sec_bugs",
- "name": "Find Security Bugs"
- },
- "location": {
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "start_line": 29,
- "end_line": 29,
- "class": "com.gitlab.security_products.tests.App",
- "method": "insecureCypher"
- },
- "identifiers": [
- {
- "type": "find_sec_bugs_type",
- "name": "Find Security Bugs-CIPHER_INTEGRITY",
- "value": "CIPHER_INTEGRITY",
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY"
- }
- ],
- "priority": "Medium",
- "file": "groovy/src/main/java/com/gitlab/security_products/tests/App.groovy",
- "line": 29,
- "url": "https://find-sec-bugs.github.io/bugs.htm#CIPHER_INTEGRITY",
- "tool": "find_sec_bugs"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:63dd4d626855555b816985d82c4614a790462a0a3ada89dc58eb97f9c50f3077:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 14,
- "end_line": 14
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 14,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Probable insecure usage of temp file/directory.",
- "cve": "python/hardcoded/hardcoded-tmp.py:4ad6d4c40a8c263fc265f3384724014e0a4f8dd6200af83e51ff120420038031:B108",
- "severity": "Medium",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-tmp.py",
- "start_line": 10,
- "end_line": 10
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B108",
- "value": "B108",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html"
- }
- ],
- "priority": "Medium",
- "file": "python/hardcoded/hardcoded-tmp.py",
- "line": 10,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b108_hardcoded_tmp_directory.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-aliases.py:2c3e1fa1e54c3c6646e8bcfaee2518153c6799b77587ff8d9a7b0631f6d34785:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 1,
- "end_line": 1
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports.py:af58d07f6ad519ef5287fcae65bf1a6999448a1a3a8bc1ac2a11daa80d0b96bf:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports.py:8de9bc98029d212db530785a5f6780cfa663548746ff228ab8fa96c5bb82f089:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:97c30f1d76d2a88913e3ce9ae74087874d740f87de8af697a9c455f01119f633:B106",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 22,
- "end_line": 22
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B106",
- "value": "B106",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 22,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b106_hardcoded_password_funcarg.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'root'",
- "cve": "python/hardcoded/hardcoded-passwords.py:7431c73a0bc16d94ece2a2e75ef38f302574d42c37ac0c3c38ad0b3bf8a59f10:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 5,
- "end_line": 5
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 5,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: ''",
- "cve": "python/hardcoded/hardcoded-passwords.py:d2d1857c27caedd49c57bfbcdc23afcc92bd66a22701fcdc632869aab4ca73ee:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'ajklawejrkl42348swfgkg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:fb3866215a61393a5c9c32a3b60e2058171a23219c353f722cbd3567acab21d2:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 13,
- "end_line": 13
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 13,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:63c62a8b7e1e5224439bd26b28030585ac48741e28ca64561a6071080c560a5f:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 23,
- "end_line": 23
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 23,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Possible hardcoded password: 'blerg'",
- "cve": "python/hardcoded/hardcoded-passwords.py:4311b06d08df8fa58229b341c531da8e1a31ec4520597bdff920cd5c098d86f9:B105",
- "severity": "Low",
- "confidence": "Medium",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/hardcoded/hardcoded-passwords.py",
- "start_line": 24,
- "end_line": 24
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B105",
- "value": "B105",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html"
- }
- ],
- "priority": "Low",
- "file": "python/hardcoded/hardcoded-passwords.py",
- "line": 24,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b105_hardcoded_password_string.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-function.py:5858400c2f39047787702de44d03361ef8d954c9d14bd54ee1c2bef9e6a7df93:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 4,
- "end_line": 4
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 4,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-function.py:dbda3cf4190279d30e0aad7dd137eca11272b0b225e8af4e8bf39682da67d956:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-function.py",
- "start_line": 2,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-function.py",
- "line": 2,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:eb8a0db9cd1a8c1ab39a77e6025021b1261cc2a0b026b2f4a11fca4e0636d8dd:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 7,
- "end_line": 7
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "subprocess call with shell=True seems safe, but may be changed in the future, consider rewriting without shell",
- "cve": "python/imports/imports-aliases.py:f99f9721e27537fbcb6699a4cf39c6740d6234d2c6f06cfc2d9ea977313c483d:B602",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 9,
- "end_line": 9
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B602",
- "value": "B602",
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 9,
- "url": "https://docs.openstack.org/bandit/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html",
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with subprocess module.",
- "cve": "python/imports/imports-from.py:332a12ab1146698f614a905ce6a6a5401497a12281aef200e80522711c69dcf4:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with Popen module.",
- "cve": "python/imports/imports-from.py:0a48de4a3d5348853a03666cb574697e3982998355e7a095a798bd02a5947276:B404",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-from.py",
- "start_line": 1,
- "end_line": 2
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B404",
- "value": "B404"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-from.py",
- "line": 1,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with pickle module.",
- "cve": "python/imports/imports-aliases.py:51b71661dff994bde3529639a727a678c8f5c4c96f00d300913f6d5be1bbdf26:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 7,
- "end_line": 8
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 7,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Consider possible security implications associated with loads module.",
- "cve": "python/imports/imports-aliases.py:6ff02aeb3149c01ab68484d794a94f58d5d3e3bb0d58557ef4153644ea68ea54:B403",
- "severity": "Low",
- "confidence": "High",
- "scanner": {
- "id": "bandit",
- "name": "Bandit"
- },
- "location": {
- "file": "python/imports/imports-aliases.py",
- "start_line": 6,
- "end_line": 6
- },
- "identifiers": [
- {
- "type": "bandit_test_id",
- "name": "Bandit Test ID B403",
- "value": "B403"
- }
- ],
- "priority": "Low",
- "file": "python/imports/imports-aliases.py",
- "line": 6,
- "tool": "bandit"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "c/subdir/utils.c:b466873101951fe96e1332f6728eb7010acbbd5dfc3b65d7d53571d091a06d9e:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 4
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - char",
- "value": "char"
- },
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 4,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Check when opening files - can an attacker redirect it (via symlinks), force the opening of special file type (e.g., device files), move things around to create a race condition, control its ancestors, or change its contents? (CWE-362)",
- "cve": "c/subdir/utils.c:bab681140fcc8fc3085b6bba74081b44ea145c1c98b5e70cf19ace2417d30770:CWE-362",
- "confidence": "Low",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "c/subdir/utils.c",
- "start_line": 8
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - fopen",
- "value": "fopen"
- },
- {
- "type": "cwe",
- "name": "CWE-362",
- "value": "362",
- "url": "https://cwe.mitre.org/data/definitions/362.html"
- }
- ],
- "file": "c/subdir/utils.c",
- "line": 8,
- "url": "https://cwe.mitre.org/data/definitions/362.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Statically-sized arrays can be improperly restricted, leading to potential overflows or other issues (CWE-119!/CWE-120)",
- "cve": "cplusplus/src/hello.cpp:c8c6dd0afdae6814194cf0930b719f757ab7b379cf8f261e7f4f9f2f323a818a:CWE-119!/CWE-120",
- "confidence": "Low",
- "solution": "Perform bounds checking, use functions that limit length, or ensure that the size is larger than the maximum possible length",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 6
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - char",
- "value": "char"
- },
- {
- "type": "cwe",
- "name": "CWE-119",
- "value": "119",
- "url": "https://cwe.mitre.org/data/definitions/119.html"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 6,
- "url": "https://cwe.mitre.org/data/definitions/119.html",
- "tool": "flawfinder"
- },
- {
- "category": "sast",
- "message": "Does not check for buffer overflows when copying to destination [MS-banned] (CWE-120)",
- "cve": "cplusplus/src/hello.cpp:331c04062c4fe0c7c486f66f59e82ad146ab33cdd76ae757ca41f392d568cbd0:CWE-120",
- "confidence": "Low",
- "solution": "Consider using snprintf, strcpy_s, or strlcpy (warning: strncpy easily misused)",
- "scanner": {
- "id": "flawfinder",
- "name": "Flawfinder"
- },
- "location": {
- "file": "cplusplus/src/hello.cpp",
- "start_line": 7
- },
- "identifiers": [
- {
- "type": "flawfinder_func_name",
- "name": "Flawfinder - strcpy",
- "value": "strcpy"
- },
- {
- "type": "cwe",
- "name": "CWE-120",
- "value": "120",
- "url": "https://cwe.mitre.org/data/definitions/120.html"
- }
- ],
- "file": "cplusplus/src/hello.cpp",
- "line": 7,
- "url": "https://cwe.mitre.org/data/definitions/120.html",
- "tool": "flawfinder"
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
deleted file mode 100644
index c96e831b027..00000000000
--- a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "version": "2.0",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "cve": "yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://nodesecurity.io/advisories/534"
- },
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [
- {
- "fixes": [
- {
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"
- }
- ],
- "summary": "Upgrade saml2-js",
- "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95YXJuLmxvY2sKKysrIGIveWFybi5sb2NrCkBAIC0yLDEwMyArMiwxMjQgQEAKICMgeWFybiBsb2NrZmlsZSB2MQogCiAKLWFzeW5jQH4wLjIuNzoKLSAgdmVyc2lvbiAiMC4yLjEwIgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy9hc3luYy8tL2FzeW5jLTAuMi4xMC50Z3ojYjZiYmUwYjA2NzRiOWQ3MTk3MDhjYTM4ZGU4YzIzN2NiNTI2YzNkMSIKLQotYXN5bmNAfjEuNS4yOgotICB2ZXJzaW9uICIxLjUuMiIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvYXN5bmMvLS9hc3luYy0xLjUuMi50Z3ojZWM2YTYxYWU1NjQ4MGMwYzNjYjI0MWM5NTYxOGUyMDg5MmY5NjcyYSIKK2FzeW5jQF4yLjEuNSwgYXN5bmNAXjIuNS4wOgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vYXN5bmMvLS9hc3luYy0yLjYuMS50Z3ojYjI0NWEyM2NhNzE5MzAwNDRlYzUzZmE0NmFhMDBhM2U4N2M2YTYxMCIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mTkVpTDIrQVp0NkFsQXcvMjlDcjBVRGU0c1JBSENwRUhoNTRXTXorQmI3UWZOY0Z3NGgzbG9vZnlKcExlUXM0WXg3eXVxdS8yZExnTTVoS09zNkhsUT09CisgIGRlcGVuZGVuY2llczoKKyAgICBsb2Rhc2ggIl40LjE3LjEwIgogCi1kZWJ1Z0BeMS4wLjQ6Ci0gIHZlcnNpb24gIjEuMC41IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9kZWJ1Zy8tL2RlYnVnLTEuMC41LnRneiNmNzI0MTIxNzQzMGY5OWRlYzRjMmI0NzNlYWI5MjIyOGU4NzRjMmFjIgorZGVidWdAXjIuNi4wOgorICB2ZXJzaW9uICIyLjYuOSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZGVidWcvLS9kZWJ1Zy0yLjYuOS50Z3ojNWQxMjg1MTVkZjEzNGZmMzI3ZTkwYTRjOTNmNGUwNzdhNTM2MzQxZiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1iQzdFbHJkSmFKblBiQVArMUVvdFl2cVpzYjNlY2w1d2k2QmZpNkJKVFVjTm93cDZjdnNwZzBqWHpuUlRLRGptL0U3QWRnRkJWZUFQVk1OY0tHc0hNQT09CiAgIGRlcGVuZGVuY2llczoKICAgICBtcyAiMi4wLjAiCiAKLWVqc0B+MC44LjM6Ci0gIHZlcnNpb24gIjAuOC44IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9lanMvLS9lanMtMC44LjgudGd6I2ZmZGM1NmRjYzM1ZDAyOTI2ZGQ1MGFkMTM0MzliYmM1NDA2MWQ1OTgiCitlanNAXjIuNS42OgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZWpzLy0vZWpzLTIuNi4xLnRneiM0OThlYzBkNDk1NjU1YWJjNmYyM2NkNjE4NjhkOTI2NDY0MDcxYWEwIgorICBpbnRlZ3JpdHkgc2hhNTEyLTB4eTRBL3R3ZnJSQ25raGZrOEVyRGk1RHFkQXNBcWVHeGh0NHhrQ1Vyc3ZoaGJRTnM3RSs0alYwQ043K05LSVkwYUhFNzIrWHZxdEJJWHpEMzFaYlhRPT0KKworbG9kYXNoLW5vZGVAfjIuNC4xOgorICB2ZXJzaW9uICIyLjQuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbG9kYXNoLW5vZGUvLS9sb2Rhc2gtbm9kZS0yLjQuMS50Z3ojZWE4MmY3YjEwMGM3MzNkMWE0MmFmNzY4MDFlNTA2MTA1ZTJhODBlYyIKKyAgaW50ZWdyaXR5IHNoYTEtNm9MM3NRREhNOUdrS3Zkb0FlVUdFRjRxZ093PQorCitsb2Rhc2hAXjQuMTcuMTA6CisgIHZlcnNpb24gIjQuMTcuMTEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL2xvZGFzaC8tL2xvZGFzaC00LjE3LjExLnRneiNiMzllYTYyMjllZjYwN2VjZDg5ZTJjOGRmMTI1MzY4OTFjYWM5YjhkIgorICBpbnRlZ3JpdHkgc2hhNTEyLWNRS2g4aWdvNVFVaFo3bGczOERZV0F4TXZqU0FLRzBBOHdHU1ZpbVAwN1NJVUVLMlVPK2FyU1JLYlJaV3RlbE10TjVWMEhrd2g1cnlPdG8vU3NoWUlnPT0KIAogbXNAMi4wLjA6CiAgIHZlcnNpb24gIjIuMC4wIgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9tcy8tL21zLTIuMC4wLnRneiM1NjA4YWVhZGZjMDBiZTZjMjkwMWRmNWY5ODYxNzg4ZGUwZDU5N2M4IgorICBpbnRlZ3JpdHkgc2hhMS1WZ2l1cmZ3QXZtd3BBZDlmbUdGNGplRFZsOGc9CiAKLW5vZGUtZm9yZ2VAMC4yLjI0OgotICB2ZXJzaW9uICIwLjIuMjQiCi0gIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL25vZGUtZm9yZ2UvLS9ub2RlLWZvcmdlLTAuMi4yNC50Z3ojZmE2Zjg0NmY0MmZhOTNmNjNhMGEzMGM5ZmJmZjdiNGUxMzBlMDg1OCIKK25vZGUtZm9yZ2VAXjAuNy4wOgorICB2ZXJzaW9uICIwLjcuNiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbm9kZS1mb3JnZS8tL25vZGUtZm9yZ2UtMC43LjYudGd6I2ZkZjNiNDE4YWVlMWY5NGYwZWY2NDJjZDYzNDg2Yzc3Y2E5NzI0YWMiCisgIGludGVncml0eSBzaGE1MTItc29sMzBMVXB6MWpRRkJqT0t3Ymp4aWppRTNiNnBqZDc0WXdmRDBmSk9LUGpGK2ZPTktiMllnOHJZZ1M2K2JLNlZEbCsvd2ZyNElZcEM3akR6TFVJZnc9PQogCiBzYW1sMi1qc0BeMS41LjA6Ci0gIHZlcnNpb24gIjEuNS4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYW1sMi1qcy8tL3NhbWwyLWpzLTEuNS4wLnRneiNjMGQyMjY4YTE3OWU3MzI5ZDI5ZWIyNWFhODJkZjU1MDM3NzRiMGQ5IgorICB2ZXJzaW9uICIxLjEyLjQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3NhbWwyLWpzLy0vc2FtbDItanMtMS4xMi40LnRneiNjMjg4ZjIwYmRhNmQyYjkxMDczYjE2Yzk0ZWE3MmYyMjM0OWFjM2IzIgorICBpbnRlZ3JpdHkgc2hhMS13b2p5QzlwdEs1RUhPeGJKVHFjdklqU2F3N009CiAgIGRlcGVuZGVuY2llczoKLSAgICBhc3luYyAifjEuNS4yIgotICAgIGRlYnVnICJeMS4wLjQiCi0gICAgdW5kZXJzY29yZSAifjEuNi4wIgotICAgIHhtbC1jcnlwdG8gIl4wLjguMSIKLSAgICB4bWwtZW5jcnlwdGlvbiAifjAuNy40IgotICAgIHhtbDJqcyAifjAuNC4xIgotICAgIHhtbGJ1aWxkZXIgIn4yLjEuMCIKLSAgICB4bWxkb20gIn4wLjEuMTkiCisgICAgYXN5bmMgIl4yLjUuMCIKKyAgICBkZWJ1ZyAiXjIuNi4wIgorICAgIHVuZGVyc2NvcmUgIl4xLjguMCIKKyAgICB4bWwtY3J5cHRvICJeMC4xMC4wIgorICAgIHhtbC1lbmNyeXB0aW9uICJeMC4xMS4wIgorICAgIHhtbDJqcyAiXjAuNC4wIgorICAgIHhtbGJ1aWxkZXIgIn4yLjIuMCIKKyAgICB4bWxkb20gIl4wLjEuMCIKIAogc2F4QD49MC42LjA6CiAgIHZlcnNpb24gIjEuMi40IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYXgvLS9zYXgtMS4yLjQudGd6IzI4MTYyMzRlMjM3OGJkZGM0ZTUzNTRmYWI1Y2FhODk1ZGY3MTAwZDkiCisgIGludGVncml0eSBzaGE1MTItTnFWRHY5VHBBTlVqRm0wTjh1TTVHeEwzNlVnS2k5L2F0WncreDdZRm5ROGNrd0ZHS3JsNHhYNHlXdHJleTNVSm01blAxa1VibllnTG9wcVdOU1JoV3c9PQogCi11bmRlcnNjb3JlQD49MS41Lng6Cit1bmRlcnNjb3JlQF4xLjguMDoKICAgdmVyc2lvbiAiMS45LjEiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3VuZGVyc2NvcmUvLS91bmRlcnNjb3JlLTEuOS4xLnRneiMwNmRjZTM0YTBlNjhhN2JhYmMyOWIzNjViOGU3NGI4OTI1MjAzOTYxIgorICBpbnRlZ3JpdHkgc2hhNTEyLTUvNGV0bkNrZDljOGd3Z293aTUvb20vbVlPNWFqQ2FPZ2R6ai9vVyswZVFWOVd4S0JEWnc1K3ljbUttZWFUWGpJblMvVzBCenBHTG8yeFIyYUJ3WmRnPT0KIAotdW5kZXJzY29yZUB+MS42LjA6Ci0gIHZlcnNpb24gIjEuNi4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS91bmRlcnNjb3JlLy0vdW5kZXJzY29yZS0xLjYuMC50Z3ojOGIzOGIxMGNhY2RlZjYzMzM3YjhiMjRlNGZmODZkNDVhZWE1MjlhOCIKLQoteG1sLWNyeXB0b0BeMC44LjE6Ci0gIHZlcnNpb24gIjAuOC41IgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjguNS50Z3ojMmJiY2ZiM2ViMzNmM2E4MmEyMThiODIyYmY2NzJiNmIxYzIwZTUzOCIKK3htbC1jcnlwdG9AXjAuMTAuMDoKKyAgdmVyc2lvbiAiMC4xMC4xIgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjEwLjEudGd6I2Y4MzJmNzRjY2Y1NmYyNGFmY2FlMTE2M2ExZmNhYjQ0ZDk2Nzc0YTgiCisgIGludGVncml0eSBzaGExLStETDNUTTlXOGtyOHJoRmpvZnlyUk5sbmRLZz0KICAgZGVwZW5kZW5jaWVzOgogICAgIHhtbGRvbSAiPTAuMS4xOSIKICAgICB4cGF0aC5qcyAiPj0wLjAuMyIKIAoteG1sLWVuY3J5cHRpb25AfjAuNy40OgotICB2ZXJzaW9uICIwLjcuNCIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjcuNC50Z3ojNDI3OTFlYzY0ZDU1NmQyNDU1ZGNiOWRhMGE1NDEyMzY2NWFjNjVjNyIKK3htbC1lbmNyeXB0aW9uQF4wLjExLjA6CisgIHZlcnNpb24gIjAuMTEuMiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjExLjIudGd6I2MyMTdmNTUwOTU0N2UzNGI1MDBiODI5ZjJjMGJjYTg1Y2NhNzNhMjEiCisgIGludGVncml0eSBzaGE1MTItalZ2RVM3aTVvdmRPN04rTmpnbmNBMzI2eFlLamhxZUFubnZJZ1JuWTdST0xDZkZxRURMd1AwU3hwLzMwU0hHMEFYUVYxMDQ4VDV5aW5PRnl2d0dGemc9PQogICBkZXBlbmRlbmNpZXM6Ci0gICAgYXN5bmMgIn4wLjIuNyIKLSAgICBlanMgIn4wLjguMyIKLSAgICBub2RlLWZvcmdlICIwLjIuMjQiCisgICAgYXN5bmMgIl4yLjEuNSIKKyAgICBlanMgIl4yLjUuNiIKKyAgICBub2RlLWZvcmdlICJeMC43LjAiCiAgICAgeG1sZG9tICJ+MC4xLjE1IgotICAgIHhwYXRoICIwLjAuNSIKKyAgICB4cGF0aCAiMC4wLjI3IgogCi14bWwyanNAfjAuNC4xOgoreG1sMmpzQF4wLjQuMDoKICAgdmVyc2lvbiAiMC40LjE5IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwyanMvLS94bWwyanMtMC40LjE5LnRneiM2ODZjMjBmMjEzMjA5ZTk0YWJmMGQxYmNmMWVmYWEyOTFjNzgyN2E3IgorICBpbnRlZ3JpdHkgc2hhNTEyLWVzWm5KWkpPaUpSOXdXS015dXZTRTF5NkRxNUxDdUphbnFoeHNsSDJieE02ZHVhaE5aK0hNcENMaEJRR1prYlg2eFJmOHgxWTJlSmxndDJxM3FvNDlRPT0KICAgZGVwZW5kZW5jaWVzOgogICAgIHNheCAiPj0wLjYuMCIKICAgICB4bWxidWlsZGVyICJ+OS4wLjEiCiAKLXhtbGJ1aWxkZXJAfjIuMS4wOgotICB2ZXJzaW9uICIyLjEuMCIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItMi4xLjAudGd6IzZkZGFlMzE2ODNiNmRmMTIxMDBiMjlmYzhhMGQ0ZjQ2MzQ5YWJiZWQiCit4bWxidWlsZGVyQH4yLjIuMDoKKyAgdmVyc2lvbiAiMi4yLjEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTIuMi4xLnRneiM5MzI2NDMwZjEzMGQ4NzQzNWQ0YzQwODY2NDNhYTI5MjZlMTA1YTMyIgorICBpbnRlZ3JpdHkgc2hhMS1reVpERHhNTmgwTmRURUNHWkRxaWttNFFXakk9CiAgIGRlcGVuZGVuY2llczoKLSAgICB1bmRlcnNjb3JlICI+PTEuNS54IgorICAgIGxvZGFzaC1ub2RlICJ+Mi40LjEiCiAKIHhtbGJ1aWxkZXJAfjkuMC4xOgogICB2ZXJzaW9uICI5LjAuNyIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItOS4wLjcudGd6IzEzMmVlNjNkMmVjNTU2NWM1NTdlMjBmNGMyMmRmOWFjYTY4NmIxMGQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTkuMC43LnRneiMxMzJlZTYzZDJlYzU1NjVjNTU3ZTIwZjRjMjJkZjlhY2E2ODZiMTBkIgorICBpbnRlZ3JpdHkgc2hhMS1FeTdtUFM3RlZseFZmaUQwd2kzNXJLYUdzUTA9CiAKIHhtbGRvbUA9MC4xLjE5OgogICB2ZXJzaW9uICIwLjEuMTkiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGRvbS8tL3htbGRvbS0wLjEuMTkudGd6IzYzMWZjMDc3NzZlZmQ4NDExOGJmMjUxNzFiMzdlZDRkMDc1YTBhYmMiCisgIGludGVncml0eSBzaGExLVl4L0FkM2J2MkVFWXZ5VVhHemZ0VFFkYUNydz0KIAoteG1sZG9tQH4wLjEuMTUsIHhtbGRvbUB+MC4xLjE5OgoreG1sZG9tQF4wLjEuMCwgeG1sZG9tQH4wLjEuMTU6CiAgIHZlcnNpb24gIjAuMS4yNyIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sZG9tLy0veG1sZG9tLTAuMS4yNy50Z3ojZDUwMWY5N2IzYmRiNDAzYWY4ZWY5ZWNjMjA1NzMxODdhYWRhYzBlOSIKKyAgaW50ZWdyaXR5IHNoYTEtMVFINWV6dmJRRHI0NzU3TUlGY3hoNnJhd09rPQogCiB4cGF0aC5qc0A+PTAuMC4zOgogICB2ZXJzaW9uICIxLjEuMCIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGguanMvLS94cGF0aC5qcy0xLjEuMC50Z3ojMzgxNmE0NGVkNGJiMzUyMDkxMDgzZDAwMmEzODNkZDUxMDRhNWZmMSIKKyAgaW50ZWdyaXR5IHNoYTUxMi1qZytxa2ZTNEs4RTc5NjVzcWFVbDhtUm5nWGlLYjNXWkdmT05nRTE4cHIwM0ZVUWl1U1Y2RytFajR0UzU1QitySVFTRkVJdzNwaGRWQVE0cFBxTldmUT09CiAKLXhwYXRoQDAuMC41OgotICB2ZXJzaW9uICIwLjAuNSIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGgvLS94cGF0aC0wLjAuNS50Z3ojNDU0MDM2ZjZlZjBmM2RmNWFmNWQ0YmE0YTExOWZiNzU2NzRiM2U2YyIKK3hwYXRoQDAuMC4yNzoKKyAgdmVyc2lvbiAiMC4wLjI3IgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94cGF0aC8tL3hwYXRoLTAuMC4yNy50Z3ojZGQzNDIxZmJkY2M1NjQ2YWMzMmM0ODUzMWI0ZDdlOWQwYzJjZmE5MiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mZzAzV1J4dGtDVjZvaENsZVBOQUVDWXNtcEtLVHY1TDh5L1gzRG4xaFFyZWMzUE94MmpIWi8wUDJxUTZIdnNyVTFCbWVxWGNvZjNOR0d1ZUc2THh3UT09Cg=="
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/remediations/remediation.patch b/spec/fixtures/security-reports/remediations/remediation.patch
deleted file mode 100644
index bbfb6874627..00000000000
--- a/spec/fixtures/security-reports/remediations/remediation.patch
+++ /dev/null
@@ -1,180 +0,0 @@
-diff --git a/yarn.lock b/yarn.lock
-index 0ecc92f..7fa4554 100644
---- a/yarn.lock
-+++ b/yarn.lock
-@@ -2,103 +2,124 @@
- # yarn lockfile v1
-
-
--async@~0.2.7:
-- version "0.2.10"
-- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
--
--async@~1.5.2:
-- version "1.5.2"
-- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-+async@^2.1.5, async@^2.5.0:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-+ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-+ dependencies:
-+ lodash "^4.17.10"
-
--debug@^1.0.4:
-- version "1.0.5"
-- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
-+debug@^2.6.0:
-+ version "2.6.9"
-+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
-+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
--ejs@~0.8.3:
-- version "0.8.8"
-- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-+ejs@^2.5.6:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
-+ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-+
-+lodash-node@~2.4.1:
-+ version "2.4.1"
-+ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec"
-+ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=
-+
-+lodash@^4.17.10:
-+ version "4.17.11"
-+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-+ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-
- ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
--node-forge@0.2.24:
-- version "0.2.24"
-- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-+node-forge@^0.7.0:
-+ version "0.7.6"
-+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
-+ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
-
- saml2-js@^1.5.0:
-- version "1.5.0"
-- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
-+ version "1.12.4"
-+ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3"
-+ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M=
- dependencies:
-- async "~1.5.2"
-- debug "^1.0.4"
-- underscore "~1.6.0"
-- xml-crypto "^0.8.1"
-- xml-encryption "~0.7.4"
-- xml2js "~0.4.1"
-- xmlbuilder "~2.1.0"
-- xmldom "~0.1.19"
-+ async "^2.5.0"
-+ debug "^2.6.0"
-+ underscore "^1.8.0"
-+ xml-crypto "^0.10.0"
-+ xml-encryption "^0.11.0"
-+ xml2js "^0.4.0"
-+ xmlbuilder "~2.2.0"
-+ xmldom "^0.1.0"
-
- sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-
--underscore@>=1.5.x:
-+underscore@^1.8.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-+ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
-
--underscore@~1.6.0:
-- version "1.6.0"
-- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
--
--xml-crypto@^0.8.1:
-- version "0.8.5"
-- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
-+xml-crypto@^0.10.0:
-+ version "0.10.1"
-+ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8"
-+ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
--xml-encryption@~0.7.4:
-- version "0.7.4"
-- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
-+xml-encryption@^0.11.0:
-+ version "0.11.2"
-+ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21"
-+ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==
- dependencies:
-- async "~0.2.7"
-- ejs "~0.8.3"
-- node-forge "0.2.24"
-+ async "^2.1.5"
-+ ejs "^2.5.6"
-+ node-forge "^0.7.0"
- xmldom "~0.1.15"
-- xpath "0.0.5"
-+ xpath "0.0.27"
-
--xml2js@~0.4.1:
-+xml2js@^0.4.0:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
-+ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
--xmlbuilder@~2.1.0:
-- version "2.1.0"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
-+xmlbuilder@~2.2.0:
-+ version "2.2.1"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32"
-+ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=
- dependencies:
-- underscore ">=1.5.x"
-+ lodash-node "~2.4.1"
-
- xmlbuilder@~9.0.1:
- version "9.0.7"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
- xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-+ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
-
--xmldom@~0.1.15, xmldom@~0.1.19:
-+xmldom@^0.1.0, xmldom@~0.1.15:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-+ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
-
- xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-+ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==
-
--xpath@0.0.5:
-- version "0.0.5"
-- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
-+xpath@0.0.27:
-+ version "0.0.27"
-+ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"
-+ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
diff --git a/spec/fixtures/security-reports/remediations/yarn.lock b/spec/fixtures/security-reports/remediations/yarn.lock
deleted file mode 100644
index 0ecc92fb711..00000000000
--- a/spec/fixtures/security-reports/remediations/yarn.lock
+++ /dev/null
@@ -1,104 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-async@~0.2.7:
- version "0.2.10"
- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
-async@~1.5.2:
- version "1.5.2"
- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
-debug@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
- dependencies:
- ms "2.0.0"
-
-ejs@~0.8.3:
- version "0.8.8"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-
-node-forge@0.2.24:
- version "0.2.24"
- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-
-saml2-js@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
- dependencies:
- async "~1.5.2"
- debug "^1.0.4"
- underscore "~1.6.0"
- xml-crypto "^0.8.1"
- xml-encryption "~0.7.4"
- xml2js "~0.4.1"
- xmlbuilder "~2.1.0"
- xmldom "~0.1.19"
-
-sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-
-underscore@>=1.5.x:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-
-xml-crypto@^0.8.1:
- version "0.8.5"
- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
-xml-encryption@~0.7.4:
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
- dependencies:
- async "~0.2.7"
- ejs "~0.8.3"
- node-forge "0.2.24"
- xmldom "~0.1.15"
- xpath "0.0.5"
-
-xml2js@~0.4.1:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
-xmlbuilder@~2.1.0:
- version "2.1.0"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
- dependencies:
- underscore ">=1.5.x"
-
-xmlbuilder@~9.0.1:
- version "9.0.7"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-
-xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-
-xmldom@~0.1.15, xmldom@~0.1.19:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-
-xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-
-xpath@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js
index 4d9c8f96d62..33d402388c9 100644
--- a/spec/frontend/autosave_spec.js
+++ b/spec/frontend/autosave_spec.js
@@ -63,12 +63,15 @@ describe('Autosave', () => {
expect(field.trigger).toHaveBeenCalled();
});
- it('triggers native event', done => {
- autosave.field.get(0).addEventListener('change', () => {
- done();
- });
+ it('triggers native event', () => {
+ const fieldElement = autosave.field.get(0);
+ const eventHandler = jest.fn();
+ fieldElement.addEventListener('change', eventHandler);
Autosave.prototype.restore.call(autosave);
+
+ expect(eventHandler).toHaveBeenCalledTimes(1);
+ fieldElement.removeEventListener('change', eventHandler);
});
});
diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
new file mode 100644
index 00000000000..ff079082ca7
--- /dev/null
+++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
@@ -0,0 +1,177 @@
+import { mount, shallowMount } from '@vue/test-utils';
+import StageNavItem from '~/cycle_analytics/components/stage_nav_item.vue';
+
+describe('StageNavItem', () => {
+ let wrapper = null;
+ const title = 'Cool stage';
+ const value = '1 day';
+
+ function createComponent(props, shallow = true) {
+ const func = shallow ? shallowMount : mount;
+ return func(StageNavItem, {
+ propsData: {
+ canEdit: false,
+ isActive: false,
+ isUserAllowed: false,
+ isDefaultStage: true,
+ title,
+ value,
+ ...props,
+ },
+ });
+ }
+
+ function hasStageName() {
+ const stageName = wrapper.find('.stage-name');
+ expect(stageName.exists()).toBe(true);
+ expect(stageName.text()).toEqual(title);
+ }
+
+ it('renders stage name', () => {
+ wrapper = createComponent({ isUserAllowed: true });
+ hasStageName();
+ wrapper.destroy();
+ });
+
+ describe('User has access', () => {
+ describe('with a value', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: true });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders the value for median value', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(false);
+ expect(wrapper.find('.not-available').exists()).toBe(false);
+ expect(wrapper.find('.stage-median').text()).toEqual(value);
+ });
+ });
+
+ describe('without a value', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: true, value: null });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has the stage-empty class', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(true);
+ });
+
+ it('renders Not enough data for the median value', () => {
+ expect(wrapper.find('.stage-median').text()).toEqual('Not enough data');
+ });
+ });
+ });
+
+ describe('is active', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isActive: true }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('has the active class', () => {
+ expect(wrapper.find('.stage-nav-item').classes('active')).toBe(true);
+ });
+ });
+
+ describe('is not active', () => {
+ beforeEach(() => {
+ wrapper = createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('emits the `select` event when clicked', () => {
+ expect(wrapper.emitted().select).toBeUndefined();
+ wrapper.trigger('click');
+ expect(wrapper.emitted().select.length).toBe(1);
+ });
+ });
+
+ describe('User does not have access', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ isUserAllowed: false }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders stage name', () => {
+ hasStageName();
+ });
+
+ it('has class not-available', () => {
+ expect(wrapper.find('.stage-empty').exists()).toBe(false);
+ expect(wrapper.find('.not-available').exists()).toBe(true);
+ });
+
+ it('renders Not available for the median value', () => {
+ expect(wrapper.find('.stage-median').text()).toBe('Not available');
+ });
+ it('does not render options menu', () => {
+ expect(wrapper.find('.more-actions-toggle').exists()).toBe(false);
+ });
+ });
+
+ describe('User can edit stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ canEdit: true, isUserAllowed: true }, false);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+ it('renders stage name', () => {
+ hasStageName();
+ });
+
+ it('renders options menu', () => {
+ expect(wrapper.find('.more-actions-toggle').exists()).toBe(true);
+ });
+
+ describe('Default stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent(
+ { canEdit: true, isUserAllowed: true, isDefaultStage: true },
+ false,
+ );
+ });
+ it('can hide the stage', () => {
+ expect(wrapper.text()).toContain('Hide stage');
+ });
+ it('can not edit the stage', () => {
+ expect(wrapper.text()).not.toContain('Edit stage');
+ });
+ it('can not remove the stage', () => {
+ expect(wrapper.text()).not.toContain('Remove stage');
+ });
+ });
+
+ describe('Custom stages', () => {
+ beforeEach(() => {
+ wrapper = createComponent(
+ { canEdit: true, isUserAllowed: true, isDefaultStage: false },
+ false,
+ );
+ });
+ it('can edit the stage', () => {
+ expect(wrapper.text()).toContain('Edit stage');
+ });
+ it('can remove the stage', () => {
+ expect(wrapper.text()).toContain('Remove stage');
+ });
+
+ it('can not hide the stage', () => {
+ expect(wrapper.text()).not.toContain('Hide stage');
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index a986bc49f28..b0bdd924921 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -94,6 +94,12 @@ describe('URL utility', () => {
it('adds and updates encoded params', () => {
expect(urlUtils.mergeUrlParams({ a: '&', q: '?' }, '?a=%23#frag')).toBe('?a=%26&q=%3F#frag');
});
+
+ it('treats "+" as "%20"', () => {
+ expect(urlUtils.mergeUrlParams({ ref: 'bogus' }, '?a=lorem+ipsum&ref=charlie')).toBe(
+ '?a=lorem%20ipsum&ref=bogus',
+ );
+ });
});
describe('removeParams', () => {
diff --git a/spec/frontend/mocks/mocks_helper_spec.js b/spec/frontend/mocks/mocks_helper_spec.js
index 34be110a7e3..b8bb02c2f43 100644
--- a/spec/frontend/mocks/mocks_helper_spec.js
+++ b/spec/frontend/mocks/mocks_helper_spec.js
@@ -46,7 +46,9 @@ describe('mocks_helper.js', () => {
readdir.sync.mockReturnValue([]);
setupManualMocks();
- readdir.mock.calls.forEach(call => {
+ const readdirSpy = readdir.sync;
+ expect(readdirSpy).toHaveBeenCalled();
+ readdirSpy.mock.calls.forEach(call => {
expect(call[1].deep).toBeLessThan(100);
});
});
diff --git a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
index 6d50713999d..8881bedf3cc 100644
--- a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
+++ b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
@@ -74,4 +74,31 @@ describe('notes/components/discussion_keyboard_navigator', () => {
expect(wrapper.vm.currentDiscussionId).toEqual(expectedPrevId);
});
});
+
+ describe('on destroy', () => {
+ beforeEach(() => {
+ jest.spyOn(Mousetrap, 'unbind');
+
+ createComponent();
+
+ wrapper.destroy();
+ });
+
+ it('unbinds keys', () => {
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('n');
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('p');
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `n`', () => {
+ Mousetrap.trigger('n');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `p`', () => {
+ Mousetrap.trigger('p');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
new file mode 100644
index 00000000000..806602877ef
--- /dev/null
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -0,0 +1,123 @@
+import { shallowMount } from '@vue/test-utils';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const changedFile = () => ({ changed: true });
+const stagedFile = () => ({ changed: false, staged: true });
+const changedAndStagedFile = () => ({ changed: true, staged: true });
+const newFile = () => ({ changed: true, tempFile: true });
+const unchangedFile = () => ({ changed: false, tempFile: false, staged: false, deleted: false });
+
+describe('Changed file icon', () => {
+ let wrapper;
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(ChangedFileIcon, {
+ propsData: {
+ file: changedFile(),
+ showTooltip: true,
+ ...props,
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findIcon = () => wrapper.find(Icon);
+ const findIconName = () => findIcon().props('name');
+ const findIconClasses = () =>
+ findIcon()
+ .props('cssClasses')
+ .split(' ');
+ const findTooltipText = () => wrapper.attributes('data-original-title');
+
+ it('with isCentered true, adds center class', () => {
+ factory({
+ isCentered: true,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(true);
+ });
+
+ it('with isCentered false, does not center', () => {
+ factory({
+ isCentered: false,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(false);
+ });
+
+ it('with showTooltip false, does not show tooltip', () => {
+ factory({
+ showTooltip: false,
+ });
+
+ expect(findTooltipText()).toBeFalsy();
+ });
+
+ describe.each`
+ file | iconName | tooltipText | desc
+ ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'}
+ ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'}
+ ${changedAndStagedFile()} | ${'file-modified'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'}
+ ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'}
+ `('$desc', ({ file, iconName, tooltipText }) => {
+ beforeEach(() => {
+ factory({ file });
+ });
+
+ it('renders icon', () => {
+ expect(findIconName()).toBe(iconName);
+ expect(findIconClasses()).toContain(iconName);
+ });
+
+ it('renders tooltip text', () => {
+ expect(findTooltipText()).toBe(tooltipText);
+ });
+ });
+
+ describe('with file unchanged', () => {
+ beforeEach(() => {
+ factory({
+ file: unchangedFile(),
+ });
+ });
+
+ it('does not show icon', () => {
+ expect(findIcon().exists()).toBe(false);
+ });
+
+ it('does not have tooltip text', () => {
+ expect(findTooltipText()).toBe('');
+ });
+ });
+
+ it('with size set, sets icon size', () => {
+ const size = 8;
+
+ factory({
+ file: changedFile(),
+ size,
+ });
+
+ expect(findIcon().props('size')).toBe(size);
+ });
+
+ // NOTE: It looks like 'showStagedIcon' behavior is backwards to what the name suggests
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66071
+ it.each`
+ showStagedIcon | iconName | desc
+ ${false} | ${'file-modified-solid'} | ${'with showStagedIcon false, renders staged icon'}
+ ${true} | ${'file-modified'} | ${'with showStagedIcon true, renders regular icon'}
+ `('$desc', ({ showStagedIcon, iconName }) => {
+ factory({
+ file: stagedFile(),
+ showStagedIcon,
+ });
+
+ expect(findIconName()).toEqual(iconName);
+ });
+});
diff --git a/spec/frontend/wikis_spec.js b/spec/frontend/wikis_spec.js
new file mode 100644
index 00000000000..b2475488d97
--- /dev/null
+++ b/spec/frontend/wikis_spec.js
@@ -0,0 +1,74 @@
+import Wikis from '~/pages/projects/wikis/wikis';
+import { setHTMLFixture } from './helpers/fixtures';
+
+describe('Wikis', () => {
+ describe('setting the commit message when the title changes', () => {
+ const editFormHtmlFixture = args => `<form class="wiki-form ${
+ args.newPage ? 'js-new-wiki-page' : ''
+ }">
+ <input type="text" id="wiki_title" value="My title" />
+ <input type="text" id="wiki_message" />
+ </form>`;
+
+ let wikis;
+ let titleInput;
+ let messageInput;
+
+ describe('when the wiki page is being created', () => {
+ const formHtmlFixture = editFormHtmlFixture({ newPage: true });
+
+ beforeEach(() => {
+ setHTMLFixture(formHtmlFixture);
+
+ titleInput = document.getElementById('wiki_title');
+ messageInput = document.getElementById('wiki_message');
+ wikis = new Wikis();
+ });
+
+ it('binds an event listener to the title input', () => {
+ wikis.handleWikiTitleChange = jest.fn();
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(wikis.handleWikiTitleChange).toHaveBeenCalled();
+ });
+
+ it('sets the commit message when title changes', () => {
+ titleInput.value = 'My title';
+ messageInput.value = '';
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Create My title');
+ });
+
+ it('replaces hyphens with spaces', () => {
+ titleInput.value = 'my-hyphenated-title';
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Create my hyphenated title');
+ });
+ });
+
+ describe('when the wiki page is being updated', () => {
+ const formHtmlFixture = editFormHtmlFixture({ newPage: false });
+
+ beforeEach(() => {
+ setHTMLFixture(formHtmlFixture);
+
+ titleInput = document.getElementById('wiki_title');
+ messageInput = document.getElementById('wiki_message');
+ wikis = new Wikis();
+ });
+
+ it('sets the commit message when title changes, prefixing with "Update"', () => {
+ titleInput.value = 'My title';
+ messageInput.value = '';
+
+ titleInput.dispatchEvent(new Event('keyup'));
+
+ expect(messageInput.value).toEqual('Update My title');
+ });
+ });
+ });
+});
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 037b16c90ed..98719697cea 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -262,4 +262,44 @@ describe GroupsHelper do
expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
end
end
+
+ describe '#can_disable_group_emails?' do
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, name: 'group') }
+ let(:subgroup) { create(:group, name: 'subgroup', parent: group) }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
+
+ it 'returns true for the group owner' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { true }
+
+ expect(helper.can_disable_group_emails?(group)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { false }
+
+ expect(helper.can_disable_group_emails?(group)).to be_falsey
+ end
+
+ context 'when subgroups' do
+ before do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, subgroup) { true }
+ end
+
+ it 'returns false if parent group is disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_falsey
+ end
+
+ it 'returns true if parent group is not disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(false)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 9ecaabc04ed..5717b15d656 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe NotificationsHelper do
describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
+ it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') }
it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') }
it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') }
@@ -19,4 +20,14 @@ describe NotificationsHelper do
it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
end
+
+ describe '#notification_icon_level' do
+ let(:user) { create(:user) }
+ let(:global_setting) { user.global_notification_setting }
+ let(:notification_setting) { create(:notification_setting, level: :watch) }
+
+ it { expect(notification_icon_level(notification_setting, true)).to eq 'owner_disabled' }
+ it { expect(notification_icon_level(notification_setting)).to eq 'watch' }
+ it { expect(notification_icon_level(global_setting)).to eq 'participating' }
+ end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 3716879c458..a70bfc2adc7 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -107,6 +107,30 @@ describe ProjectsHelper do
end
end
+ describe '#can_disable_emails?' do
+ let(:project) { create(:project) }
+ let(:user) { create(:project_member, :maintainer, user: create(:user), project: project).user }
+
+ it 'returns true for the project owner' do
+ allow(helper).to receive(:can?).with(project.owner, :set_emails_disabled, project) { true }
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(user, :set_emails_disabled, project) { false }
+
+ expect(helper.can_disable_emails?(project, user)).to be_falsey
+ end
+
+ it 'returns false if group emails disabled' do
+ project = create(:project, group: create(:group))
+ allow(project.group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_falsey
+ end
+ end
+
describe "readme_cache_key" do
let(:project) { create(:project, :repository) }
@@ -477,6 +501,7 @@ describe ProjectsHelper do
it 'returns the command to push to create project over SSH' do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
end
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index d4280d3ec2c..356e7a8f1fe 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -372,7 +372,7 @@ describe('diff_file_header', () => {
});
it('displays old and new path if the file was renamed', () => {
- props.diffFile.viewer.name = diffViewerModes.renamed;
+ props.diffFile.renamed_file = true;
vm = mountComponentWithStore(Component, { props, store });
diff --git a/spec/javascripts/ide/stores/utils_spec.js b/spec/javascripts/ide/stores/utils_spec.js
index bceb3a8db91..0fc9519a6bf 100644
--- a/spec/javascripts/ide/stores/utils_spec.js
+++ b/spec/javascripts/ide/stores/utils_spec.js
@@ -261,6 +261,41 @@ describe('Multi-file store utils', () => {
},
]);
});
+
+ it('filters out folders from the list', () => {
+ const files = [
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c',
+ type: 'tree',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ];
+
+ const flattendFiles = utils.getCommitFiles(files);
+
+ expect(flattendFiles).toEqual([
+ {
+ path: 'a',
+ type: 'blob',
+ deleted: true,
+ },
+ {
+ path: 'c/d',
+ type: 'blob',
+ deleted: true,
+ },
+ ]);
+ });
});
describe('mergeTrees', () => {
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 7e00fbf2745..e10426a9858 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -1,5 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
+import '~/behaviors/markdown/render_gfm';
import Description from '~/issue_show/components/description.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
@@ -91,6 +92,7 @@ describe('Description component', () => {
let TaskList;
beforeEach(() => {
+ vm.$destroy();
vm = mountComponent(
DescriptionComponent,
Object.assign({}, props, {
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index b78896c45fc..f3ec7520c6f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -1,4 +1,6 @@
import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlToast } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
@@ -11,8 +13,9 @@ import MonitoringMock, {
environmentData,
singleGroupResponse,
dashboardGitResponse,
-} from './mock_data';
+} from '../mock_data';
+const localVue = createLocalVue();
const propsData = {
hasMetrics: false,
documentationPath: '/path/to/docs',
@@ -59,7 +62,9 @@ describe('Dashboard', () => {
});
afterEach(() => {
- component.$destroy();
+ if (component) {
+ component.$destroy();
+ }
mock.restore();
});
@@ -373,6 +378,71 @@ describe('Dashboard', () => {
});
});
+ describe('link to chart', () => {
+ let wrapper;
+ const currentDashboard = 'TEST_DASHBOARD';
+ localVue.use(GlToast);
+ const link = () => wrapper.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ wrapper = shallowMount(DashboardComponent, {
+ localVue,
+ sync: false,
+ attachToDocument: true,
+ propsData: { ...propsData, hasMetrics: true, currentDashboard },
+ store,
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('adds a copy button to the dropdown', () => {
+ expect(link().text()).toContain('Generate link to chart');
+ });
+
+ it('contains a link to the dashboard', () => {
+ expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
+ expect(clipboardText()).toContain(`group=`);
+ expect(clipboardText()).toContain(`title=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ });
+
+ it('undefined parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: undefined });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('null parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: null });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('creates a toast when clicked', () => {
+ spyOn(wrapper.vm.$toast, 'show').and.stub();
+
+ link().vm.$emit('click');
+
+ expect(wrapper.vm.$toast.show).toHaveBeenCalled();
+ });
+ });
+
describe('when the window resizes', () => {
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
index 8ce24041e97..086be628093 100644
--- a/spec/javascripts/monitoring/panel_type_spec.js
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -1,20 +1,25 @@
import { shallowMount } from '@vue/test-utils';
import PanelType from '~/monitoring/components/panel_type.vue';
import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import AreaChart from '~/monitoring/components/charts/area.vue';
import { graphDataPrometheusQueryRange } from './mock_data';
+import { createStore } from '~/monitoring/stores';
describe('Panel Type component', () => {
+ let store;
let panelType;
const dashboardWidth = 100;
describe('When no graphData is available', () => {
let glEmptyChart;
- const graphDataNoResult = graphDataPrometheusQueryRange;
+ // Deep clone object before modifying
+ const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
graphDataNoResult.queries[0].result = [];
beforeEach(() => {
panelType = shallowMount(PanelType, {
propsData: {
+ clipboardText: 'dashboard_link',
dashboardWidth,
graphData: graphDataNoResult,
},
@@ -41,4 +46,33 @@ describe('Panel Type component', () => {
});
});
});
+
+ describe('when Graph data is available', () => {
+ const exampleText = 'example_text';
+
+ beforeEach(() => {
+ store = createStore();
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: exampleText,
+ dashboardWidth,
+ graphData: graphDataPrometheusQueryRange,
+ },
+ store,
+ });
+ });
+
+ describe('Area Chart panel type', () => {
+ it('is rendered', () => {
+ expect(panelType.find(AreaChart).exists()).toBe(true);
+ });
+
+ it('sets clipboard text on the dropdown', () => {
+ const link = () => panelType.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ expect(clipboardText()).toBe(exampleText);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
deleted file mode 100644
index 634ba8403d5..00000000000
--- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import changedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
-import createComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Changed file icon', () => {
- let vm;
-
- function factory(props = {}) {
- const component = Vue.extend(changedFileIcon);
-
- vm = createComponent(component, {
- ...props,
- file: {
- tempFile: false,
- changed: true,
- },
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('centers icon', () => {
- factory({
- isCentered: true,
- });
-
- expect(vm.$el.classList).toContain('ml-auto');
- });
-
- describe('changedIcon', () => {
- it('equals file-modified when not a temp file and has changes', () => {
- factory();
-
- expect(vm.changedIcon).toBe('file-modified');
- });
-
- it('equals file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIcon).toBe('file-addition');
- });
- });
-
- describe('changedIconClass', () => {
- it('includes file-modified when not a temp file', () => {
- factory();
-
- expect(vm.changedIconClass).toContain('file-modified');
- });
-
- it('includes file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIconClass).toContain('file-addition');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
new file mode 100644
index 00000000000..f1ca5f61496
--- /dev/null
+++ b/spec/javascripts/vue_shared/directives/autofocusonshow_spec.js
@@ -0,0 +1,38 @@
+import autofocusonshow from '~/vue_shared/directives/autofocusonshow';
+
+/**
+ * We're testing this directive's hooks as pure functions
+ * since behaviour of this directive is highly-dependent
+ * on underlying DOM methods.
+ */
+describe('AutofocusOnShow directive', () => {
+ describe('with input invisible on component render', () => {
+ let el;
+
+ beforeAll(() => {
+ setFixtures('<div id="container" style="display: none;"><input id="inputel"/></div>');
+ el = document.querySelector('#inputel');
+ });
+
+ it('should bind IntersectionObserver on input element', () => {
+ spyOn(el, 'focus');
+
+ autofocusonshow.inserted(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.focus).not.toHaveBeenCalled();
+ });
+
+ it('should stop IntersectionObserver on input element on unbind hook', () => {
+ el.visibilityObserver = {
+ disconnect: () => {},
+ };
+ spyOn(el.visibilityObserver, 'disconnect');
+
+ autofocusonshow.unbind(el);
+
+ expect(el.visibilityObserver).toBeDefined();
+ expect(el.visibilityObserver.disconnect).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/lib/gitlab/action_rate_limiter_spec.rb b/spec/lib/gitlab/action_rate_limiter_spec.rb
index 8dbad32dfb4..8b510a475d2 100644
--- a/spec/lib/gitlab/action_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/action_rate_limiter_spec.rb
@@ -74,9 +74,9 @@ describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do
{
message: 'Action_Rate_Limiter_Request',
env: type,
- ip: '127.0.0.1',
+ remote_ip: '127.0.0.1',
request_method: 'GET',
- fullpath: fullpath
+ path: fullpath
}
end
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
new file mode 100644
index 00000000000..7d67dc0251d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -0,0 +1,296 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadMover do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+ let(:filename) { 'image.png' }
+
+ let!(:namespace) { create(:namespace) }
+ let!(:legacy_project) { create(:project, :legacy_storage, namespace: namespace) }
+ let!(:hashed_project) { create(:project, namespace: namespace) }
+ # default project
+ let(:project) { legacy_project }
+
+ let!(:issue) { create(:issue, project: project) }
+ let!(:note) { create(:note, note: 'some note', project: project, noteable: issue) }
+
+ let(:legacy_upload) { create_upload(note, filename) }
+
+ def create_remote_upload(model, filename)
+ create(:upload, :attachment_upload,
+ path: "note/attachment/#{model.id}/#{filename}", secret: nil,
+ store: ObjectStorage::Store::REMOTE, model: model)
+ end
+
+ def create_upload(model, filename, with_file = true)
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ def new_upload
+ Upload.find_by(model_id: project.id, model_type: 'Project')
+ end
+
+ def expect_error_log
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
+ expect(logger).to receive(:warn)
+ end
+ end
+
+ shared_examples 'legacy upload deletion' do
+ it 'removes the upload record' do
+ described_class.new(legacy_upload).execute
+
+ expect { legacy_upload.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ shared_examples 'move error' do
+ it 'does not remove the upload file' do
+ expect_error_log
+
+ described_class.new(legacy_upload).execute
+
+ expect(legacy_upload.reload).to eq(legacy_upload)
+ end
+ end
+
+ shared_examples 'migrates the file correctly' do
+ before do
+ described_class.new(legacy_upload).execute
+ end
+
+ it 'creates a new uplaod record correctly' do
+ expect(new_upload.secret).not_to be_nil
+ expect(new_upload.path).to end_with("#{new_upload.secret}/image.png")
+ expect(new_upload.model_id).to eq(project.id)
+ expect(new_upload.model_type).to eq('Project')
+ expect(new_upload.uploader).to eq('FileUploader')
+ end
+
+ it 'updates the legacy upload note so that it references the file in the markdown' do
+ expected_path = File.join('/uploads', new_upload.secret, 'image.png')
+ expected_markdown = "some note \n ![image](#{expected_path})"
+ expect(note.reload.note).to eq(expected_markdown)
+ end
+
+ it 'removes the attachment from the note model' do
+ expect(note.reload.attachment.file).to be_nil
+ end
+ end
+
+ context 'when no model found for the upload' do
+ before do
+ legacy_upload.model = nil
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the upload move fails' do
+ before do
+ expect(FileUploader).to receive(:copy_to).and_raise('failed')
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the upload is in local storage' do
+ shared_examples 'legacy local file' do
+ it 'removes the file correctly' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+
+ described_class.new(legacy_upload).execute
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ end
+
+ it 'moves legacy uploads to the correct location' do
+ described_class.new(legacy_upload).execute
+
+ expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename)
+ expect(File.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ let(:legacy_upload) { create_upload(note, filename, false) }
+
+ before do
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when an upload belongs to a legacy_diff_note' do
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:note) do
+ create(:legacy_diff_note_on_merge_request,
+ note: 'some note', project: project, noteable: merge_request)
+ end
+ let(:legacy_upload) do
+ create(:upload, :with_file, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ context 'when the file does not exist for the upload' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file does not exist on expected path' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system/note/attachment/some_part/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file path does not include system/note/attachment' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file move raises an error' do
+ before do
+ allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file can be handled correctly' do
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is disabled for FileUploader' do
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is enabled for FileUploader' do
+ # The process of migrating to object storage is a manual one,
+ # so it would go against expectations to automatically migrate these files
+ # to object storage during this migration.
+ # After this migration, these files should be able to successfully migrate to object storage.
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+ end
+
+ context 'when legacy uploads are stored in object storage' do
+ let(:legacy_upload) { create_remote_upload(note, filename) }
+ let(:remote_file) do
+ { key: "#{legacy_upload.path}" }
+ end
+ let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:bucket) { connection.directories.create(key: 'uploads') }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ shared_examples 'legacy remote file' do
+ it 'removes the file correctly' do
+ # expect(bucket.files.get(remote_file[:key])).to be_nil
+
+ described_class.new(legacy_upload).execute
+
+ expect(bucket.files.get(remote_file[:key])).to be_nil
+ end
+
+ it 'moves legacy uploads to the correct remote location' do
+ described_class.new(legacy_upload).execute
+
+ connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
+ expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200)
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a legacy project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
new file mode 100644
index 00000000000..ed8cbfeb11f
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+
+ let!(:hashed_project) { create(:project) }
+ let!(:legacy_project) { create(:project, :legacy_storage) }
+ let!(:issue) { create(:issue, project: hashed_project) }
+ let!(:issue_legacy) { create(:issue, project: legacy_project) }
+
+ let!(:note1) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note2) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note_legacy) { create(:note, project: legacy_project, noteable: issue_legacy) }
+
+ def create_upload(model, with_file = true)
+ filename = 'image.png'
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ let!(:legacy_upload) { create_upload(note1) }
+ let!(:legacy_upload_no_file) { create_upload(note2, false) }
+ let!(:legacy_upload_legacy_project) { create_upload(note_legacy) }
+
+ let(:start_id) { 1 }
+ let(:end_id) { 10000 }
+
+ subject { described_class.new.perform(start_id, end_id) }
+
+ it 'removes all legacy files' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_truthy
+
+ subject
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_falsey
+ end
+
+ it 'removes all AttachmentUploader records' do
+ expect { subject }.to change { Upload.where(uploader: 'AttachmentUploader').count }.from(3).to(0)
+ end
+
+ it 'creates new uploads for successfully migrated records' do
+ expect { subject }.to change { Upload.where(uploader: 'FileUploader').count }.from(0).to(2)
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index f712f47a558..7140c14facb 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -13,7 +13,12 @@ describe Gitlab::Ci::Build::Policy::Variables do
build(:ci_build, pipeline: pipeline, project: project, ref: 'master')
end
- let(:seed) { double('build seed', to_resource: ci_build) }
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
before do
pipeline.variables.build(key: 'CI_PROJECT_NAME', value: '')
@@ -83,7 +88,12 @@ describe Gitlab::Ci::Build::Policy::Variables do
build(:ci_bridge, pipeline: pipeline, project: project, ref: 'master')
end
- let(:seed) { double('bridge seed', to_resource: bridge) }
+ let(:seed) do
+ double('bridge seed',
+ to_resource: bridge,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
it 'is satisfied by a matching expression for a bridge job' do
policy = described_class.new(['$MY_VARIABLE'])
diff --git a/spec/lib/gitlab/ci/build/rules/rule_spec.rb b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
new file mode 100644
index 00000000000..99852bd4228
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules/rule_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules::Rule do
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
+
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { build(:ci_build, pipeline: pipeline) }
+ let(:rule) { described_class.new(rule_hash) }
+
+ describe '#matches?' do
+ subject { rule.matches?(pipeline, seed) }
+
+ context 'with one matching clause' do
+ let(:rule_hash) do
+ { if: '$VAR == null', when: 'always' }
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'with two matching clauses' do
+ let(:rule_hash) do
+ { if: '$VAR == null', changes: '**/*', when: 'always' }
+ end
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'with a matching and non-matching clause' do
+ let(:rule_hash) do
+ { if: '$VAR != null', changes: '$VAR == null', when: 'always' }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'with two non-matching clauses' do
+ let(:rule_hash) do
+ { if: '$VAR != null', changes: 'README', when: 'always' }
+ end
+
+ it { is_expected.to eq(false) }
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/build/rules_spec.rb b/spec/lib/gitlab/ci/build/rules_spec.rb
new file mode 100644
index 00000000000..d7793ebc806
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/rules_spec.rb
@@ -0,0 +1,168 @@
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Rules do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:ci_build) { build(:ci_build, pipeline: pipeline) }
+
+ let(:seed) do
+ double('build seed',
+ to_resource: ci_build,
+ scoped_variables_hash: ci_build.scoped_variables_hash
+ )
+ end
+
+ let(:rules) { described_class.new(rule_list) }
+
+ describe '.new' do
+ let(:rules_ivar) { rules.instance_variable_get :@rule_list }
+ let(:default_when) { rules.instance_variable_get :@default_when }
+
+ context 'with no rules' do
+ let(:rule_list) { [] }
+
+ it 'sets @rule_list to an empty array' do
+ expect(rules_ivar).to eq([])
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with one rule' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with multiple rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'always' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "on_success"' do
+ expect(default_when).to eq('on_success')
+ end
+ end
+
+ context 'with a specified default when:' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it 'sets @rule_list to an array of a single rule' do
+ expect(rules_ivar).to be_an(Array)
+ end
+
+ it 'sets @default_when to "manual"' do
+ expect(default_when).to eq('manual')
+ end
+ end
+ end
+
+ describe '#evaluate' do
+ subject { rules.evaluate(pipeline, seed) }
+
+ context 'with nil rules' do
+ let(:rule_list) { nil }
+
+ it { is_expected.to eq(described_class::Result.new('on_success')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it { is_expected.to eq(described_class::Result.new('manual')) }
+ end
+ end
+
+ context 'with no rules' do
+ let(:rule_list) { [] }
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+ end
+ end
+
+ context 'with one rule without any clauses' do
+ let(:rule_list) { [{ when: 'manual' }] }
+
+ it { is_expected.to eq(described_class::Result.new('manual')) }
+ end
+
+ context 'with one matching rule' do
+ let(:rule_list) { [{ if: '$VAR == null', when: 'always' }] }
+
+ it { is_expected.to eq(described_class::Result.new('always')) }
+ end
+
+ context 'with two matching rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it 'returns the value of the first matched rule in the list' do
+ expect(subject).to eq(described_class::Result.new('delayed', '1 day'))
+ end
+ end
+
+ context 'with a non-matching and matching rule' do
+ let(:rule_list) do
+ [
+ { if: '$VAR =! null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR == null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('always')) }
+ end
+
+ context 'with a matching and non-matching rule' do
+ let(:rule_list) do
+ [
+ { if: '$VAR == null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR != null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('delayed', '1 day')) }
+ end
+
+ context 'with non-matching rules' do
+ let(:rule_list) do
+ [
+ { if: '$VAR != null', when: 'delayed', start_in: '1 day' },
+ { if: '$VAR != null', when: 'always' }
+ ]
+ end
+
+ it { is_expected.to eq(described_class::Result.new('never')) }
+
+ context 'and when:manual set as the default' do
+ let(:rules) { described_class.new(rule_list, 'manual') }
+
+ it 'does not return the default when:' do
+ expect(subject).to eq(described_class::Result.new('never'))
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 415ade7a096..1853efde350 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::Ci::Config::Entry::Job do
let(:result) do
%i[before_script script stage type after_script cache
- image services only except variables artifacts
+ image services only except rules variables artifacts
environment coverage retry]
end
@@ -201,6 +201,21 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include 'job parallel must be an integer'
end
end
+
+ context 'when it uses both "when:" and "rules:"' do
+ let(:config) do
+ {
+ script: 'echo',
+ when: 'on_failure',
+ rules: [{ if: '$VARIABLE', when: 'on_success' }]
+ }
+ end
+
+ it 'returns an error about when: being combined with rules' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config key may not be used with `rules`: when'
+ end
+ end
end
context 'when delayed job' do
@@ -240,6 +255,100 @@ describe Gitlab::Ci::Config::Entry::Job do
end
end
+ context 'when only: is used with rules:' do
+ let(:config) { { only: ['merge_requests'], rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and only: is blank' do
+ let(:config) { { only: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { only: ['merge_requests'], rules: nil } }
+
+ it 'returns error about mixing only: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when except: is used with rules:' do
+ let(:config) { { except: { refs: %w[master] }, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'and except: is blank' do
+ let(:config) { { except: nil, rules: [{ if: '$THIS' }] } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'and rules: is blank' do
+ let(:config) { { except: { refs: %w[master] }, rules: nil } }
+
+ it 'returns error about mixing except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
+ context 'when only: and except: are both used with rules:' do
+ let(:config) do
+ {
+ only: %w[merge_requests],
+ except: { refs: %w[master] },
+ rules: [{ if: '$THIS' }]
+ }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+
+ context 'when only: and except: as both blank' do
+ let(:config) do
+ { only: nil, except: nil, rules: [{ if: '$THIS' }] }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+
+ context 'when rules: is blank' do
+ let(:config) do
+ { only: %w[merge_requests], except: { refs: %w[master] }, rules: nil }
+ end
+
+ it 'returns errors about mixing both only: and except: with rules:' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include /may not be used with `rules`/
+ expect(entry.errors).to include /may not be used with `rules`/
+ end
+ end
+ end
+
context 'when start_in specified without delayed specification' do
let(:config) { { start_in: '1 day' } }
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 266a27c1e47..a606eb303e7 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -51,8 +51,6 @@ describe Gitlab::Ci::Config::Entry::Policy do
let(:config) { ['/^(?!master).+/'] }
- subject { described_class.new([regexp]) }
-
context 'when allow_unsafe_ruby_regexp is disabled' do
before do
stub_feature_flags(allow_unsafe_ruby_regexp: false)
@@ -113,8 +111,6 @@ describe Gitlab::Ci::Config::Entry::Policy do
let(:config) { { refs: ['/^(?!master).+/'] } }
- subject { described_class.new([regexp]) }
-
context 'when allow_unsafe_ruby_regexp is disabled' do
before do
stub_feature_flags(allow_unsafe_ruby_regexp: false)
@@ -204,6 +200,14 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
context 'when changes policy is invalid' do
+ let(:config) { { changes: 'some/*' } }
+
+ it 'returns errors' do
+ expect(entry.errors).to include /changes should be an array of strings/
+ end
+ end
+
+ context 'when changes policy is invalid' do
let(:config) { { changes: [1, 2] } }
it 'returns errors' do
diff --git a/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
new file mode 100644
index 00000000000..c25344ec1a4
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/rules/rule_spec.rb
@@ -0,0 +1,208 @@
+require 'fast_spec_helper'
+require 'chronic_duration'
+require 'support/helpers/stub_feature_flags'
+require_dependency 'active_model'
+
+describe Gitlab::Ci::Config::Entry::Rules::Rule do
+ let(:entry) { described_class.new(config) }
+
+ describe '.new' do
+ subject { entry }
+
+ context 'with a when: value but no clauses' do
+ let(:config) { { when: 'manual' } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT', when: 'manual' } }
+
+ it { is_expected.to be_valid }
+
+ describe '#when' do
+ subject { entry.when }
+
+ it { is_expected.to eq('manual') }
+ end
+ end
+
+ context 'using a list of multiple expressions' do
+ let(:config) { { if: ['$MY_VAR == "this"', '$YOUR_VAR == "that"'] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid format' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when specifying an invalid if: clause expression' do
+ let(:config) { { if: ['$MY_VAR =='] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid statement' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when specifying an if: clause expression with an invalid token' do
+ let(:config) { { if: ['$MY_VAR == 123'] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid statement' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when using invalid regex in an if: clause' do
+ let(:config) { { if: ['$MY_VAR =~ /some ( thing/'] } }
+
+ it 'reports an error about invalid expression' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+
+ context 'when using an if: clause with lookahead regex character "?"' do
+ let(:config) { { if: '$CI_COMMIT_REF =~ /^(?!master).+/' } }
+
+ context 'when allow_unsafe_ruby_regexp is disabled' do
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid expression syntax' do
+ expect(subject.errors).to include(/invalid expression syntax/)
+ end
+ end
+ end
+
+ context 'when using a changes: clause' do
+ let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to be_valid }
+ end
+
+ context 'when using a string as an invalid changes: clause' do
+ let(:config) { { changes: 'a regular string' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'reports an error about invalid policy' do
+ expect(subject.errors).to include(/should be an array of strings/)
+ end
+ end
+
+ context 'when using a list as an invalid changes: clause' do
+ let(:config) { { changes: [1, 2] } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns errors' do
+ expect(subject.errors).to include(/changes should be an array of strings/)
+ end
+ end
+
+ context 'specifying a delayed job' do
+ let(:config) { { if: '$THIS || $THAT', when: 'delayed', start_in: '15 minutes' } }
+
+ it { is_expected.to be_valid }
+
+ it 'sets attributes for the job delay' do
+ expect(entry.when).to eq('delayed')
+ expect(entry.start_in).to eq('15 minutes')
+ end
+
+ context 'without a when: key' do
+ let(:config) { { if: '$THIS || $THAT', start_in: '15 minutes' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about the disallowed key' do
+ expect(entry.errors).to include(/disallowed keys: start_in/)
+ end
+ end
+
+ context 'without a start_in: key' do
+ let(:config) { { if: '$THIS || $THAT', when: 'delayed' } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about tstart_in being blank' do
+ expect(entry.errors).to include(/start in can't be blank/)
+ end
+ end
+ end
+
+ context 'when specifying unknown policy' do
+ let(:config) { { invalid: :something } }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns error about invalid key' do
+ expect(entry.errors).to include(/unknown keys: invalid/)
+ end
+ end
+
+ context 'when clause is empty' do
+ let(:config) { {} }
+
+ it { is_expected.not_to be_valid }
+
+ it 'is not a valid configuration' do
+ expect(entry.errors).to include(/can't be blank/)
+ end
+ end
+
+ context 'when policy strategy does not match' do
+ let(:config) { 'string strategy' }
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns information about errors' do
+ expect(entry.errors)
+ .to include(/should be a hash/)
+ end
+ end
+ end
+
+ describe '#value' do
+ subject { entry.value }
+
+ context 'when specifying an if: clause' do
+ let(:config) { { if: '$THIS || $THAT', when: 'manual' } }
+
+ it 'stores the expression as "if"' do
+ expect(subject).to eq(if: '$THIS || $THAT', when: 'manual')
+ end
+ end
+
+ context 'when using a changes: clause' do
+ let(:config) { { changes: %w[app/ lib/ spec/ other/* paths/**/*.rb] } }
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'when default value has been provided' do
+ let(:config) { { changes: %w[app/**/*.rb] } }
+
+ before do
+ entry.default = { changes: %w[**/*] }
+ end
+
+ it 'does not set a default value' do
+ expect(entry.default).to eq(nil)
+ end
+
+ it 'does not add to provided configuration' do
+ expect(entry.value).to eq(config)
+ end
+ end
+ end
+
+ describe '.default' do
+ it 'does not have default value' do
+ expect(described_class.default).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/config/entry/rules_spec.rb b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
new file mode 100644
index 00000000000..291e7373daf
--- /dev/null
+++ b/spec/lib/gitlab/ci/config/entry/rules_spec.rb
@@ -0,0 +1,135 @@
+require 'fast_spec_helper'
+require 'support/helpers/stub_feature_flags'
+require_dependency 'active_model'
+
+describe Gitlab::Ci::Config::Entry::Rules do
+ let(:entry) { described_class.new(config) }
+
+ describe '.new' do
+ subject { entry }
+
+ context 'with a list of rule rule' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'never' }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.to be_valid }
+ end
+ end
+
+ context 'with a single rule object' do
+ let(:config) do
+ { if: '$SKIP', when: 'never' }
+ end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'with an invalid boolean when:' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: false }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid when:' do
+ expect(subject.errors).to include(/when unknown value: false/)
+ end
+ end
+ end
+
+ context 'with an invalid string when:' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'explode' }]
+ end
+
+ it { is_expected.to be_a(described_class) }
+ it { is_expected.to be_valid }
+
+ context 'after #compose!' do
+ before do
+ subject.compose!
+ end
+
+ it { is_expected.not_to be_valid }
+
+ it 'returns an error about invalid when:' do
+ expect(subject.errors).to include(/when unknown value: explode/)
+ end
+ end
+ end
+ end
+
+ describe '#value' do
+ subject { entry.value }
+
+ context 'with a list of rule rule' do
+ let(:config) do
+ [{ if: '$THIS == "that"', when: 'never' }]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with a list of two rules' do
+ let(:config) do
+ [
+ { if: '$THIS == "that"', when: 'always' },
+ { if: '$SKIP', when: 'never' }
+ ]
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with a single rule object' do
+ let(:config) do
+ { if: '$SKIP', when: 'never' }
+ end
+
+ it { is_expected.to eq(config) }
+ end
+ end
+
+ describe '.default' do
+ it 'does not have default policy' do
+ expect(described_class.default).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 5d4dec5899a..89431b80be3 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -15,25 +15,95 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.to be_a(Hash) }
it { is_expected.to include(:name, :project, :ref) }
+
+ context 'with job:when' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
+
+ it { is_expected.to include(when: 'on_failure') }
+ end
+
+ context 'with job:when:delayed' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', start_in: '3 hours' } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'with job:rules:[when:]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'always') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules:[when:delayed]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules but no explicit when:' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null' }] } }
+
+ it { is_expected.to include(when: 'on_success') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
end
describe '#bridge?' do
subject { seed_build.bridge? }
- context 'when job is a bridge' do
+ context 'when job is a downstream bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
it { is_expected.to be_truthy }
+
+ context 'when trigger definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
- context 'when trigger definition is empty' do
+ context 'when job is an upstream bridge' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
end
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
+
+ context 'when upstream definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
context 'when job is not a bridge' do
@@ -350,9 +420,25 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.not_to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ },
+ except: {
+ refs: ["branches@#{pipeline.project_full_path}"]
+ }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
- context 'when repository path does not matches' do
+ context 'when repository path does not match' do
context 'when using only' do
let(:attributes) do
{ name: 'rspec', only: { refs: %w[branches@fork] } }
@@ -381,6 +467,215 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.not_to be_included }
end
end
+
+ context 'using rules:' do
+ using RSpec::Parameterized
+
+ let(:attributes) { { name: 'rspec', rules: rule_set } }
+
+ context 'with a matching if: rule' do
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE == null', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'never' }, { if: '$VARIABLE == null', when: 'always' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'always' }]],
+ [[{ if: '$VARIABLE == null', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'always' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
+ end
+ end
+
+ context 'with an explicit `when: on_failure`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$CI_JOB_NAME == "rspec" && $VAR == null', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE != null', when: 'delayed', start_in: '1 day' }, { if: '$CI_JOB_NAME == "rspec"', when: 'on_failure' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$CI_BUILD_NAME == "rspec"', when: 'on_failure' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_failure')
+ end
+ end
+ end
+
+ context 'with an explicit `when: delayed`' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }]],
+ [[{ if: '$VARIABLE == null', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]],
+ [[{ if: '$VARIABLE != "the wrong value"', when: 'delayed', start_in: '1 day' }, { if: '$VARIABLE == null', when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'delayed', start_in: '1 day')
+ end
+ end
+ end
+
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE == null' }, { if: '$VARIABLE == null' }]],
+ [[{ if: '$VARIABLE != "the wrong value"' }, { if: '$VARIABLE == null' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
+ end
+ end
+ end
+
+ context 'with a matching changes: rule' do
+ let(:pipeline) do
+ create(:ci_pipeline, project: project).tap do |pipeline|
+ stub_pipeline_modified_paths(pipeline, %w[app/models/ci/pipeline.rb spec/models/ci/pipeline_spec.rb .gitlab-ci.yml])
+ end
+ end
+
+ context 'with an explicit `when: never`' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb], when: 'never' }, { changes: %w[*/**/*.rb], when: 'always' }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb], when: 'never' }, { changes: %w[app/models/ci/pipeline.rb], when: 'always' }]],
+ [[{ changes: %w[spec/**/*.rb], when: 'never' }, { changes: %w[spec/**/*.rb], when: 'always' }]],
+ [[{ changes: %w[*.yml], when: 'never' }, { changes: %w[*.yml], when: 'always' }]],
+ [[{ changes: %w[.*.yml], when: 'never' }, { changes: %w[.*.yml], when: 'always' }]],
+ [[{ changes: %w[**/*], when: 'never' }, { changes: %w[**/*], when: 'always' }]],
+ [[{ changes: %w[*/**/*.rb *.yml], when: 'never' }, { changes: %w[*/**/*.rb *.yml], when: 'always' }]],
+ [[{ changes: %w[.*.yml **/*], when: 'never' }, { changes: %w[.*.yml **/*], when: 'always' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with an explicit `when: always`' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb], when: 'always' }, { changes: %w[*/**/*.rb], when: 'never' }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb], when: 'always' }, { changes: %w[app/models/ci/pipeline.rb], when: 'never' }]],
+ [[{ changes: %w[spec/**/*.rb], when: 'always' }, { changes: %w[spec/**/*.rb], when: 'never' }]],
+ [[{ changes: %w[*.yml], when: 'always' }, { changes: %w[*.yml], when: 'never' }]],
+ [[{ changes: %w[.*.yml], when: 'always' }, { changes: %w[.*.yml], when: 'never' }]],
+ [[{ changes: %w[**/*], when: 'always' }, { changes: %w[**/*], when: 'never' }]],
+ [[{ changes: %w[*/**/*.rb *.yml], when: 'always' }, { changes: %w[*/**/*.rb *.yml], when: 'never' }]],
+ [[{ changes: %w[.*.yml **/*], when: 'always' }, { changes: %w[.*.yml **/*], when: 'never' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'always')
+ end
+ end
+ end
+
+ context 'without an explicit when: value' do
+ where(:rule_set) do
+ [
+ [[{ changes: %w[*/**/*.rb] }]],
+ [[{ changes: %w[app/models/ci/pipeline.rb] }]],
+ [[{ changes: %w[spec/**/*.rb] }]],
+ [[{ changes: %w[*.yml] }]],
+ [[{ changes: %w[.*.yml] }]],
+ [[{ changes: %w[**/*] }]],
+ [[{ changes: %w[*/**/*.rb *.yml] }]],
+ [[{ changes: %w[.*.yml **/*] }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'on_success')
+ end
+ end
+ end
+ end
+
+ context 'with no matching rule' do
+ where(:rule_set) do
+ [
+ [[{ if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'never' }, { if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }]],
+ [[{ if: '$VARIABLE != null', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE == "the wrong value"', when: 'always' }, { if: '$VARIABLE != null', when: 'never' }]],
+ [[{ if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE != null' }, { if: '$VARIABLE != null' }]],
+ [[{ if: '$VARIABLE == "the wrong value"' }, { if: '$VARIABLE != null' }]]
+ ]
+ end
+
+ with_them do
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
+
+ context 'with no rules' do
+ let(:rule_set) { [] }
+
+ it { is_expected.not_to be_included }
+
+ it 'correctly populates when:' do
+ expect(seed_build.attributes).to include(when: 'never')
+ end
+ end
+ end
end
describe 'applying needs: dependency' do
@@ -460,4 +755,10 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
end
end
end
+
+ describe '#scoped_variables_hash' do
+ subject { seed_build.scoped_variables_hash }
+
+ it { is_expected.to eq(seed_build.to_resource.scoped_variables_hash) }
+ end
end
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 4ffa1fc9fd8..91c559dcd9b 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -125,9 +125,11 @@ module Gitlab
describe 'delayed job entry' do
context 'when delayed is defined' do
let(:config) do
- YAML.dump(rspec: { script: 'rollout 10%',
- when: 'delayed',
- start_in: '1 day' })
+ YAML.dump(rspec: {
+ script: 'rollout 10%',
+ when: 'delayed',
+ start_in: '1 day'
+ })
end
it 'has the attributes' do
@@ -726,12 +728,12 @@ module Gitlab
end
end
- describe "When" do
- %w(on_success on_failure always).each do |when_state|
- it "returns #{when_state} when defined" do
+ describe 'when:' do
+ (Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN - %w[delayed]).each do |when_state|
+ it "#{when_state} creates one build and sets when:" do
config = YAML.dump({
- rspec: { script: "rspec", when: when_state }
- })
+ rspec: { script: 'rspec', when: when_state }
+ })
config_processor = Gitlab::Ci::YamlProcessor.new(config)
builds = config_processor.stage_builds_attributes("test")
@@ -740,6 +742,35 @@ module Gitlab
expect(builds.first[:when]).to eq(when_state)
end
end
+
+ context 'delayed' do
+ context 'with start_in' do
+ it 'creates one build and sets when:' do
+ config = YAML.dump({
+ rspec: { script: 'rspec', when: 'delayed', start_in: '1 hour' }
+ })
+
+ config_processor = Gitlab::Ci::YamlProcessor.new(config)
+ builds = config_processor.stage_builds_attributes("test")
+
+ expect(builds.size).to eq(1)
+ expect(builds.first[:when]).to eq('delayed')
+ expect(builds.first[:options][:start_in]).to eq('1 hour')
+ end
+ end
+
+ context 'without start_in' do
+ it 'raises an error' do
+ config = YAML.dump({
+ rspec: { script: 'rspec', when: 'delayed' }
+ })
+
+ expect do
+ Gitlab::Ci::YamlProcessor.new(config)
+ end.to raise_error(YamlProcessor::ValidationError, /start in should be a duration/)
+ end
+ end
+ end
end
describe 'Parallel' do
@@ -1132,7 +1163,7 @@ module Gitlab
it { expect { subject }.not_to raise_error }
end
- context 'needs to builds' do
+ context 'needs two builds' do
let(:needs) { %w(build1 build2) }
it "does create jobs with valid specification" do
@@ -1153,7 +1184,10 @@ module Gitlab
stage_idx: 1,
name: "test1",
options: {
- script: ["test"]
+ script: ["test"],
+ # This does not make sense, there is a follow-up:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/65569
+ bridge_needs: %w[build1 build2]
},
needs_attributes: [
{ name: "build1" },
@@ -1166,7 +1200,7 @@ module Gitlab
end
end
- context 'needs to builds defined as symbols' do
+ context 'needs two builds defined as symbols' do
let(:needs) { [:build1, :build2] }
it { expect { subject }.not_to raise_error }
@@ -1192,6 +1226,67 @@ module Gitlab
end
end
+ describe 'rules' do
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ let(:config) do
+ {
+ var_default: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null' }] },
+ var_when: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', when: 'always' }] },
+ var_and_changes: { stage: 'build', script: 'test', rules: [{ if: '$VAR == null', changes: %w[README], when: 'always' }] },
+ changes_not_var: { stage: 'test', script: 'test', rules: [{ if: '$VAR != null', changes: %w[README] }] },
+ var_not_changes: { stage: 'test', script: 'test', rules: [{ if: '$VAR == null', changes: %w[other/file.rb], when: 'always' }] },
+ nothing: { stage: 'test', script: 'test', rules: [{ when: 'manual' }] },
+ var_never: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'never' }] },
+ var_delayed: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] },
+ two_rules: { stage: 'deploy', script: 'test', rules: [{ if: '$VAR == null', when: 'on_success' }, { changes: %w[README], when: 'manual' }] }
+ }
+ end
+
+ it 'raises no exceptions' do
+ expect { subject }.not_to raise_error
+ end
+
+ it 'returns all jobs regardless of their inclusion' do
+ expect(subject.builds.count).to eq(config.keys.count)
+ end
+
+ context 'used with job-level when' do
+ let(:config) do
+ {
+ var_default: {
+ stage: 'build',
+ script: 'test',
+ when: 'always',
+ rules: [{ if: '$VAR == null' }]
+ }
+ }
+ end
+
+ it 'raises a ValidationError' do
+ expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when/)
+ end
+ end
+
+ context 'used with job-level when:delayed' do
+ let(:config) do
+ {
+ var_default: {
+ stage: 'build',
+ script: 'test',
+ when: 'delayed',
+ start_in: '10 minutes',
+ rules: [{ if: '$VAR == null' }]
+ }
+ }
+ end
+
+ it 'raises a ValidationError' do
+ expect { subject }.to raise_error(YamlProcessor::ValidationError, /may not be used with `rules`: when, start_in/)
+ end
+ end
+ end
+
describe "Hidden jobs" do
let(:config_processor) { Gitlab::Ci::YamlProcessor.new(config) }
subject { config_processor.stage_builds_attributes("test") }
@@ -1510,7 +1605,7 @@ module Gitlab
config = YAML.dump({ rspec: { script: "test", when: 1 } })
expect do
Gitlab::Ci::YamlProcessor.new(config)
- end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure, always, manual or delayed")
+ end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec when should be one of: #{Gitlab::Ci::Config::Entry::Job::ALLOWED_WHEN.join(', ')}")
end
it "returns errors if job artifacts:name is not an a string" do
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
index 4c20d945585..f0df3794e29 100644
--- a/spec/lib/gitlab/git_post_receive_spec.rb
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe ::Gitlab::GitPostReceive do
- let(:project) { create(:project) }
+ set(:project) { create(:project, :repository) }
subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
@@ -92,4 +92,47 @@ describe ::Gitlab::GitPostReceive do
end
end
end
+
+ describe '#includes_default_branch?' do
+ context 'with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ 654322 210986 refs/tags/#{project.default_branch}
+ 654323 210985 refs/heads/test3
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_default_branch?).to be_falsey
+ end
+ end
+
+ context 'with a project with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(project).to receive(:default_branch).and_return(nil)
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+
+ context 'with default branch' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/test2
+ 654323 210985 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 0295138fc3a..04fc24b6205 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
+ let(:redis_set_cache) { repository.send(:redis_set_cache) }
describe '#cache_method_output', :use_clean_rails_memory_store_caching do
let(:fallback) { 10 }
@@ -206,9 +207,11 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
- expect(cache).to receive(:expire).with(:gitignore)
+ expect(cache).to receive(:expire).with(:branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
+ expect(redis_set_cache).to receive(:expire).with(:branch_names)
- repository.expire_method_caches(%i(rendered_readme gitignore))
+ repository.expire_method_caches(%i(rendered_readme branch_names))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
new file mode 100644
index 00000000000..9695e13d842
--- /dev/null
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
+ let(:cache) { described_class.new(repository) }
+
+ describe '#cache_key' do
+ subject { cache.cache_key(:foo) }
+
+ it 'includes the namespace' do
+ is_expected.to eq("foo:#{namespace}:set")
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
+
+ it 'includes the full namespace' do
+ is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
+ end
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.read(:foo)).to contain_exactly('value')
+ expect(cache.expire(:foo)).to eq(1)
+ expect(cache.read(:foo)).to be_empty
+ end
+ end
+
+ describe '#exist?' do
+ it 'checks whether the key exists' do
+ expect(cache.exist?(:foo)).to be(false)
+
+ cache.write(:foo, ['value'])
+
+ expect(cache.exist?(:foo)).to be(true)
+ end
+ end
+
+ describe '#fetch' do
+ let(:blk) { -> { ['block value'] } }
+
+ subject { cache.fetch(:foo, &blk) }
+
+ it 'fetches the key from the cache when filled' do
+ cache.write(:foo, ['value'])
+
+ is_expected.to contain_exactly('value')
+ end
+
+ it 'writes the value of the provided block when empty' do
+ cache.expire(:foo)
+
+ is_expected.to contain_exactly('block value')
+ expect(cache.read(:foo)).to contain_exactly('block value')
+ end
+ end
+
+ describe '#include?' do
+ it 'checks inclusion in the Redis set' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.include?(:foo, 'value')).to be(true)
+ expect(cache.include?(:foo, 'bar')).to be(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index af8b059b984..a48cc0d128a 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -65,6 +65,7 @@ describe Gitlab::Sentry do
context '.track_acceptable_exception' do
let(:exception) { RuntimeError.new('boom') }
+ let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1' }
before do
allow(described_class).to receive(:enabled?).and_return(true)
@@ -74,7 +75,7 @@ describe Gitlab::Sentry do
it 'calls Raven.capture_exception' do
expected_extras = {
some_other_info: 'info',
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
+ issue_url: issue_url
}
expected_tags = {
@@ -88,9 +89,33 @@ describe Gitlab::Sentry do
described_class.track_acceptable_exception(
exception,
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1',
+ issue_url: issue_url,
extra: { some_other_info: 'info' }
)
end
+
+ context 'the exception implements :sentry_extra_data' do
+ let(:extra_info) { { event: 'explosion', size: :massive } }
+ let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info) }
+
+ it 'includes the extra data from the exception in the tracking information' do
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception)
+ end
+ end
+
+ context 'the exception implements :sentry_extra_data, which returns nil' do
+ let(:exception) { double(message: 'bang!', sentry_extra_data: nil) }
+
+ it 'just includes the other extra info' do
+ extra_info = { issue_url: issue_url }
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception, extra_info)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
index c6df1c6a0d8..e430599bd94 100644
--- a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
@@ -13,7 +13,7 @@ describe Gitlab::SidekiqMiddleware::Metrics do
let(:running_jobs_metric) { double('running jobs metric') }
before do
- allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything).and_return(completion_seconds_metric)
+ allow(Gitlab::Metrics).to receive(:histogram).with(:sidekiq_jobs_completion_seconds, anything, anything).and_return(completion_seconds_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_failed_total, anything).and_return(failed_total_metric)
allow(Gitlab::Metrics).to receive(:counter).with(:sidekiq_jobs_retried_total, anything).and_return(retried_total_metric)
allow(Gitlab::Metrics).to receive(:gauge).with(:sidekiq_running_jobs, anything, {}, :livesum).and_return(running_jobs_metric)
diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
index 1669a22879f..b385d1b07c7 100644
--- a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
@@ -26,16 +26,22 @@ describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_stat
end
it_behaves_like 'a note usage counter', :create, 'Snippet'
+ it_behaves_like 'a note usage counter', :create, 'MergeRequest'
+ it_behaves_like 'a note usage counter', :create, 'Commit'
describe '.totals' do
let(:combinations) do
[
- [:create, 'Snippet', 3]
+ [:create, 'Snippet', 3],
+ [:create, 'MergeRequest', 4],
+ [:create, 'Commit', 5]
]
end
let(:expected_totals) do
- { snippet_comment: 3 }
+ { snippet_comment: 3,
+ merge_request_comment: 4,
+ commit_comment: 5 }
end
before do
@@ -57,14 +63,18 @@ describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_stat
let(:unknown_event_error) { Gitlab::UsageDataCounters::BaseCounter::UnknownEvent }
where(:event, :noteable_type, :expected_count, :should_raise) do
- :create | 'Snippet' | 1 | false
- :wibble | 'Snippet' | 0 | true
- :create | 'Issue' | 0 | false
- :wibble | 'Issue' | 0 | false
+ :create | 'Snippet' | 1 | false
+ :wibble | 'Snippet' | 0 | true
+ :create | 'MergeRequest' | 1 | false
+ :wibble | 'MergeRequest' | 0 | true
+ :create | 'Commit' | 1 | false
+ :wibble | 'Commit' | 0 | true
+ :create | 'Issue' | 0 | false
+ :wibble | 'Issue' | 0 | false
end
with_them do
- it "handles event" do
+ it 'handles event' do
if should_raise
expect { described_class.count(event, noteable_type) }.to raise_error(unknown_event_error)
else
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 588c68d1fb0..dda119e09b1 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UsageData do
@@ -34,7 +36,7 @@ describe Gitlab::UsageData do
subject { described_class.data }
- it "gathers usage data" do
+ it 'gathers usage data' do
expect(subject.keys).to include(*%i(
active_user_count
counts
@@ -66,6 +68,8 @@ describe Gitlab::UsageData do
snippet_create: a_kind_of(Integer),
snippet_update: a_kind_of(Integer),
snippet_comment: a_kind_of(Integer),
+ merge_request_comment: a_kind_of(Integer),
+ commit_comment: a_kind_of(Integer),
wiki_pages_create: a_kind_of(Integer),
wiki_pages_update: a_kind_of(Integer),
wiki_pages_delete: a_kind_of(Integer),
@@ -78,7 +82,7 @@ describe Gitlab::UsageData do
)
end
- it "gathers usage counts" do
+ it 'gathers usage counts' do
expected_keys = %i(
assignee_lists
boards
@@ -154,11 +158,6 @@ describe Gitlab::UsageData do
expect(expected_keys - count_data.keys).to be_empty
end
- it 'does not gather user preferences usage data when the feature is disabled' do
- stub_feature_flags(group_overview_security_dashboard: false)
- expect(subject[:counts].keys).not_to include(:user_preferences)
- end
-
it 'gathers projects data correctly' do
count_data = subject[:counts]
@@ -253,7 +252,7 @@ describe Gitlab::UsageData do
describe '#license_usage_data' do
subject { described_class.license_usage_data }
- it "gathers license data" do
+ it 'gathers license data' do
expect(subject[:uuid]).to eq(Gitlab::CurrentSettings.uuid)
expect(subject[:version]).to eq(Gitlab::VERSION)
expect(subject[:installation_type]).to eq('gitlab-development-kit')
@@ -271,6 +270,12 @@ describe Gitlab::UsageData do
expect(described_class.count(relation)).to eq(1)
end
+ it 'returns the count for count_by when provided' do
+ allow(relation).to receive(:count).with(:creator_id).and_return(2)
+
+ expect(described_class.count(relation, count_by: :creator_id)).to eq(2)
+ end
+
it 'returns the fallback value when counting fails' do
allow(relation).to receive(:count).and_raise(ActiveRecord::StatementInvalid.new(''))
diff --git a/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
new file mode 100644
index 00000000000..c7302a1a656
--- /dev/null
+++ b/spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Prometheus::CleanupMultiprocDirService do
+ describe '.call' do
+ subject { described_class.new.execute }
+
+ let(:metrics_multiproc_dir) { Dir.mktmpdir }
+ let(:metrics_file_path) { File.join(metrics_multiproc_dir, 'counter_puma_master-0.db') }
+
+ before do
+ FileUtils.touch(metrics_file_path)
+ end
+
+ after do
+ FileUtils.rm_r(metrics_multiproc_dir)
+ end
+
+ context 'when `multiprocess_files_dir` is defined' do
+ before do
+ expect(Prometheus::Client.configuration)
+ .to receive(:multiprocess_files_dir)
+ .and_return(metrics_multiproc_dir)
+ .at_least(:once)
+ end
+
+ it 'removes old metrics' do
+ expect { subject }
+ .to change { File.exist?(metrics_file_path) }
+ .from(true)
+ .to(false)
+ end
+ end
+
+ context 'when `multiprocess_files_dir` is not defined' do
+ before do
+ expect(Prometheus::Client.configuration)
+ .to receive(:multiprocess_files_dir)
+ .and_return(nil)
+ .at_least(:once)
+ end
+
+ it 'does not remove any files' do
+ expect { subject }
+ .not_to change { File.exist?(metrics_file_path) }
+ .from(true)
+ end
+ end
+ end
+end
diff --git a/spec/models/ci/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index eb32198265b..a871f9b3fe6 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -23,7 +23,7 @@ describe Ci::Bridge do
let(:status) { bridge.detailed_status(user) }
it 'returns detailed status object' do
- expect(status).to be_a Gitlab::Ci::Status::Success
+ expect(status).to be_a Gitlab::Ci::Status::Created
end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 1fb83fbb088..78be4a8131a 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1929,6 +1929,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to be_an(Array) }
end
+ describe '.bridgeable_statuses' do
+ subject { described_class.bridgeable_statuses }
+
+ it { is_expected.to be_an(Array) }
+ it { is_expected.not_to include('created', 'preparing', 'pending') }
+ end
+
describe '#status' do
let(:build) do
create(:ci_build, :created, pipeline: pipeline, name: 'test')
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index ebb0bfca369..ad7dfac87af 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -3,19 +3,29 @@
require 'spec_helper'
describe GroupMember do
- describe '.count_users_by_group_id' do
- it 'counts users by group ID' do
- user_1 = create(:user)
- user_2 = create(:user)
- group_1 = create(:group)
- group_2 = create(:group)
-
- group_1.add_owner(user_1)
- group_1.add_owner(user_2)
- group_2.add_owner(user_1)
-
- expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
- group_2.id => 1)
+ context 'scopes' do
+ describe '.count_users_by_group_id' do
+ it 'counts users by group ID' do
+ user_1 = create(:user)
+ user_2 = create(:user)
+ group_1 = create(:group)
+ group_2 = create(:group)
+
+ group_1.add_owner(user_1)
+ group_1.add_owner(user_2)
+ group_2.add_owner(user_1)
+
+ expect(described_class.count_users_by_group_id).to eq(group_1.id => 2,
+ group_2.id => 1)
+ end
+ end
+
+ describe '.of_ldap_type' do
+ it 'returns ldap type users' do
+ group_member = create(:group_member, :ldap)
+
+ expect(described_class.of_ldap_type).to eq([group_member])
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index e68de2e73a8..79395fcc994 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1223,36 +1223,66 @@ describe Repository do
end
describe '#branch_exists?' do
- it 'uses branch_names' do
- allow(repository).to receive(:branch_names).and_return(['foobar'])
+ let(:branch) { repository.root_ref }
- expect(repository.branch_exists?('foobar')).to eq(true)
- expect(repository.branch_exists?('master')).to eq(false)
+ subject { repository.branch_exists?(branch) }
+
+ it 'delegates to branch_names when the cache is empty' do
+ repository.expire_branches_cache
+
+ expect(repository).to receive(:branch_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.branch_names # ensure the branch name cache is filled
+
+ expect(repository)
+ .to receive(:branch_names_include?)
+ .with(branch)
+ .and_call_original
+
+ is_expected.to eq(true)
end
end
describe '#tag_exists?' do
- it 'uses tag_names' do
- allow(repository).to receive(:tag_names).and_return(['foobar'])
+ let(:tag) { repository.tags.first.name }
- expect(repository.tag_exists?('foobar')).to eq(true)
- expect(repository.tag_exists?('master')).to eq(false)
+ subject { repository.tag_exists?(tag) }
+
+ it 'delegates to tag_names when the cache is empty' do
+ repository.expire_tags_cache
+
+ expect(repository).to receive(:tag_names).and_call_original
+ is_expected.to eq(true)
+ end
+
+ it 'uses redis set caching when the cache is filled' do
+ repository.tag_names # ensure the tag name cache is filled
+
+ expect(repository)
+ .to receive(:tag_names_include?)
+ .with(tag)
+ .and_call_original
+
+ is_expected.to eq(true)
end
end
- describe '#branch_names', :use_clean_rails_memory_store_caching do
+ describe '#branch_names', :clean_gitlab_redis_cache do
let(:fake_branch_names) { ['foobar'] }
it 'gets cached across Repository instances' do
allow(repository.raw_repository).to receive(:branch_names).once.and_return(fake_branch_names)
- expect(repository.branch_names).to eq(fake_branch_names)
+ expect(repository.branch_names).to match_array(fake_branch_names)
fresh_repository = Project.find(project.id).repository
expect(fresh_repository.object_id).not_to eq(repository.object_id)
expect(fresh_repository.raw_repository).not_to receive(:branch_names)
- expect(fresh_repository.branch_names).to eq(fake_branch_names)
+ expect(fresh_repository.branch_names).to match_array(fake_branch_names)
end
end
@@ -1815,22 +1845,36 @@ describe Repository do
end
describe '#after_create' do
+ it 'calls expire_status_cache' do
+ expect(repository).to receive(:expire_status_cache)
+
+ repository.after_create
+ end
+
+ it 'logs an event' do
+ expect(repository).to receive(:repository_event).with(:create_repository)
+
+ repository.after_create
+ end
+ end
+
+ describe '#expire_status_cache' do
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
- repository.after_create
+ repository.expire_status_cache
end
end
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
index ab3f8080257..83004809536 100644
--- a/spec/presenters/blobs/unfold_presenter_spec.rb
+++ b/spec/presenters/blobs/unfold_presenter_spec.rb
@@ -10,16 +10,31 @@ describe Blobs::UnfoldPresenter do
let(:subject) { described_class.new(blob, params) }
describe '#initialize' do
+ let(:result) { subject }
+
+ context 'with empty params' do
+ let(:params) { {} }
+
+ it 'sets default attributes' do
+ expect(result.full?).to eq(false)
+ expect(result.since).to eq(1)
+ expect(result.to).to eq(1)
+ expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(true)
+ expect(result.offset).to eq(0)
+ expect(result.indent).to eq(0)
+ end
+ end
+
context 'when full is false' do
let(:params) { { full: false, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
it 'sets attributes' do
- result = subject
-
expect(result.full?).to eq(false)
expect(result.since).to eq(2)
expect(result.to).to eq(3)
expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(true)
expect(result.offset).to eq(1)
expect(result.indent).to eq(1)
end
@@ -29,12 +44,11 @@ describe Blobs::UnfoldPresenter do
let(:params) { { full: true, since: 2, to: 3, bottom: false, offset: 1, indent: 1 } }
it 'sets other attributes' do
- result = subject
-
expect(result.full?).to eq(true)
expect(result.since).to eq(1)
expect(result.to).to eq(blob.lines.size)
expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(false)
expect(result.offset).to eq(0)
expect(result.indent).to eq(0)
end
@@ -44,12 +58,11 @@ describe Blobs::UnfoldPresenter do
let(:params) { { full: false, since: 2, to: -1, bottom: true, offset: 1, indent: 1 } }
it 'sets other attributes' do
- result = subject
-
expect(result.full?).to eq(false)
expect(result.since).to eq(2)
expect(result.to).to eq(blob.lines.size)
expect(result.bottom).to eq(false)
+ expect(result.unfold).to eq(false)
expect(result.offset).to eq(0)
expect(result.indent).to eq(0)
end
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index e8e17228523..5e6ff40e8cf 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -126,6 +126,12 @@ describe API::Commits do
end
end
+ context "with empty ref_name parameter" do
+ let(:route) { "/projects/#{project_id}/repository/commits?ref_name=" }
+
+ it_behaves_like 'project commits'
+ end
+
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 5916bb11516..c487471e4a1 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -342,6 +342,14 @@ describe API::Issues do
group_project.add_reporter(user)
end
+ it 'exposes known attributes' do
+ get api(base_url, admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last).not_to have_key('subscribed')
+ end
+
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index f7ca6fd1e0a..521d6b88734 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -575,6 +575,7 @@ describe API::Issues do
expect(json_response['assignee']).to be_a Hash
expect(json_response['author']).to be_a Hash
expect(json_response['confidential']).to be_falsy
+ expect(json_response['subscribed']).to be_truthy
end
it 'exposes the closed_at attribute' do
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index d195f54be11..27cf66629fe 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -216,6 +216,10 @@ describe API::Issues do
expect_paginated_array_response([issue.id, closed_issue.id])
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
+ # Calculating the value of subscribed field triggers Markdown
+ # processing. We can't do that for multiple issues / merge
+ # requests in a single API request.
+ expect(json_response.last).not_to have_key('subscribed')
end
it 'returns an array of closed issues' do
diff --git a/spec/requests/rack_attack_global_spec.rb b/spec/requests/rack_attack_global_spec.rb
index d832963292c..478f09a7881 100644
--- a/spec/requests/rack_attack_global_spec.rb
+++ b/spec/requests/rack_attack_global_spec.rb
@@ -112,9 +112,9 @@ describe 'Rack Attack global throttles' do
arguments = {
message: 'Rack_Attack',
env: :throttle,
- ip: '127.0.0.1',
+ remote_ip: '127.0.0.1',
request_method: 'GET',
- fullpath: get_args.first,
+ path: get_args.first,
user_id: user.id,
username: user.username
}
@@ -213,9 +213,9 @@ describe 'Rack Attack global throttles' do
arguments = {
message: 'Rack_Attack',
env: :throttle,
- ip: '127.0.0.1',
+ remote_ip: '127.0.0.1',
request_method: 'GET',
- fullpath: '/users/sign_in'
+ path: '/users/sign_in'
}
expect(Gitlab::AuthLogger).to receive(:error).with(arguments)
@@ -377,9 +377,9 @@ describe 'Rack Attack global throttles' do
arguments = {
message: 'Rack_Attack',
env: :throttle,
- ip: '127.0.0.1',
+ remote_ip: '127.0.0.1',
request_method: 'GET',
- fullpath: '/dashboard/snippets',
+ path: '/dashboard/snippets',
user_id: user.id,
username: user.username
}
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 76ad2aee5c5..c0ea2b3c389 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -32,6 +32,10 @@ describe DeploymentEntity do
expect(subject).to include(:created_at)
end
+ it 'exposes finished_at' do
+ expect(subject).to include(:finished_at)
+ end
+
context 'when the pipeline has another manual action' do
let(:other_build) { create(:ci_build, :manual, name: 'another deploy', pipeline: pipeline) }
let!(:other_deployment) { create(:deployment, deployable: other_build) }
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 8af51848b7b..2bf7dc32436 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Git::BranchHooksService do
include RepoHelpers
+ include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
@@ -158,9 +159,13 @@ describe Git::BranchHooksService do
let(:blank_sha) { Gitlab::Git::BLANK_SHA }
def clears_cache(extended: [])
- expect(ProjectCacheWorker)
- .to receive(:perform_async)
- .with(project.id, extended, %i[commit_count repository_size])
+ expect(service).to receive(:invalidated_file_types).and_return(extended)
+
+ if extended.present?
+ expect(ProjectCacheWorker)
+ .to receive(:perform_async)
+ .with(project.id, extended, [], false)
+ end
service.execute
end
@@ -268,10 +273,10 @@ describe Git::BranchHooksService do
end
describe 'Processing commit messages' do
- # Create 4 commits, 2 of which have references. Limiting to 2 commits, we
- # expect to see one commit message processor enqueued.
- let(:commit_ids) do
- Array.new(4) do |i|
+ # Create 6 commits, 3 of which have references. Limiting to 4 commits, we
+ # expect to see two commit message processors enqueued.
+ let!(:commit_ids) do
+ Array.new(6) do |i|
message = "Issue #{'#' if i.even?}#{i}"
project.repository.update_file(
user, 'README.md', '', message: message, branch_name: branch
@@ -279,18 +284,18 @@ describe Git::BranchHooksService do
end
end
- let(:oldrev) { commit_ids.first }
+ let(:oldrev) { project.commit(commit_ids.first).parent_id }
let(:newrev) { commit_ids.last }
before do
- stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 2)
+ stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 4)
end
context 'creating the default branch' do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -298,7 +303,7 @@ describe Git::BranchHooksService do
context 'updating the default branch' do
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -319,7 +324,7 @@ describe Git::BranchHooksService do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -329,7 +334,7 @@ describe Git::BranchHooksService do
let(:branch) { 'fix' }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -345,6 +350,55 @@ describe Git::BranchHooksService do
service.execute
end
end
+
+ context 'when the project is forked' do
+ let(:upstream_project) { project }
+ let(:forked_project) { fork_project(upstream_project, user, repository: true) }
+
+ let!(:forked_service) do
+ described_class.new(forked_project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+
+ context 'when commits already exists in the upstream project' do
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ forked_service.execute
+ end
+ end
+
+ context 'when a commit does not exist in the upstream repo' do
+ # On top of the existing 6 commits, 3 of which have references,
+ # create 2 more, 1 of which has a reference. Limiting to 4 commits, we
+ # expect to see one commit message processor enqueued.
+ let!(:forked_commit_ids) do
+ Array.new(2) do |i|
+ message = "Issue #{'#' if i.even?}#{i}"
+ forked_project.repository.update_file(
+ user, 'README.md', '', message: message, branch_name: branch
+ )
+ end
+ end
+
+ let(:newrev) { forked_commit_ids.last }
+
+ it 'processes the commit message' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ forked_service.execute
+ end
+ end
+
+ context 'when the upstream project no longer exists' do
+ it 'processes the commit messages' do
+ upstream_project.destroy!
+
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
+
+ forked_service.execute
+ end
+ end
+ end
end
describe 'New branch detection' do
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index ad5d296f5c1..d9e607cd251 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -76,6 +76,22 @@ describe Git::BranchPushService, services: true do
stub_ci_pipeline_to_return_yaml_file
end
+ it 'creates a pipeline with the right parameters' do
+ expect(Ci::CreatePipelineService)
+ .to receive(:new)
+ .with(project,
+ user,
+ {
+ before: oldrev,
+ after: newrev,
+ ref: ref,
+ checkout_sha: SeedRepo::Commit::ID,
+ push_options: {}
+ }).and_call_original
+
+ subject
+ end
+
it "creates a new pipeline" do
expect { subject }.to change { Ci::Pipeline.count }
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index d9f35afee06..fd9a63b79cc 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -229,10 +229,10 @@ describe Issues::UpdateService, :mailer do
it 'creates zoom_link_added system note when a zoom link is added to the description' do
update_issue(description: 'Changed description https://zoom.us/j/5873603787')
- note = find_note('a Zoom call was added')
+ note = find_note('added a Zoom call')
expect(note).not_to be_nil
- expect(note.note).to eq('a Zoom call was added to this issue')
+ expect(note.note).to eq('added a Zoom call to this issue')
end
context 'when issue turns confidential' do
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 486d0ca0c56..f46f9633c1c 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -521,7 +521,7 @@ describe SystemNoteService do
end
it 'sets the zoom link added note text' do
- expect(subject.note).to eq('a Zoom call was added to this issue')
+ expect(subject.note).to eq('added a Zoom call to this issue')
end
end
@@ -533,7 +533,7 @@ describe SystemNoteService do
end
it 'sets the zoom link removed note text' do
- expect(subject.note).to eq('a Zoom call was removed from this issue')
+ expect(subject.note).to eq('removed a Zoom call from this issue')
end
end
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index badea94352a..7d10cffe920 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -22,6 +22,10 @@ module StubGitlabCalls
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
end
+ def stub_pipeline_modified_paths(pipeline, modified_paths)
+ allow(pipeline).to receive(:modified_paths).and_return(modified_paths)
+ end
+
def stub_repository_ci_yaml_file(sha:, path: '.gitlab-ci.yml')
allow_any_instance_of(Repository)
.to receive(:gitlab_ci_yml_for).with(sha, path)
diff --git a/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
new file mode 100644
index 00000000000..76d82649c5f
--- /dev/null
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+shared_examples_for 'multiple issue boards' do
+ dropdown_selector = '.js-boards-selector .dropdown-menu'
+
+ context 'authorized user' do
+ before do
+ parent.add_maintainer(user)
+
+ login_as(user)
+
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'shows current board name' do
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board.name)
+ end
+ end
+
+ it 'shows a list of boards' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'switches current board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'creates new board without detailed configuration' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_button 'Create new board'
+ end
+
+ fill_in 'board-new-name', with: 'This is a new board'
+ click_button 'Create board'
+ wait_for_requests
+
+ expect(page).to have_button('This is a new board')
+ end
+
+ it 'deletes board' do
+ click_button board.name
+
+ wait_for_requests
+
+ page.within(dropdown_selector) do
+ click_button 'Delete board'
+ end
+
+ expect(page).to have_content('Are you sure you want to delete this board?')
+ click_button 'Delete'
+
+ click_button board2.name
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'adds a list to the none default board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+
+ click_button 'Add list'
+
+ wait_for_requests
+
+ page.within '.dropdown-menu-issues-board-new' do
+ click_link planning.title
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 3)
+
+ click_button board2.name
+
+ page.within(dropdown_selector) do
+ click_link board.name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 2)
+ end
+
+ it 'maintains sidebar state over board switch' do
+ assert_boards_nav_active
+
+ find('.boards-switcher').click
+ wait_for_requests
+ click_link board2.name
+
+ assert_boards_nav_active
+ end
+ end
+
+ context 'unauthorized user' do
+ before do
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'does not show action links' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content('Create new board')
+ expect(page).not_to have_content('Delete board')
+ end
+ end
+ end
+
+ def assert_boards_nav_active
+ expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards')
+ end
+end
diff --git a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
index 2faa0cf8c1c..d8a1ae83f61 100644
--- a/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb
@@ -8,7 +8,7 @@ shared_examples 'disabled when using an external authorization service' do
it 'works when the feature is not enabled' do
subject
- expect(response).to be_success
+ expect(response).to be_successful
end
it 'renders a 404 with a message when the feature is enabled' do
diff --git a/spec/views/projects/pages_domains/show.html.haml_spec.rb b/spec/views/projects/pages_domains/show.html.haml_spec.rb
new file mode 100644
index 00000000000..da27a04bfe9
--- /dev/null
+++ b/spec/views/projects/pages_domains/show.html.haml_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe 'projects/pages_domains/show' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ assign(:project, project)
+ assign(:domain, domain)
+ end
+
+ context 'when auto_ssl is enabled' do
+ context 'when domain is disabled' do
+ let(:domain) { create(:pages_domain, :disabled, project: project, auto_ssl_enabled: true) }
+
+ it 'shows verification warning' do
+ render
+
+ expect(rendered).to have_content("A Let's Encrypt SSL certificate can not be obtained until your domain is verified.")
+ end
+ end
+
+ context 'when certificate is absent' do
+ let(:domain) { create(:pages_domain, :without_key, :without_certificate, project: project, auto_ssl_enabled: true) }
+
+ it 'shows alert about time of obtaining certificate' do
+ render
+
+ expect(rendered).to have_content("GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.")
+ end
+ end
+
+ context 'when certificate is present' do
+ let(:domain) { create(:pages_domain, :letsencrypt, project: project) }
+
+ it 'shows certificate info' do
+ render
+
+ # test just a random part of cert represenations(X509v3 Subject Key Identifier:)
+ expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90")
+ end
+ end
+ end
+
+ context 'when auto_ssl is disabled' do
+ context 'when certificate is present' do
+ let(:domain) { create(:pages_domain, project: project) }
+
+ it 'shows certificate info' do
+ render
+
+ # test just a random part of cert represenations(X509v3 Subject Key Identifier:)
+ expect(rendered).to have_content("C6:5F:56:4B:10:69:AC:1D:33:D2:26:C9:B3:7A:D7:12:4D:3E:F7:90")
+ end
+ end
+
+ context 'when certificate is absent' do
+ let(:domain) { create(:pages_domain, :without_certificate, :without_key, project: project) }
+
+ it 'shows missing certificate' do
+ render
+
+ expect(rendered).to have_content("missing")
+ end
+ end
+ end
+end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 3b69b81f12e..c8a0c22b0e8 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -37,6 +37,29 @@ describe PostReceive do
end
describe "#process_project_changes" do
+ context 'with an empty project' do
+ let(:empty_project) { create(:project, :empty_repo) }
+ let(:changes) { "123456 789012 refs/heads/tést1\n" }
+
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner)
+ allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, Gitlab::GlRepository::PROJECT])
+ end
+
+ it 'expire the status cache' do
+ expect(empty_project.repository).to receive(:expire_status_cache)
+
+ perform
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(empty_project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
+ end
+
context 'empty changes' do
it "does not call any PushService but runs after project hooks" do
expect(Git::BranchPushService).not_to receive(:new)
@@ -67,15 +90,22 @@ describe PostReceive do
context "branches" do
let(:changes) do
<<~EOF
- '123456 789012 refs/heads/tést1'
- '123456 789012 refs/heads/tést2'
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
EOF
end
it 'expires the branches cache' do
expect(project.repository).to receive(:expire_branches_cache).once
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
+ end
+
+ it 'expires the status cache' do
+ expect(project).to receive(:empty_repo?).and_return(true)
+ expect(project.repository).to receive(:expire_status_cache)
+
+ perform
end
it 'calls Git::BranchPushService' do
@@ -87,6 +117,30 @@ describe PostReceive do
perform
end
+
+ it 'schedules a cache update for repository size only' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
+ end
+
+ context 'with a default branch' do
+ let(:changes) do
+ <<~EOF
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
+ 678912 123455 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
+ end
end
context "tags" do
@@ -107,7 +161,7 @@ describe PostReceive do
it 'does not expire branches cache' do
expect(project.repository).not_to receive(:expire_branches_cache)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
it "only invalidates tags once" do
@@ -115,7 +169,7 @@ describe PostReceive do
expect(project.repository).to receive(:expire_caches_for_tags).once.and_call_original
expect(project.repository).to receive(:expire_tags_cache).once.and_call_original
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
it "calls Git::TagPushService" do
@@ -129,6 +183,13 @@ describe PostReceive do
perform
end
+
+ it 'schedules a single ProjectCacheWorker update' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
+ end
end
context "merge-requests" do
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 47bac63511e..eb1d3c364ac 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
describe ProcessCommitWorker do
- include ProjectForksHelper
-
let(:worker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -35,44 +33,6 @@ describe ProcessCommitWorker do
worker.perform(project.id, user.id, commit.to_hash)
end
-
- context 'when the project is forked' do
- context 'when commit already exists in the upstream project' do
- it 'does not process the commit message' do
- forked = fork_project(project, user, repository: true)
-
- expect(worker).not_to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the commit does not exist in the upstream project' do
- it 'processes the commit message' do
- empty_project = create(:project, :public)
- forked = fork_project(empty_project, user, repository: true)
-
- TestEnv.copy_repo(forked,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the upstream project no longer exists' do
- it 'processes the commit message' do
- forked = fork_project(project, user, repository: true)
- project.destroy!
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
- end
end
describe '#process_commit_message' do
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index edc55920b8e..7f3c4881b89 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -49,6 +49,16 @@ describe ProjectCacheWorker do
worker.perform(project.id, %w(readme))
end
+ context 'with statistics disabled' do
+ let(:statistics) { [] }
+
+ it 'does not update the project statistics' do
+ expect(worker).not_to receive(:update_statistics)
+
+ worker.perform(project.id, [], [], false)
+ end
+ end
+
context 'with statistics' do
let(:statistics) { %w(repository_size) }
diff --git a/yarn.lock b/yarn.lock
index a295039ec54..525c77e41d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9,7 +9,7 @@
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.1.2", "@babel/core@^7.4.4":
+"@babel/core@>=7.2.2", "@babel/core@^7.1.0", "@babel/core@^7.1.2", "@babel/core@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.5.5.tgz#17b2686ef0d6bc58f963dddd68ab669755582c30"
integrity sha512-i4qoSr2KTtce0DmkuuQBV4AuQgGPUcPXMr9L5MyYAtk06z068lQ10a4O009fe5OB/DfNV+h+qqT7ddNV8UnRjg==
@@ -139,16 +139,16 @@
"@babel/types" "^7.0.0"
"@babel/helper-module-transforms@^7.1.0", "@babel/helper-module-transforms@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.4.4.tgz#96115ea42a2f139e619e98ed46df6019b94414b8"
- integrity sha512-3Z1yp8TVQf+B4ynN7WoHPKS8EkdTbgAEy0nU0rs/1Kw4pDgmvYH3rz3aI11KgxKCba2cn7N+tqzV1mY2HMN96w==
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz#f84ff8a09038dcbca1fd4355661a500937165b4a"
+ integrity sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
"@babel/template" "^7.4.4"
- "@babel/types" "^7.4.4"
- lodash "^4.17.11"
+ "@babel/types" "^7.5.5"
+ lodash "^4.17.13"
"@babel/helper-optimise-call-expression@^7.0.0":
version "7.0.0"
@@ -163,11 +163,11 @@
integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA==
"@babel/helper-regex@^7.0.0", "@babel/helper-regex@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.4.4.tgz#a47e02bc91fb259d2e6727c2a30013e3ac13c4a2"
- integrity sha512-Y5nuB/kESmR3tKjU8Nkn1wMGEx1tjJX076HBMeL3XLQCu6vA/YRzuTW0bbb+qRnXvQGn+d6Rx953yffl8vEy7Q==
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.5.5.tgz#0aa6824f7100a2e0e89c1527c23936c152cab351"
+ integrity sha512-CkCYQLkfkiugbRDO8eZn6lRuR8kzZoGXCg3149iTk5se7g6qykSpy3+hELSwquhu+TgHn8nkLiBwHvNX8Hofcw==
dependencies:
- lodash "^4.17.11"
+ lodash "^4.17.13"
"@babel/helper-remap-async-to-generator@^7.1.0":
version "7.1.0"
@@ -225,9 +225,9 @@
"@babel/types" "^7.5.5"
"@babel/highlight@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4"
- integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.5.0.tgz#56d11312bd9248fa619591d02472be6e8cb32540"
+ integrity sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==
dependencies:
chalk "^2.0.0"
esutils "^2.0.2"
@@ -252,7 +252,7 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.0"
-"@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.4.4":
+"@babel/plugin-proposal-class-properties@^7.1.0", "@babel/plugin-proposal-class-properties@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.5.5.tgz#a974cfae1e37c3110e71f3c6a2e48b8e71958cd4"
integrity sha512-AF79FsnWFxjlaosgdi421vmYG6/jg79bVD0dpD44QdgobzHKuLZ6S3vl8la9qIeSwGi8i1fS0O1mfuDAAdo1/A==
@@ -833,7 +833,7 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.5.4"
-"@babel/preset-env@^7.1.0", "@babel/preset-env@^7.4.4":
+"@babel/preset-env@^7.1.0", "@babel/preset-env@^7.5.5":
version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.5.5.tgz#bc470b53acaa48df4b8db24a570d6da1fef53c9a"
integrity sha512-GMZQka/+INwsMz1A5UEql8tG015h5j/qjptpKY2gJ7giy8ohzU710YciJB5rcKsWGWHiW3RUnHib0E5/m3Tp3A==
@@ -914,9 +914,9 @@
integrity sha512-FBMd0IiARPtH5aaOFUVki6evHiJQiY0pFy7fizyRF7dtwc+el3nwpzvhb9qBNzceG1OIJModG1xpE0DDFjPXwA==
"@babel/standalone@^7.0.0":
- version "7.3.4"
- resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.3.4.tgz#b622c1e522acef91b2a14f22bdcdd4f935a1a474"
- integrity sha512-4L9c5i4WlGqbrjOVX0Yp8TIR5cEiw1/tPYYZENW/iuO2uI6viY38U7zALidzNfGdZIwNc+A/AWqMEWKeScWkBg==
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.5.5.tgz#9d3143f6078ff408db694a4254bd6f03c5c33962"
+ integrity sha512-YIp5taErC4uvp4d5urJtWMui3cpvZt83x57l4oVJNvFtDzumf3pMgRmoTSpGuEzh1yzo7jHhg3mbQmMhmKPbjA==
"@babel/template@^7.1.0", "@babel/template@^7.4.0", "@babel/template@^7.4.4":
version "7.4.4"