summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.dockerignore72
-rw-r--r--.gitignore2
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml10
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml12
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml30
-rw-r--r--.mdlrc.style2
-rw-r--r--CHANGELOG.md253
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile5
-rw-r--r--Gemfile.lock11
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/api.js15
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_sidebar.js2
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue1
-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/event_tracking/issue_sidebar.js2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/actions.vue12
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue33
-rw-r--r--app/assets/javascripts/ide/stores/getters.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/actions.js23
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js12
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutation_types.js1
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/mutations.js3
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/state.js3
-rw-r--r--app/assets/javascripts/ide/stores/utils.js2
-rw-r--r--app/assets/javascripts/issue_show/index.js4
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js60
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js8
-rw-r--r--app/assets/javascripts/main.js17
-rw-r--r--app/assets/javascripts/members.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue5
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue342
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue28
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue8
-rw-r--r--app/assets/javascripts/monitoring/constants.js9
-rw-r--r--app/assets/javascripts/notes/stores/getters.js39
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js42
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue4
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue4
-rw-r--r--app/assets/javascripts/registry/components/app.vue101
-rw-r--r--app/assets/javascripts/registry/components/svg_message.vue26
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue48
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue83
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignee_title.vue13
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/assignees.vue212
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue27
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue121
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue96
-rw-r--r--app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue8
-rw-r--r--app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue7
-rw-r--r--app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue3
-rw-r--r--app/assets/javascripts/test_utils/simulate_drag.js6
-rw-r--r--app/assets/javascripts/tracking.js8
-rw-r--r--app/assets/javascripts/users_select.js66
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment.js39
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js31
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_post.js145
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment_storage.js20
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/form_elements.js17
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/index.js23
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/login.js47
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/mr_id.js63
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/note.js35
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/utils.js51
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/wrapper.js79
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js15
-rw-r--r--app/assets/javascripts/visual_review_toolbar/index.js51
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/constants.js56
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/index.js55
-rw-r--r--app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js42
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/events.js73
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/index.js11
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/state.js95
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/utils.js15
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css188
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-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/header.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-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/boards.scss3
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss4
-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.scss29
-rw-r--r--app/assets/stylesheets/pages/reports.scss5
-rw-r--r--app/assets/stylesheets/pages/wiki.scss29
-rw-r--r--app/controllers/autocomplete_controller.rb2
-rw-r--r--app/controllers/concerns/invisible_captcha.rb4
-rw-r--r--app/controllers/concerns/issuable_collections.rb56
-rw-r--r--app/controllers/concerns/issuable_collections_action.rb2
-rw-r--r--app/controllers/concerns/sorting_preference.rb85
-rw-r--r--app/controllers/concerns/toggle_award_emoji.rb19
-rw-r--r--app/controllers/dashboard/projects_controller.rb22
-rw-r--r--app/controllers/explore/projects_controller.rb19
-rw-r--r--app/controllers/jwt_controller.rb1
-rw-r--r--app/controllers/projects/blob_controller.rb6
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/pipelines_controller.rb33
-rw-r--r--app/controllers/projects/starrers_controller.rb4
-rw-r--r--app/controllers/projects/wikis_controller.rb31
-rw-r--r--app/controllers/projects_controller.rb5
-rw-r--r--app/finders/award_emojis_finder.rb55
-rw-r--r--app/finders/awarded_emoji_finder.rb21
-rw-r--r--app/graphql/mutations/award_emojis/add.rb9
-rw-r--r--app/graphql/mutations/award_emojis/remove.rb15
-rw-r--r--app/graphql/mutations/award_emojis/toggle.rb14
-rw-r--r--app/graphql/types/namespace_type.rb5
-rw-r--r--app/graphql/types/root_storage_statistics_type.rb16
-rw-r--r--app/helpers/ci_status_helper.rb3
-rw-r--r--app/helpers/import_helper.rb11
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/helpers/notifications_helper.rb4
-rw-r--r--app/mailers/notify.rb15
-rw-r--r--app/models/analytics/cycle_analytics/project_stage.rb5
-rw-r--r--app/models/award_emoji.rb6
-rw-r--r--app/models/ci/build.rb2
-rw-r--r--app/models/ci/runner.rb28
-rw-r--r--app/models/concerns/analytics/cycle_analytics/stage.rb68
-rw-r--r--app/models/concerns/awardable.rb26
-rw-r--r--app/models/concerns/issuable.rb19
-rw-r--r--app/models/concerns/sortable.rb6
-rw-r--r--app/models/deployment.rb8
-rw-r--r--app/models/issue.rb9
-rw-r--r--app/models/members/group_member.rb2
-rw-r--r--app/models/namespace/root_storage_statistics.rb2
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb12
-rw-r--r--app/models/users_star_project.rb1
-rw-r--r--app/policies/group_policy.rb2
-rw-r--r--app/policies/namespace/root_storage_statistics_policy.rb5
-rw-r--r--app/policies/namespace_policy.rb1
-rw-r--r--app/presenters/blobs/unfold_presenter.rb43
-rw-r--r--app/serializers/deployment_entity.rb4
-rw-r--r--app/serializers/deployment_serializer.rb2
-rw-r--r--app/serializers/issuable_sidebar_basic_entity.rb1
-rw-r--r--app/serializers/merge_request_serializer.rb2
-rw-r--r--app/serializers/merge_request_sidebar_basic_entity.rb9
-rw-r--r--app/serializers/merge_request_widget_entity.rb4
-rw-r--r--app/services/award_emojis/add_service.rb42
-rw-r--r--app/services/award_emojis/base_service.rb32
-rw-r--r--app/services/award_emojis/collect_user_emoji_service.rb23
-rw-r--r--app/services/award_emojis/destroy_service.rb21
-rw-r--r--app/services/award_emojis/toggle_service.rb13
-rw-r--r--app/services/ci/update_build_queue_service.rb4
-rw-r--r--app/services/git/base_hooks_service.rb19
-rw-r--r--app/services/issuable_base_service.rb5
-rw-r--r--app/services/merge_requests/base_service.rb8
-rw-r--r--app/services/self_monitoring/project/create_service.rb219
-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/ci/status/_icon.html.haml3
-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/import/github/new.html.haml34
-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/personal_access_tokens/index.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/deployments/_deployment.html.haml4
-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.haml4
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml2
-rw-r--r--app/views/projects/serverless/functions/index.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.haml8
-rw-r--r--app/views/projects/wikis/edit.html.haml15
-rw-r--r--app/views/projects/wikis/git_access.html.haml12
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml4
-rw-r--r--app/views/projects/wikis/show.html.haml4
-rw-r--r--app/views/shared/boards/_show.html.haml2
-rw-r--r--app/views/shared/boards/components/_board.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml12
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-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/projects/_project.html.haml2
-rw-r--r--app/workers/ci/archive_traces_cron_worker.rb2
-rw-r--r--app/workers/git_garbage_collect_worker.rb2
-rw-r--r--changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml5
-rw-r--r--changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml5
-rw-r--r--changelogs/unreleased/11090-export-design-management-lfs-data.yml5
-rw-r--r--changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml5
-rw-r--r--changelogs/unreleased/19186-redirect-wiki-git-route-to-wiki.yml5
-rw-r--r--changelogs/unreleased/20137-starrers.yml5
-rw-r--r--changelogs/unreleased/21671-multiple-pipeline-status-api.yml5
-rw-r--r--changelogs/unreleased/24705-multi-selection-for-delete-on-registry-page.yml5
-rw-r--r--changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml6
-rw-r--r--changelogs/unreleased/30974-issue-search-by-number.yml5
-rw-r--r--changelogs/unreleased/31434-make-issue-boards-importable.yml5
-rw-r--r--changelogs/unreleased/32032-html-code-shown-in-merge-request.yml5
-rw-r--r--changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml5
-rw-r--r--changelogs/unreleased/34414-update-personal-access-token-scope-descriptions-to-reflect-registry-permissions.yml6
-rw-r--r--changelogs/unreleased/39217-remove-kubernetes-service-integration.yml5
-rw-r--r--changelogs/unreleased/43080-speed-up-deploy-keys.yml5
-rw-r--r--changelogs/unreleased/44036-fix-someone-edited-the-issue-at-the-same-time-false-warning.yml5
-rw-r--r--changelogs/unreleased/46299-wiki-page-creation.yml5
-rw-r--r--changelogs/unreleased/47814-search-view-labels.yml5
-rw-r--r--changelogs/unreleased/48717-rate-limit-raw-controller-show.yml5
-rw-r--r--changelogs/unreleased/49392-exempt-jwt-auth-for-user-gitlab-ci-token-from-rate-limiting.yml5
-rw-r--r--changelogs/unreleased/50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.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/50130-cluster-cluster-details-update-automatically-after-cluster-is-created.yml5
-rw-r--r--changelogs/unreleased/51470-webide-default-commit.yml5
-rw-r--r--changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml5
-rw-r--r--changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml5
-rw-r--r--changelogs/unreleased/55564-remove-if-in-before-after-action.yml5
-rw-r--r--changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml5
-rw-r--r--changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml5
-rw-r--r--changelogs/unreleased/56130-deployed_at.yml5
-rw-r--r--changelogs/unreleased/56130-deployment-date.yml5
-rw-r--r--changelogs/unreleased/56883-migration.yml5
-rw-r--r--changelogs/unreleased/57402-upate-issues-list-sort-options.yml5
-rw-r--r--changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml5
-rw-r--r--changelogs/unreleased/58035-expand-mr-diff.yml5
-rw-r--r--changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml5
-rw-r--r--changelogs/unreleased/59053-no-oauth-for-cicd-github-fe.yml5
-rw-r--r--changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml5
-rw-r--r--changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml5
-rw-r--r--changelogs/unreleased/59590-keyboard-shortcut-for-jump-to-next-unresolved-discussion.yml5
-rw-r--r--changelogs/unreleased/59712-resolve-the-search-problem-issue.yml5
-rw-r--r--changelogs/unreleased/59786-show-renamed-file-in-mr.yml5
-rw-r--r--changelogs/unreleased/59829-fix-style-lint-wiki.yml5
-rw-r--r--changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml5
-rw-r--r--changelogs/unreleased/60516-uninstall-tiller.yml5
-rw-r--r--changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml5
-rw-r--r--changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml5
-rw-r--r--changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml5
-rw-r--r--changelogs/unreleased/60949-display-projectid-on-project-admin-page.yml5
-rw-r--r--changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml5
-rw-r--r--changelogs/unreleased/61332-web-ide-mr-branch-dropdown-closes-unexpectedly.yml5
-rw-r--r--changelogs/unreleased/61335-fix-file-icon-status.yml5
-rw-r--r--changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml6
-rw-r--r--changelogs/unreleased/61776-fixing-the-U2F-warning-message-text-colour.yml5
-rw-r--r--changelogs/unreleased/61787-broadcast-messages-colour-selector-provide-default-options-with-descriptive-labels.yml5
-rw-r--r--changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml5
-rw-r--r--changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml5
-rw-r--r--changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml5
-rw-r--r--changelogs/unreleased/62373-badge-counter-very-low-contrast-between-foreground-and-background-c.yml6
-rw-r--r--changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml5
-rw-r--r--changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml5
-rw-r--r--changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml5
-rw-r--r--changelogs/unreleased/63181-collapsible-line.yml5
-rw-r--r--changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml5
-rw-r--r--changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml5
-rw-r--r--changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml5
-rw-r--r--changelogs/unreleased/63568-access-email-notifications-custom-email.yml5
-rw-r--r--changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml5
-rw-r--r--changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml5
-rw-r--r--changelogs/unreleased/63730-fix-500-status-labels-pd.yml5
-rw-r--r--changelogs/unreleased/63833-fix-jira-issues-url.yml5
-rw-r--r--changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml5
-rw-r--r--changelogs/unreleased/63905-discussion-expand-collapse-button-is-only-clickable-on-one-side.yml5
-rw-r--r--changelogs/unreleased/63942-remove-config-action_dispatch-use_authenticated_cookie_encryption-configuration.yml5
-rw-r--r--changelogs/unreleased/64081-override-helm-release-name.yml5
-rw-r--r--changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml5
-rw-r--r--changelogs/unreleased/64160-fix-duplicate-buttons.yml5
-rw-r--r--changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml5
-rw-r--r--changelogs/unreleased/64190-add-mr-form.yml5
-rw-r--r--changelogs/unreleased/64251-branch-name-set-cache.yml5
-rw-r--r--changelogs/unreleased/64257-active_session_lookup_key_cleanup.yml5
-rw-r--r--changelogs/unreleased/64257-warden_set_user_fix.yml5
-rw-r--r--changelogs/unreleased/64265-center-loading-icon.yml5
-rw-r--r--changelogs/unreleased/64269-pipeline-api-fails-with-401.yml5
-rw-r--r--changelogs/unreleased/64295-predictable-environment-slugs.yml5
-rw-r--r--changelogs/unreleased/64341-user-callout-deferred-link-support.yml5
-rw-r--r--changelogs/unreleased/64383-pattern-matching-with-variables-causes-gitlabs-ci-lint-to-throw-500.yml5
-rw-r--r--changelogs/unreleased/64385-charts-scroll-handle-icon-has-disappeared.yml5
-rw-r--r--changelogs/unreleased/64608-double-tooltips.yml5
-rw-r--r--changelogs/unreleased/64630-add-warning-to-pages-domains-that-obtaining-deploying-ssl-certifica.yml6
-rw-r--r--changelogs/unreleased/64675-Dashboard-URL-legend-border.yml5
-rw-r--r--changelogs/unreleased/64677-delete-directory-webide.yml5
-rw-r--r--changelogs/unreleased/64697-markdown-issues-checkbox-inside-blockquote-status-won-t-be-saved.yml5
-rw-r--r--changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml5
-rw-r--r--changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml5
-rw-r--r--changelogs/unreleased/64746-Commit-authors-avatar-sretched-in-commit-view-if-no-image-is-loaded.yml5
-rw-r--r--changelogs/unreleased/64763-fix-tags-page-layout.yml5
-rw-r--r--changelogs/unreleased/64764-fix-serverless-layout.yml5
-rw-r--r--changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml5
-rw-r--r--changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml5
-rw-r--r--changelogs/unreleased/64972-fix-unicorn-workers-metric.yml5
-rw-r--r--changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml5
-rw-r--r--changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml5
-rw-r--r--changelogs/unreleased/65263-manual-action.yml5
-rw-r--r--changelogs/unreleased/65278-fix-puma-master-counter-wipe.yml5
-rw-r--r--changelogs/unreleased/65412-add-support-for-line-charts.yml5
-rw-r--r--changelogs/unreleased/65427-improve-system-notes-for-zoom-links.yml5
-rw-r--r--changelogs/unreleased/65483-add-a-resend-confirmation-link.yml5
-rw-r--r--changelogs/unreleased/65530-add-externalization-and-fix-regression-in-shortcuts-helper-modal.yml6
-rw-r--r--changelogs/unreleased/65660-update-karma-to-4-2-0.yml5
-rw-r--r--changelogs/unreleased/65671-update-mini_magick-to-4-9-5.yml5
-rw-r--r--changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml5
-rw-r--r--changelogs/unreleased/65705-two-buttons.yml5
-rw-r--r--changelogs/unreleased/65790-highlight.yml5
-rw-r--r--changelogs/unreleased/65803-invalidate-branches-cache-on-refresh.yml5
-rw-r--r--changelogs/unreleased/66008-fix-project-image-in-slack-notifications.yml5
-rw-r--r--changelogs/unreleased/66022-git-clone-url-box-on-wiki-git-access-page-is-broken.yml5
-rw-r--r--changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml5
-rw-r--r--changelogs/unreleased/66037-deployment-user.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/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml5
-rw-r--r--changelogs/unreleased/66161-replace-expand-icons.yml5
-rw-r--r--changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml5
-rw-r--r--changelogs/unreleased/FixLocaleEN.yml5
-rw-r--r--changelogs/unreleased/GL-12412.yml5
-rw-r--r--changelogs/unreleased/GL-12757.yml5
-rw-r--r--changelogs/unreleased/ab-add-index-on-environments.yml5
-rw-r--r--changelogs/unreleased/ab-count-strategies.yml5
-rw-r--r--changelogs/unreleased/ac-graphql-root-namespace-stats.yml5
-rw-r--r--changelogs/unreleased/add-caching-to-archive-endpoint.yml5
-rw-r--r--changelogs/unreleased/add-git-blame-api.yml5
-rw-r--r--changelogs/unreleased/add-outbound-requests-whitelist-for-local-networks.yml5
-rw-r--r--changelogs/unreleased/add-release-to-github-importer.yml5
-rw-r--r--changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml5
-rw-r--r--changelogs/unreleased/add_links_to_latest_pipelines.yml5
-rw-r--r--changelogs/unreleased/adjust-group-level-analytics-to-accept-multiple-ids.yml5
-rw-r--r--changelogs/unreleased/alipniagov-fix-wiki_can_not_be_created_total-counter.yml5
-rw-r--r--changelogs/unreleased/allow-all-users-to-see-history.yml4
-rw-r--r--changelogs/unreleased/an-fix-sidekiq-histogram-buckets.yml5
-rw-r--r--changelogs/unreleased/an-sidekiq-chaos.yml5
-rw-r--r--changelogs/unreleased/an-sidekiq-scheduling_latency.yml5
-rw-r--r--changelogs/unreleased/avoid-race-condition-of-archive-trace-cron-worker.yml5
-rw-r--r--changelogs/unreleased/bjk-64064_cache_metrics.yml5
-rw-r--r--changelogs/unreleased/bjk-usage_ping.yml5
-rw-r--r--changelogs/unreleased/bring-scoped-environment-variables-to-core.yml5
-rw-r--r--changelogs/unreleased/bump_helm_kubectl_gitlab.yml5
-rw-r--r--changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml5
-rw-r--r--changelogs/unreleased/bvl-mr-commit-note-counter.yml5
-rw-r--r--changelogs/unreleased/bvl-remote-mirror-exception-handling.yml6
-rw-r--r--changelogs/unreleased/bw-add-index-for-relative-position.yml5
-rw-r--r--changelogs/unreleased/ce-22058-improve-ux-multi-assignees-in-mr.yml5
-rw-r--r--changelogs/unreleased/ce-xanf-add-links-to-admin-area.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/dblessing-fix-admin-user-radio-labels.yml5
-rw-r--r--changelogs/unreleased/dblessing-fix-public-project-ssh-only-ci-failure.yml5
-rw-r--r--changelogs/unreleased/delete-designs-v2.yml4
-rw-r--r--changelogs/unreleased/dm-process-commit-worker-n-1.yml5
-rw-r--r--changelogs/unreleased/double-slash-64592.yml5
-rw-r--r--changelogs/unreleased/ee-2502-refactor-ee-app-assets-javascripts-approvals-components-approvers_select-vue-to-remove-approverusers.yml5
-rw-r--r--changelogs/unreleased/enable-specific-embeds.yml5
-rw-r--r--changelogs/unreleased/extract_auto_deploy_into_base_image.yml5
-rw-r--r--changelogs/unreleased/fe-delete-old-boardservice.yml6
-rw-r--r--changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml5
-rw-r--r--changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml5
-rw-r--r--changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml5
-rw-r--r--changelogs/unreleased/feat-smime-signed-notification-emails.yml5
-rw-r--r--changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml5
-rw-r--r--changelogs/unreleased/filter-title-description-and-body-from-logs.yml5
-rw-r--r--changelogs/unreleased/fix-alignment-on-security-reports.yml5
-rw-r--r--changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.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/fix-job-log-formatting.yml5
-rw-r--r--changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/fj-avoid-incresaing-usage-ping-when-not-enabled.yml5
-rw-r--r--changelogs/unreleased/fj-count-web-ide-merge-requests.yml5
-rw-r--r--changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-18720-persistent-dashboard-sort.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml6
-rw-r--r--changelogs/unreleased/georgekoltsov-51260-add-filtering-to-bitbucket-server-import.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-55474-outbound-setting-system-hooks.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-63408-user-mapping.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-64311-set-visibility-private-if-internal-restricted.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-64377-add-better-log-msg-to-members-mapper.yml6
-rw-r--r--changelogs/unreleased/gitaly-version-v1.57.0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.59.0.yml5
-rw-r--r--changelogs/unreleased/group-milestones-dashboard-blunceford.yml5
-rw-r--r--changelogs/unreleased/id-mr-widget-etag-caching.yml5
-rw-r--r--changelogs/unreleased/id-source-code-smau.yml5
-rw-r--r--changelogs/unreleased/implement-dag.yml5
-rw-r--r--changelogs/unreleased/improve-quick-action-messages.yml5
-rw-r--r--changelogs/unreleased/issue-61873-no-error-message-for-general-settings.yml5
-rw-r--r--changelogs/unreleased/issue_58494.yml5
-rw-r--r--changelogs/unreleased/je-separate-namespace-fe.yml5
-rw-r--r--changelogs/unreleased/jivanvl-add-chart-empty-state.yml5
-rw-r--r--changelogs/unreleased/jprovazn-fix-positioning.yml5
-rw-r--r--changelogs/unreleased/jprovazn-project-search.yml5
-rw-r--r--changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml5
-rw-r--r--changelogs/unreleased/jupyter-fixes-v1.yml5
-rw-r--r--changelogs/unreleased/khair1-master-patch-79459.yml5
-rw-r--r--changelogs/unreleased/label-descr-push-opts.yml5
-rw-r--r--changelogs/unreleased/labkit-cache-tracing.yml5
-rw-r--r--changelogs/unreleased/latest-pipeline.yml5
-rw-r--r--changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/load-search-counts-async.yml5
-rw-r--r--changelogs/unreleased/maintainers-can-create-subgroup.yml5
-rw-r--r--changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml5
-rw-r--r--changelogs/unreleased/mc-feature-manual-job-variables.yml5
-rw-r--r--changelogs/unreleased/new-cycle-analytics-backend-migrations.yml5
-rw-r--r--changelogs/unreleased/optimise-build-queue-service.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/post-migrate-private-profile.yml5
-rw-r--r--changelogs/unreleased/rails-template-update.yml5
-rw-r--r--changelogs/unreleased/remove-line-profile-from-performance-bar.yml5
-rw-r--r--changelogs/unreleased/remove-margin-from-user-header.yml5
-rw-r--r--changelogs/unreleased/remove-peek-gc.yml5
-rw-r--r--changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml6
-rw-r--r--changelogs/unreleased/report-missing-job-dependency.yml6
-rw-r--r--changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml5
-rw-r--r--changelogs/unreleased/rm-src-branch.yml5
-rw-r--r--changelogs/unreleased/safe-archiving-for-traces.yml5
-rw-r--r--changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml5
-rw-r--r--changelogs/unreleased/security-60551-fix-upload-scope.yml5
-rw-r--r--changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml5
-rw-r--r--changelogs/unreleased/sh-add-index-extern-uid.yml5
-rw-r--r--changelogs/unreleased/sh-add-missing-csp-report-uri.yml5
-rw-r--r--changelogs/unreleased/sh-add-rugged-logs.yml5
-rw-r--r--changelogs/unreleased/sh-add-rugged-to-peek.yml5
-rw-r--r--changelogs/unreleased/sh-break-out-invited-group-members.yml5
-rw-r--r--changelogs/unreleased/sh-disable-redis-peek.yml5
-rw-r--r--changelogs/unreleased/sh-disable-registry-delete.yml5
-rw-r--r--changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml5
-rw-r--r--changelogs/unreleased/sh-enable-bootsnap.yml5
-rw-r--r--changelogs/unreleased/sh-fix-discussions-api-perf.yml5
-rw-r--r--changelogs/unreleased/sh-fix-import-export-suggestions.yml5
-rw-r--r--changelogs/unreleased/sh-fix-special-role-error-500.yml5
-rw-r--r--changelogs/unreleased/sh-ignore-git-errors-delete-project.yml5
-rw-r--r--changelogs/unreleased/sh-make-githost-json.yml5
-rw-r--r--changelogs/unreleased/sh-only-flush-tags-once-per-push.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml5
-rw-r--r--changelogs/unreleased/sh-post-receive-cache-clear-once.yml5
-rw-r--r--changelogs/unreleased/sh-remove-pdfjs-deprecations.yml5
-rw-r--r--changelogs/unreleased/sh-rename-githost-to-gitjson.yml5
-rw-r--r--changelogs/unreleased/sh-support-csp-nonce.yml5
-rw-r--r--changelogs/unreleased/sh-update-mermaid.yml5
-rw-r--r--changelogs/unreleased/sh-update-rouge-3-7-0.yml5
-rw-r--r--changelogs/unreleased/sh-update-rugged-0-28-3.yml5
-rw-r--r--changelogs/unreleased/sh-use-redis-caching-store.yml5
-rw-r--r--changelogs/unreleased/sh-use-shared-state-cluster-pubsub.yml5
-rw-r--r--changelogs/unreleased/snowplow-ee-to-ce.yml5
-rw-r--r--changelogs/unreleased/speed-up-labels-api.yml5
-rw-r--r--changelogs/unreleased/tr-embed-metric-links.yml5
-rw-r--r--changelogs/unreleased/tr-remove-embed-metrics-flag.yml5
-rw-r--r--changelogs/unreleased/uncomment_commit_signatures_feature_flag.yml5
-rw-r--r--changelogs/unreleased/update-babel-to-7-5-5.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-7-0.yml5
-rw-r--r--changelogs/unreleased/update-graphicsmagick-to-1-3-33.yml5
-rw-r--r--changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml5
-rw-r--r--changelogs/unreleased/visual-review-tools-constant-storage-keys.yml5
-rw-r--r--changelogs/unreleased/wiki-usage-pings.yml5
-rw-r--r--changelogs/unreleased/winh-deduplicate-board-headers.yml5
-rw-r--r--config.ru18
-rw-r--r--config/application.rb5
-rw-r--r--config/gitlab.yml.example42
-rw-r--r--config/initializers/0_inject_enterprise_edition_module.rb2
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/7_prometheus_metrics.rb3
-rw-r--r--config/initializers/action_mailer_hooks.rb5
-rw-r--r--config/initializers/rack_attack_logging.rb4
-rw-r--r--config/initializers/sidekiq.rb4
-rw-r--r--config/initializers/tracing.rb4
-rw-r--r--config/initializers/zz_metrics.rb6
-rw-r--r--config/prometheus/common_metrics.yml8
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/project.rb3
-rw-r--r--config/routes/wiki.rb1
-rw-r--r--config/sidekiq_queues.yml1
-rw-r--r--config/smime_signature_settings.rb11
-rw-r--r--config/webpack.config.js7
-rw-r--r--config/webpack.config.review_toolbar.js58
-rw-r--r--danger/changelog/Dangerfile13
-rw-r--r--danger/only_documentation/Dangerfile2
-rw-r--r--db/fixtures/development/14_pipelines.rb75
-rw-r--r--db/fixtures/development/15_award_emoji.rb35
-rw-r--r--db/migrate/20171230123729_init_schema.rb2
-rw-r--r--db/migrate/20180101160629_create_prometheus_metrics.rb2
-rw-r--r--db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb2
-rw-r--r--db/migrate/20180129193323_add_uploads_builder_context.rb2
-rw-r--r--db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb2
-rw-r--r--db/migrate/20180214093516_create_badges.rb2
-rw-r--r--db/migrate/20180214155405_create_clusters_applications_runners.rb2
-rw-r--r--db/migrate/20180216120000_add_pages_domain_verification.rb2
-rw-r--r--db/migrate/20180222043024_add_ip_address_to_runner.rb2
-rw-r--r--db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb2
-rw-r--r--db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb2
-rw-r--r--db/migrate/20180319190020_create_deploy_tokens.rb2
-rw-r--r--db/migrate/20180502122856_create_project_mirror_data.rb2
-rw-r--r--db/migrate/20180503131624_create_remote_mirrors.rb2
-rw-r--r--db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb4
-rw-r--r--db/migrate/20180511131058_create_clusters_applications_jupyter.rb6
-rw-r--r--db/migrate/20180515121227_create_notes_diff_files.rb2
-rw-r--r--db/migrate/20180529093006_ensure_remote_mirror_columns.rb2
-rw-r--r--db/migrate/20180531185349_add_repository_languages.rb2
-rw-r--r--db/migrate/20180613081317_create_ci_builds_runner_session.rb2
-rw-r--r--db/migrate/20180713092803_create_user_statuses.rb2
-rw-r--r--db/migrate/20180814153625_add_commit_email_to_users.rb2
-rw-r--r--db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb2
-rw-r--r--db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb2
-rw-r--r--db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb2
-rw-r--r--db/migrate/20180912111628_add_knative_application.rb2
-rw-r--r--db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb2
-rw-r--r--db/migrate/20181019032400_add_shards_table.rb2
-rw-r--r--db/migrate/20181019032408_add_repositories_table.rb2
-rw-r--r--db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb2
-rw-r--r--db/migrate/20181031190559_drop_gcp_clusters_table.rb2
-rw-r--r--db/migrate/20181101191341_create_clusters_applications_cert_manager.rb2
-rw-r--r--db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb2
-rw-r--r--db/migrate/20181116050532_knative_external_ip.rb2
-rw-r--r--db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb2
-rw-r--r--db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb2
-rw-r--r--db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb2
-rw-r--r--db/migrate/20181122160027_create_project_repositories.rb2
-rw-r--r--db/migrate/20181123144235_create_suggestions.rb2
-rw-r--r--db/migrate/20181128123704_add_state_to_pool_repository.rb2
-rw-r--r--db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb2
-rw-r--r--db/migrate/20181203002526_add_project_bfg_object_map_column.rb2
-rw-r--r--db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb4
-rw-r--r--db/migrate/20181212171634_create_error_tracking_settings.rb2
-rw-r--r--db/migrate/20181228175414_create_releases_link_table.rb2
-rw-r--r--db/migrate/20190109153125_add_merge_request_external_diffs.rb2
-rw-r--r--db/migrate/20190114172110_add_domain_to_cluster.rb2
-rw-r--r--db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb4
-rw-r--r--db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb4
-rw-r--r--db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb4
-rw-r--r--db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb2
-rw-r--r--db/migrate/20190325105715_add_fields_to_user_preferences.rb2
-rw-r--r--db/migrate/20190327163904_add_notification_email_to_notification_settings.rb2
-rw-r--r--db/migrate/20190402150158_backport_enterprise_schema.rb2
-rw-r--r--db/migrate/20190409224933_add_name_to_geo_nodes.rb2
-rw-r--r--db/migrate/20190422082247_create_project_metrics_settings.rb2
-rw-r--r--db/migrate/20190429082448_create_pages_domain_acme_orders.rb2
-rw-r--r--db/migrate/20190430131225_create_issue_tracker_data.rb2
-rw-r--r--db/migrate/20190430142025_create_jira_tracker_data.rb2
-rw-r--r--db/migrate/20190514105711_create_ip_restriction.rb2
-rw-r--r--db/migrate/20190527011309_add_required_template_name_to_application_settings.rb2
-rw-r--r--db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb2
-rw-r--r--db/migrate/20190613044655_add_username_to_deploy_tokens.rb2
-rw-r--r--db/migrate/20190613073003_create_project_aliases.rb2
-rw-r--r--db/migrate/20190621151636_add_merge_request_rebase_jid.rb2
-rw-r--r--db/migrate/20190624123615_add_grafana_url_to_settings.rb2
-rw-r--r--db/migrate/20190711124721_create_job_variables.rb2
-rw-r--r--db/migrate/20190805140353_remove_rendundant_index_from_releases.rb21
-rw-r--r--db/migrate/20190808152507_add_projects_sorting_field_to_user_preferences.rb13
-rw-r--r--db/migrate/20190814205640_import_common_metrics_line_charts.rb13
-rw-r--r--db/post_migrate/20181013005024_remove_koding_from_application_settings.rb2
-rw-r--r--db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb2
-rw-r--r--db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb2
-rw-r--r--db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb16
-rw-r--r--db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb73
-rw-r--r--db/schema.rb2
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/audit_events.md2
-rw-r--r--doc/administration/container_registry.md19
-rw-r--r--doc/administration/custom_hooks.md20
-rw-r--r--doc/administration/dependency_proxy.md2
-rw-r--r--doc/administration/geo/disaster_recovery/index.md2
-rw-r--r--doc/administration/geo/replication/index.md4
-rw-r--r--doc/administration/git_protocol.md2
-rw-r--r--doc/administration/gitaly/index.md114
-rw-r--r--doc/administration/high_availability/load_balancer.md57
-rw-r--r--doc/administration/incoming_email.md4
-rw-r--r--doc/administration/index.md3
-rw-r--r--doc/administration/instance_review.md1
-rw-r--r--doc/administration/issue_closing_pattern.md2
-rw-r--r--doc/administration/job_artifacts.md4
-rw-r--r--doc/administration/logs.md10
-rw-r--r--doc/administration/merge_request_diffs.md2
-rw-r--r--doc/administration/monitoring/gitlab_instance_administration_project/index.md2
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md2
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md143
-rw-r--r--doc/administration/packages.md2
-rw-r--r--doc/administration/plugins.md4
-rw-r--r--doc/administration/reply_by_email_postfix_setup.md4
-rw-r--r--doc/administration/repository_checks.md2
-rw-r--r--doc/administration/repository_storage_types.md6
-rw-r--r--doc/administration/smime_signing_email.md49
-rw-r--r--doc/administration/troubleshooting/kubernetes_cheat_sheet.md46
-rw-r--r--doc/administration/troubleshooting/sidekiq.md118
-rw-r--r--doc/administration/uploads.md6
-rw-r--r--doc/api/README.md11
-rw-r--r--doc/api/api_resources.md2
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/dependencies.md22
-rw-r--r--doc/api/deploy_keys.md1
-rw-r--r--doc/api/discussions.md2
-rw-r--r--doc/api/epics.md8
-rw-r--r--doc/api/features.md4
-rw-r--r--doc/api/geo_nodes.md2
-rw-r--r--doc/api/graphql/reference/index.md12
-rw-r--r--doc/api/group_boards.md2
-rw-r--r--doc/api/issues.md10
-rw-r--r--doc/api/labels.md8
-rw-r--r--doc/api/lint.md4
-rw-r--r--doc/api/merge_request_approvals.md294
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/projects.md5
-rw-r--r--doc/api/protected_branches.md32
-rw-r--r--doc/api/repository_files.md2
-rw-r--r--doc/api/settings.md6
-rw-r--r--doc/api/tags.md4
-rw-r--r--doc/ci/README.md4
-rw-r--r--doc/ci/directed_acyclic_graph/index.md2
-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.md2
-rw-r--r--doc/ci/pipelines.md4
-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.md24
-rw-r--r--doc/customization/libravatar.md4
-rw-r--r--doc/customization/system_header_and_footer_messages.md2
-rw-r--r--doc/development/README.md4
-rw-r--r--doc/development/api_styleguide.md4
-rw-r--r--doc/development/architecture.md22
-rw-r--r--doc/development/automatic_ce_ee_merge.md10
-rw-r--r--doc/development/contributing/issue_workflow.md31
-rw-r--r--doc/development/distributed_tracing.md1
-rw-r--r--doc/development/documentation/feature-change-workflow.md2
-rw-r--r--doc/development/ee_features.md4
-rw-r--r--doc/development/elasticsearch.md43
-rw-r--r--doc/development/emails.md4
-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/file_storage.md2
-rw-r--r--doc/development/hash_indexes.md2
-rw-r--r--doc/development/img/elasticsearch_architecture.svg1
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/development/interacting_components.md4
-rw-r--r--doc/development/lfs.md2
-rw-r--r--doc/development/namespaces_storage_statistics.md178
-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/repository_mirroring.md2
-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/end_to_end/index.md8
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md2
-rw-r--r--doc/development/testing_guide/flaky_tests.md4
-rw-r--r--doc/development/testing_guide/frontend_testing.md515
-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/uploads.md271
-rw-r--r--doc/development/verifying_database_capabilities.md8
-rw-r--r--doc/development/what_requires_downtime.md25
-rw-r--r--doc/gitlab-basics/start-using-git.md21
-rw-r--r--doc/install/azure/index.md2
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/integration/jenkins.md2
-rw-r--r--doc/integration/oauth2_generic.md2
-rw-r--r--doc/migrate_ci_to_ce/README.md2
-rw-r--r--doc/policy/maintenance.md1
-rw-r--r--doc/public_access/public_access.md2
-rw-r--r--doc/raketasks/backup_restore.md1
-rw-r--r--doc/raketasks/cleanup.md6
-rw-r--r--doc/raketasks/import.md6
-rw-r--r--doc/raketasks/web_hooks.md12
-rw-r--r--doc/security/rate_limits.md1
-rw-r--r--doc/topics/autodevops/index.md61
-rw-r--r--doc/topics/autodevops/quick_start_guide.md2
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/update/upgrading_from_ce_to_ee.md2
-rw-r--r--doc/update/upgrading_from_source.md2
-rw-r--r--doc/user/admin_area/abuse_reports.md2
-rw-r--r--doc/user/admin_area/broadcast_messages.md2
-rw-r--r--doc/user/admin_area/diff_limits.md2
-rw-r--r--doc/user/admin_area/monitoring/health_check.md84
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md2
-rw-r--r--doc/user/admin_area/settings/email.md2
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md4
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md2
-rw-r--r--doc/user/admin_area/settings/terms.md2
-rw-r--r--doc/user/admin_area/settings/third_party_offers.md2
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md2
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md2
-rw-r--r--doc/user/admin_area/settings/visibility_and_access_controls.md2
-rw-r--r--doc/user/analytics/cycle_analytics.md182
-rw-r--r--doc/user/analytics/index.md22
-rw-r--r--doc/user/application_security/index.md2
-rw-r--r--doc/user/application_security/license_management/index.md48
-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.md99
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/group/saml_sso/index.md19
-rw-r--r--doc/user/group/saml_sso/scim_setup.md15
-rw-r--r--doc/user/markdown.md21
-rw-r--r--doc/user/permissions.md21
-rw-r--r--doc/user/profile/account/two_factor_authentication.md1
-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/index.md2
-rw-r--r--doc/user/project/integrations/img/download_as_csv.pngbin0 -> 33801 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.md12
-rw-r--r--doc/user/project/issues/design_management.md14
-rw-r--r--doc/user/project/members/img/access_requests_management.pngbin11005 -> 10436 bytes
-rw-r--r--doc/user/project/members/img/request_access_button.pngbin25271 -> 27958 bytes
-rw-r--r--doc/user/project/members/img/withdraw_access_request_button.pngbin26123 -> 28154 bytes
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md10
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit.pngbin14507 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.pngbin0 -> 24542 bytes
-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.md13
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md2
-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/wiki/index.md9
-rw-r--r--doc/workflow/git_annex.md2
-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.md4
-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/award_emoji.rb8
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/helpers/issues_helpers.rb9
-rw-r--r--lib/api/helpers/label_helpers.rb32
-rw-r--r--lib/api/helpers/notes_helpers.rb2
-rw-r--r--lib/api/issues.rb8
-rw-r--r--lib/api/labels.rb8
-rw-r--r--lib/api/notes.rb13
-rw-r--r--lib/api/pipelines.rb3
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/feature/gitaly.rb7
-rw-r--r--lib/gitlab/action_rate_limiter.rb4
-rw-r--r--lib/gitlab/analytics/cycle_analytics/default_stages.rb98
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events.rb71
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb23
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb13
-rw-r--r--lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb28
-rw-r--r--lib/gitlab/auth.rb6
-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/expression/lexeme/matches.rb3
-rw-r--r--lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb3
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb67
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml3
-rw-r--r--lib/gitlab/config/entry/validators.rb24
-rw-r--r--lib/gitlab/daemon.rb5
-rw-r--r--lib/gitlab/data_builder/push.rb2
-rw-r--r--lib/gitlab/database/migration_helpers.rb5
-rw-r--r--lib/gitlab/database_importers/self_monitoring/project/create_service.rb251
-rw-r--r--lib/gitlab/email/hook/smime_signature_interceptor.rb50
-rw-r--r--lib/gitlab/email/smime/certificate.rb36
-rw-r--r--lib/gitlab/email/smime/signer.rb29
-rw-r--r--lib/gitlab/gitaly_client.rb3
-rw-r--r--lib/gitlab/grape_logging/loggers/client_env_logger.rb16
-rw-r--r--lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb23
-rw-r--r--lib/gitlab/profiler.rb2
-rw-r--r--lib/gitlab/quick_actions/substitution_definition.rb3
-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_logging/structured_logger.rb46
-rw-r--r--lib/gitlab/sidekiq_middleware/metrics.rb6
-rw-r--r--lib/gitlab/sidekiq_middleware/monitor.rb20
-rw-r--r--lib/gitlab/sidekiq_monitor.rb182
-rw-r--r--lib/gitlab/usage_data.rb4
-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/assets.rake6
-rw-r--r--locale/gitlab.pot160
-rw-r--r--package.json14
-rw-r--r--qa/Dockerfile12
-rw-r--r--qa/Gemfile1
-rw-r--r--qa/Gemfile.lock13
-rw-r--r--qa/README.md12
-rw-r--r--qa/qa.rb3
-rw-r--r--qa/qa/page/project/import/github.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb5
-rw-r--r--qa/qa/resource/issue.rb6
-rw-r--r--qa/qa/resource/label.rb1
-rw-r--r--qa/qa/runtime/api/request.rb4
-rw-r--r--qa/qa/runtime/fixtures.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.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/repository/user_views_commit_diff_patch_spec.rb2
-rw-r--r--rubocop/cop/migration/add_limit_to_string_columns.rb59
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/config/smime_signature_settings_spec.rb56
-rw-r--r--spec/controllers/concerns/issuable_collections_spec.rb72
-rw-r--r--spec/controllers/concerns/sorting_preference_spec.rb93
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb8
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb95
-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/issues_controller_spec.rb33
-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/notes_controller_spec.rb19
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb83
-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.rb14
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb77
-rw-r--r--spec/controllers/projects_controller_spec.rb96
-rw-r--r--spec/controllers/registrations_controller_spec.rb4
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb6
-rw-r--r--spec/controllers/users_controller_spec.rb6
-rw-r--r--spec/factories/group_members.rb4
-rw-r--r--spec/features/boards/boards_spec.rb14
-rw-r--r--spec/features/dashboard/projects_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb2
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb8
-rw-r--r--spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb2
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb13
-rw-r--r--spec/features/projects/tree/create_file_spec.rb7
-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/finders/award_emojis_finder_spec.rb49
-rw-r--r--spec/fixtures/api/schemas/deployment.json4
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar.json1
-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/api_spec.js22
-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/ide/stores/modules/commit/mutations_spec.js8
-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/monitoring/embed/embed_spec.js8
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js85
-rw-r--r--spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js78
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js189
-rw-r--r--spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js49
-rw-r--r--spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js103
-rw-r--r--spec/frontend/sidebar/user_data_mock.js9
-rw-r--r--spec/frontend/test_setup.js6
-rw-r--r--spec/frontend/tracking_spec.js27
-rw-r--r--spec/frontend/wikis_spec.js74
-rw-r--r--spec/graphql/types/namespace_type_spec.rb2
-rw-r--r--spec/graphql/types/root_storage_statistics_type_spec.rb14
-rw-r--r--spec/helpers/ci_status_helper_spec.rb76
-rw-r--r--spec/initializers/action_mailer_hooks_spec.rb46
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/actions_spec.js188
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js160
-rw-r--r--spec/javascripts/ide/mock_data.js34
-rw-r--r--spec/javascripts/ide/stores/getters_spec.js32
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js177
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js162
-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/lib/utils/common_utils_spec.js39
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js8
-rw-r--r--spec/javascripts/monitoring/charts/time_series_spec.js335
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js (renamed from spec/javascripts/monitoring/dashboard_spec.js)22
-rw-r--r--spec/javascripts/monitoring/mock_data.js4
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js7
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js130
-rw-r--r--spec/javascripts/registry/components/app_spec.js11
-rw-r--r--spec/javascripts/sidebar/assignee_title_spec.js14
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js206
-rw-r--r--spec/javascripts/sidebar/confidential_issue_sidebar_spec.js8
-rw-r--r--spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js8
-rw-r--r--spec/javascripts/sidebar/subscriptions_spec.js9
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js18
-rw-r--r--spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js42
-rw-r--r--spec/javascripts/vue_shared/directives/autofocusonshow_spec.js38
-rw-r--r--spec/lib/api/helpers/label_helpers_spec.rb33
-rw-r--r--spec/lib/gitlab/action_rate_limiter_spec.rb4
-rw-r--r--spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb10
-rw-r--r--spec/lib/gitlab/auth_spec.rb4
-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/expression/lexeme/matches_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb28
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb287
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb114
-rw-r--r--spec/lib/gitlab/daemon_spec.rb30
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb8
-rw-r--r--spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb (renamed from spec/services/self_monitoring/project/create_service_spec.rb)123
-rw-r--r--spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb3
-rw-r--r--spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb52
-rw-r--r--spec/lib/gitlab/email/smime/certificate_spec.rb77
-rw-r--r--spec/lib/gitlab/email/smime/signer_spec.rb26
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb10
-rw-r--r--spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb18
-rw-r--r--spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/loop_helpers_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/manifest_spec.rb2
-rw-r--r--spec/lib/gitlab/manifest_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/markup_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/background_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/delta_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/method_call_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/methods_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/prometheus_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/system_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/web_transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/multipart_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/rails_queue_duration_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/release_env_spec.rb2
-rw-r--r--spec/lib/gitlab/multi_collection_paginator_spec.rb2
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/omniauth_initializer_spec.rb2
-rw-r--r--spec/lib/gitlab/optimistic_locking_spec.rb2
-rw-r--r--spec/lib/gitlab/other_markup_spec.rb2
-rw-r--r--spec/lib/gitlab/otp_key_rotator_spec.rb2
-rw-r--r--spec/lib/gitlab/pages_client_spec.rb2
-rw-r--r--spec/lib/gitlab/path_regex_spec.rb2
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/user_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb2
-rw-r--r--spec/lib/gitlab/plugin_spec.rb2
-rw-r--r--spec/lib/gitlab/polling_interval_spec.rb2
-rw-r--r--spec/lib/gitlab/popen/runner_spec.rb2
-rw-r--r--spec/lib/gitlab/popen_spec.rb2
-rw-r--r--spec/lib/gitlab/profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb2
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/project_template_spec.rb2
-rw-r--r--spec/lib/gitlab/project_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb2
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/query_limiting_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb2
-rw-r--r--spec/lib/gitlab/quick_actions/substitution_definition_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/queues_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/shared_state_spec.rb2
-rw-r--r--spec/lib/gitlab/redis/wrapper_spec.rb2
-rw-r--r--spec/lib/gitlab/reference_counter_spec.rb2
-rw-r--r--spec/lib/gitlab/regex_spec.rb2
-rw-r--r--spec/lib/gitlab/repo_path_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb9
-rw-r--r--spec/lib/gitlab/repository_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb75
-rw-r--r--spec/lib/gitlab/request_context_spec.rb2
-rw-r--r--spec/lib/gitlab/request_forgery_protection_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler/profile_spec.rb2
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/route_map_spec.rb2
-rw-r--r--spec/lib/gitlab/routing_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/exif_spec.rb2
-rw-r--r--spec/lib/gitlab/sanitizers/svg_spec.rb2
-rw-r--r--spec/lib/gitlab/search/found_blob_spec.rb3
-rw-r--r--spec/lib/gitlab/search/query_spec.rb2
-rw-r--r--spec/lib/gitlab/search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/sentry_spec.rb31
-rw-r--r--spec/lib/gitlab/serializer/ci/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/serializer/pagination_spec.rb2
-rw-r--r--spec/lib/gitlab/shard_health_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/shell_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/file_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_profiler_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/line_sample_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/location_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/query_spec.rb2
-rw-r--r--spec/lib/gitlab/sherlock/transaction_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_config_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb39
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb49
-rw-r--r--spec/lib/gitlab/sidekiq_monitor_spec.rb261
-rw-r--r--spec/lib/gitlab/sidekiq_signals_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_status_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning/manager_spec.rb2
-rw-r--r--spec/lib/gitlab/sidekiq_versioning_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/command_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/access_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb2
-rw-r--r--spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb2
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/glob_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/union_spec.rb2
-rw-r--r--spec/lib/gitlab/ssh_public_key_spec.rb2
-rw-r--r--spec/lib/gitlab/string_placeholder_replacer_spec.rb2
-rw-r--r--spec/lib/gitlab/string_range_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/tcp_checker_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/global_template_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/template/finders/repo_template_finders_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitignore_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/issue_template_spec.rb2
-rw-r--r--spec/lib/gitlab/template/merge_request_template_spec.rb2
-rw-r--r--spec/lib/gitlab/themes_spec.rb2
-rw-r--r--spec/lib/gitlab/tree_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb2
-rw-r--r--spec/lib/gitlab/untrusted_regexp_spec.rb2
-rw-r--r--spec/lib/gitlab/uploads_transfer_spec.rb2
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb2
-rw-r--r--spec/lib/gitlab/url_builder_spec.rb2
-rw-r--r--spec/lib/gitlab/url_sanitizer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb24
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb16
-rw-r--r--spec/lib/gitlab/user_access_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/deep_size_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/merge_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb2
-rw-r--r--spec/lib/gitlab/utils/strong_memoize_spec.rb2
-rw-r--r--spec/lib/gitlab/utils_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/job_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb2
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/version_info_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/base_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/delegated_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/view/presenter/simple_spec.rb2
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb2
-rw-r--r--spec/lib/gitlab/wiki_file_finder_spec.rb2
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb2
-rw-r--r--spec/lib/gitlab_spec.rb2
-rw-r--r--spec/lib/google_api/auth_spec.rb2
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb2
-rw-r--r--spec/lib/json_web_token/rsa_token_spec.rb2
-rw-r--r--spec/lib/json_web_token/token_spec.rb2
-rw-r--r--spec/lib/mattermost/client_spec.rb2
-rw-r--r--spec/lib/mattermost/command_spec.rb2
-rw-r--r--spec/lib/mattermost/session_spec.rb2
-rw-r--r--spec/lib/mattermost/team_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/activity_spec.rb2
-rw-r--r--spec/lib/microsoft_teams/notifier_spec.rb2
-rw-r--r--spec/lib/milestone_array_spec.rb2
-rw-r--r--spec/lib/object_storage/direct_upload_spec.rb2
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb2
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb51
-rw-r--r--spec/lib/rspec_flaky/config_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_example_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/flaky_examples_collection_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/listener_spec.rb2
-rw-r--r--spec/lib/rspec_flaky/report_spec.rb2
-rw-r--r--spec/lib/safe_zip/entry_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_params_spec.rb2
-rw-r--r--spec/lib/safe_zip/extract_spec.rb2
-rw-r--r--spec/lib/serializers/json_spec.rb2
-rw-r--r--spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb2
-rw-r--r--spec/lib/system_check/base_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/namespace_check_spec.rb2
-rw-r--r--spec/lib/system_check/orphans/repository_check_spec.rb2
-rw-r--r--spec/lib/system_check/simple_executor_spec.rb2
-rw-r--r--spec/lib/system_check_spec.rb2
-rw-r--r--spec/lib/uploaded_file_spec.rb2
-rw-r--r--spec/mailers/notify_spec.rb67
-rw-r--r--spec/migrations/add_gitlab_instance_administration_project_spec.rb252
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb14
-rw-r--r--spec/models/award_emoji_spec.rb23
-rw-r--r--spec/models/ci/build_spec.rb50
-rw-r--r--spec/models/ci/runner_spec.rb7
-rw-r--r--spec/models/concerns/awardable_spec.rb10
-rw-r--r--spec/models/deployment_spec.rb26
-rw-r--r--spec/models/members/group_member_spec.rb36
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb13
-rw-r--r--spec/models/repository_spec.rb52
-rw-r--r--spec/policies/namespace/root_storage_statistics_policy_spec.rb80
-rw-r--r--spec/policies/namespace_policy_spec.rb2
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb25
-rw-r--r--spec/requests/api/award_emoji_spec.rb16
-rw-r--r--spec/requests/api/discussions_spec.rb54
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/add_spec.rb17
-rw-r--r--spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb17
-rw-r--r--spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb55
-rw-r--r--spec/requests/api/graphql/project/project_statistics_spec.rb2
-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.rb20
-rw-r--r--spec/requests/api/labels_spec.rb336
-rw-r--r--spec/requests/api/notes_spec.rb7
-rw-r--r--spec/requests/api/pipelines_spec.rb11
-rw-r--r--spec/requests/api/projects_spec.rb11
-rw-r--r--spec/requests/jwt_controller_spec.rb8
-rw-r--r--spec/requests/rack_attack_global_spec.rb12
-rw-r--r--spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb268
-rw-r--r--spec/serializers/deployment_entity_spec.rb4
-rw-r--r--spec/serializers/merge_request_sidebar_basic_entity_spec.rb22
-rw-r--r--spec/services/award_emojis/add_service_spec.rb103
-rw-r--r--spec/services/award_emojis/collect_user_emoji_service_spec.rb (renamed from spec/finders/awarded_emoji_finder_spec.rb)2
-rw-r--r--spec/services/award_emojis/destroy_service_spec.rb89
-rw-r--r--spec/services/award_emojis/toggle_service_spec.rb72
-rw-r--r--spec/services/ci/update_build_queue_service_spec.rb110
-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/projects/create_service_spec.rb1
-rw-r--r--spec/services/system_note_service_spec.rb4
-rw-r--r--spec/support/capybara.rb3
-rw-r--r--spec/support/helpers/drag_to_helper.rb19
-rw-r--r--spec/support/helpers/smime_helper.rb55
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb4
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb3
-rw-r--r--spec/support/shared_examples/award_emoji_todo_shared_examples.rb59
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/cycle_analytics_stage_examples.rb74
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb54
-rw-r--r--spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb235
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb66
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb16
-rw-r--r--yarn.lock49
1253 files changed, 15556 insertions, 11904 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/.gitignore b/.gitignore
index cb718a6939f..104c6930050 100644
--- a/.gitignore
+++ b/.gitignore
@@ -75,6 +75,8 @@ eslint-report.html
/.rspec
/plugins/*
/.gitlab_pages_secret
+/.gitlab_smime_key
+/.gitlab_smime_cert
package-lock.json
/junit_*.xml
/coverage-frontend/
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 78ef346d417..56fe9739d5f 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -76,3 +76,15 @@
- apk add --update openssl
- wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/$SCRIPT_NAME
- chmod 755 $(basename $SCRIPT_NAME)
+
+.review-only: &review-only
+ only:
+ refs:
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+ kubernetes: active
+ except:
+ refs:
+ - master
+ - /^\d+-\d+-auto-deploy-\d+$/
+ - /(^docs[\/-].+|.+-docs$)/
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index dcc681294d2..144e5392e55 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -15,13 +15,13 @@
- branches@gitlab-org/gitlab-ce
- branches@gitlab-org/gitlab-ee
-package-and-qa:
- extends: .package-and-qa-base
+package-and-qa-manual:
+ extends:
+ - .package-and-qa-base
+ - .no-docs-and-no-qa
when: manual
- except:
- - /(^qa[\/-].*|.*-qa$)/
-package-and-qa-always:
+package-and-qa:
extends: .package-and-qa-base
allow_failure: true
only:
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index ca55bbd32a7..ccd7d6ec84d 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,14 @@ dependency_scanning:
tags: []
before_script: []
cache: {}
+
+dast:
+ extends:
+ - .dedicated-runner
+ - .review-only
+ 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..beb049c0b3b 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -1,15 +1,3 @@
-.review-only: &review-only
- only:
- refs:
- - branches@gitlab-org/gitlab-ce
- - branches@gitlab-org/gitlab-ee
- kubernetes: active
- except:
- refs:
- - master
- - /^\d+-\d+-auto-deploy-\d+$/
- - /(^docs[\/-].+|.+-docs$)/
-
.review-schedules-only: &review-schedules-only
only:
refs:
@@ -24,8 +12,9 @@
- /(^docs[\/-].+|.+-docs$)/
.review-base: &review-base
- extends: .dedicated-runner
- <<: *review-only
+ extends:
+ - .dedicated-runner
+ - .review-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
cache: {}
dependencies: []
@@ -50,7 +39,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}
@@ -68,7 +57,7 @@ build-qa-image:
- BUILD_TRIGGER_TOKEN=$REVIEW_APPS_BUILD_TRIGGER_TOKEN ./scripts/trigger-build cng
review-build-cng:
- <<: *review-only
+ extends: .review-only
<<: *review-build-cng-base
schedule:review-build-cng:
@@ -117,8 +106,9 @@ schedule:review-deploy:
<<: *review-schedules-only
review-stop:
- <<: *review-only
- extends: .single-script-job-dedicated-runner
+ extends:
+ - .single-script-job-dedicated-runner
+ - .review-only
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: review
when: manual
@@ -181,7 +171,9 @@ review-qa-all:
- gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}" -- --format RspecJunitFormatter --out tmp/rspec-${CI_JOB_ID}.xml --format html --out tmp/rspec.htm --color --format documentation
parallel-spec-reports:
- extends: .dedicated-runner
+ extends:
+ - .dedicated-runner
+ - .no-docs
dependencies:
- review-qa-all
image: ruby:2.6-alpine
diff --git a/.mdlrc.style b/.mdlrc.style
index b315c99e3fc..85bc3aaa99b 100644
--- a/.mdlrc.style
+++ b/.mdlrc.style
@@ -18,7 +18,7 @@ rule "MD025"
rule "MD028"
rule "MD029", :style => :one
rule "MD030"
-rule "MD032"
+# rule "MD032"
rule "MD034"
rule "MD037"
rule "MD038"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 267a1caafec..ffca09a92e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,259 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.2.1
+
+### Fixed (3 changes)
+
+- Fix for embedded metrics undefined params. !31975
+- Fix "ERR value is not an integer or out of range" errors. !32126
+- Prevent duplicated trigger action button.
+
+### Performance (1 change)
+
+- Fix Gitaly N+1 calls with listing issues/MRs via API. !31938
+
+
+## 12.2.0
+
+### Security (4 changes, 1 of them is from the community)
+
+- Update mini_magick to 4.9.5. !31505 (Takuya Noguchi)
+- Upgrade Rugged to 0.28.3. !31794
+- Queries for Upload should be scoped by model.
+- Restrict slash commands to users who can log in.
+
+### Removed (3 changes)
+
+- Remove Kubernetes service integration page. !31365
+- Remove line profiler from performance bar.
+- Remove GC metrics from performance bar.
+
+### Fixed (74 changes, 4 of them are from the community)
+
+- Resolve Incorrect empty state message on Explore projects. !25578
+- Search issuables by iids. !28302 (Riccardo Padovani)
+- Make it easier to find invited group members. !28436
+- fix: updates to include units for the y axis label. !30330
+- Align access permissions for wiki history to those of wiki pages. !30470
+- Add index for issues on relative position, project, and state for manual sorting. !30542
+- Fix suggestion on lines that are not part of an MR. !30606
+- Add empty chart component. !30682
+- Remove blank block from job sidebar. !30754
+- Remove duplicate buttons in diff discussion. !30757
+- Order projects in 'Move issue' dropdown by name. !30778
+- Fix bug in dashboard display of closed milestones. !30820
+- Fixes alignment issues with reports. !30839
+- Ensure visibility icons in group/project listings are grey. !30858
+- Fix admin labels page when there are invalid records. !30885
+- Extra logging for new live trace architecture. !30892
+- Fix pipeline emails not respecting group notification email setting. !30907
+- Handle trailing slashes when generating Jira issue URLs. !30911
+- Optimize relative re-positioning when moving issues. !30938
+- Better support clickable tasklists inside blockquotes. !30952
+- Add space to "merged by" widget. !30972
+- Remove duplicated mapping key in config/locales/en.yml. !30980 (Peter Dave Hello)
+- Update Mermaid to v8.2.3. !30985
+- Use persistent Redis cluster for Workhorse pub/sub notifications. !30990
+- Remove :livesum from RubySampler metrics. !31047
+- Fix pid discovery for Unicorn processes in `PidProvider`. !31056
+- Respect group notification email when sending group access notifications. !31089
+- Default dependency job stage index to Infinity, and correctly report it as undefined in prior stages. !31116
+- Fix incorrect use of message interpolation. !31121
+- Moved labels out of fields on Search page. !31137
+- Ensure Warden triggers after_authentication callback. !31138
+- Fix admin area user access level radio button labels. !31154
+- Ignore Gitaly errors if cache flushing fails on project destruction. !31164
+- Prevent double slash in review apps path. !31212
+- Make pdf.js render CJK characters. !31220
+- Prevent discussion filter from persisting to `Show all activity` when opening links to notes. !31229
+- Improve layout of dropdowns in the metrics dashboard page. !31239
+- Remove pdf.js deprecation warnings. !31253
+- Fix GC::Profiler metrics fetching. !31331
+- Jupyter fixes. !31332 (Amit Rathi)
+- Fix first-time contributor notes not rendering. !31340
+- Fix inline rendering of relative paths to SVGs from the current repository. !31352
+- Make `bin/web_puma` consider RAILS_ENV. !31378
+- Removed extrenal dashboard legend border. !31407
+- Fix visual review app storage keys. !31427
+- Fix flashing conflict warning when editing issues. !31469
+- Fix broken issue links and possible 500 error on cycle analytics page when project name and path are different. !31471
+- Prevent turning plain links into embedded when moving issues. !31489
+- Add a field for released_at to GH importer. !31496
+- Adjust size and align MR-widget loading icon. !31503
+- Fix an issue where clicking outside the MR/branch search box in WebIDE closed the dropdown. !31523
+- Don't attempt to contact registry if it is disabled. !31553
+- Fix IDE new files icon in tree. !31560
+- Fix missing author line (`Created by: <user>`) in MRs/issues/comments of imported Bitbucket Cloud project. !31579
+- Add missing report-uri to CSP config. !31593
+- Fixed display of some sections and externalized all text in the shortcuts modal overlay. !31594
+- Remove extra padding from disabled comment box. !31603
+- Allow CI to clone public projects when HTTP protocol is disabled. !31632
+- error message for general settings. !31636 (Mesut Güneş)
+- Invalidate branches cache on PostReceive. !31653
+- Fix active metric files being wiped after the app starts. !31668
+- Fix :wiki_can_not_be_created_total counter. !31673
+- Fix job logs where style changes were broken down into separate lines. !31674
+- Properly save suggestions in project exports. !31690
+- Fix project avatar image in Slack pipeline notifications. !31788
+- Fix empty error flash message on profile:account page when updating username with username that has already been taken. !31809
+- Fix starrers counts after searching. !31823
+- Fix pipelines not always being created after a push. !31927
+- Fix 500 errors in commits api caused by empty ref_name parameter.
+- Center loading icon in CI action component.
+- Prevents showing 2 tooltips in pipelines table.
+- Fix tag page layout.
+- Prevent duplicated trigger action button.
+- Hides loading spinner in pipelines actions after request has been fullfiled.
+
+### Changed (31 changes, 5 of them are from the community)
+
+- Update cluster page automatically when cluster is created. !27189
+- Add branch/tags/commits dropdown filter on the search page for searching codes. !28282 (minghuan lei)
+- Add support for start_sha to commits API. !29598
+- Maintainers can create subgroups. !29718 (Fabio Papa)
+- Extract Auto DevOps deploy functions into a base image. !30404
+- Add MR form to Visual Review (EE) runtime configuration. !30481
+- Adjust redis cache metrics. !30572
+- Add DS_PIP_DEPENDENCY_PATH option to configure Dependency Scanning for projects using pip. !30762
+- Bring scoped environment variables to core. !30779
+- Add Web IDE Usage Ping for Create SMAU. !30800
+- Update the container scanning CI template to use v12 of the clair scanner. !30809
+- Multiple pipeline support for Commit status. !30828 (Gaetan Semet)
+- Add support for exporting repository type data for LFS objects. !30830
+- Avoid increasing redis counters when usage_ping is disabled. !30949
+- Added navbar searches usage ping counter. !30953
+- Convert githost.log to JSON format. !30967
+- Adjusted the clickable area of collapsed sidebar elements. !30974 (Michel Engelen)
+- Mark push mirrors as failed after 1 hour. !30999
+- Allows masking @ and : characters. !31065
+- Remove incorrect fallback when determining which cluster to use when retrieving MR performance metrics. !31126
+- Retry push mirrors faster when running concurrently, improve error handling when push mirrors fail. !31247
+- Make issue boards importable. !31434 (Jason Colyer)
+- Allow users to resend a confirmation link when the grace period has expired. !31476
+- Remove counts from default labels API responses. !31543
+- Upgrade to Gitaly v1.57.0. !31568
+- Rename githost.log -> git_json.log. !31634
+- Load search result counts asynchronously. !31663
+- feat: adds a download to csv functionality to the dropdown in prometheus metrics. !31679
+- Adjust copy for adding additional members. !31726
+- Upgrade to Gitaly v1.59.0. !31743
+- Filter title, description, and body parameters from logs.
+
+### Performance (17 changes, 1 of them is from the community)
+
+- Add partial index on identities table to speed up LDAP lookups. !26710
+- Improve MembersFinder query performance using UNION. !30451 (Jacopo Beschi @jacopo-beschi)
+- Rake task to cleanup expired ActiveSession lookup keys. !30668
+- Update usage ping cron behavior. !30842
+- Make Bootsnap available via ENABLE_BOOTSNAP=1. !30963
+- Batch processing of commit refs in markdown processing. !31037
+- Use tablesample approximate counting by default. !31048
+- Create index on environments by state. !31231
+- Split MR widget into etag-cached and non-cached serializers. !31354
+- Speed up loading and filtering deploy keys and their projects. !31384
+- Only track Redis calls if Peek is enabled. !31438
+- Only expire tag cache once per push. !31641
+- Reduce Gitaly calls in PostReceive. !31741
+- Eliminate many Gitaly calls in discussions API. !31834
+- Optimize DB indexes for ES indexing of notes. !31846
+- Expire project caches once per push instead of once per ref. !31876
+- Look up upstream commits once before queuing ProcessCommitWorkers.
+
+### Added (51 changes, 11 of them are from the community)
+
+- Make starred projects and starrers of a project publicly visible. !24690
+- Make quick action commands applied banner more useful. !26672 (Jacopo Beschi @jacopo-beschi)
+- Allow Helm to be uninstalled from the UI. !27359
+- Improve pipeline status Slack notifications. !27683
+- Add links to relevant configuration areas in admin area overview. !29306
+- Display project id on project admin page. !29734 (Zsolt Kovari)
+- Display group id on group admin page. !29735 (Zsolt Kovari)
+- Resolve Keyboard shortcut for jump to NEXT unresolved discussion. !30144
+- Personal access tokens are accepted using OAuth2 header format. !30277
+- Add Outbound requests whitelist for local networks. !30350 (Istvan Szalai)
+- Allow multiple Auto DevOps projects to deploy to a single namespace within a k8s cluster. !30360 (James Keogh)
+- Allow Knative to be uninstalled from the UI. !30458
+- Add admin-configurable "Support page URL" link to top Help dropdown menu. !30459 (Diego Louzán)
+- Allow specifying variables when running manual jobs. !30485
+- Use predictable environment slugs. !30551
+- Return an ETag header for the archive endpoint. !30581
+- Add Rate Request Limiter to RawController#show endpoint. !30635
+- Add git blame to GitLab API. !30675 (Oleg Zubchenko)
+- Use separate Kubernetes namespaces per environment. !30711
+- Support remove source branch on merge w/ push options. !30728
+- Deploy serverless apps with gitlabktl. !30740
+- Adjust group level analytics to accept multiple ids. !30744
+- Adds event enum column to DesignsVersions join table. !30745
+- Allow email notifications to be disabled for all members of a group or project. !30755 (Dustin Spicuzza)
+- Export and download CSV from metrics charts. !30760
+- Add API endpoints to return container repositories and tags from the group level. !30817
+- Add support for deferred links in persistent user callouts. !30818
+- Add system notes for when a Zoom call was added/removed from an issue. !30857 (Jacopo Beschi @jacopo-beschi)
+- Count wiki creation, update and delete events. !30864
+- Add new expansion options for merge request diffs. !30927
+- Count snippet creation, update and comment events. !30930
+- Update namespace label for GitLab-managed clusters. !30935
+- UI for disabling group/project email notifications. !30961 (Dustin Spicuzza)
+- Support setting of merge request title and description using git push options. !31068
+- Add new table to store email domain per group. !31071
+- Redirect from a project wiki git route to the project wiki home. !31085
+- Link and embed metrics in GitLab Flavored Markdown. !31106
+- Moves snowplow tracking from ee to ce. !31160 (jejacks0n)
+- Allow Cert-Manager to be uninstalled. !31166
+- Add new outbound network requests application setting for system hooks. !31177
+- Allow links to metrics dashboard at a specific time. !31283
+- Enable embedding of specific metrics charts in GFM. !31304
+- Support creating DAGs in CI config through the `needs` key. !31328
+- Generate shareable link for specific metric charts. !31339
+- Add support for Content-Security-Policy. !31402
+- Add BitBucketServer project import filtering. !31420
+- Embed specific metrics chart in issue. !31644
+- Track page views for cycle analytics show page. !31717
+- Add usage pings for source code pushes. !31734
+- Makes collapsible title clickable in job log.
+- Adds highlight to the collapsible section.
+
+### Other (36 changes, 9 of them are from the community)
+
+- Rewrite `if:` argument in before_action and alike when `only:` is also used. !24412 (George Thomas @thegeorgeous)
+- Create rake tasks for migrating legacy uploads out of deprecated paths. !29409
+- Remove the warning style from the U2F device message in user settings > account. !30119 (matejlatin)
+- Set visibility level 'Private' for restricted 'Internal' imported projects when 'Internal' visibility setting is restricted in admin settings. !30522
+- Change BoardService in favor of boardsStore on board blank state of the component board. !30546 (eduarmreyes)
+- Adds Sidekiq scheduling latency structured logging field. !30784
+- Adds chaos endpoints to Sidekiq. !30814
+- Added multi-select deletion of container registry images. !30837
+- When GitLab import fails during importer user mapping step, add an explicit error message mentioning importer. !30838
+- Add Rugged calls and duration to API and Rails logs. !30871
+- Fixed distorted avatars when resource not reachable. !30904 (Marc Schwede)
+- Update GitLab Runner Helm Chart to 0.7.0. !30950
+- Use Rails 5.2 Redis caching store. !30966
+- Add Rugged calls to performance bar. !30983
+- add color selector to broadcast messages form. !30988
+- Harmonize selections in user settings. !31110 (Marc Schwede)
+- Update rouge to v3.7.0. !31254
+- Update 'Ruby on Rails' project template. !31310
+- Fix mirroring help text. !31348 (jramsay)
+- Enhance style of the shared runners limit. !31386
+- Enables storage statistics for root namespaces on database. !31392
+- Improve quick action error messages. !31451
+- Enable authenticated cookie encryption. !31463
+- Update karma to 4.2.0. !31495 (Takuya Noguchi)
+- Add max_replication_slots to PG HA documentation. !31534
+- Create database tables for the new cycle analytics backend. !31621
+- Updated the detached pipeline badge tooltip text to offer a better explanation. !31626
+- Add Gitaly and Rugged call timing in Sidekiq logs. !31651
+- Fix the style-lint errors and warnings for `app/assets/stylesheets/pages/wiki.scss`. !31656
+- Update GraphicsMagick from 1.3.29 to 1.3.33 for CI tests. !31692 (Takuya Noguchi)
+- Migrate remaining users with null private_profile. !31708
+- Bump Helm to 2.14.3 and kubectl to 1.11.10 for Kubernetes integration. !31716
+- Updated the personal access token api scope description to reflect the permissions it grants. !31759
+- Add finished_at to the internal API Deployment entity. !31808
+- Remove Security Dashboard feature flag. !31820
+- Update Packer.gitlab-ci.yml to use latest image. (Kelly Hair)
+
+
## 12.1.5
### Security (2 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index bb120e876c6..4d5fde5bd16 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.59.0
+1.60.0
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..e5023a6f67d 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
@@ -284,7 +283,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.9.7'
# LabKit: Tracing and Correlation
-gem 'gitlab-labkit', '~> 0.4.2'
+gem 'gitlab-labkit', '~> 0.5'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
@@ -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..d9a32027f3c 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)
@@ -313,12 +311,13 @@ GEM
gitaly (1.58.0)
grpc (~> 1.0)
github-markup (1.7.0)
- gitlab-labkit (0.4.2)
+ gitlab-labkit (0.5.2)
actionpack (~> 5)
activesupport (~> 5)
grpc (~> 1.19)
jaeger-client (~> 0.10)
opentracing (~> 0.4)
+ redis (> 3.0.0, < 5.0.0)
gitlab-markup (1.7.0)
gitlab-sidekiq-fetcher (0.5.1)
sidekiq (~> 5)
@@ -714,7 +713,7 @@ GEM
rails-dom-testing (2.0.3)
activesupport (>= 4.2.0)
nokogiri (>= 1.6)
- rails-html-sanitizer (1.0.4)
+ rails-html-sanitizer (1.2.0)
loofah (~> 2.2, >= 2.2.2)
rails-i18n (5.1.1)
i18n (>= 0.7, < 2)
@@ -1028,7 +1027,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)
@@ -1103,7 +1101,7 @@ DEPENDENCIES
gettext_i18n_rails_js (~> 1.3)
gitaly (~> 1.58.0)
github-markup (~> 1.7.0)
- gitlab-labkit (~> 0.4.2)
+ gitlab-labkit (~> 0.5)
gitlab-markup (~> 1.7.0)
gitlab-sidekiq-fetcher (= 0.5.1)
gitlab-styles (~> 2.7)
@@ -1255,7 +1253,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/VERSION b/VERSION
index 4dd919b3b06..80212c6e1f0 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.2.0-pre
+12.3.0-pre
diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js
index a649c521405..136ffdf8b9d 100644
--- a/app/assets/javascripts/api.js
+++ b/app/assets/javascripts/api.js
@@ -14,6 +14,7 @@ const Api = {
projectPath: '/api/:version/projects/:id',
forkedProjectsPath: '/api/:version/projects/:id/forks',
projectLabelsPath: '/:namespace_path/:project_path/-/labels',
+ projectUsersPath: '/api/:version/projects/:id/users',
projectMergeRequestsPath: '/api/:version/projects/:id/merge_requests',
projectMergeRequestPath: '/api/:version/projects/:id/merge_requests/:mrid',
projectMergeRequestChangesPath: '/api/:version/projects/:id/merge_requests/:mrid/changes',
@@ -108,6 +109,20 @@ const Api = {
});
},
+ projectUsers(projectPath, query = '', options = {}) {
+ const url = Api.buildUrl(this.projectUsersPath).replace(':id', encodeURIComponent(projectPath));
+
+ return axios
+ .get(url, {
+ params: {
+ search: query,
+ per_page: 20,
+ ...options,
+ },
+ })
+ .then(({ data }) => data);
+ },
+
// Return single project
project(projectPath) {
const url = Api.buildUrl(Api.projectPath).replace(':id', encodeURIComponent(projectPath));
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 179148b6887..faf722f61af 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -83,6 +83,7 @@ export default {
}"
:index="index"
:data-issue-id="issue.id"
+ data-qa-selector="board_card"
class="board-card p-3 rounded"
@mousedown="mouseDown"
@mousemove="mouseMove"
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 03a8a92575e..de41698ca04 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -227,6 +227,7 @@ export default {
<div
:class="{ 'd-none': !list.isExpanded, 'd-flex flex-column': list.isExpanded }"
class="board-list-component position-relative h-100"
+ data-qa-selector="board_list_cards_area"
>
<div v-if="loading" class="board-list-loading text-center" :aria-label="__('Loading issues')">
<gl-loading-icon />
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js
index 2ace0060c42..ba1fe9202fc 100644
--- a/app/assets/javascripts/boards/components/board_sidebar.js
+++ b/app/assets/javascripts/boards/components/board_sidebar.js
@@ -22,6 +22,8 @@ export default Vue.extend({
components: {
AssigneeTitle,
Assignees,
+ SidebarEpicsSelect: () =>
+ import('ee_component/sidebar/components/sidebar_item_epics_select.vue'),
RemoveBtn,
Subscriptions,
TimeTracker,
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
index b05de4538f2..7296426549a 100644
--- a/app/assets/javascripts/boards/components/boards_selector.vue
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -226,6 +226,7 @@ export default {
<div class="boards-switcher js-boards-selector append-right-10">
<span class="boards-selector-wrapper js-boards-selector-wrapper">
<gl-dropdown
+ data-qa-selector="boards_dropdown"
toggle-class="dropdown-menu-toggle js-dropdown-toggle"
menu-class="flex-column dropdown-extended-height"
:text="board.name"
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/event_tracking/issue_sidebar.js b/app/assets/javascripts/event_tracking/issue_sidebar.js
new file mode 100644
index 00000000000..6909f82c66f
--- /dev/null
+++ b/app/assets/javascripts/event_tracking/issue_sidebar.js
@@ -0,0 +1,2 @@
+export const initSidebarTracking = () => {};
+export const trackEvent = () => {};
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
index 685d8a6b245..8b356ee6e97 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/actions.vue
@@ -41,10 +41,16 @@ export default {
methods: {
...mapCommitActions(['updateCommitAction']),
updateSelectedCommitAction() {
- if (this.currentBranch && !this.currentBranch.can_push) {
- this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
- } else if (this.containsStagedChanges) {
+ if (!this.currentBranch) {
+ return;
+ }
+
+ const { can_push: canPush = false, default: isDefault = false } = this.currentBranch;
+
+ if (canPush && !isDefault) {
this.updateCommitAction(consts.COMMIT_TO_CURRENT_BRANCH);
+ } else {
+ this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH);
}
},
},
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
index b2e7b15089c..daa44a42765 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -1,43 +1,36 @@
<script>
-import { mapGetters, createNamespacedHelpers } from 'vuex';
+import { createNamespacedHelpers } from 'vuex';
const {
mapState: mapCommitState,
- mapGetters: mapCommitGetters,
mapActions: mapCommitActions,
+ mapGetters: mapCommitGetters,
} = createNamespacedHelpers('commit');
export default {
computed: {
...mapCommitState(['shouldCreateMR']),
- ...mapCommitGetters(['isCommittingToCurrentBranch', 'isCommittingToDefaultBranch']),
- ...mapGetters(['hasMergeRequest', 'isOnDefaultBranch']),
- currentBranchHasMr() {
- return this.hasMergeRequest && this.isCommittingToCurrentBranch;
- },
- showNewMrOption() {
- return (
- this.isCommittingToDefaultBranch || !this.currentBranchHasMr || this.isCommittingToNewBranch
- );
- },
- },
- mounted() {
- this.setShouldCreateMR();
+ ...mapCommitGetters(['shouldHideNewMrOption']),
},
methods: {
- ...mapCommitActions(['toggleShouldCreateMR', 'setShouldCreateMR']),
+ ...mapCommitActions(['toggleShouldCreateMR']),
},
};
</script>
<template>
- <div v-if="showNewMrOption">
+ <fieldset v-if="!shouldHideNewMrOption">
<hr class="my-2" />
- <label class="mb-0">
- <input :checked="shouldCreateMR" type="checkbox" @change="toggleShouldCreateMR" />
+ <label class="mb-0 js-ide-commit-new-mr">
+ <input
+ :checked="shouldCreateMR"
+ type="checkbox"
+ data-qa-selector="start_new_mr_checkbox"
+ @change="toggleShouldCreateMR"
+ />
<span class="prepend-left-10">
{{ __('Start a new merge request') }}
</span>
</label>
- </div>
+ </fieldset>
</template>
diff --git a/app/assets/javascripts/ide/stores/getters.js b/app/assets/javascripts/ide/stores/getters.js
index 406903129db..85fd45358be 100644
--- a/app/assets/javascripts/ide/stores/getters.js
+++ b/app/assets/javascripts/ide/stores/getters.js
@@ -104,5 +104,8 @@ export const packageJson = state => state.entries[packageJsonPath];
export const isOnDefaultBranch = (_state, getters) =>
getters.currentProject && getters.currentProject.default_branch === getters.branchName;
+export const canPushToBranch = (_state, getters) =>
+ getters.currentBranch && getters.currentBranch.can_push;
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/actions.js b/app/assets/javascripts/ide/stores/modules/commit/actions.js
index ac34491c1ad..23caf2d48ed 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/actions.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/actions.js
@@ -18,34 +18,15 @@ export const discardDraft = ({ commit }) => {
commit(types.UPDATE_COMMIT_MESSAGE, '');
};
-export const updateCommitAction = ({ commit, dispatch }, commitAction) => {
+export const updateCommitAction = ({ commit, getters }, commitAction) => {
commit(types.UPDATE_COMMIT_ACTION, {
commitAction,
});
- dispatch('setShouldCreateMR');
+ commit(types.TOGGLE_SHOULD_CREATE_MR, !getters.shouldHideNewMrOption);
};
export const toggleShouldCreateMR = ({ commit }) => {
commit(types.TOGGLE_SHOULD_CREATE_MR);
- commit(types.INTERACT_WITH_NEW_MR);
-};
-
-export const setShouldCreateMR = ({
- commit,
- getters,
- rootGetters,
- state: { interactedWithNewMR },
-}) => {
- const committingToExistingMR =
- getters.isCommittingToCurrentBranch &&
- rootGetters.hasMergeRequest &&
- !rootGetters.isOnDefaultBranch;
-
- if ((getters.isCommittingToDefaultBranch && !interactedWithNewMR) || committingToExistingMR) {
- commit(types.TOGGLE_SHOULD_CREATE_MR, false);
- } else if (!interactedWithNewMR) {
- commit(types.TOGGLE_SHOULD_CREATE_MR, true);
- }
};
export const updateBranchName = ({ commit }, branchName) => {
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 64779e9e4df..de289e27199 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -20,7 +20,7 @@ export const placeholderBranchName = (state, _, rootState) =>
)}`;
export const branchName = (state, getters, rootState) => {
- if (state.commitAction === consts.COMMIT_TO_NEW_BRANCH) {
+ if (getters.isCreatingNewBranch) {
if (state.newBranchName === '') {
return getters.placeholderBranchName;
}
@@ -48,11 +48,11 @@ export const preBuiltCommitMessage = (state, _, rootState) => {
export const isCreatingNewBranch = state => state.commitAction === consts.COMMIT_TO_NEW_BRANCH;
-export const isCommittingToCurrentBranch = state =>
- state.commitAction === consts.COMMIT_TO_CURRENT_BRANCH;
-
-export const isCommittingToDefaultBranch = (_state, getters, _rootState, rootGetters) =>
- getters.isCommittingToCurrentBranch && rootGetters.isOnDefaultBranch;
+export const shouldHideNewMrOption = (_state, getters, _rootState, rootGetters) =>
+ !getters.isCreatingNewBranch &&
+ (rootGetters.hasMergeRequest ||
+ (!rootGetters.hasMergeRequest && rootGetters.isOnDefaultBranch)) &&
+ rootGetters.canPushToBranch;
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
index b81918156b0..7ad8f3570b7 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutation_types.js
@@ -3,4 +3,3 @@ export const UPDATE_COMMIT_ACTION = 'UPDATE_COMMIT_ACTION';
export const UPDATE_NEW_BRANCH_NAME = 'UPDATE_NEW_BRANCH_NAME';
export const UPDATE_LOADING = 'UPDATE_LOADING';
export const TOGGLE_SHOULD_CREATE_MR = 'TOGGLE_SHOULD_CREATE_MR';
-export const INTERACT_WITH_NEW_MR = 'INTERACT_WITH_NEW_MR';
diff --git a/app/assets/javascripts/ide/stores/modules/commit/mutations.js b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
index 14957d283bb..73b618e250f 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/mutations.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/mutations.js
@@ -24,7 +24,4 @@ export default {
shouldCreateMR: shouldCreateMR === undefined ? !state.shouldCreateMR : shouldCreateMR,
});
},
- [types.INTERACT_WITH_NEW_MR](state) {
- Object.assign(state, { interactedWithNewMR: true });
- },
};
diff --git a/app/assets/javascripts/ide/stores/modules/commit/state.js b/app/assets/javascripts/ide/stores/modules/commit/state.js
index 53647a7e3e3..259577e48e0 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/state.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/state.js
@@ -3,6 +3,5 @@ export default () => ({
commitAction: '1',
newBranchName: '',
submitCommitLoading: false,
- shouldCreateMR: false,
- interactedWithNewMR: false,
+ shouldCreateMR: true,
});
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/issue_show/index.js b/app/assets/javascripts/issue_show/index.js
index 529b6386221..5a9dd91817e 100644
--- a/app/assets/javascripts/issue_show/index.js
+++ b/app/assets/javascripts/issue_show/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import { initSidebarTracking } from 'ee_else_ce/event_tracking/issue_sidebar';
import issuableApp from './components/app.vue';
import { parseIssuableData } from './utils/parse_data';
import '../vue_shared/vue_resource_interceptor';
@@ -9,6 +10,9 @@ export default function initIssueableApp() {
components: {
issuableApp,
},
+ mounted() {
+ initSidebarTracking();
+ },
render(createElement) {
return createElement('issuable-app', {
props: parseIssuableData(),
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 31c4a920bbe..6e8f63a10a4 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -732,6 +732,66 @@ export const NavigationType = {
};
/**
+ * Method to perform case-insensitive search for a string
+ * within multiple properties and return object containing
+ * properties in case there are multiple matches or `null`
+ * if there's no match.
+ *
+ * Eg; Suppose we want to allow user to search using for a string
+ * within `iid`, `title`, `url` or `reference` props of a target object;
+ *
+ * const objectToSearch = {
+ * "iid": 1,
+ * "title": "Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate. &3",
+ * "url": "/groups/gitlab-org/-/epics/1",
+ * "reference": "&1",
+ * };
+ *
+ * Following is how we call searchBy and the return values it will yield;
+ *
+ * - `searchBy('omnis', objectToSearch);`: This will return `{ title: ... }` as our
+ * query was found within title prop we only return that.
+ * - `searchBy('1', objectToSearch);`: This will return `{ "iid": ..., "reference": ..., "url": ... }`.
+ * - `searchBy('https://gitlab.com/groups/gitlab-org/-/epics/1', objectToSearch);`:
+ * This will return `{ "url": ... }`.
+ * - `searchBy('foo', objectToSearch);`: This will return `null` as no property value
+ * matched with our query.
+ *
+ * You can learn more about behaviour of this method by referring to tests
+ * within `spec/javascripts/lib/utils/common_utils_spec.js`.
+ *
+ * @param {string} query String to search for
+ * @param {object} searchSpace Object containing properties to search in for `query`
+ */
+export const searchBy = (query = '', searchSpace = {}) => {
+ const targetKeys = searchSpace !== null ? Object.keys(searchSpace) : [];
+
+ if (!query || !targetKeys.length) {
+ return null;
+ }
+
+ const normalizedQuery = query.toLowerCase();
+ const matches = targetKeys
+ .filter(item => {
+ const searchItem = `${searchSpace[item]}`.toLowerCase();
+
+ return (
+ searchItem.indexOf(normalizedQuery) > -1 ||
+ normalizedQuery.indexOf(searchItem) > -1 ||
+ normalizedQuery === searchItem
+ );
+ })
+ .reduce((acc, prop) => {
+ const match = acc;
+ match[prop] = searchSpace[prop];
+
+ return acc;
+ }, {});
+
+ return Object.keys(matches).length ? matches : null;
+};
+
+/**
* Checks if the given Label has a special syntax `::` in
* it's title.
*
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/main.js b/app/assets/javascripts/main.js
index ba33d72b1f3..39f2097c174 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -9,7 +9,11 @@ import './commons';
import './behaviors';
// lib/utils
-import { handleLocationHash, addSelectOnFocusBehaviour } from './lib/utils/common_utils';
+import {
+ handleLocationHash,
+ addSelectOnFocusBehaviour,
+ getCspNonceValue,
+} from './lib/utils/common_utils';
import { localTimeAgo } from './lib/utils/datetime_utility';
import { getLocationHash, visitUrl } from './lib/utils/url_utility';
@@ -39,6 +43,17 @@ import 'ee_else_ce/main_ee';
window.jQuery = jQuery;
window.$ = jQuery;
+// Add nonce to jQuery script handler
+jQuery.ajaxSetup({
+ converters: {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings, func-names
+ 'text script': function(text) {
+ jQuery.globalEval(text, { nonce: getCspNonceValue() });
+ return text;
+ },
+ },
+});
+
// inject test utilities if necessary
if (process.env.NODE_ENV !== 'production' && gon && gon.test_env) {
$.fx.off = true;
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 90c764587a3..cac10474d06 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -12,6 +12,9 @@ import { graphDataValidatorForValues } from '../../utils';
let debouncedResize;
+// TODO: Remove this component in favor of the more general time_series.vue
+// Please port all changes here to time_series.vue as well.
+
export default {
components: {
GlAreaChart,
@@ -123,7 +126,7 @@ export default {
},
},
series: this.scatterSeries,
- dataZoom: this.dataZoomConfig,
+ dataZoom: [this.dataZoomConfig],
};
},
dataZoomConfig() {
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
new file mode 100644
index 00000000000..02e7a7ba0a6
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -0,0 +1,342 @@
+<script>
+import { __ } from '~/locale';
+import { mapState } from 'vuex';
+import { GlLink, GlButton } from '@gitlab/ui';
+import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import dateFormat from 'dateformat';
+import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
+import { getSvgIconPathContent } from '~/lib/utils/icon_utils';
+import Icon from '~/vue_shared/components/icon.vue';
+import { chartHeight, graphTypes, lineTypes, symbolSizes, dateFormats } from '../../constants';
+import { makeDataSeries } from '~/helpers/monitor_helper';
+import { graphDataValidatorForValues } from '../../utils';
+
+let debouncedResize;
+
+export default {
+ components: {
+ GlAreaChart,
+ GlLineChart,
+ GlButton,
+ GlChartSeriesLabel,
+ GlLink,
+ Icon,
+ },
+ inheritAttrs: false,
+ props: {
+ graphData: {
+ type: Object,
+ required: true,
+ validator: graphDataValidatorForValues.bind(null, false),
+ },
+ containerWidth: {
+ type: Number,
+ required: true,
+ },
+ deploymentData: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ projectPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showBorder: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ singleEmbed: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ thresholds: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ },
+ data() {
+ return {
+ tooltip: {
+ title: '',
+ content: [],
+ commitUrl: '',
+ isDeployment: false,
+ sha: '',
+ },
+ width: 0,
+ height: chartHeight,
+ svgs: {},
+ primaryColor: null,
+ };
+ },
+ computed: {
+ ...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
+ chartData() {
+ // Transforms & supplements query data to render appropriate labels & styles
+ // Input: [{ queryAttributes1 }, { queryAttributes2 }]
+ // Output: [{ seriesAttributes1 }, { seriesAttributes2 }]
+ return this.graphData.queries.reduce((acc, query) => {
+ const { appearance } = query;
+ const lineType =
+ appearance && appearance.line && appearance.line.type
+ ? appearance.line.type
+ : lineTypes.default;
+ const lineWidth =
+ appearance && appearance.line && appearance.line.width
+ ? appearance.line.width
+ : undefined;
+ const areaStyle = {
+ opacity:
+ appearance && appearance.area && typeof appearance.area.opacity === 'number'
+ ? appearance.area.opacity
+ : undefined,
+ };
+
+ const series = makeDataSeries(query.result, {
+ name: this.formatLegendLabel(query),
+ lineStyle: {
+ type: lineType,
+ width: lineWidth,
+ },
+ showSymbol: false,
+ areaStyle: this.graphData.type === 'area-chart' ? areaStyle : undefined,
+ });
+
+ return acc.concat(series);
+ }, []);
+ },
+ chartOptions() {
+ return {
+ xAxis: {
+ name: __('Time'),
+ type: 'time',
+ axisLabel: {
+ formatter: date => dateFormat(date, dateFormats.timeOfDay),
+ },
+ axisPointer: {
+ snap: true,
+ },
+ },
+ yAxis: {
+ name: this.yAxisLabel,
+ axisLabel: {
+ formatter: num => roundOffFloat(num, 3).toString(),
+ },
+ },
+ series: this.scatterSeries,
+ dataZoom: this.dataZoomConfig,
+ };
+ },
+ dataZoomConfig() {
+ const handleIcon = this.svgs['scroll-handle'];
+
+ return handleIcon ? { handleIcon } : {};
+ },
+ earliestDatapoint() {
+ return this.chartData.reduce((acc, series) => {
+ const { data } = series;
+ const { length } = data;
+ if (!length) {
+ return acc;
+ }
+
+ const [first] = data[0];
+ const [last] = data[length - 1];
+ const seriesEarliest = first < last ? first : last;
+
+ return seriesEarliest < acc || acc === null ? seriesEarliest : acc;
+ }, null);
+ },
+ glChartComponent() {
+ const chartTypes = {
+ 'area-chart': GlAreaChart,
+ 'line-chart': GlLineChart,
+ };
+ return chartTypes[this.graphData.type] || GlAreaChart;
+ },
+ isMultiSeries() {
+ return this.tooltip.content.length > 1;
+ },
+ recentDeployments() {
+ return this.deploymentData.reduce((acc, deployment) => {
+ if (deployment.created_at >= this.earliestDatapoint) {
+ const { id, created_at, sha, ref, tag } = deployment;
+ acc.push({
+ id,
+ createdAt: created_at,
+ sha,
+ commitUrl: `${this.projectPath}/commit/${sha}`,
+ tag,
+ tagUrl: tag ? `${this.tagsPath}/${ref.name}` : null,
+ ref: ref.name,
+ showDeploymentFlag: false,
+ });
+ }
+
+ return acc;
+ }, []);
+ },
+ scatterSeries() {
+ return {
+ type: graphTypes.deploymentData,
+ data: this.recentDeployments.map(deployment => [deployment.createdAt, 0]),
+ symbol: this.svgs.rocket,
+ symbolSize: symbolSizes.default,
+ itemStyle: {
+ color: this.primaryColor,
+ },
+ };
+ },
+ yAxisLabel() {
+ return `${this.graphData.y_label}`;
+ },
+ csvText() {
+ const chartData = this.chartData[0].data;
+ const header = `timestamp,${this.graphData.y_label}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+ return chartData.reduce((csv, data) => {
+ const row = data.join(',');
+ return `${csv}${row}\r\n`;
+ }, header);
+ },
+ downloadLink() {
+ const data = new Blob([this.csvText], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
+ },
+ watch: {
+ containerWidth: 'onResize',
+ },
+ beforeDestroy() {
+ window.removeEventListener('resize', debouncedResize);
+ },
+ created() {
+ debouncedResize = debounceByAnimationFrame(this.onResize);
+ window.addEventListener('resize', debouncedResize);
+ this.setSvg('rocket');
+ this.setSvg('scroll-handle');
+ },
+ methods: {
+ formatLegendLabel(query) {
+ return `${query.label}`;
+ },
+ formatTooltipText(params) {
+ this.tooltip.title = dateFormat(params.value, dateFormats.default);
+ this.tooltip.content = [];
+ params.seriesData.forEach(dataPoint => {
+ const [xVal, yVal] = dataPoint.value;
+ this.tooltip.isDeployment = dataPoint.componentSubType === graphTypes.deploymentData;
+ if (this.tooltip.isDeployment) {
+ const [deploy] = this.recentDeployments.filter(
+ deployment => deployment.createdAt === xVal,
+ );
+ this.tooltip.sha = deploy.sha.substring(0, 8);
+ this.tooltip.commitUrl = deploy.commitUrl;
+ } else {
+ const { seriesName, color } = dataPoint;
+ const value = yVal.toFixed(3);
+ this.tooltip.content.push({
+ name: seriesName,
+ value,
+ color,
+ });
+ }
+ });
+ },
+ setSvg(name) {
+ getSvgIconPathContent(name)
+ .then(path => {
+ if (path) {
+ this.$set(this.svgs, name, `path://${path}`);
+ }
+ })
+ .catch(e => {
+ // eslint-disable-next-line no-console, @gitlab/i18n/no-non-i18n-strings
+ console.error('SVG could not be rendered correctly: ', e);
+ });
+ },
+ onChartUpdated(chart) {
+ [this.primaryColor] = chart.getOption().color;
+ },
+ onResize() {
+ if (!this.$refs.chart) return;
+ const { width } = this.$refs.chart.$el.getBoundingClientRect();
+ this.width = width;
+ },
+ },
+};
+</script>
+
+<template>
+ <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 class="prometheus-graph-title js-graph-title">{{ graphData.title }}</h5>
+ <gl-button
+ v-if="exportMetricsToCsvEnabled"
+ :href="downloadLink"
+ :title="__('Download CSV')"
+ :aria-label="__('Download CSV')"
+ style="margin-left: 200px;"
+ download="chart_metrics.csv"
+ >
+ {{ __('Download CSV') }}
+ </gl-button>
+ <div class="prometheus-graph-widgets js-graph-widgets">
+ <slot></slot>
+ </div>
+ </div>
+
+ <component
+ :is="glChartComponent"
+ ref="chart"
+ v-bind="$attrs"
+ :data="chartData"
+ :option="chartOptions"
+ :format-tooltip-text="formatTooltipText"
+ :thresholds="thresholds"
+ :width="width"
+ :height="height"
+ @updated="onChartUpdated"
+ >
+ <template v-if="tooltip.isDeployment">
+ <template slot="tooltipTitle">
+ {{ __('Deployed') }}
+ </template>
+ <div slot="tooltipContent" class="d-flex align-items-center">
+ <icon name="commit" class="mr-2" />
+ <gl-link :href="tooltip.commitUrl">{{ tooltip.sha }}</gl-link>
+ </div>
+ </template>
+ <template v-else>
+ <template slot="tooltipTitle">
+ <div class="text-nowrap">
+ {{ tooltip.title }}
+ </div>
+ </template>
+ <template slot="tooltipContent">
+ <div
+ v-for="(content, key) in tooltip.content"
+ :key="key"
+ class="d-flex justify-content-between"
+ >
+ <gl-chart-series-label :color="isMultiSeries ? content.color : ''">
+ {{ content.name }}
+ </gl-chart-series-label>
+ <div class="prepend-left-32">
+ {{ content.value }}
+ </div>
+ </div>
+ </template>
+ </template>
+ </component>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index dfeeba238ca..d330ceb836c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -14,9 +14,9 @@ import { __, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
-import MonitorAreaChart from './charts/area.vue';
+import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import MonitorTimeSeriesChart from './charts/time_series.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';
@@ -26,7 +26,7 @@ let sidebarMutationObserver;
export default {
components: {
- MonitorAreaChart,
+ MonitorTimeSeriesChart,
MonitorSingleStatChart,
PanelType,
GraphGroup,
@@ -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 {
@@ -264,12 +274,12 @@ export default {
showToast() {
this.$toast.show(__('Link copied to clipboard'));
},
+ // TODO: END
generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path;
- const params = { dashboard, group, title, y_label: yLabel };
+ const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
return mergeUrlParams(params, window.location.href);
},
- // TODO: END
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -449,11 +459,13 @@ export default {
: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>
<template v-else>
- <monitor-area-chart
+ <monitor-time-series-chart
v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
:key="graphIndex"
:graph-data="graphData"
@@ -461,7 +473,7 @@ export default {
:thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth"
:project-path="projectPath"
- group-id="monitor-area-chart"
+ group-id="monitor-time-series-chart"
>
<div class="d-flex align-items-center">
<alert-widget
@@ -503,7 +515,7 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
</div>
- </monitor-area-chart>
+ </monitor-time-series-chart>
</template>
</graph-group>
</div>
diff --git a/app/assets/javascripts/monitoring/components/embed.vue b/app/assets/javascripts/monitoring/components/embed.vue
index e3256147618..b516a82c170 100644
--- a/app/assets/javascripts/monitoring/components/embed.vue
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -2,7 +2,7 @@
import { mapActions, mapState } from 'vuex';
import { getParameterValues, removeParams } from '~/lib/utils/url_utility';
import GraphGroup from './graph_group.vue';
-import MonitorAreaChart from './charts/area.vue';
+import MonitorTimeSeriesChart from './charts/time_series.vue';
import { sidebarAnimationDuration } from '../constants';
import { getTimeDiff } from '../utils';
@@ -11,7 +11,7 @@ let sidebarMutationObserver;
export default {
components: {
GraphGroup,
- MonitorAreaChart,
+ MonitorTimeSeriesChart,
},
props: {
dashboardUrl: {
@@ -92,7 +92,7 @@ export default {
<template>
<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
+ <monitor-time-series-chart
v-for="graphData in charts"
:key="graphData.title"
:graph-data="graphData"
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 96f62bc85ee..73ff651d510 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -10,14 +10,14 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
-import MonitorAreaChart from './charts/area.vue';
+import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
export default {
components: {
- MonitorAreaChart,
MonitorSingleStatChart,
+ MonitorTimeSeriesChart,
MonitorEmptyChart,
Icon,
GlDropdown,
@@ -92,7 +92,7 @@ export default {
v-if="isPanelType('single-stat') && graphDataHasMetrics"
:graph-data="graphData"
/>
- <monitor-area-chart
+ <monitor-time-series-chart
v-else-if="graphDataHasMetrics"
:graph-data="graphData"
:deployment-data="deploymentData"
@@ -136,6 +136,6 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
</div>
- </monitor-area-chart>
+ </monitor-time-series-chart>
<monitor-empty-chart v-else :graph-title="graphData.title" />
</template>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index d7d89522732..13aba3d9f44 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -8,6 +8,10 @@ export const graphTypes = {
deploymentData: 'scatter',
};
+export const symbolSizes = {
+ default: 14,
+};
+
export const lineTypes = {
default: 'solid',
};
@@ -21,6 +25,11 @@ export const timeWindows = {
oneWeek: __('1 week'),
};
+export const dateFormats = {
+ timeOfDay: 'h:MM TT',
+ default: 'dd mmm yyyy, h:MMTT',
+};
+
export const secondsIn = {
thirtyMinutes: 60 * 30,
threeHours: 60 * 60 * 3,
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 52410f18d4a..3d0ec8cd3a7 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -171,26 +171,33 @@ export const isLastUnresolvedDiscussion = (state, getters) => (discussionId, dif
return lastDiscussionId === discussionId;
};
-// Gets the ID of the discussion following the one provided, respecting order (diff or date)
-// @param {Boolean} discussionId - id of the current discussion
-// @param {Boolean} diffOrder - is ordered by diff?
-export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
- const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
- const currentIndex = idsOrdered.indexOf(discussionId);
- const slicedIds = idsOrdered.slice(currentIndex + 1, currentIndex + 2);
+export const findUnresolvedDiscussionIdNeighbor = (state, getters) => ({
+ discussionId,
+ diffOrder,
+ step,
+}) => {
+ const ids = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
+ const index = ids.indexOf(discussionId) + step;
+
+ if (index < 0 && step < 0) {
+ return ids[ids.length - 1];
+ }
+
+ if (index === ids.length && step > 0) {
+ return ids[0];
+ }
- // Get the first ID if there is none after the currentIndex
- return slicedIds.length ? idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0] : idsOrdered[0];
+ return ids[index];
};
-export const previousUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
- const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
- const currentIndex = idsOrdered.indexOf(discussionId);
- const slicedIds = idsOrdered.slice(currentIndex - 1, currentIndex);
+// Gets the ID of the discussion following the one provided, respecting order (diff or date)
+// @param {Boolean} discussionId - id of the current discussion
+// @param {Boolean} diffOrder - is ordered by diff?
+export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) =>
+ getters.findUnresolvedDiscussionIdNeighbor({ discussionId, diffOrder, step: 1 });
- // Get the last ID if there is none after the currentIndex
- return slicedIds.length ? slicedIds[0] : idsOrdered[idsOrdered.length - 1];
-};
+export const previousUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) =>
+ getters.findUnresolvedDiscussionIdNeighbor({ discussionId, diffOrder, step: -1 });
// @param {Boolean} diffOrder - is ordered by diff?
export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
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..a08f732dda7 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -67,7 +67,7 @@ export default {
<span
v-if="pipeline.flags.latest"
v-gl-tooltip
- :title="__('Latest pipeline for this branch')"
+ :title="__('Latest pipeline for the most recent commit on this branch')"
class="js-pipeline-url-latest badge badge-success"
>
{{ __('latest') }}
@@ -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/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index 03281aa1317..12ee1ce2f0c 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -38,7 +38,9 @@ export default {
},
computed: {
statusTitle() {
- return sprintf(s__('Commits|Commit: %{commitText}'), { commitText: this.ciStatus.text });
+ return sprintf(s__('PipelineStatusTooltip|Pipeline: %{ciStatus}'), {
+ ciStatus: this.ciStatus.text,
+ });
},
},
mounted() {
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index efbf0a4e3cf..346dc470a59 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -1,10 +1,9 @@
<script>
import { mapGetters, mapActions } from 'vuex';
-import { GlLoadingIcon } from '@gitlab/ui';
+import { GlLoadingIcon, GlEmptyState } from '@gitlab/ui';
import store from '../stores';
import clipboardButton from '../../vue_shared/components/clipboard_button.vue';
import CollapsibleContainer from './collapsible_container.vue';
-import SvgMessage from './svg_message.vue';
import { s__, sprintf } from '../../locale';
export default {
@@ -12,8 +11,8 @@ export default {
components: {
clipboardButton,
CollapsibleContainer,
+ GlEmptyState,
GlLoadingIcon,
- SvgMessage,
},
props: {
endpoint: {
@@ -93,7 +92,9 @@ export default {
this.setMainEndpoint(this.endpoint);
},
mounted() {
- this.fetchRepos();
+ if (!this.characterError) {
+ this.fetchRepos();
+ }
},
methods: {
...mapActions(['setMainEndpoint', 'fetchRepos']),
@@ -102,61 +103,63 @@ export default {
</script>
<template>
<div>
- <svg-message v-if="characterError" id="invalid-characters" :svg-path="containersErrorImage">
- <h4>
- {{ s__('ContainerRegistry|Docker connection error') }}
- </h4>
- <p v-html="dockerConnectionErrorText"></p>
- </svg-message>
+ <gl-empty-state
+ v-if="characterError"
+ :title="s__('ContainerRegistry|Docker connection error')"
+ :svg-path="containersErrorImage"
+ >
+ <template #description>
+ <p v-html="dockerConnectionErrorText"></p>
+ </template>
+ </gl-empty-state>
- <gl-loading-icon v-else-if="isLoading && !characterError" size="md" class="prepend-top-16" />
+ <gl-loading-icon v-else-if="isLoading" size="md" class="prepend-top-16" />
- <div v-else-if="!isLoading && !characterError && repos.length">
+ <div v-else-if="!isLoading && repos.length">
<h4>{{ s__('ContainerRegistry|Container Registry') }}</h4>
<p v-html="introText"></p>
<collapsible-container v-for="item in repos" :key="item.id" :repo="item" />
</div>
- <svg-message
- v-else-if="!isLoading && !characterError && !repos.length"
- id="no-container-images"
+ <gl-empty-state
+ v-else
+ :title="s__('ContainerRegistry|There are no container images stored for this project')"
:svg-path="noContainersImage"
+ class="container-message"
>
- <h4>
- {{ s__('ContainerRegistry|There are no container images stored for this project') }}
- </h4>
- <p v-html="noContainerImagesText"></p>
-
- <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
- <p>
- {{
- s__(
- 'ContainerRegistry|You can add an image to this registry with the following commands:',
- )
- }}
- </p>
+ <template #description>
+ <p class="js-no-container-images-text" v-html="noContainerImagesText"></p>
+ <h5>{{ s__('ContainerRegistry|Quick Start') }}</h5>
+ <p>
+ {{
+ s__(
+ 'ContainerRegistry|You can add an image to this registry with the following commands:',
+ )
+ }}
+ </p>
- <div class="input-group append-bottom-10">
- <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
- <span class="input-group-append">
- <clipboard-button
- :text="dockerBuildCommand"
- :title="s__('ContainerRegistry|Copy build command to clipboard')"
- class="input-group-text"
- />
- </span>
- </div>
+ <div class="input-group append-bottom-10">
+ <input :value="dockerBuildCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerBuildCommand"
+ :title="s__('ContainerRegistry|Copy build command to clipboard')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
- <div class="input-group">
- <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
- <span class="input-group-append">
- <clipboard-button
- :text="dockerPushCommand"
- :title="s__('ContainerRegistry|Copy push command to clipboard')"
- class="input-group-text"
- />
- </span>
- </div>
- </svg-message>
+ <div class="input-group">
+ <input :value="dockerPushCommand" type="text" class="form-control monospace" readonly />
+ <span class="input-group-append">
+ <clipboard-button
+ :text="dockerPushCommand"
+ :title="s__('ContainerRegistry|Copy push command to clipboard')"
+ class="input-group-text"
+ />
+ </span>
+ </div>
+ </template>
+ </gl-empty-state>
</div>
</template>
diff --git a/app/assets/javascripts/registry/components/svg_message.vue b/app/assets/javascripts/registry/components/svg_message.vue
deleted file mode 100644
index 617093e054e..00000000000
--- a/app/assets/javascripts/registry/components/svg_message.vue
+++ /dev/null
@@ -1,26 +0,0 @@
-<script>
-export default {
- name: 'RegistrySvgMessage',
- props: {
- id: {
- type: String,
- required: true,
- },
- svgPath: {
- type: String,
- required: true,
- },
- },
-};
-</script>
-
-<template>
- <div :id="id" class="empty-state container-message">
- <div class="svg-content">
- <img :src="svgPath" class="flex-align-self-center" />
- </div>
- <div class="text-content">
- <slot></slot>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
new file mode 100644
index 00000000000..71a1fc31315
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar.vue
@@ -0,0 +1,48 @@
+<script>
+import { __, sprintf } from '~/locale';
+
+export default {
+ props: {
+ user: {
+ type: Object,
+ required: true,
+ },
+ imgSize: {
+ type: Number,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ },
+ computed: {
+ assigneeAlt() {
+ return sprintf(__("%{userName}'s avatar"), { userName: this.user.name });
+ },
+ avatarUrl() {
+ return this.user.avatar || this.user.avatar_url || gon.default_avatar_url;
+ },
+ isMergeRequest() {
+ return this.issuableType === 'merge_request';
+ },
+ hasMergeIcon() {
+ return this.isMergeRequest && !this.user.can_merge;
+ },
+ },
+};
+</script>
+
+<template>
+ <span class="position-relative">
+ <img
+ :alt="assigneeAlt"
+ :src="avatarUrl"
+ :width="imgSize"
+ :class="`s${imgSize}`"
+ class="avatar avatar-inline m-0"
+ />
+ <i v-if="hasMergeIcon" aria-hidden="true" class="fa fa-exclamation-triangle merge-icon"></i>
+ </span>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
new file mode 100644
index 00000000000..6633a63d046
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_avatar_link.vue
@@ -0,0 +1,83 @@
+<script>
+import { __, sprintf } from '~/locale';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
+import { joinPaths } from '~/lib/utils/url_utility';
+import AssigneeAvatar from './assignee_avatar.vue';
+
+export default {
+ components: {
+ AssigneeAvatar,
+ GlLink,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ user: {
+ type: Object,
+ required: true,
+ },
+ rootPath: {
+ type: String,
+ required: true,
+ },
+ tooltipPlacement: {
+ type: String,
+ default: 'bottom',
+ required: false,
+ },
+ tooltipHasName: {
+ type: Boolean,
+ default: true,
+ required: false,
+ },
+ issuableType: {
+ type: String,
+ default: 'issue',
+ required: false,
+ },
+ },
+ computed: {
+ cannotMerge() {
+ return this.issuableType === 'merge_request' && !this.user.can_merge;
+ },
+ tooltipTitle() {
+ if (this.cannotMerge && this.tooltipHasName) {
+ return sprintf(__('%{userName} (cannot merge)'), { userName: this.user.name });
+ } else if (this.cannotMerge) {
+ return __('Cannot merge');
+ } else if (this.tooltipHasName) {
+ return this.user.name;
+ }
+
+ return '';
+ },
+ tooltipOption() {
+ return {
+ container: 'body',
+ placement: this.tooltipPlacement,
+ boundary: 'viewport',
+ };
+ },
+ assigneeUrl() {
+ return joinPaths(`${this.rootPath}`, `${this.user.username}`);
+ },
+ },
+};
+</script>
+
+<template>
+ <!-- must be `d-inline-block` or parent flex-basis causes width issues -->
+ <gl-link
+ v-gl-tooltip="tooltipOption"
+ :href="assigneeUrl"
+ :title="tooltipTitle"
+ class="d-inline-block"
+ >
+ <!-- use d-flex so that slot can be appropriately styled -->
+ <span class="d-flex">
+ <assignee-avatar :user="user" :img-size="32" :issuable-type="issuableType" />
+ <slot :user="user"></slot>
+ </span>
+ </gl-link>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
index fa6b6bfaef1..63b93a80ead 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignee_title.vue
@@ -1,5 +1,6 @@
<script>
import { n__ } from '~/locale';
+import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
name: 'AssigneeTitle',
@@ -29,13 +30,23 @@ export default {
return n__('Assignee', `%d Assignees`, assignees);
},
},
+ methods: {
+ trackEdit() {
+ trackEvent('click_edit_button', 'assignee');
+ },
+ },
};
</script>
<template>
<div class="title hide-collapsed">
{{ assigneeTitle }}
<i v-if="loading" aria-hidden="true" class="fa fa-spinner fa-spin block-loading"></i>
- <a v-if="editable" class="js-sidebar-dropdown-toggle edit-link float-right" href="#">
+ <a
+ v-if="editable"
+ class="js-sidebar-dropdown-toggle edit-link float-right"
+ href="#"
+ @click.prevent="trackEdit"
+ >
{{ __('Edit') }}
</a>
<a
diff --git a/app/assets/javascripts/sidebar/components/assignees/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index 631e2e28d4d..d9739e8d197 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -1,13 +1,14 @@
<script>
-import { __, sprintf } from '~/locale';
-import tooltip from '~/vue_shared/directives/tooltip';
+import CollapsedAssigneeList from '../assignees/collapsed_assignee_list.vue';
+import UncollapsedAssigneeList from '../assignees/uncollapsed_assignee_list.vue';
export default {
// name: 'Assignees' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'Assignees',
- directives: {
- tooltip,
+ components: {
+ CollapsedAssigneeList,
+ UncollapsedAssigneeList,
},
props: {
rootPath: {
@@ -24,171 +25,34 @@ export default {
},
issuableType: {
type: String,
- require: true,
+ required: false,
default: 'issue',
},
},
- data() {
- return {
- defaultRenderCount: 5,
- defaultMaxCounter: 99,
- showLess: true,
- };
- },
computed: {
- firstUser() {
- return this.users[0];
- },
- hasMoreThanTwoAssignees() {
- return this.users.length > 2;
- },
- hasMoreThanOneAssignee() {
- return this.users.length > 1;
- },
- hasAssignees() {
- return this.users.length > 0;
- },
hasNoUsers() {
return !this.users.length;
},
- hasOneUser() {
- return this.users.length === 1;
- },
- renderShowMoreSection() {
- return this.users.length > this.defaultRenderCount;
- },
- numberOfHiddenAssignees() {
- return this.users.length - this.defaultRenderCount;
- },
- isHiddenAssignees() {
- return this.numberOfHiddenAssignees > 0;
- },
- hiddenAssigneesLabel() {
- const { numberOfHiddenAssignees } = this;
- return sprintf(__('+ %{numberOfHiddenAssignees} more'), { numberOfHiddenAssignees });
- },
- collapsedTooltipTitle() {
- const maxRender = Math.min(this.defaultRenderCount, this.users.length);
- const renderUsers = this.users.slice(0, maxRender);
- const names = renderUsers.map(u => u.name);
-
- if (this.users.length > maxRender) {
- names.push(`+ ${this.users.length - maxRender} more`);
- }
-
- if (!this.users.length) {
- const emptyTooltipLabel = __('Assignee(s)');
- names.push(emptyTooltipLabel);
- }
-
- return names.join(', ');
- },
- sidebarAvatarCounter() {
- let counter = `+${this.users.length - 1}`;
-
- if (this.users.length > this.defaultMaxCounter) {
- counter = `${this.defaultMaxCounter}+`;
- }
+ sortedAssigness() {
+ const canMergeUsers = this.users.filter(user => user.can_merge);
+ const canNotMergeUsers = this.users.filter(user => !user.can_merge);
- return counter;
- },
- mergeNotAllowedTooltipMessage() {
- const assigneesCount = this.users.length;
-
- if (this.issuableType !== 'merge_request' || assigneesCount === 0) {
- return null;
- }
-
- const cannotMergeCount = this.users.filter(u => u.can_merge === false).length;
- const canMergeCount = assigneesCount - cannotMergeCount;
-
- if (canMergeCount === assigneesCount) {
- // Everyone can merge
- return null;
- } else if (cannotMergeCount === assigneesCount && assigneesCount > 1) {
- return __('No one can merge');
- } else if (assigneesCount === 1) {
- return __('Cannot merge');
- }
-
- return sprintf(__('%{canMergeCount}/%{assigneesCount} can merge'), {
- canMergeCount,
- assigneesCount,
- });
+ return [...canMergeUsers, ...canNotMergeUsers];
},
},
methods: {
assignSelf() {
this.$emit('assign-self');
},
- toggleShowLess() {
- this.showLess = !this.showLess;
- },
- renderAssignee(index) {
- return !this.showLess || (index < this.defaultRenderCount && this.showLess);
- },
- avatarUrl(user) {
- return user.avatar || user.avatar_url || gon.default_avatar_url;
- },
- assigneeUrl(user) {
- return `${this.rootPath}${user.username}`;
- },
- assigneeAlt(user) {
- return sprintf(__("%{userName}'s avatar"), { userName: user.name });
- },
- assigneeUsername(user) {
- return `@${user.username}`;
- },
- shouldRenderCollapsedAssignee(index) {
- const firstTwo = this.users.length <= 2 && index <= 2;
-
- return index === 0 || firstTwo;
- },
},
};
</script>
<template>
<div>
- <div
- v-tooltip
- :class="{ 'multiple-users': hasMoreThanOneAssignee }"
- :title="collapsedTooltipTitle"
- class="sidebar-collapsed-icon sidebar-collapsed-user"
- data-container="body"
- data-placement="left"
- data-boundary="viewport"
- >
- <i v-if="hasNoUsers" :aria-label="__('None')" class="fa fa-user"> </i>
- <button
- v-for="(user, index) in users"
- v-if="shouldRenderCollapsedAssignee(index)"
- :key="user.id"
- type="button"
- class="btn-link"
- >
- <img
- :alt="assigneeAlt(user)"
- :src="avatarUrl(user)"
- width="24"
- class="avatar avatar-inline s24"
- />
- <span class="author"> {{ user.name }} </span>
- </button>
- <button v-if="hasMoreThanTwoAssignees" class="btn-link" type="button">
- <span class="avatar-counter sidebar-avatar-counter"> {{ sidebarAvatarCounter }} </span>
- </button>
- </div>
+ <collapsed-assignee-list :users="sortedAssigness" :issuable-type="issuableType" />
+
<div class="value hide-collapsed">
- <span
- v-if="mergeNotAllowedTooltipMessage"
- v-tooltip
- :title="mergeNotAllowedTooltipMessage"
- data-placement="left"
- class="float-right cannot-be-merged"
- >
- <i aria-hidden="true" data-hidden="true" class="fa fa-exclamation-triangle"></i>
- </span>
<template v-if="hasNoUsers">
<span class="assign-yourself no-value qa-assign-yourself">
{{ __('None') }}
@@ -200,51 +64,13 @@ export default {
</template>
</span>
</template>
- <template v-else-if="hasOneUser">
- <a :href="assigneeUrl(firstUser)" class="author-link bold">
- <img
- :alt="assigneeAlt(firstUser)"
- :src="avatarUrl(firstUser)"
- width="32"
- class="avatar avatar-inline s32"
- />
- <span class="author"> {{ firstUser.name }} </span>
- <span class="username"> {{ assigneeUsername(firstUser) }} </span>
- </a>
- </template>
- <template v-else>
- <div class="user-list">
- <div
- v-for="(user, index) in users"
- v-if="renderAssignee(index)"
- :key="user.id"
- class="user-item"
- >
- <a
- :href="assigneeUrl(user)"
- :data-title="user.name"
- class="user-link has-tooltip"
- data-container="body"
- data-placement="bottom"
- >
- <img
- :alt="assigneeAlt(user)"
- :src="avatarUrl(user)"
- width="32"
- class="avatar avatar-inline s32"
- />
- </a>
- </div>
- </div>
- <div v-if="renderShowMoreSection" class="user-list-more">
- <button type="button" class="btn-link" @click="toggleShowLess">
- <template v-if="showLess">
- {{ hiddenAssigneesLabel }}
- </template>
- <template v-else>{{ __('- show less') }}</template>
- </button>
- </div>
- </template>
+
+ <uncollapsed-assignee-list
+ v-else
+ :users="sortedAssigness"
+ :root-path="rootPath"
+ :issuable-type="issuableType"
+ />
</div>
</div>
</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue
new file mode 100644
index 00000000000..2f654409561
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee.vue
@@ -0,0 +1,27 @@
+<script>
+import AssigneeAvatar from './assignee_avatar.vue';
+
+export default {
+ components: {
+ AssigneeAvatar,
+ },
+ props: {
+ user: {
+ type: Object,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ },
+};
+</script>
+
+<template>
+ <button type="button" class="btn-link">
+ <assignee-avatar :user="user" :img-size="24" :issuable-type="issuableType" />
+ <span class="author"> {{ user.name }} </span>
+ </button>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue
new file mode 100644
index 00000000000..5b4a43399ca
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/collapsed_assignee_list.vue
@@ -0,0 +1,121 @@
+<script>
+import { __, sprintf } from '~/locale';
+import { GlTooltipDirective } from '@gitlab/ui';
+import CollapsedAssignee from './collapsed_assignee.vue';
+
+const DEFAULT_MAX_COUNTER = 99;
+const DEFAULT_RENDER_COUNT = 5;
+
+export default {
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ components: {
+ CollapsedAssignee,
+ },
+ props: {
+ users: {
+ type: Array,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ },
+ computed: {
+ isMergeRequest() {
+ return this.issuableType === 'merge_request';
+ },
+ hasNoUsers() {
+ return !this.users.length;
+ },
+ hasMoreThanOneAssignee() {
+ return this.users.length > 1;
+ },
+ hasMoreThanTwoAssignees() {
+ return this.users.length > 2;
+ },
+ allAssigneesCanMerge() {
+ return this.users.every(user => user.can_merge);
+ },
+ sidebarAvatarCounter() {
+ if (this.users.length > DEFAULT_MAX_COUNTER) {
+ return `${DEFAULT_MAX_COUNTER}+`;
+ }
+
+ return `+${this.users.length - 1}`;
+ },
+ collapsedUsers() {
+ const collapsedLength = this.hasMoreThanTwoAssignees ? 1 : this.users.length;
+
+ return this.users.slice(0, collapsedLength);
+ },
+ tooltipTitleMergeStatus() {
+ if (!this.isMergeRequest) {
+ return '';
+ }
+
+ const mergeLength = this.users.filter(u => u.can_merge).length;
+
+ if (mergeLength === this.users.length) {
+ return '';
+ } else if (mergeLength > 0) {
+ return sprintf(__('%{mergeLength}/%{usersLength} can merge'), {
+ mergeLength,
+ usersLength: this.users.length,
+ });
+ }
+
+ return this.users.length === 1 ? __('cannot merge') : __('no one can merge');
+ },
+ tooltipTitle() {
+ const maxRender = Math.min(DEFAULT_RENDER_COUNT, this.users.length);
+ const renderUsers = this.users.slice(0, maxRender);
+ const names = renderUsers.map(u => u.name);
+
+ if (!this.users.length) {
+ return __('Assignee(s)');
+ }
+
+ if (this.users.length > names.length) {
+ names.push(sprintf(__('+ %{amount} more'), { amount: this.users.length - names.length }));
+ }
+
+ const text = names.join(', ');
+
+ return this.tooltipTitleMergeStatus ? `${text} (${this.tooltipTitleMergeStatus})` : text;
+ },
+
+ tooltipOptions() {
+ return { container: 'body', placement: 'left', boundary: 'viewport' };
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ v-gl-tooltip="tooltipOptions"
+ :class="{ 'multiple-users': hasMoreThanOneAssignee }"
+ :title="tooltipTitle"
+ class="sidebar-collapsed-icon sidebar-collapsed-user"
+ >
+ <i v-if="hasNoUsers" :aria-label="__('None')" class="fa fa-user"> </i>
+ <collapsed-assignee
+ v-for="user in collapsedUsers"
+ :key="user.id"
+ :user="user"
+ :issuable-type="issuableType"
+ />
+ <button v-if="hasMoreThanTwoAssignees" class="btn-link" type="button">
+ <span class="avatar-counter sidebar-avatar-counter"> {{ sidebarAvatarCounter }} </span>
+ <i
+ v-if="isMergeRequest && !allAssigneesCanMerge"
+ aria-hidden="true"
+ class="fa fa-exclamation-triangle merge-icon"
+ ></i>
+ </button>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
index be1e4811856..c6cc04a139f 100644
--- a/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/sidebar_assignees.vue
@@ -29,7 +29,7 @@ export default {
},
issuableType: {
type: String,
- require: true,
+ required: false,
default: 'issue',
},
},
diff --git a/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
new file mode 100644
index 00000000000..3a4623121f4
--- /dev/null
+++ b/app/assets/javascripts/sidebar/components/assignees/uncollapsed_assignee_list.vue
@@ -0,0 +1,96 @@
+<script>
+import { __, sprintf } from '~/locale';
+import AssigneeAvatarLink from './assignee_avatar_link.vue';
+
+const DEFAULT_RENDER_COUNT = 5;
+
+export default {
+ components: {
+ AssigneeAvatarLink,
+ },
+ props: {
+ users: {
+ type: Array,
+ required: true,
+ },
+ rootPath: {
+ type: String,
+ required: true,
+ },
+ issuableType: {
+ type: String,
+ required: false,
+ default: 'issue',
+ },
+ },
+ data() {
+ return {
+ showLess: true,
+ };
+ },
+ computed: {
+ firstUser() {
+ return this.users[0];
+ },
+ hasOneUser() {
+ return this.users.length === 1;
+ },
+ hiddenAssigneesLabel() {
+ const { numberOfHiddenAssignees } = this;
+ return sprintf(__('+ %{numberOfHiddenAssignees} more'), { numberOfHiddenAssignees });
+ },
+ renderShowMoreSection() {
+ return this.users.length > DEFAULT_RENDER_COUNT;
+ },
+ numberOfHiddenAssignees() {
+ return this.users.length - DEFAULT_RENDER_COUNT;
+ },
+ uncollapsedUsers() {
+ const uncollapsedLength = this.showLess
+ ? Math.min(this.users.length, DEFAULT_RENDER_COUNT)
+ : this.users.length;
+ return this.showLess ? this.users.slice(0, uncollapsedLength) : this.users;
+ },
+ username() {
+ return `@${this.firstUser.username}`;
+ },
+ },
+ methods: {
+ toggleShowLess() {
+ this.showLess = !this.showLess;
+ },
+ },
+};
+</script>
+
+<template>
+ <assignee-avatar-link
+ v-if="hasOneUser"
+ v-slot="{ user }"
+ tooltip-placement="left"
+ :tooltip-has-name="false"
+ :user="firstUser"
+ :root-path="rootPath"
+ :issuable-type="issuableType"
+ >
+ <div class="ml-2">
+ <span class="author"> {{ user.name }} </span>
+ <span class="username"> {{ username }} </span>
+ </div>
+ </assignee-avatar-link>
+ <div v-else>
+ <div class="user-list">
+ <div v-for="user in uncollapsedUsers" :key="user.id" class="user-item">
+ <assignee-avatar-link :user="user" :root-path="rootPath" :issuable-type="issuableType" />
+ </div>
+ </div>
+ <div v-if="renderShowMoreSection" class="user-list-more">
+ <button type="button" class="btn-link" @click="toggleShowLess">
+ <template v-if="showLess">
+ {{ hiddenAssigneesLabel }}
+ </template>
+ <template v-else>{{ __('- show less') }}</template>
+ </button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
index 597b723a9d9..1c75b6148e8 100644
--- a/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/confidential/confidential_issue_sidebar.vue
@@ -5,6 +5,7 @@ import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
+import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
@@ -51,6 +52,11 @@ export default {
toggleForm() {
this.edit = !this.edit;
},
+ onEditClick() {
+ this.toggleForm();
+
+ trackEvent('click_edit_button', 'confidentiality');
+ },
updateConfidentialAttribute(confidential) {
this.service
.update('issue', { confidential })
@@ -82,7 +88,7 @@ export default {
v-if="isEditable"
class="float-right confidential-edit"
href="#"
- @click.prevent="toggleForm"
+ @click.prevent="onEditClick"
>
{{ __('Edit') }}
</a>
diff --git a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
index c5cfa92f3c8..ec2a7b93a98 100644
--- a/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
+++ b/app/assets/javascripts/sidebar/components/lock/lock_issue_sidebar.vue
@@ -6,6 +6,7 @@ import issuableMixin from '~/vue_shared/mixins/issuable';
import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '~/sidebar/event_hub';
import editForm from './edit_form.vue';
+import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
export default {
components: {
@@ -65,7 +66,11 @@ export default {
toggleForm() {
this.mediator.store.isLockDialogOpen = !this.mediator.store.isLockDialogOpen;
},
+ onEditClick() {
+ this.toggleForm();
+ trackEvent('click_edit_button', 'lock_issue');
+ },
updateLockedAttribute(locked) {
this.mediator.service
.update(this.issuableType, {
@@ -109,7 +114,7 @@ export default {
v-if="isEditable"
class="float-right lock-edit"
type="button"
- @click.prevent="toggleForm"
+ @click.prevent="onEditClick"
>
{{ __('Edit') }}
</button>
diff --git a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index 0d1faceef11..1f5f19d1931 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -4,6 +4,7 @@ import icon from '~/vue_shared/components/icon.vue';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
import tooltip from '~/vue_shared/directives/tooltip';
import eventHub from '../../event_hub';
+import { trackEvent } from 'ee_else_ce/event_tracking/issue_sidebar';
const ICON_ON = 'notifications';
const ICON_OFF = 'notifications-off';
@@ -63,6 +64,8 @@ export default {
// Component event emission.
this.$emit('toggleSubscription', this.id);
+
+ trackEvent('toggle_button', 'notifications', this.subscribed ? 0 : 1);
},
onClickCollapsedIcon() {
this.$emit('toggleSidebar');
diff --git a/app/assets/javascripts/test_utils/simulate_drag.js b/app/assets/javascripts/test_utils/simulate_drag.js
index be9ebc81c6b..c9bf234fcce 100644
--- a/app/assets/javascripts/test_utils/simulate_drag.js
+++ b/app/assets/javascripts/test_utils/simulate_drag.js
@@ -153,7 +153,11 @@ export default function simulateDrag(options) {
if (progress >= 1) {
if (options.ondragend) options.ondragend();
- simulateEvent(toEl, 'mouseup');
+
+ if (options.performDrop) {
+ simulateEvent(toEl, 'mouseup');
+ }
+
clearInterval(dragInterval);
window.SIMULATE_DRAG_ACTIVE = 0;
}
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
index 2d0b099cf0b..a852f937eec 100644
--- a/app/assets/javascripts/tracking.js
+++ b/app/assets/javascripts/tracking.js
@@ -15,8 +15,14 @@ const extractData = (el, opts = {}) => {
};
export default class Tracking {
+ static trackable() {
+ return !['1', 'yes'].includes(
+ window.doNotTrack || navigator.doNotTrack || navigator.msDoNotTrack,
+ );
+ }
+
static enabled() {
- return typeof window.snowplow === 'function';
+ return typeof window.snowplow === 'function' && this.trackable();
}
static event(category = document.body.dataset.page, event = 'generic', data = {}) {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index 33cedf78331..12c939aa70f 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -62,6 +62,8 @@ function UsersSelect(currentUser, els, options = {}) {
options.showCurrentUser = $dropdown.data('currentUser');
options.todoFilter = $dropdown.data('todoFilter');
options.todoStateFilter = $dropdown.data('todoStateFilter');
+ options.iid = $dropdown.data('iid');
+ options.issuableType = $dropdown.data('issuableType');
showNullUser = $dropdown.data('nullUser');
defaultNullUser = $dropdown.data('nullUserDefault');
showMenuAbove = $dropdown.data('showMenuAbove');
@@ -239,7 +241,7 @@ function UsersSelect(currentUser, els, options = {}) {
'<% if( avatar ) { %> <a class="author-link" href="/<%- username %>"> <img width="24" class="avatar avatar-inline s24" alt="" src="<%- avatar %>"> </a> <% } else { %> <i class="fa fa-user"></i> <% } %>',
);
assigneeTemplate = _.template(
- `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
+ `<% if (username) { %> <a class="author-link bold" href="/<%- username %>"> <% if( avatar ) { %> <img width="32" class="avatar avatar-inline s32" alt="" src="<%- avatar %>"> <% } %> <span class="author"><%- name %></span> <span class="username"> @<%- username %> </span> </a> <% } else { %> <span class="no-value assign-yourself">
${sprintf(s__('UsersSelect|No assignee - %{openingTag} assign yourself %{closingTag}'), {
openingTag: '<a href="#" class="js-assign-yourself">',
closingTag: '</a>',
@@ -423,6 +425,8 @@ function UsersSelect(currentUser, els, options = {}) {
const { $el, e, isMarking } = options;
const user = options.selectedObj;
+ $el.tooltip('dispose');
+
if ($dropdown.hasClass('js-multiselect')) {
const isActive = $el.hasClass('is-active');
const previouslySelected = $dropdown
@@ -570,20 +574,11 @@ function UsersSelect(currentUser, els, options = {}) {
user.name,
)}</a></li>`;
} else {
- img = "<img src='" + avatar + "' class='avatar avatar-inline' width='32' />";
+ // 0 margin, because it's now handled by a wrapper
+ img = "<img src='" + avatar + "' class='avatar avatar-inline m-0' width='32' />";
}
- return `
- <li data-user-id=${user.id}>
- <a href='#' class='dropdown-menu-user-link ${selected === true ? 'is-active' : ''}'>
- ${img}
- <strong class='dropdown-menu-user-full-name'>
- ${_.escape(user.name)}
- </strong>
- ${username ? `<span class='dropdown-menu-user-username'>${username}</span>` : ''}
- </a>
- </li>
- `;
+ return _this.renderRow(options.issuableType, user, selected, username, img);
},
});
};
@@ -764,6 +759,11 @@ UsersSelect.prototype.users = function(query, options, callback) {
author_id: options.authorId || null,
skip_users: options.skipUsers || null,
};
+
+ if (options.issuableType === 'merge_request') {
+ params.merge_request_iid = options.iid || null;
+ }
+
return axios.get(url, { params }).then(({ data }) => {
callback(data);
});
@@ -776,4 +776,44 @@ UsersSelect.prototype.buildUrl = function(url) {
return url;
};
+UsersSelect.prototype.renderRow = function(issuableType, user, selected, username, img) {
+ const tooltip = issuableType === 'merge_request' && !user.can_merge ? __('Cannot merge') : '';
+ const tooltipClass = tooltip ? `has-tooltip` : '';
+ const selectedClass = selected === true ? 'is-active' : '';
+ const linkClasses = `${selectedClass} ${tooltipClass}`;
+ const tooltipAttributes = tooltip
+ ? `data-container="body" data-placement="left" data-title="${tooltip}"`
+ : '';
+
+ return `
+ <li data-user-id=${user.id}>
+ <a href="#" class="dropdown-menu-user-link d-flex align-items-center ${linkClasses}" ${tooltipAttributes}>
+ ${this.renderRowAvatar(issuableType, user, img)}
+ <span class="d-flex flex-column overflow-hidden">
+ <strong class="dropdown-menu-user-full-name">
+ ${_.escape(user.name)}
+ </strong>
+ ${username ? `<span class="dropdown-menu-user-username">${username}</span>` : ''}
+ </span>
+ </a>
+ </li>
+ `;
+};
+
+UsersSelect.prototype.renderRowAvatar = function(issuableType, user, img) {
+ if (user.beforeDivider) {
+ return img;
+ }
+
+ const mergeIcon =
+ issuableType === 'merge_request' && !user.can_merge
+ ? '<i class="fa fa-exclamation-triangle merge-icon"></i>'
+ : '';
+
+ return `<span class="position-relative mr-2">
+ ${img}
+ ${mergeIcon}
+ </span>`;
+};
+
export default UsersSelect;
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment.js b/app/assets/javascripts/visual_review_toolbar/components/comment.js
deleted file mode 100644
index a03dc14b319..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, COMMENT_BOX, LOGOUT, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
-import { clearNote } from './note';
-import { buttonClearStyles } from './utils';
-import { addForm } from './wrapper';
-import { changeSelectedMr, selectedMrNote } from './comment_mr_note';
-import postComment from './comment_post';
-import { saveComment, getSavedComment } from './comment_storage';
-
-const comment = state => {
- const savedComment = getSavedComment();
-
- return `
- <div>
- <textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true">${savedComment}</textarea>
- ${selectedMrNote(state)}
- <p class="gitlab-metadata-note">Additional metadata will be included: browser, OS, current page, user agent, and viewport dimensions.</p>
- </div>
- <div class="gitlab-button-wrapper">
- <button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
- <button class="gitlab-button gitlab-button-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button>
- </div>
- `;
-};
-
-// This function is here becaause it is called only from the comment view
-// If we reach a design where we can logout from multiple views, promote this
-// to it's own package
-const logoutUser = state => {
- localStorage.removeItem(STORAGE_TOKEN);
- localStorage.removeItem(STORAGE_MR_ID);
- state.token = '';
- state.mergeRequestId = '';
-
- clearNote();
- addForm(nextView(state, COMMENT_BOX));
-};
-
-export { changeSelectedMr, comment, logoutUser, postComment, saveComment };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js
deleted file mode 100644
index da67763261c..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, CHANGE_MR_ID_BUTTON, COMMENT_BOX, STORAGE_MR_ID } from '../shared';
-import { clearNote } from './note';
-import { buttonClearStyles } from './utils';
-import { addForm } from './wrapper';
-
-const selectedMrNote = state => {
- const { mrUrl, projectPath, mergeRequestId } = state;
-
- const mrLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}`;
-
- return `
- <p class="gitlab-metadata-note">
- This posts to merge request <a class="gitlab-link" href="${mrLink}">!${mergeRequestId}</a>.
- <button style="${buttonClearStyles}" type="button" id="${CHANGE_MR_ID_BUTTON}" class="gitlab-link gitlab-link-button">Change</button>
- </p>
- `;
-};
-
-const clearMrId = state => {
- localStorage.removeItem(STORAGE_MR_ID);
- state.mergeRequestId = '';
-};
-
-const changeSelectedMr = state => {
- clearMrId(state);
- clearNote();
- addForm(nextView(state, COMMENT_BOX));
-};
-
-export { changeSelectedMr, selectedMrNote };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js b/app/assets/javascripts/visual_review_toolbar/components/comment_post.js
deleted file mode 100644
index ee5f2b62425..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_post.js
+++ /dev/null
@@ -1,145 +0,0 @@
-import { BLACK, COMMENT_BOX, MUTED } from '../shared';
-import { clearSavedComment } from './comment_storage';
-import { clearNote, postError } from './note';
-import { selectCommentBox, selectCommentButton, selectNote, selectNoteContainer } from './utils';
-
-const resetCommentButton = () => {
- const commentButton = selectCommentButton();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Send feedback';
- commentButton.classList.replace('gitlab-button-secondary', 'gitlab-button-success');
- commentButton.style.opacity = 1;
-};
-
-const resetCommentBox = () => {
- const commentBox = selectCommentBox();
- commentBox.style.pointerEvents = 'auto';
- commentBox.style.color = BLACK;
-};
-
-const resetCommentText = () => {
- const commentBox = selectCommentBox();
- commentBox.value = '';
- clearSavedComment();
-};
-
-const resetComment = () => {
- resetCommentButton();
- resetCommentBox();
- resetCommentText();
-};
-
-const confirmAndClear = feedbackInfo => {
- const commentButton = selectCommentButton();
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Feedback sent';
- noteContainer.style.visibility = 'visible';
- currentNote.insertAdjacentHTML('beforeend', feedbackInfo);
-
- setTimeout(resetComment, 1000);
- setTimeout(clearNote, 6000);
-};
-
-const setInProgressState = () => {
- const commentButton = selectCommentButton();
- const commentBox = selectCommentBox();
-
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- commentButton.innerText = 'Sending feedback';
- commentButton.classList.replace('gitlab-button-success', 'gitlab-button-secondary');
- commentButton.style.opacity = 0.5;
- commentBox.style.color = MUTED;
- commentBox.style.pointerEvents = 'none';
-};
-
-const commentErrors = error => {
- switch (error.status) {
- case 401:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Unauthorized. You may have entered an incorrect authentication token.';
- case 404:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Not found. You may have entered an incorrect merge request ID.';
- default:
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return `Your comment could not be sent. Please try again. Error: ${error.message}`;
- }
-};
-
-const postComment = ({
- platform,
- browser,
- userAgent,
- innerWidth,
- innerHeight,
- projectId,
- projectPath,
- mergeRequestId,
- mrUrl,
- token,
-}) => {
- // Clear any old errors
- clearNote(COMMENT_BOX);
-
- setInProgressState();
-
- const commentText = selectCommentBox().value.trim();
- // Get the href at the last moment to support SPAs
- const { href } = window.location;
-
- if (!commentText) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- postError('Your comment appears to be empty.', COMMENT_BOX);
- resetCommentBox();
- resetCommentButton();
- return;
- }
-
- const detailText = `
- \n
-<details>
- <summary>Metadata</summary>
- Posted from ${href} | ${platform} | ${browser} | ${innerWidth} x ${innerHeight}.
- <br /><br />
- <em>User agent: ${userAgent}</em>
-</details>
- `;
-
- const url = `
- ${mrUrl}/api/v4/projects/${projectId}/merge_requests/${mergeRequestId}/discussions`;
-
- const body = `${commentText} ${detailText}`;
-
- fetch(url, {
- method: 'POST',
- headers: {
- 'PRIVATE-TOKEN': token,
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ body }),
- })
- .then(response => {
- if (response.ok) {
- return response.json();
- }
-
- throw response;
- })
- .then(data => {
- const commentId = data.notes[0].id;
- const feedbackLink = `${mrUrl}/${projectPath}/merge_requests/${mergeRequestId}#note_${commentId}`;
- const feedbackInfo = `Feedback sent. View at <a class="gitlab-link" href="${feedbackLink}">${projectPath} !${mergeRequestId} (comment ${commentId})</a>`;
- confirmAndClear(feedbackInfo);
- })
- .catch(err => {
- postError(commentErrors(err), COMMENT_BOX);
- resetCommentBox();
- resetCommentButton();
- });
-};
-
-export default postComment;
diff --git a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js
deleted file mode 100644
index 49c9400437e..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js
+++ /dev/null
@@ -1,20 +0,0 @@
-import { selectCommentBox } from './utils';
-import { sessionStorage, STORAGE_COMMENT } from '../shared';
-
-const getSavedComment = () => sessionStorage.getItem(STORAGE_COMMENT) || '';
-
-const saveComment = () => {
- const currentComment = selectCommentBox();
-
- // This may be added to any view via top-level beforeunload listener
- // so let's skip if it does not apply
- if (currentComment && currentComment.value) {
- sessionStorage.setItem(STORAGE_COMMENT, currentComment.value);
- }
-};
-
-const clearSavedComment = () => {
- sessionStorage.removeItem(STORAGE_COMMENT);
-};
-
-export { getSavedComment, saveComment, clearSavedComment };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js b/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
deleted file mode 100644
index 608488a6fea..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
+++ /dev/null
@@ -1,17 +0,0 @@
-import { REMEMBER_ITEM } from '../shared';
-import { buttonClearStyles } from './utils';
-
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const rememberBox = (rememberText = 'Remember me') => `
- <div class="gitlab-checkbox-wrapper">
- <input type="checkbox" id="${REMEMBER_ITEM}" name="${REMEMBER_ITEM}" value="remember">
- <label for="${REMEMBER_ITEM}" class="gitlab-checkbox-label">${rememberText}</label>
- </div>
-`;
-
-const submitButton = buttonId => `
- <div class="gitlab-button-wrapper">
- <button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${buttonId}"> Submit </button>
- </div>
-`;
-export { rememberBox, submitButton };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/index.js b/app/assets/javascripts/visual_review_toolbar/components/index.js
deleted file mode 100644
index e88b3637ad8..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/index.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import { changeSelectedMr, comment, logoutUser, postComment, saveComment } from './comment';
-import { authorizeUser, login } from './login';
-import { addMr, mrForm } from './mr_id';
-import { note } from './note';
-import { selectContainer, selectForm } from './utils';
-import { buttonAndForm, toggleForm } from './wrapper';
-
-export {
- addMr,
- authorizeUser,
- buttonAndForm,
- changeSelectedMr,
- comment,
- login,
- logoutUser,
- mrForm,
- note,
- postComment,
- saveComment,
- selectContainer,
- selectForm,
- toggleForm,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js
deleted file mode 100644
index 20ab01bc690..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/login.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import { nextView } from '../store';
-import { localStorage, LOGIN, TOKEN_BOX, STORAGE_TOKEN } from '../shared';
-import { clearNote, postError } from './note';
-import { rememberBox, submitButton } from './form_elements';
-import { selectRemember, selectToken } from './utils';
-import { addForm } from './wrapper';
-
-const labelText = `
- Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a>
-`;
-
-const login = `
- <div>
- <label for="${TOKEN_BOX}" class="gitlab-label">${labelText}</label>
- <input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" autocomplete="current-password" aria-required="true">
- </div>
- ${rememberBox()}
- ${submitButton(LOGIN)}
-`;
-
-const storeToken = (token, state) => {
- const rememberMe = selectRemember().checked;
-
- if (rememberMe) {
- localStorage.setItem(STORAGE_TOKEN, token);
- }
-
- state.token = token;
-};
-
-const authorizeUser = state => {
- // Clear any old errors
- clearNote(TOKEN_BOX);
-
- const token = selectToken().value;
-
- if (!token) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- postError('Please enter your token.', TOKEN_BOX);
- return;
- }
-
- storeToken(token, state);
- addForm(nextView(state, LOGIN));
-};
-
-export { authorizeUser, login, storeToken };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js
deleted file mode 100644
index 695b3af8aa0..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/mr_id.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import { nextView } from '../store';
-import { MR_ID, MR_ID_BUTTON, STORAGE_MR_ID, localStorage } from '../shared';
-import { clearNote, postError } from './note';
-import { rememberBox, submitButton } from './form_elements';
-import { selectForm, selectMrBox, selectRemember } from './utils';
-import { addForm } from './wrapper';
-
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const mrLabel = `Enter your merge request ID`;
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const mrRememberText = `Remember this number`;
-
-const mrForm = `
- <div>
- <label for="${MR_ID}" class="gitlab-label">${mrLabel}</label>
- <input class="gitlab-input" type="text" pattern="[1-9][0-9]*" id="${MR_ID}" name="${MR_ID}" placeholder="e.g., 321" aria-required="true">
- </div>
- ${rememberBox(mrRememberText)}
- ${submitButton(MR_ID_BUTTON)}
-`;
-
-const storeMR = (id, state) => {
- const rememberMe = selectRemember().checked;
-
- if (rememberMe) {
- localStorage.setItem(STORAGE_MR_ID, id);
- }
-
- state.mergeRequestId = id;
-};
-
-const getFormError = (mrNumber, form) => {
- if (!mrNumber) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Please enter your merge request ID number.';
- }
-
- if (!form.checkValidity()) {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- return 'Please remove any non-number values from the field.';
- }
-
- return null;
-};
-
-const addMr = state => {
- // Clear any old errors
- clearNote(MR_ID);
-
- const mrNumber = selectMrBox().value;
- const form = selectForm();
- const formError = getFormError(mrNumber, form);
-
- if (formError) {
- postError(formError, MR_ID);
- return;
- }
-
- storeMR(mrNumber, state);
- addForm(nextView(state, MR_ID));
-};
-
-export { addMr, mrForm, storeMR };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/note.js b/app/assets/javascripts/visual_review_toolbar/components/note.js
deleted file mode 100644
index 9cddcb710f2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/note.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import { NOTE, NOTE_CONTAINER, RED } from '../shared';
-import { selectById, selectNote, selectNoteContainer } from './utils';
-
-const note = `
- <div id="${NOTE_CONTAINER}" style="visibility: hidden;">
- <p id="${NOTE}" class="gitlab-message"></p>
- </div>
-`;
-
-const clearNote = inputId => {
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
-
- currentNote.innerText = '';
- currentNote.style.color = '';
- noteContainer.style.visibility = 'hidden';
-
- if (inputId) {
- const field = document.getElementById(inputId);
- field.style.borderColor = '';
- }
-};
-
-const postError = (message, inputId) => {
- const currentNote = selectNote();
- const noteContainer = selectNoteContainer();
- const field = selectById(inputId);
- field.style.borderColor = RED;
- currentNote.style.color = RED;
- currentNote.innerText = message;
- noteContainer.style.visibility = 'visible';
- setTimeout(clearNote.bind(null, inputId), 5000);
-};
-
-export { clearNote, note, postError };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js
deleted file mode 100644
index 4ec9bd4a32a..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/utils.js
+++ /dev/null
@@ -1,51 +0,0 @@
-/* global document */
-
-import {
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- MR_ID,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
-} from '../shared';
-
-// this style must be applied inline in a handful of components
-/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
-const buttonClearStyles = `
- -webkit-appearance: none;
-`;
-
-// selector functions to abstract out a little
-const selectById = id => document.getElementById(id);
-const selectCollapseButton = () => document.getElementById(COLLAPSE_BUTTON);
-const selectCommentBox = () => document.getElementById(COMMENT_BOX);
-const selectCommentButton = () => document.getElementById(COMMENT_BUTTON);
-const selectContainer = () => document.getElementById(REVIEW_CONTAINER);
-const selectForm = () => document.getElementById(FORM);
-const selectFormContainer = () => document.getElementById(FORM_CONTAINER);
-const selectMrBox = () => document.getElementById(MR_ID);
-const selectNote = () => document.getElementById(NOTE);
-const selectNoteContainer = () => document.getElementById(NOTE_CONTAINER);
-const selectRemember = () => document.getElementById(REMEMBER_ITEM);
-const selectToken = () => document.getElementById(TOKEN_BOX);
-
-export {
- buttonClearStyles,
- selectById,
- selectCollapseButton,
- selectContainer,
- selectCommentBox,
- selectCommentButton,
- selectForm,
- selectFormContainer,
- selectMrBox,
- selectNote,
- selectNoteContainer,
- selectRemember,
- selectToken,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
deleted file mode 100644
index fdf8ad7c41f..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
+++ /dev/null
@@ -1,79 +0,0 @@
-import { CLEAR, FORM, FORM_CONTAINER, WHITE } from '../shared';
-import {
- selectCollapseButton,
- selectForm,
- selectFormContainer,
- selectNoteContainer,
-} from './utils';
-import { collapseButton, commentIcon, compressIcon } from './wrapper_icons';
-
-const form = content => `
- <form id="${FORM}" novalidate>
- ${content}
- </form>
-`;
-
-const buttonAndForm = content => `
- <div id="${FORM_CONTAINER}" class="gitlab-form-open">
- ${collapseButton}
- ${form(content)}
- </div>
-`;
-
-const addForm = nextForm => {
- const formWrapper = selectForm();
- formWrapper.innerHTML = nextForm;
-};
-
-function toggleForm() {
- const toggleButton = selectCollapseButton();
- const currentForm = selectForm();
- const formContainer = selectFormContainer();
- const noteContainer = selectNoteContainer();
- const OPEN = 'open';
- const CLOSED = 'closed';
-
- /*
- You may wonder why we spread the arrays before we reverse them.
- In the immortal words of MDN,
- Careful: reverse is destructive. It also changes the original array
- */
-
- const openButtonClasses = ['gitlab-collapse-closed', 'gitlab-collapse-open'];
- const closedButtonClasses = [...openButtonClasses].reverse();
- const openContainerClasses = ['gitlab-wrapper-closed', 'gitlab-wrapper-open'];
- const closedContainerClasses = [...openContainerClasses].reverse();
-
- const stateVals = {
- [OPEN]: {
- buttonClasses: openButtonClasses,
- containerClasses: openContainerClasses,
- icon: compressIcon,
- display: 'flex',
- backgroundColor: WHITE,
- },
- [CLOSED]: {
- buttonClasses: closedButtonClasses,
- containerClasses: closedContainerClasses,
- icon: commentIcon,
- display: 'none',
- backgroundColor: CLEAR,
- },
- };
-
- const nextState = toggleButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
- const currentVals = stateVals[nextState];
-
- formContainer.classList.replace(...currentVals.containerClasses);
- formContainer.style.backgroundColor = currentVals.backgroundColor;
- formContainer.classList.toggle('gitlab-form-open');
- currentForm.style.display = currentVals.display;
- toggleButton.classList.replace(...currentVals.buttonClasses);
- toggleButton.innerHTML = currentVals.icon;
-
- if (noteContainer && noteContainer.innerText.length > 0) {
- noteContainer.style.display = currentVals.display;
- }
-}
-
-export { addForm, buttonAndForm, toggleForm };
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
deleted file mode 100644
index b686fd4f5c2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/wrapper_icons.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import { buttonClearStyles } from './utils';
-
-const commentIcon = `
- <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/comment</title><path d="M4 11.132l1.446-.964A1 1 0 0 1 6 10h5a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1H5a1 1 0 0 0-1 1v6.132zM6.303 12l-2.748 1.832A1 1 0 0 1 2 13V5a3 3 0 0 1 3-3h6a3 3 0 0 1 3 3v4a3 3 0 0 1-3 3H6.303z" id="gitlab-comment-icon"/></svg>
-`;
-
-const compressIcon = `
- <svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>icn/compress</title><path d="M5.27 12.182l-1.562 1.561a1 1 0 0 1-1.414 0h-.001a1 1 0 0 1 0-1.415l1.56-1.56L2.44 9.353a.5.5 0 0 1 .353-.854H7.09a.5.5 0 0 1 .5.5v4.294a.5.5 0 0 1-.853.353l-1.467-1.465zm6.911-6.914l1.464 1.464a.5.5 0 0 1-.353.854H8.999a.5.5 0 0 1-.5-.5V2.793a.5.5 0 0 1 .854-.354l1.414 1.415 1.56-1.561a1 1 0 1 1 1.415 1.414l-1.561 1.56z" id="gitlab-compress-icon"/></svg>
-`;
-
-const collapseButton = `
- <button id='gitlab-collapse' style='${buttonClearStyles}' class='gitlab-button gitlab-button-secondary gitlab-collapse gitlab-collapse-open'>${compressIcon}</button>
-`;
-
-export { commentIcon, compressIcon, collapseButton };
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
deleted file mode 100644
index 67b3fadd772..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/index.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import './styles/toolbar.css';
-
-import { buttonAndForm, note, selectForm, selectContainer } from './components';
-import { REVIEW_CONTAINER } from './shared';
-import { eventLookup, getInitialView, initializeGlobalListeners, initializeState } from './store';
-
-/*
-
- Welcome to the visual review toolbar files. A few useful notes:
-
- - These files build a static script that is served from our webpack
- assets folder. (https://gitlab.com/assets/webpack/visual_review_toolbar.js)
-
- - To compile this file, run `yarn webpack-vrt`.
-
- - Vue is not used in these files because we do not want to ask users to
- install another library at this time. It's all pure vanilla javascript.
-
-*/
-
-window.addEventListener('load', () => {
- initializeState(window, document);
-
- const mainContent = buttonAndForm(getInitialView());
- const container = document.createElement('div');
- container.setAttribute('id', REVIEW_CONTAINER);
- container.insertAdjacentHTML('beforeend', note);
- container.insertAdjacentHTML('beforeend', mainContent);
-
- document.body.insertBefore(container, document.body.firstChild);
-
- selectContainer().addEventListener('click', event => {
- eventLookup(event.target.id)();
- });
-
- selectForm().addEventListener('submit', event => {
- // this is important to prevent the form from adding data
- // as URL params and inadvertently revealing secrets
- event.preventDefault();
-
- const id =
- event.target.querySelector('.gitlab-button-wrapper') &&
- event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0] &&
- event.target.querySelector('.gitlab-button-wrapper').getElementsByTagName('button')[0].id;
-
- // even if this is called with false, it's ok; it will get the default no-op
- eventLookup(id)();
- });
-
- initializeGlobalListeners();
-});
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/constants.js b/app/assets/javascripts/visual_review_toolbar/shared/constants.js
deleted file mode 100644
index 0d5b666ab0a..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/constants.js
+++ /dev/null
@@ -1,56 +0,0 @@
-// component selectors
-const CHANGE_MR_ID_BUTTON = 'gitlab-change-mr';
-const COLLAPSE_BUTTON = 'gitlab-collapse';
-const COMMENT_BOX = 'gitlab-comment';
-const COMMENT_BUTTON = 'gitlab-comment-button';
-const FORM = 'gitlab-form';
-const FORM_CONTAINER = 'gitlab-form-wrapper';
-const LOGIN = 'gitlab-login-button';
-const LOGOUT = 'gitlab-logout-button';
-const MR_ID = 'gitlab-submit-mr';
-const MR_ID_BUTTON = 'gitlab-submit-mr-button';
-const NOTE = 'gitlab-validation-note';
-const NOTE_CONTAINER = 'gitlab-note-wrapper';
-const REMEMBER_ITEM = 'gitlab-remember-item';
-const REVIEW_CONTAINER = 'gitlab-review-container';
-const TOKEN_BOX = 'gitlab-token';
-
-// Storage keys
-const STORAGE_PREFIX = '--gitlab'; // Using `--` to make the prefix more unique
-const STORAGE_MR_ID = `${STORAGE_PREFIX}-merge-request-id`;
-const STORAGE_TOKEN = `${STORAGE_PREFIX}-token`;
-const STORAGE_COMMENT = `${STORAGE_PREFIX}-comment`;
-
-// colors — these are applied programmatically
-// rest of styles belong in ./styles
-const BLACK = 'rgba(46, 46, 46, 1)';
-const CLEAR = 'rgba(255, 255, 255, 0)';
-const MUTED = 'rgba(223, 223, 223, 0.5)';
-const RED = 'rgba(219, 59, 33, 1)';
-const WHITE = 'rgba(250, 250, 250, 1)';
-
-export {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/index.js b/app/assets/javascripts/visual_review_toolbar/shared/index.js
deleted file mode 100644
index d8ccb170592..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/index.js
+++ /dev/null
@@ -1,55 +0,0 @@
-import {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-} from './constants';
-
-import { localStorage, sessionStorage } from './storage_utils';
-
-export {
- localStorage,
- sessionStorage,
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- MR_ID,
- MR_ID_BUTTON,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_ITEM,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- STORAGE_MR_ID,
- STORAGE_TOKEN,
- STORAGE_COMMENT,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js b/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js
deleted file mode 100644
index 00456d3536e..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js
+++ /dev/null
@@ -1,42 +0,0 @@
-import { setUsingGracefulStorageFlag } from '../store/state';
-
-const TEST_KEY = 'gitlab-storage-test';
-
-const createStorageStub = () => {
- const items = {};
-
- return {
- getItem(key) {
- return items[key];
- },
- setItem(key, value) {
- items[key] = value;
- },
- removeItem(key) {
- delete items[key];
- },
- };
-};
-
-const hasStorageSupport = storage => {
- // Support test taken from https://stackoverflow.com/a/11214467/1708147
- try {
- storage.setItem(TEST_KEY, TEST_KEY);
- storage.removeItem(TEST_KEY);
- setUsingGracefulStorageFlag(true);
-
- return true;
- } catch (err) {
- setUsingGracefulStorageFlag(false);
- return false;
- }
-};
-
-const useGracefulStorage = storage =>
- // If a browser does not support local storage, let's return a graceful implementation.
- hasStorageSupport(storage) ? storage : createStorageStub();
-
-const localStorage = useGracefulStorage(window.localStorage);
-const sessionStorage = useGracefulStorage(window.sessionStorage);
-
-export { localStorage, sessionStorage };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/events.js b/app/assets/javascripts/visual_review_toolbar/store/events.js
deleted file mode 100644
index c9095c77ef1..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/events.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import {
- addMr,
- authorizeUser,
- changeSelectedMr,
- logoutUser,
- postComment,
- saveComment,
- toggleForm,
-} from '../components';
-
-import {
- CHANGE_MR_ID_BUTTON,
- COLLAPSE_BUTTON,
- COMMENT_BUTTON,
- LOGIN,
- LOGOUT,
- MR_ID_BUTTON,
-} from '../shared';
-
-import { state } from './state';
-import debounce from './utils';
-
-const noop = () => {};
-
-// State needs to be bound here to be acted on
-// because these are called by click events and
-// as such are called with only the `event` object
-const eventLookup = id => {
- switch (id) {
- case CHANGE_MR_ID_BUTTON:
- return () => {
- saveComment();
- changeSelectedMr(state);
- };
- case COLLAPSE_BUTTON:
- return toggleForm;
- case COMMENT_BUTTON:
- return postComment.bind(null, state);
- case LOGIN:
- return authorizeUser.bind(null, state);
- case LOGOUT:
- return () => {
- saveComment();
- logoutUser(state);
- };
- case MR_ID_BUTTON:
- return addMr.bind(null, state);
- default:
- return noop;
- }
-};
-
-const updateWindowSize = wind => {
- state.innerWidth = wind.innerWidth;
- state.innerHeight = wind.innerHeight;
-};
-
-const initializeGlobalListeners = () => {
- window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
- window.addEventListener('beforeunload', event => {
- if (state.usingGracefulStorage) {
- // if there is no browser storage support, reloading will lose the comment; this way, the user will be warned
- // we assign the return value because it is required by Chrome see: https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload#Example,
- event.preventDefault();
- /* eslint-disable-next-line no-param-reassign */
- event.returnValue = '';
- }
-
- saveComment();
- });
-};
-
-export { eventLookup, initializeGlobalListeners };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/index.js b/app/assets/javascripts/visual_review_toolbar/store/index.js
deleted file mode 100644
index 07c8dd6f1d2..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/index.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { eventLookup, initializeGlobalListeners } from './events';
-import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state';
-
-export {
- eventLookup,
- getInitialView,
- initializeGlobalListeners,
- initializeState,
- nextView,
- setUsingGracefulStorageFlag,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/store/state.js b/app/assets/javascripts/visual_review_toolbar/store/state.js
deleted file mode 100644
index b7853bb0723..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/state.js
+++ /dev/null
@@ -1,95 +0,0 @@
-import { comment, login, mrForm } from '../components';
-import { localStorage, COMMENT_BOX, LOGIN, MR_ID, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
-
-const state = {
- browser: '',
- usingGracefulStorage: '',
- innerWidth: '',
- innerHeight: '',
- mergeRequestId: '',
- mrUrl: '',
- platform: '',
- projectId: '',
- userAgent: '',
- token: '',
-};
-
-// adapted from https://developer.mozilla.org/en-US/docs/Web/API/Window/navigator#Example_2_Browser_detect_and_return_an_index
-const getBrowserId = sUsrAg => {
- /* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
- const aKeys = ['MSIE', 'Edge', 'Firefox', 'Safari', 'Chrome', 'Opera'];
- let nIdx = aKeys.length - 1;
-
- for (nIdx; nIdx > -1 && sUsrAg.indexOf(aKeys[nIdx]) === -1; nIdx -= 1);
- return aKeys[nIdx];
-};
-
-const nextView = (appState, form = 'none') => {
- const formsList = {
- [COMMENT_BOX]: currentState => (currentState.token ? mrForm : login),
- [LOGIN]: currentState => (currentState.mergeRequestId ? comment(currentState) : mrForm),
- [MR_ID]: currentState => (currentState.token ? comment(currentState) : login),
- none: currentState => {
- if (!currentState.token) {
- return login;
- }
-
- if (currentState.token && !currentState.mergeRequestId) {
- return mrForm;
- }
-
- return comment(currentState);
- },
- };
-
- return formsList[form](appState);
-};
-
-const initializeState = (wind, doc) => {
- const {
- innerWidth,
- innerHeight,
- navigator: { platform, userAgent },
- } = wind;
-
- const browser = getBrowserId(userAgent);
-
- const scriptEl = doc.getElementById('review-app-toolbar-script');
- const { projectId, mergeRequestId, mrUrl, projectPath } = scriptEl.dataset;
-
- // This mutates our default state object above. It's weird but it makes the linter happy.
- Object.assign(state, {
- browser,
- innerWidth,
- innerHeight,
- mergeRequestId,
- mrUrl,
- platform,
- projectId,
- projectPath,
- userAgent,
- });
-
- return state;
-};
-
-const getInitialView = () => {
- const token = localStorage.getItem(STORAGE_TOKEN);
- const mrId = localStorage.getItem(STORAGE_MR_ID);
-
- if (token) {
- state.token = token;
- }
-
- if (mrId) {
- state.mergeRequestId = mrId;
- }
-
- return nextView(state);
-};
-
-const setUsingGracefulStorageFlag = flag => {
- state.usingGracefulStorage = !flag;
-};
-
-export { initializeState, getInitialView, nextView, setUsingGracefulStorageFlag, state };
diff --git a/app/assets/javascripts/visual_review_toolbar/store/utils.js b/app/assets/javascripts/visual_review_toolbar/store/utils.js
deleted file mode 100644
index 5cf145351b3..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/store/utils.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const debounce = (fn, time) => {
- let current;
-
- const debounced = () => {
- if (current) {
- clearTimeout(current);
- }
-
- current = setTimeout(fn, time);
- };
-
- return debounced;
-};
-
-export default debounce;
diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
deleted file mode 100644
index d1a8d66ef40..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
+++ /dev/null
@@ -1,188 +0,0 @@
-/*
- As a standalone script, the toolbar has its own css
- */
-
-#gitlab-collapse > * {
- pointer-events: none;
-}
-
-#gitlab-comment {
- background-color: #fafafa;
-}
-
-#gitlab-form {
- display: flex;
- flex-direction: column;
- width: 100%;
- margin-bottom: 0;
-}
-
-#gitlab-note-wrapper {
- display: flex;
- flex-direction: column;
- background-color: #fafafa;
- border-radius: 4px;
- margin-bottom: .5rem;
- padding: 1rem;
-}
-
-#gitlab-form-wrapper {
- overflow: auto;
- display: flex;
- flex-direction: row-reverse;
- border-radius: 4px;
-}
-
-#gitlab-review-container {
- max-width: 22rem;
- max-height: 22rem;
- overflow: auto;
- display: flex;
- flex-direction: column;
- position: fixed;
- bottom: 1rem;
- right: 1rem;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans', Ubuntu, Cantarell,
- 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
- 'Noto Color Emoji';
- font-size: .8rem;
- font-weight: 400;
- color: #2e2e2e;
- z-index: 9999; /* toolbar should always be on top */
-}
-
-.gitlab-wrapper-open {
- max-width: 22rem;
- max-height: 22rem;
-}
-
-.gitlab-wrapper-closed {
- max-width: 3.4rem;
- max-height: 3.4rem;
-}
-
-.gitlab-button {
- cursor: pointer;
- transition: background-color 100ms linear, border-color 100ms linear, color 100ms linear, box-shadow 100ms linear;
-}
-
-.gitlab-button-secondary {
- background: none #fafafa;
- margin: 0 .5rem;
- border: 1px solid #e3e3e3;
-}
-
-.gitlab-button-secondary:hover {
- background-color: #f0f0f0;
- border-color: #e3e3e3;
- color: #2e2e2e;
-}
-
-.gitlab-button-secondary:active {
- color: #2e2e2e;
- background-color: #e1e1e1;
- border-color: #dadada;
-}
-
-.gitlab-button-success:hover {
- color: #fff;
- background-color: #137e3f;
- border-color: #127339;
-}
-
-.gitlab-button-success:active {
- background-color: #168f48;
- border-color: #12753a;
- color: #fff;
-}
-
-.gitlab-button-success {
- background-color: #1aaa55;
- border: 1px solid #168f48;
- color: #fff;
-}
-
-.gitlab-button-wide {
- width: 100%;
-}
-
-.gitlab-button-wrapper {
- margin-top: 0.5rem;
- display: flex;
- align-items: baseline;
- /*
- this makes sure the hit enter to submit picks the correct button
- on the comment view
- */
- flex-direction: row-reverse;
-}
-
-.gitlab-collapse {
- width: 2.4rem;
- height: 2.2rem;
- margin-left: 1rem;
- padding: .5rem;
-}
-
-.gitlab-collapse-closed {
- align-self: center;
-}
-
-.gitlab-checkbox-label {
- padding: 0 .2rem;
-}
-
-.gitlab-checkbox-wrapper {
- display: flex;
- align-items: baseline;
-}
-
-.gitlab-form-open {
- padding: 1rem;
- background-color: #fafafa;
-}
-
-.gitlab-label {
- font-weight: 600;
- display: inline-block;
- width: 100%;
-}
-
-.gitlab-link {
- color: #1b69b6;
- text-decoration: none;
- background-color: transparent;
- background-image: none;
-}
-
-.gitlab-link:hover {
- text-decoration: underline;
-}
-
-.gitlab-link-button {
- border: none;
- cursor: pointer;
- padding: 0 .15rem;
-}
-
-.gitlab-message {
- padding: .25rem 0;
- margin: 0;
- line-height: 1.2rem;
-}
-
-.gitlab-metadata-note {
- font-size: .7rem;
- line-height: 1rem;
- color: #666;
- margin-bottom: .5rem;
-}
-
-.gitlab-input {
- width: 100%;
- border: 1px solid #dfdfdf;
- border-radius: 4px;
- padding: .1rem .2rem;
- min-height: 2rem;
- max-width: 17rem;
-}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
index c7b064b8506..339e154affc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_rebase.vue
@@ -50,6 +50,7 @@ export default {
startTag: '<span class="label-branch">',
endTag: '</span>',
},
+ false,
);
},
},
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 0f55bebd3fc..7843409f4a7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -87,9 +87,6 @@ export default class MergeRequestStore {
this.allowCollaboration = data.allow_collaboration;
this.sourceProjectId = data.source_project_id;
this.targetProjectId = data.target_project_id;
- this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled);
- this.mergeTrainsCount = data.merge_trains_count || 0;
- this.mergeTrainIndex = data.merge_train_index;
// CI related
this.hasCI = data.has_ci;
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/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/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 92190f8979e..9871771542d 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -469,6 +469,7 @@ $link-active-background: rgba(0, 0, 0, 0.04);
$link-hover-background: rgba(0, 0, 0, 0.06);
$inactive-badge-background: rgba(0, 0, 0, 0.08);
$sidebar-toggle-height: 60px;
+$sidebar-toggle-width: 40px;
$sidebar-milestone-toggle-bottom-margin: 10px;
/*
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/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 343cca96851..e77a2d1e333 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -86,6 +86,9 @@
}
.board {
+ // the next line cannot be replaced with .d-inline-block because it breaks display: none of SortableJS
+ // see https://gitlab.com/gitlab-org/gitlab-ce/issues/64828
+ display: inline-block;
width: calc(85vw - 15px);
@include media-breakpoint-up(sm) {
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
index 0f4bdb219a3..b88bd78cf3d 100644
--- a/app/assets/stylesheets/pages/container_registry.scss
+++ b/app/assets/stylesheets/pages/container_registry.scss
@@ -3,10 +3,6 @@
*/
.container-message {
- pre {
- white-space: pre-line;
- }
-
span .btn {
margin: 0;
}
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..0e844b0e4a5 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -126,6 +126,16 @@
}
}
+.assignee {
+ .merge-icon {
+ color: $orange-500;
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ text-shadow: -1px -1px 0 $white-light, 1px -1px 0 $white-light, -1px 1px 0 $white-light, 1px 1px 0 $white-light;
+ }
+}
+
.right-sidebar {
position: fixed;
top: $header-height;
@@ -202,7 +212,6 @@
&.assignee {
.author-link {
display: block;
- padding-left: 42px;
position: relative;
&:hover {
@@ -210,12 +219,6 @@
text-decoration: underline;
}
}
-
- .avatar {
- left: 0;
- position: absolute;
- top: 0;
- }
}
}
}
@@ -354,13 +357,6 @@
margin-top: 0;
}
- .assignee .avatar {
- float: left;
- margin-right: 10px;
- margin-bottom: 0;
- margin-left: 0;
- }
-
.assignee .user-list .avatar {
margin: 0;
}
@@ -521,7 +517,12 @@
display: none;
}
+ .merge-icon {
+ font-size: 10px;
+ }
+
.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/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 379df1c4db1..0b65b915abf 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -32,13 +32,11 @@
color: $gl-text-color-secondary;
}
- .git-access-header {
- padding: $gl-padding 0 $gl-padding-top;
- }
-
.git-clone-holder {
- width: 100%;
- padding-bottom: 40px;
+ .input-group-prepend,
+ .input-group-append {
+ background-color: transparent;
+ }
}
button.sidebar-toggle {
@@ -48,19 +46,8 @@
display: block;
}
- @include media-breakpoint-up(sm) {
- &.has-sidebar-toggle {
- padding-right: 40px;
- }
-
- .git-clone-holder {
- width: 480px;
- padding-bottom: $gl-padding;
- }
-
- .nav-controls {
- width: auto;
- }
+ &.has-sidebar-toggle .git-access-header {
+ padding-right: $sidebar-toggle-width;
}
@include media-breakpoint-up(md) {
@@ -105,10 +92,6 @@
padding: 0 $gl-padding;
}
- .block {
- width: 100%;
- }
-
a {
color: $layout-link-gray;
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index f111c7ca8cc..30a567c3bef 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -36,7 +36,7 @@ class AutocompleteController < ApplicationController
end
def award_emojis
- render json: AwardedEmojiFinder.new(current_user).execute
+ render json: AwardEmojis::CollectUserEmojiService.new(current_user).execute
end
def merge_request_target_branches
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/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 3489ea78b77..8ea77b994de 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -2,8 +2,8 @@
module IssuableCollections
extend ActiveSupport::Concern
- include CookiesHelper
include SortingHelper
+ include SortingPreference
include Gitlab::IssuableMetadata
include Gitlab::Utils::StrongMemoize
@@ -127,47 +127,8 @@ module IssuableCollections
'opened'
end
- def set_sort_order
- set_sort_order_from_user_preference || set_sort_order_from_cookie || default_sort_order
- end
-
- def set_sort_order_from_user_preference
- return unless current_user
- return unless issuable_sorting_field
-
- user_preference = current_user.user_preference
-
- sort_param = params[:sort]
- sort_param ||= user_preference[issuable_sorting_field]
-
- return sort_param if Gitlab::Database.read_only?
-
- if user_preference[issuable_sorting_field] != sort_param
- user_preference.update(issuable_sorting_field => sort_param)
- end
-
- sort_param
- end
-
- # Implement issuable_sorting_field method on controllers
- # to choose which column to store the sorting parameter.
- def issuable_sorting_field
- nil
- end
-
- def set_sort_order_from_cookie
- sort_param = params[:sort] if params[:sort].present?
- # fallback to legacy cookie value for backward compatibility
- sort_param ||= cookies['issuable_sort']
- sort_param ||= cookies[remember_sorting_key]
-
- sort_value = update_cookie_value(sort_param)
- set_secure_cookie(remember_sorting_key, sort_value)
- sort_value
- end
-
- def remember_sorting_key
- @remember_sorting_key ||= "#{collection_type.downcase}_sort"
+ def legacy_sort_cookie_name
+ 'issuable_sort'
end
def default_sort_order
@@ -178,17 +139,6 @@ module IssuableCollections
end
end
- # Update old values to the actual ones.
- def update_cookie_value(value)
- case value
- when 'id_asc' then sort_value_oldest_created
- when 'id_desc' then sort_value_recently_created
- when 'downvotes_asc' then sort_value_popularity
- when 'downvotes_desc' then sort_value_popularity
- else value
- end
- end
-
def finder
@finder ||= issuable_finder_for(finder_type)
end
diff --git a/app/controllers/concerns/issuable_collections_action.rb b/app/controllers/concerns/issuable_collections_action.rb
index 4ad287c4a13..0a6f684a9fc 100644
--- a/app/controllers/concerns/issuable_collections_action.rb
+++ b/app/controllers/concerns/issuable_collections_action.rb
@@ -32,7 +32,7 @@ module IssuableCollectionsAction
private
- def issuable_sorting_field
+ def sorting_field
case action_name
when 'issues'
Issue::SORTING_PREFERENCE_FIELD
diff --git a/app/controllers/concerns/sorting_preference.rb b/app/controllers/concerns/sorting_preference.rb
new file mode 100644
index 00000000000..a51b68147d5
--- /dev/null
+++ b/app/controllers/concerns/sorting_preference.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+module SortingPreference
+ include SortingHelper
+ include CookiesHelper
+
+ def set_sort_order
+ set_sort_order_from_user_preference || set_sort_order_from_cookie || params[:sort] || default_sort_order
+ end
+
+ # Implement sorting_field method on controllers
+ # to choose which column to store the sorting parameter.
+ def sorting_field
+ nil
+ end
+
+ # Implement default_sort_order method on controllers
+ # to choose which default sort should be applied if
+ # sort param is not provided.
+ def default_sort_order
+ nil
+ end
+
+ # Implement legacy_sort_cookie_name method on controllers
+ # to set sort from cookie for backwards compatibility.
+ def legacy_sort_cookie_name
+ nil
+ end
+
+ private
+
+ def set_sort_order_from_user_preference
+ return unless current_user
+ return unless sorting_field
+
+ user_preference = current_user.user_preference
+
+ sort_param = params[:sort]
+ sort_param ||= user_preference[sorting_field]
+
+ return sort_param if Gitlab::Database.read_only?
+
+ if user_preference[sorting_field] != sort_param
+ user_preference.update(sorting_field => sort_param)
+ end
+
+ sort_param
+ end
+
+ def set_sort_order_from_cookie
+ return unless legacy_sort_cookie_name
+
+ sort_param = params[:sort] if params[:sort].present?
+ # fallback to legacy cookie value for backward compatibility
+ sort_param ||= cookies[legacy_sort_cookie_name]
+ sort_param ||= cookies[remember_sorting_key]
+
+ sort_value = update_cookie_value(sort_param)
+ set_secure_cookie(remember_sorting_key, sort_value)
+ sort_value
+ end
+
+ # Convert sorting_field to legacy cookie name for backwards compatibility
+ # :merge_requests_sort => 'mergerequest_sort'
+ # :issues_sort => 'issue_sort'
+ def remember_sorting_key
+ @remember_sorting_key ||= sorting_field
+ .to_s
+ .split('_')[0..-2]
+ .map(&:singularize)
+ .join('')
+ .concat('_sort')
+ end
+
+ # Update old values to the actual ones.
+ def update_cookie_value(value)
+ case value
+ when 'id_asc' then sort_value_oldest_created
+ when 'id_desc' then sort_value_recently_created
+ when 'downvotes_asc' then sort_value_popularity
+ when 'downvotes_desc' then sort_value_popularity
+ else value
+ end
+ end
+end
diff --git a/app/controllers/concerns/toggle_award_emoji.rb b/app/controllers/concerns/toggle_award_emoji.rb
index 97b343f8b1a..24d178781d6 100644
--- a/app/controllers/concerns/toggle_award_emoji.rb
+++ b/app/controllers/concerns/toggle_award_emoji.rb
@@ -7,12 +7,9 @@ module ToggleAwardEmoji
authenticate_user!
name = params.require(:name)
- if awardable.user_can_award?(current_user)
- awardable.toggle_award_emoji(name, current_user)
-
- todoable = to_todoable(awardable)
- TodoService.new.new_award_emoji(todoable, current_user) if todoable
+ service = AwardEmojis::ToggleService.new(awardable, name, current_user).execute
+ if service[:status] == :success
render json: { ok: true }
else
render json: { ok: false }
@@ -21,18 +18,6 @@ module ToggleAwardEmoji
private
- def to_todoable(awardable)
- case awardable
- when Note
- # we don't create todos for personal snippet comments for now
- awardable.for_personal_snippet? ? nil : awardable.noteable
- when MergeRequest, Issue
- awardable
- when Snippet
- nil
- end
- end
-
def awardable
raise NotImplementedError
end
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index daeb8fda417..71f18694613 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -4,10 +4,12 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
include ParamsBackwardCompatibility
include RendersMemberAccess
include OnboardingExperimentHelper
+ include SortingHelper
+ include SortingPreference
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
before_action :set_non_archived_param
- before_action :default_sorting
+ before_action :set_sorting
before_action :projects, only: [:index]
skip_cross_project_access_check :index, :starred
@@ -59,11 +61,6 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
end
- def default_sorting
- params[:sort] ||= 'latest_activity_desc'
- @sort = params[:sort]
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def load_projects(finder_params)
@total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
@@ -88,4 +85,17 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
Events::RenderService.new(current_user).execute(@events, atom_request: request.format.atom?)
end
+
+ def set_sorting
+ params[:sort] = set_sort_order
+ @sort = params[:sort]
+ end
+
+ def default_sort_order
+ sort_value_latest_activity
+ end
+
+ def sorting_field
+ Project::SORTING_PREFERENCE_FIELD
+ end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index ef86d5f981a..271f2b4b57d 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -3,12 +3,13 @@
class Explore::ProjectsController < Explore::ApplicationController
include ParamsBackwardCompatibility
include RendersMemberAccess
+ include SortingHelper
+ include SortingPreference
before_action :set_non_archived_param
+ before_action :set_sorting
def index
- params[:sort] ||= 'latest_activity_desc'
- @sort = params[:sort]
@projects = load_projects
respond_to do |format|
@@ -23,7 +24,6 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending
params[:trending] = true
- @sort = params[:sort]
@projects = load_projects
respond_to do |format|
@@ -67,4 +67,17 @@ class Explore::ProjectsController < Explore::ApplicationController
prepare_projects_for_rendering(projects)
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def set_sorting
+ params[:sort] = set_sort_order
+ @sort = params[:sort]
+ end
+
+ def default_sort_order
+ sort_value_latest_activity
+ end
+
+ def sorting_field
+ Project::SORTING_PREFERENCE_FIELD
+ end
end
diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index 5ecf4f114cf..da39d64c93d 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
class JwtController < ApplicationController
+ skip_around_action :set_session_storage
skip_before_action :authenticate_user!
skip_before_action :verify_authenticity_token
before_action :authenticate_project_or_user
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/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index bc9166b9df3..b7fd286bfe0 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -190,7 +190,7 @@ class Projects::IssuesController < Projects::ApplicationController
protected
- def issuable_sorting_field
+ def sorting_field
Issue::SORTING_PREFERENCE_FIELD
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index f4d381244d9..f4cc0a5851b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -219,7 +219,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
alias_method :issuable, :merge_request
alias_method :awardable, :merge_request
- def issuable_sorting_field
+ def sorting_field
MergeRequest::SORTING_PREFERENCE_FIELD
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index db3b7c8b177..499d4918899 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -3,6 +3,7 @@
class Projects::PipelinesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts]
+ before_action :set_pipeline_path, only: [:show]
before_action :authorize_read_pipeline!
before_action :authorize_read_build!, only: [:index]
before_action :authorize_create_pipeline!, only: [:new, :create]
@@ -174,14 +175,36 @@ class Projects::PipelinesController < Projects::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def pipeline
- @pipeline ||= project
- .all_pipelines
- .includes(user: :status)
- .find_by!(id: params[:id])
- .present(current_user: current_user)
+ @pipeline ||= if params[:id].blank? && params[:latest]
+ latest_pipeline
+ else
+ project
+ .all_pipelines
+ .includes(builds: :tags, user: :status)
+ .find_by!(id: params[:id])
+ .present(current_user: current_user)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
+ def set_pipeline_path
+ @pipeline_path ||= if params[:id].blank? && params[:latest]
+ latest_project_pipelines_path(@project, params['ref'])
+ else
+ project_pipeline_path(@project, @pipeline)
+ end
+ end
+
+ def latest_pipeline
+ ref = params['ref'].presence || @project.default_branch
+ sha = @project.commit(ref)&.sha
+
+ @project.ci_pipelines
+ .newest_first(ref: ref, sha: sha)
+ .first
+ &.present(current_user: current_user)
+ end
+
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb
index e4093bed0ef..4efe956e973 100644
--- a/app/controllers/projects/starrers_controller.rb
+++ b/app/controllers/projects/starrers_controller.rb
@@ -5,11 +5,11 @@ class Projects::StarrersController < Projects::ApplicationController
def index
@starrers = UsersStarProjectsFinder.new(@project, params, current_user: @current_user).execute
+ @sort = params[:sort].presence || sort_value_name
+ @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
- @sort = params[:sort].presence || sort_value_name
- @starrers = @starrers.sort_by_attribute(@sort).page(params[:page])
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/projects_controller.rb b/app/controllers/projects_controller.rb
index e04cbf10470..5f335de4d6b 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -29,6 +29,7 @@ class ProjectsController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export]
+ before_action :authorize_archive_project!, only: [:archive, :unarchive]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -164,8 +165,6 @@ class ProjectsController < Projects::ApplicationController
end
def archive
- return access_denied! unless can?(current_user, :archive_project, @project)
-
::Projects::UpdateService.new(@project, current_user, archived: true).execute
respond_to do |format|
@@ -174,8 +173,6 @@ class ProjectsController < Projects::ApplicationController
end
def unarchive
- return access_denied! unless can?(current_user, :archive_project, @project)
-
::Projects::UpdateService.new(@project, current_user, archived: false).execute
respond_to do |format|
diff --git a/app/finders/award_emojis_finder.rb b/app/finders/award_emojis_finder.rb
new file mode 100644
index 00000000000..7320e035409
--- /dev/null
+++ b/app/finders/award_emojis_finder.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+class AwardEmojisFinder
+ attr_reader :awardable, :params
+
+ def initialize(awardable, params = {})
+ @awardable = awardable
+ @params = params
+
+ validate_params
+ end
+
+ def execute
+ awards = awardable.award_emoji
+ awards = by_name(awards)
+ awards = by_awarded_by(awards)
+ awards
+ end
+
+ private
+
+ def by_name(awards)
+ return awards unless params[:name]
+
+ awards.named(params[:name])
+ end
+
+ def by_awarded_by(awards)
+ return awards unless params[:awarded_by]
+
+ awards.awarded_by(params[:awarded_by])
+ end
+
+ def validate_params
+ return unless params.present?
+
+ validate_name_param
+ validate_awarded_by_param
+ end
+
+ def validate_name_param
+ return unless params[:name]
+
+ raise ArgumentError, 'Invalid name param' unless params[:name].in?(Gitlab::Emoji.emojis_names)
+ end
+
+ def validate_awarded_by_param
+ return unless params[:awarded_by]
+
+ # awarded_by can be a `User`, or an ID
+ unless params[:awarded_by].is_a?(User) || params[:awarded_by].to_s.match(/\A\d+\Z/)
+ raise ArgumentError, 'Invalid awarded_by param'
+ end
+ end
+end
diff --git a/app/finders/awarded_emoji_finder.rb b/app/finders/awarded_emoji_finder.rb
deleted file mode 100644
index f0cc17f3b26..00000000000
--- a/app/finders/awarded_emoji_finder.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-# Class for retrieving information about emoji awarded _by_ a particular user.
-class AwardedEmojiFinder
- attr_reader :current_user
-
- # current_user - The User to generate the data for.
- def initialize(current_user = nil)
- @current_user = current_user
- end
-
- def execute
- return [] unless current_user
-
- # We want the resulting data set to be an Array containing the emoji names
- # in descending order, based on how often they were awarded.
- AwardEmoji
- .award_counts_for_user(current_user)
- .map { |name, _| { name: name } }
- end
-end
diff --git a/app/graphql/mutations/award_emojis/add.rb b/app/graphql/mutations/award_emojis/add.rb
index 8e050dd6d29..85f3eb065bb 100644
--- a/app/graphql/mutations/award_emojis/add.rb
+++ b/app/graphql/mutations/award_emojis/add.rb
@@ -10,14 +10,11 @@ module Mutations
check_object_is_awardable!(awardable)
- # TODO this will be handled by AwardEmoji::AddService
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
- award = awardable.create_award_emoji(args[:name], current_user)
+ service = ::AwardEmojis::AddService.new(awardable, args[:name], current_user).execute
{
- award_emoji: (award if award.persisted?),
- errors: errors_on_object(award)
+ award_emoji: (service[:award] if service[:status] == :success),
+ errors: service[:errors] || []
}
end
end
diff --git a/app/graphql/mutations/award_emojis/remove.rb b/app/graphql/mutations/award_emojis/remove.rb
index 3ba85e445b8..f8a3d0ce390 100644
--- a/app/graphql/mutations/award_emojis/remove.rb
+++ b/app/graphql/mutations/award_emojis/remove.rb
@@ -10,22 +10,11 @@ module Mutations
check_object_is_awardable!(awardable)
- # TODO this check can be removed once AwardEmoji services are available.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
- unless awardable.awarded_emoji?(args[:name], current_user)
- raise Gitlab::Graphql::Errors::ResourceNotAvailable,
- 'You have not awarded emoji of type name to the awardable'
- end
-
- # TODO this will be handled by AwardEmoji::DestroyService
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
- awardable.remove_award_emoji(args[:name], current_user)
+ service = ::AwardEmojis::DestroyService.new(awardable, args[:name], current_user).execute
{
# Mutation response is always a `nil` award_emoji
- errors: []
+ errors: service[:errors] || []
}
end
end
diff --git a/app/graphql/mutations/award_emojis/toggle.rb b/app/graphql/mutations/award_emojis/toggle.rb
index c03902e8035..d822048f3a6 100644
--- a/app/graphql/mutations/award_emojis/toggle.rb
+++ b/app/graphql/mutations/award_emojis/toggle.rb
@@ -15,23 +15,15 @@ module Mutations
check_object_is_awardable!(awardable)
- # TODO this will be handled by AwardEmoji::ToggleService
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/63372 and
- # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29782
- award = awardable.toggle_award_emoji(args[:name], current_user)
-
- # Destroy returns a collection :(
- award = award.first if award.is_a?(Array)
-
- errors = errors_on_object(award)
+ service = ::AwardEmojis::ToggleService.new(awardable, args[:name], current_user).execute
toggled_on = awardable.awarded_emoji?(args[:name], current_user)
{
# For consistency with the AwardEmojis::Remove mutation, only return
# the AwardEmoji if it was created and not destroyed
- award_emoji: (award if toggled_on),
- errors: errors,
+ award_emoji: (service[:award] if toggled_on),
+ errors: service[:errors] || [],
toggled_on: toggled_on
}
end
diff --git a/app/graphql/types/namespace_type.rb b/app/graphql/types/namespace_type.rb
index f105e9e6e28..35a97b5ace0 100644
--- a/app/graphql/types/namespace_type.rb
+++ b/app/graphql/types/namespace_type.rb
@@ -19,6 +19,11 @@ module Types
field :lfs_enabled, GraphQL::BOOLEAN_TYPE, null: true, method: :lfs_enabled?
field :request_access_enabled, GraphQL::BOOLEAN_TYPE, null: true
+ field :root_storage_statistics, Types::RootStorageStatisticsType,
+ null: true,
+ description: 'The aggregated storage statistics. Only available for root namespaces',
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader.new(obj.id).find }
+
field :projects,
Types::ProjectType.connection_type,
null: false,
diff --git a/app/graphql/types/root_storage_statistics_type.rb b/app/graphql/types/root_storage_statistics_type.rb
new file mode 100644
index 00000000000..a7498ee0a2e
--- /dev/null
+++ b/app/graphql/types/root_storage_statistics_type.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Types
+ class RootStorageStatisticsType < BaseObject
+ graphql_name 'RootStorageStatistics'
+
+ authorize :read_statistics
+
+ field :storage_size, GraphQL::INT_TYPE, null: false, description: 'The total storage in bytes'
+ field :repository_size, GraphQL::INT_TYPE, null: false, description: 'The git repository size in bytes'
+ field :lfs_objects_size, GraphQL::INT_TYPE, null: false, description: 'The LFS objects size in bytes'
+ field :build_artifacts_size, GraphQL::INT_TYPE, null: false, description: 'The CI artifacts size in bytes'
+ field :packages_size, GraphQL::INT_TYPE, null: false, description: 'The packages size in bytes'
+ field :wiki_size, GraphQL::INT_TYPE, null: false, description: 'The wiki size in bytes'
+ end
+end
diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb
index f2b5b82b013..144df676304 100644
--- a/app/helpers/ci_status_helper.rb
+++ b/app/helpers/ci_status_helper.rb
@@ -105,14 +105,13 @@ module CiStatusHelper
path = pipelines_project_commit_path(project, commit, ref: ref)
render_status_with_link(
- 'commit',
commit.status(ref),
path,
tooltip_placement: tooltip_placement,
icon_size: 24)
end
- def render_status_with_link(type, status, path = nil, tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
+ def render_status_with_link(status, path = nil, type: _('pipeline'), tooltip_placement: 'left', cssclass: '', container: 'body', icon_size: 16)
klass = "ci-status-link ci-status-icon-#{status.dasherize} d-inline-flex #{cssclass}"
title = "#{type.titleize}: #{ci_label_for_status(status)}"
data = { toggle: 'tooltip', placement: tooltip_placement, container: container }
diff --git a/app/helpers/import_helper.rb b/app/helpers/import_helper.rb
index 3d494c3de6a..9122ad5b35a 100644
--- a/app/helpers/import_helper.rb
+++ b/app/helpers/import_helper.rb
@@ -45,17 +45,14 @@ module ImportHelper
end
def import_github_authorize_message
- _('To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:')
+ _('To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories.')
end
def import_github_personal_access_token_message
- personal_access_token_link = link_to _('Personal Access Token'), 'https://github.com/settings/tokens'
+ link_url = 'https://github.com/settings/tokens'
+ link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: link_url }
- if github_import_configured?
- _('Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.').html_safe % { personal_access_token_link: personal_access_token_link }
- else
- _('To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.').html_safe % { personal_access_token_link: personal_access_token_link }
- end
+ _('Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import.').html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
end
def import_configure_github_admin_message
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index e2e007eee50..b88b25eb845 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -405,7 +405,11 @@ module IssuablesHelper
placement: is_collapsed ? 'left' : nil,
container: is_collapsed ? 'body' : nil,
boundary: 'viewport',
- is_collapsed: is_collapsed
+ is_collapsed: is_collapsed,
+ track_label: "right_sidebar",
+ track_property: "update_todo",
+ track_event: "click_button",
+ track_value: ""
}
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 5678304ffcf..8855e0cdd70 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -106,9 +106,9 @@ module NotificationsHelper
end
end
- def notification_setting_icon(notification_setting)
+ def notification_setting_icon(notification_setting = nil)
sprite_icon(
- notification_setting.disabled? ? "notifications-off" : "notifications",
+ !notification_setting.present? || notification_setting.disabled? ? "notifications-off" : "notifications",
css_class: "icon notifications-icon js-notifications-icon"
)
end
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 5d292094a05..3683f2ea9a9 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -125,9 +125,8 @@ class Notify < BaseMailer
def mail_thread(model, headers = {})
add_project_headers
add_unsubscription_headers_and_links
+ add_model_headers(model)
- headers["X-GitLab-#{model.class.name}-ID"] = model.id
- headers["X-GitLab-#{model.class.name}-IID"] = model.iid if model.respond_to?(:iid)
headers['X-GitLab-Reply-Key'] = reply_key
@reason = headers['X-GitLab-NotificationReason']
@@ -196,6 +195,18 @@ class Notify < BaseMailer
@reply_key ||= SentNotification.reply_key
end
+ # This method applies threading headers to the email to identify
+ # the instance we are discussing.
+ #
+ # All model instances must have `#id`, and may implement `#iid`.
+ def add_model_headers(object)
+ # Use replacement so we don't strip the module.
+ prefix = "X-GitLab-#{object.class.name.gsub(/::/, '-')}"
+
+ headers["#{prefix}-ID"] = object.id
+ headers["#{prefix}-IID"] = object.iid if object.respond_to?(:iid)
+ end
+
def add_project_headers
return unless @project
diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
index 88c8cb40ccb..a312bd24e78 100644
--- a/app/models/analytics/cycle_analytics/project_stage.rb
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -3,7 +3,12 @@
module Analytics
module CycleAnalytics
class ProjectStage < ApplicationRecord
+ include Analytics::CycleAnalytics::Stage
+
+ validates :project, presence: true
belongs_to :project
+
+ alias_attribute :parent, :project
end
end
end
diff --git a/app/models/award_emoji.rb b/app/models/award_emoji.rb
index e26162f6151..0ab302a0f3e 100644
--- a/app/models/award_emoji.rb
+++ b/app/models/award_emoji.rb
@@ -16,8 +16,10 @@ class AwardEmoji < ApplicationRecord
participant :user
- scope :downvotes, -> { where(name: DOWNVOTE_NAME) }
- scope :upvotes, -> { where(name: UPVOTE_NAME) }
+ scope :downvotes, -> { named(DOWNVOTE_NAME) }
+ scope :upvotes, -> { named(UPVOTE_NAME) }
+ scope :named, -> (names) { where(name: names) }
+ scope :awarded_by, -> (users) { where(user: users) }
after_save :expire_etag_cache
after_destroy :expire_etag_cache
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 3c0efca31db..7930bef5cf2 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -121,6 +121,8 @@ module Ci
scope :scheduled_actions, ->() { where(when: :delayed, status: COMPLETED_STATUSES + %i[scheduled]) }
scope :ref_protected, -> { where(protected: true) }
scope :with_live_trace, -> { where('EXISTS (?)', Ci::BuildTraceChunk.where('ci_builds.id = ci_build_trace_chunks.build_id').select(1)) }
+ scope :with_stale_live_trace, -> { with_live_trace.finished_before(12.hours.ago) }
+ scope :finished_before, -> (date) { finished.where('finished_at < ?', date) }
scope :matches_tag_ids, -> (tag_ids) do
matcher = ::ActsAsTaggableOn::Tagging
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 43ff874ac23..1c1c7a5ae7a 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -23,13 +23,17 @@ module Ci
project_type: 3
}
- RUNNER_QUEUE_EXPIRY_TIME = 60.minutes
ONLINE_CONTACT_TIMEOUT = 1.hour
- UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes
+ RUNNER_QUEUE_EXPIRY_TIME = 1.hour
+
+ # This needs to be less than `ONLINE_CONTACT_TIMEOUT`
+ UPDATE_CONTACT_COLUMN_EVERY = (40.minutes..55.minutes).freeze
+
AVAILABLE_TYPES_LEGACY = %w[specific shared].freeze
AVAILABLE_TYPES = runner_types.keys.freeze
AVAILABLE_STATUSES = %w[active paused online offline].freeze
AVAILABLE_SCOPES = (AVAILABLE_TYPES_LEGACY + AVAILABLE_TYPES + AVAILABLE_STATUSES).freeze
+
FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze
ignore_column :is_shared
@@ -46,7 +50,7 @@ module Ci
scope :active, -> { where(active: true) }
scope :paused, -> { where(active: false) }
- scope :online, -> { where('contacted_at > ?', contact_time_deadline) }
+ scope :online, -> { where('contacted_at > ?', online_contact_time_deadline) }
# The following query using negation is cheaper than using `contacted_at <= ?`
# because there are less runners online than have been created. The
# resulting query is quickly finding online ones and then uses the regular
@@ -56,6 +60,8 @@ module Ci
scope :offline, -> { where.not(id: online) }
scope :ordered, -> { order(id: :desc) }
+ scope :with_recent_runner_queue, -> { where('contacted_at > ?', recent_queue_deadline) }
+
# BACKWARD COMPATIBILITY: There are needed to maintain compatibility with `AVAILABLE_SCOPES` used by `lib/api/runners.rb`
scope :deprecated_shared, -> { instance_type }
scope :deprecated_specific, -> { project_type.or(group_type) }
@@ -137,10 +143,18 @@ module Ci
fuzzy_search(query, [:token, :description])
end
- def self.contact_time_deadline
+ def self.online_contact_time_deadline
ONLINE_CONTACT_TIMEOUT.ago
end
+ def self.recent_queue_deadline
+ # we add queue expiry + online
+ # - contacted_at can be updated at any time within this interval
+ # we have always accurate `contacted_at` but it is stored in Redis
+ # and not persisted in database
+ (ONLINE_CONTACT_TIMEOUT + RUNNER_QUEUE_EXPIRY_TIME).ago
+ end
+
def self.order_by(order)
if order == 'contacted_asc'
order_contacted_at_asc
@@ -174,7 +188,7 @@ module Ci
end
def online?
- contacted_at && contacted_at > self.class.contact_time_deadline
+ contacted_at && contacted_at > self.class.online_contact_time_deadline
end
def status
@@ -275,9 +289,7 @@ module Ci
def persist_cached_data?
# Use a random threshold to prevent beating DB updates.
- # It generates a distribution between [40m, 80m].
-
- contacted_at_max_age = UPDATE_DB_RUNNER_INFO_EVERY + Random.rand(UPDATE_DB_RUNNER_INFO_EVERY)
+ contacted_at_max_age = Random.rand(UPDATE_CONTACT_COLUMN_EVERY)
real_contacted_at = read_attribute(:contacted_at)
real_contacted_at.nil? ||
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb
new file mode 100644
index 00000000000..0c603c2d5e6
--- /dev/null
+++ b/app/models/concerns/analytics/cycle_analytics/stage.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ module Stage
+ extend ActiveSupport::Concern
+
+ included do
+ validates :name, presence: true
+ validates :start_event_identifier, presence: true
+ validates :end_event_identifier, presence: true
+ validate :validate_stage_event_pairs
+
+ enum start_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :start_event_identifier
+ enum end_event_identifier: Gitlab::Analytics::CycleAnalytics::StageEvents.to_enum, _prefix: :end_event_identifier
+
+ alias_attribute :custom_stage?, :custom
+ end
+
+ def parent=(_)
+ raise NotImplementedError
+ end
+
+ def parent
+ raise NotImplementedError
+ end
+
+ def start_event
+ Gitlab::Analytics::CycleAnalytics::StageEvents[start_event_identifier].new(params_for_start_event)
+ end
+
+ def end_event
+ Gitlab::Analytics::CycleAnalytics::StageEvents[end_event_identifier].new(params_for_end_event)
+ end
+
+ def params_for_start_event
+ {}
+ end
+
+ def params_for_end_event
+ {}
+ end
+
+ def default_stage?
+ !custom
+ end
+
+ # The model that is going to be queried, Issue or MergeRequest
+ def subject_model
+ start_event.object_type
+ end
+
+ private
+
+ def validate_stage_event_pairs
+ return if start_event_identifier.nil? || end_event_identifier.nil?
+
+ unless pairing_rules.fetch(start_event.class, []).include?(end_event.class)
+ errors.add(:end_event, :not_allowed_for_the_given_start_event)
+ end
+ end
+
+ def pairing_rules
+ Gitlab::Analytics::CycleAnalytics::StageEvents.pairing_rules
+ end
+ end
+ end
+end
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index 14bc56f0eee..f229b42ade6 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -106,30 +106,6 @@ module Awardable
end
def awarded_emoji?(emoji_name, current_user)
- award_emoji.where(name: emoji_name, user: current_user).exists?
- end
-
- def create_award_emoji(name, current_user)
- return unless emoji_awardable?
-
- award_emoji.create(name: normalize_name(name), user: current_user)
- end
-
- def remove_award_emoji(name, current_user)
- award_emoji.where(name: name, user: current_user).destroy_all # rubocop: disable DestroyAll
- end
-
- def toggle_award_emoji(emoji_name, current_user)
- if awarded_emoji?(emoji_name, current_user)
- remove_award_emoji(emoji_name, current_user)
- else
- create_award_emoji(emoji_name, current_user)
- end
- end
-
- private
-
- def normalize_name(name)
- Gitlab::Emoji.normalize_emoji_name(name)
+ award_emoji.named(emoji_name).awarded_by(current_user).exists?
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index e60b6497cb7..db46d7afbb9 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -186,16 +186,15 @@ module Issuable
def sort_by_attribute(method, excluded_labels: [])
sorted =
case method.to_s
- when 'downvotes_desc' then order_downvotes_desc
- when 'label_priority' then order_labels_priority(excluded_labels: excluded_labels)
- when 'label_priority_desc' then order_labels_priority('DESC', excluded_labels: excluded_labels)
- when 'milestone', 'milestone_due_asc' then order_milestone_due_asc
- when 'milestone_due_desc' then order_milestone_due_desc
- when 'popularity', 'popularity_desc' then order_upvotes_desc
- when 'popularity_asc' then order_upvotes_asc
- when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
- when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels)
- when 'upvotes_desc' then order_upvotes_desc
+ when 'downvotes_desc' then order_downvotes_desc
+ when 'label_priority', 'label_priority_asc' then order_labels_priority(excluded_labels: excluded_labels)
+ when 'label_priority_desc' then order_labels_priority('DESC', excluded_labels: excluded_labels)
+ when 'milestone', 'milestone_due_asc' then order_milestone_due_asc
+ when 'milestone_due_desc' then order_milestone_due_desc
+ when 'popularity_asc' then order_upvotes_asc
+ when 'popularity', 'popularity_desc', 'upvotes_desc' then order_upvotes_desc
+ when 'priority', 'priority_asc' then order_due_date_and_labels_priority(excluded_labels: excluded_labels)
+ when 'priority_desc' then order_due_date_and_labels_priority('DESC', excluded_labels: excluded_labels)
else order_by(method)
end
diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb
index df1a9e3fe6e..c4af1b1fab2 100644
--- a/app/models/concerns/sortable.rb
+++ b/app/models/concerns/sortable.rb
@@ -27,14 +27,18 @@ module Sortable
def simple_sorts
{
'created_asc' => -> { order_created_asc },
+ 'created_at_asc' => -> { order_created_asc },
'created_date' => -> { order_created_desc },
'created_desc' => -> { order_created_desc },
+ 'created_at_desc' => -> { order_created_desc },
'id_asc' => -> { order_id_asc },
'id_desc' => -> { order_id_desc },
'name_asc' => -> { order_name_asc },
'name_desc' => -> { order_name_desc },
'updated_asc' => -> { order_updated_asc },
- 'updated_desc' => -> { order_updated_desc }
+ 'updated_at_asc' => -> { order_updated_asc },
+ 'updated_desc' => -> { order_updated_desc },
+ 'updated_at_desc' => -> { order_updated_desc }
}
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 68586e7a1fd..bff5d348ca0 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -162,6 +162,14 @@ class Deployment < ApplicationRecord
deployed_at&.to_time&.in_time_zone&.to_s(:medium)
end
+ def deployed_by
+ # We use deployable's user if available because Ci::PlayBuildService
+ # does not update the deployment's user, just the one for the deployable.
+ # TODO: use deployment's user once https://gitlab.com/gitlab-org/gitlab-ce/issues/66442
+ # is completed.
+ deployable&.user || user
+ end
+
private
def ref_path
diff --git a/app/models/issue.rb b/app/models/issue.rb
index c5a18f0af0f..caea8eadd18 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -128,11 +128,10 @@ class Issue < ApplicationRecord
def self.sort_by_attribute(method, excluded_labels: [])
case method.to_s
- when 'closest_future_date' then order_closest_future_date
- when 'due_date' then order_due_date_asc
- when 'due_date_asc' then order_due_date_asc
- when 'due_date_desc' then order_due_date_desc
- when 'relative_position' then order_relative_position_asc.with_order_id_desc
+ when 'closest_future_date', 'closest_future_date_asc' then order_closest_future_date
+ when 'due_date', 'due_date_asc' then order_due_date_asc
+ when 'due_date_desc' then order_due_date_desc
+ when 'relative_position', 'relative_position_asc' then order_relative_position_asc.with_order_id_desc
else
super
end
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/namespace/root_storage_statistics.rb b/app/models/namespace/root_storage_statistics.rb
index 56c430013ee..ae9b2f14343 100644
--- a/app/models/namespace/root_storage_statistics.rb
+++ b/app/models/namespace/root_storage_statistics.rb
@@ -8,6 +8,8 @@ class Namespace::RootStorageStatistics < ApplicationRecord
belongs_to :namespace
has_one :route, through: :namespace
+ scope :for_namespace_ids, ->(namespace_ids) { where(namespace_id: namespace_ids) }
+
delegate :all_projects, to: :namespace
def recalculate!
diff --git a/app/models/project.rb b/app/models/project.rb
index 8efe4b06f87..10679fb1f85 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -55,6 +55,8 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
+ SORTING_PREFERENCE_FIELD = :projects_sort
+
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
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 6f63cd32da4..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)
@@ -565,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
@@ -1130,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/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/policies/group_policy.rb b/app/policies/group_policy.rb
index c686e7763bb..5d2b74b17a2 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -124,6 +124,8 @@ class GroupPolicy < BasePolicy
rule { developer & developer_maintainer_access }.enable :create_projects
rule { create_projects_disabled }.prevent :create_projects
+ rule { owner | admin }.enable :read_statistics
+
def access_level
return GroupMember::NO_ACCESS if @user.nil?
diff --git a/app/policies/namespace/root_storage_statistics_policy.rb b/app/policies/namespace/root_storage_statistics_policy.rb
new file mode 100644
index 00000000000..63fcaf20dfe
--- /dev/null
+++ b/app/policies/namespace/root_storage_statistics_policy.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class Namespace::RootStorageStatisticsPolicy < BasePolicy
+ delegate { @subject.namespace }
+end
diff --git a/app/policies/namespace_policy.rb b/app/policies/namespace_policy.rb
index 2babcb0a2d9..937666c7e54 100644
--- a/app/policies/namespace_policy.rb
+++ b/app/policies/namespace_policy.rb
@@ -11,6 +11,7 @@ class NamespacePolicy < BasePolicy
enable :create_projects
enable :admin_namespace
enable :read_namespace
+ enable :read_statistics
end
rule { personal_project & ~can_create_personal_project }.prevent :create_projects
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 6e91317eb20..8b967459173 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -18,10 +18,10 @@ class DeploymentEntity < Grape::Entity
end
expose :created_at
- expose :finished_at
+ expose :deployed_at
expose :tag
expose :last?
- expose :user, using: UserEntity
+ expose :deployed_by, as: :user, using: UserEntity
expose :deployable do |deployment, opts|
deployment.deployable.yield_self do |deployable|
diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb
index 3fd3e1b9cc8..b48037dd53f 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, :finished_at, :tag, :last?, :id, ref: [:name]]
+ opts[:only] = [:iid, :id, :sha, :created_at, :deployed_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 c02fd024345..058c707ef9d 100644
--- a/app/serializers/issuable_sidebar_basic_entity.rb
+++ b/app/serializers/issuable_sidebar_basic_entity.rb
@@ -4,6 +4,7 @@ class IssuableSidebarBasicEntity < Grape::Entity
include RequestAwareEntity
expose :id
+ expose :iid
expose :type do |issuable|
issuable.to_ability_name
end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index 8ad1df5dfe0..bd2e682a122 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -8,7 +8,7 @@ class MergeRequestSerializer < BaseSerializer
entity ||=
case opts[:serializer]
when 'sidebar'
- IssuableSidebarBasicEntity
+ MergeRequestSidebarBasicEntity
when 'sidebar_extras'
MergeRequestSidebarExtrasEntity
when 'basic'
diff --git a/app/serializers/merge_request_sidebar_basic_entity.rb b/app/serializers/merge_request_sidebar_basic_entity.rb
new file mode 100644
index 00000000000..3c911bbe4c8
--- /dev/null
+++ b/app/serializers/merge_request_sidebar_basic_entity.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class MergeRequestSidebarBasicEntity < IssuableSidebarBasicEntity
+ expose :current_user, if: lambda { |_issuable| current_user } do
+ expose :can_merge do |merge_request|
+ merge_request.can_be_merged_by?(current_user)
+ end
+ end
+end
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 554b307d4f8..c8088608cb0 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -84,8 +84,10 @@ class MergeRequestWidgetEntity < Grape::Entity
private
+ delegate :current_user, to: :request
+
def presenter(merge_request)
@presenters ||= {}
- @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: request.current_user) # rubocop: disable CodeReuse/Presenter
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
end
end
diff --git a/app/services/award_emojis/add_service.rb b/app/services/award_emojis/add_service.rb
new file mode 100644
index 00000000000..eac15dabbf0
--- /dev/null
+++ b/app/services/award_emojis/add_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module AwardEmojis
+ class AddService < AwardEmojis::BaseService
+ include Gitlab::Utils::StrongMemoize
+
+ def execute
+ unless awardable.user_can_award?(current_user)
+ return error('User cannot award emoji to awardable', status: :forbidden)
+ end
+
+ unless awardable.emoji_awardable?
+ return error('Awardable cannot be awarded emoji', status: :unprocessable_entity)
+ end
+
+ award = awardable.award_emoji.create(name: name, user: current_user)
+
+ if award.persisted?
+ TodoService.new.new_award_emoji(todoable, current_user) if todoable
+ success(award: award)
+ else
+ error(award.errors.full_messages, award: award)
+ end
+ end
+
+ private
+
+ def todoable
+ strong_memoize(:todoable) do
+ case awardable
+ when Note
+ # We don't create todos for personal snippet comments for now
+ awardable.noteable unless awardable.for_personal_snippet?
+ when MergeRequest, Issue
+ awardable
+ when Snippet
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/award_emojis/base_service.rb b/app/services/award_emojis/base_service.rb
new file mode 100644
index 00000000000..a677d03a221
--- /dev/null
+++ b/app/services/award_emojis/base_service.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module AwardEmojis
+ class BaseService < ::BaseService
+ attr_accessor :awardable, :name
+
+ def initialize(awardable, name, current_user)
+ @awardable = awardable
+ @name = normalize_name(name)
+
+ super(awardable.project, current_user)
+ end
+
+ private
+
+ def normalize_name(name)
+ Gitlab::Emoji.normalize_emoji_name(name)
+ end
+
+ # Provide more error state data than what BaseService allows.
+ # - An array of errors
+ # - The `AwardEmoji` if present
+ def error(errors, award: nil, status: nil)
+ errors = Array.wrap(errors)
+
+ super(errors.to_sentence.presence, status).merge({
+ award: award,
+ errors: errors
+ })
+ end
+ end
+end
diff --git a/app/services/award_emojis/collect_user_emoji_service.rb b/app/services/award_emojis/collect_user_emoji_service.rb
new file mode 100644
index 00000000000..6cab23f3edf
--- /dev/null
+++ b/app/services/award_emojis/collect_user_emoji_service.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+# Class for retrieving information about emoji awarded _by_ a particular user.
+module AwardEmojis
+ class CollectUserEmojiService
+ attr_reader :current_user
+
+ # current_user - The User to generate the data for.
+ def initialize(current_user = nil)
+ @current_user = current_user
+ end
+
+ def execute
+ return [] unless current_user
+
+ # We want the resulting data set to be an Array containing the emoji names
+ # in descending order, based on how often they were awarded.
+ AwardEmoji
+ .award_counts_for_user(current_user)
+ .map { |name, _| { name: name } }
+ end
+ end
+end
diff --git a/app/services/award_emojis/destroy_service.rb b/app/services/award_emojis/destroy_service.rb
new file mode 100644
index 00000000000..3789a8403bc
--- /dev/null
+++ b/app/services/award_emojis/destroy_service.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module AwardEmojis
+ class DestroyService < AwardEmojis::BaseService
+ def execute
+ unless awardable.user_can_award?(current_user)
+ return error('User cannot destroy emoji on the awardable', status: :forbidden)
+ end
+
+ awards = AwardEmojisFinder.new(awardable, name: name, awarded_by: current_user).execute
+
+ if awards.empty?
+ return error("User has not awarded emoji of type #{name} on the awardable", status: :forbidden)
+ end
+
+ award = awards.destroy_all.first # rubocop: disable DestroyAll
+
+ success(award: award)
+ end
+ end
+end
diff --git a/app/services/award_emojis/toggle_service.rb b/app/services/award_emojis/toggle_service.rb
new file mode 100644
index 00000000000..812dd1c2889
--- /dev/null
+++ b/app/services/award_emojis/toggle_service.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module AwardEmojis
+ class ToggleService < AwardEmojis::BaseService
+ def execute
+ if awardable.awarded_emoji?(name, current_user)
+ DestroyService.new(awardable, name, current_user).execute
+ else
+ AddService.new(awardable, name, current_user).execute
+ end
+ end
+ end
+end
diff --git a/app/services/ci/update_build_queue_service.rb b/app/services/ci/update_build_queue_service.rb
index 9c589d910eb..31c7178c9e7 100644
--- a/app/services/ci/update_build_queue_service.rb
+++ b/app/services/ci/update_build_queue_service.rb
@@ -9,6 +9,10 @@ module Ci
private
def tick_for(build, runners)
+ if Feature.enabled?(:ci_update_queues_for_online_runners, build.project, default_enabled: true)
+ runners = runners.with_recent_runner_queue
+ end
+
runners.each do |runner|
runner.pick_build!(build)
end
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index 3fd38444196..47c308c8280 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -56,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
@@ -75,24 +75,29 @@ module Git
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/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 77c2224ee3b..2ab6e88599f 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -344,10 +344,7 @@ class IssuableBaseService < BaseService
def toggle_award(issuable)
award = params.delete(:emoji_award)
- if award
- todo_service.new_award_emoji(issuable, current_user)
- issuable.toggle_award_emoji(award, current_user)
- end
+ AwardEmojis::ToggleService.new(issuable, award, current_user).execute if award
end
def associations_before_update(issuable)
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 067510a8a0a..c6aae4c28f2 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -17,11 +17,9 @@ module MergeRequests
end
def execute_hooks(merge_request, action = 'open', old_rev: nil, old_associations: {})
- if merge_request.project
- merge_data = hook_data(merge_request, action, old_rev: old_rev, old_associations: old_associations)
- merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
- merge_request.project.execute_services(merge_data, :merge_request_hooks)
- end
+ merge_data = hook_data(merge_request, action, old_rev: old_rev, old_associations: old_associations)
+ merge_request.project.execute_hooks(merge_data, :merge_request_hooks)
+ merge_request.project.execute_services(merge_data, :merge_request_hooks)
end
def cleanup_environments(merge_request)
diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
deleted file mode 100644
index c925c6a1610..00000000000
--- a/app/services/self_monitoring/project/create_service.rb
+++ /dev/null
@@ -1,219 +0,0 @@
-# frozen_string_literal: true
-
-module SelfMonitoring
- module Project
- class CreateService < ::BaseService
- include Stepable
- include Gitlab::Utils::StrongMemoize
-
- VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
- PROJECT_NAME = 'GitLab Instance Administration'
- PROJECT_DESCRIPTION = <<~HEREDOC
- This project is automatically generated and will be used to help monitor this GitLab instance.
- HEREDOC
-
- GROUP_NAME = 'GitLab Instance Administrators'
- GROUP_PATH = 'gitlab-instance-administrators'
-
- steps :validate_admins,
- :create_group,
- :create_project,
- :save_project_id,
- :add_group_members,
- :add_to_whitelist,
- :add_prometheus_manual_configuration
-
- def initialize
- super(nil)
- end
-
- def execute
- execute_steps
- end
-
- private
-
- def validate_admins
- unless instance_admins.any?
- log_error('No active admin user found')
- return error('No active admin user found')
- end
-
- success
- end
-
- def create_group
- if project_created?
- log_info(_('Instance administrators group already exists'))
- @group = application_settings.instance_administration_project.owner
- return success(group: @group)
- end
-
- admin_user = group_owner
- @group = ::Groups::CreateService.new(admin_user, create_group_params).execute
-
- if @group.persisted?
- success(group: @group)
- else
- error('Could not create group')
- end
- end
-
- def create_project
- if project_created?
- log_info(_('Instance administration project already exists'))
- @project = application_settings.instance_administration_project
- return success(project: project)
- end
-
- admin_user = group_owner
- @project = ::Projects::CreateService.new(admin_user, create_project_params).execute
-
- if project.persisted?
- success(project: project)
- else
- log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
- error(_('Could not create project'))
- end
- end
-
- def save_project_id
- return success if project_created?
-
- result = ApplicationSettings::UpdateService.new(
- application_settings,
- group_owner,
- { instance_administration_project_id: @project.id }
- ).execute
-
- if result
- success
- else
- log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages })
- error(_('Could not save project ID'))
- end
- end
-
- def add_group_members
- members = @group.add_users(group_maintainers, Gitlab::Access::MAINTAINER)
- errors = members.flat_map { |member| member.errors.full_messages }
-
- if errors.any?
- log_error("Could not add admins as members to self-monitoring project. Errors: #{errors}")
- error('Could not add admins as members')
- else
- success
- end
- end
-
- def add_to_whitelist
- return success unless prometheus_enabled?
- return success unless prometheus_listen_address.present?
-
- uri = parse_url(internal_prometheus_listen_address_uri)
- return error(_('Prometheus listen_address is not a valid URI')) unless uri
-
- result = ApplicationSettings::UpdateService.new(
- application_settings,
- group_owner,
- add_to_outbound_local_requests_whitelist: [uri.normalized_host]
- ).execute
-
- if result
- success
- else
- log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages })
- error(_('Could not add prometheus URL to whitelist'))
- end
- end
-
- def add_prometheus_manual_configuration
- return success unless prometheus_enabled?
- return success unless prometheus_listen_address.present?
-
- service = project.find_or_initialize_service('prometheus')
-
- unless service.update(prometheus_service_attributes)
- log_error("Could not save prometheus manual configuration for self-monitoring project. Errors: #{service.errors.full_messages}")
- return error('Could not save prometheus manual configuration')
- end
-
- success
- end
-
- def application_settings
- strong_memoize(:application_settings) do
- Gitlab::CurrentSettings.expire_current_application_settings
- Gitlab::CurrentSettings.current_application_settings
- end
- end
-
- def project_created?
- application_settings.instance_administration_project.present?
- end
-
- def parse_url(uri_string)
- Addressable::URI.parse(uri_string)
- rescue Addressable::URI::InvalidURIError, TypeError
- end
-
- def prometheus_enabled?
- Gitlab.config.prometheus.enable
- rescue Settingslogic::MissingSetting
- false
- end
-
- def prometheus_listen_address
- Gitlab.config.prometheus.listen_address
- rescue Settingslogic::MissingSetting
- end
-
- def instance_admins
- @instance_admins ||= User.admins.active
- end
-
- def group_owner
- instance_admins.first
- end
-
- def group_maintainers
- # Exclude the first so that the group_owner is not added again as a member.
- instance_admins - [group_owner]
- end
-
- def create_group_params
- {
- name: GROUP_NAME,
- path: "#{GROUP_PATH}-#{SecureRandom.hex(4)}",
- visibility_level: VISIBILITY_LEVEL
- }
- end
-
- def create_project_params
- {
- initialize_with_readme: true,
- visibility_level: VISIBILITY_LEVEL,
- name: PROJECT_NAME,
- description: PROJECT_DESCRIPTION,
- namespace_id: @group.id
- }
- end
-
- def internal_prometheus_listen_address_uri
- if prometheus_listen_address.starts_with?('http')
- prometheus_listen_address
- else
- 'http://' + prometheus_listen_address
- end
- end
-
- def prometheus_service_attributes
- {
- api_url: internal_prometheus_listen_address_uri,
- manual_configuration: true,
- active: true
- }
- end
- 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/ci/status/_icon.html.haml b/app/views/ci/status/_icon.html.haml
index 1249b98221f..fdaacb732c7 100644
--- a/app/views/ci/status/_icon.html.haml
+++ b/app/views/ci/status/_icon.html.haml
@@ -1,13 +1,10 @@
- status = local_assigns.fetch(:status)
- size = local_assigns.fetch(:size, 16)
-- type = local_assigns.fetch(:type, 'pipeline')
- tooltip_placement = local_assigns.fetch(:tooltip_placement, "left")
- path = local_assigns.fetch(:path, status.has_details? ? status.details_path : nil)
- option_css_classes = local_assigns.fetch(:option_css_classes, '')
- css_classes = "ci-status-link ci-status-icon ci-status-icon-#{status.group} has-tooltip #{option_css_classes}"
- title = s_("PipelineStatusTooltip|Pipeline: %{ci_status}") % {ci_status: status.label}
-- if type == 'commit'
- - title = s_("PipelineStatusTooltip|Commit: %{ci_status}") % {ci_status: status.label}
- if path
= link_to path, class: css_classes, title: title, data: { placement: tooltip_placement } do
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/import/github/new.html.haml b/app/views/import/github/new.html.haml
index 72e5934574a..518c44cc687 100644
--- a/app/views/import/github/new.html.haml
+++ b/app/views/import/github/new.html.haml
@@ -1,30 +1,32 @@
-- title = has_ci_cd_only_params? ? _('Connect repositories from GitHub') : _('GitHub import')
+- title = _('Authenticate with GitHub')
- page_title title
- breadcrumb_title title
- header_title _("Projects"), root_path
-%h3.page-title
- = icon 'github', text: _('Import repositories from GitHub')
+%h2.page-title
+ = title
-- if github_import_configured?
- %p
- = import_github_authorize_message
+%p
+ = import_github_authorize_message
- = link_to _('List your GitHub repositories'), status_import_github_path(ci_cd_only: params[:ci_cd_only]), class: 'btn btn-success'
+- if github_import_configured? && !has_ci_cd_only_params?
+ = link_to icon('github', text: title), status_import_github_path, class: 'btn btn-success'
%hr
-%p
- = import_github_personal_access_token_message
+- unless github_import_configured? || has_ci_cd_only_params?
+ .bs-callout.bs-callout-info
+ = import_configure_github_admin_message
-= form_tag personal_access_token_import_github_path, method: :post, class: 'form-inline' do
+= form_tag personal_access_token_import_github_path, method: :post do
.form-group
- = text_field_tag :personal_access_token, '', class: 'form-control append-right-8', placeholder: _('Personal Access Token'), size: 40
- = submit_tag _('List your GitHub repositories'), class: 'btn btn-success'
+ %label.label-bold= _('Personal Access Token')
+ = text_field_tag :personal_access_token, '', class: 'form-control', placeholder: _('e.g. %{token}') % { token: '8d3f016698e...' }
+ %span.form-text.text-muted
+ = import_github_personal_access_token_message
= render_if_exists 'import/github/ci_cd_only'
-- unless github_import_configured?
- %hr
- %p
- = import_configure_github_admin_message
+ .form-actions.d-flex.justify-content-end
+ = link_to _('Cancel'), new_project_path, class: 'btn'
+ = submit_tag _('Authenticate'), class: 'btn btn-success ml-2'
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/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/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/deployments/_deployment.html.haml b/app/views/projects/deployments/_deployment.html.haml
index a11e23b6daa..752be02443c 100644
--- a/app/views/projects/deployments/_deployment.html.haml
+++ b/app/views/projects/deployments/_deployment.html.haml
@@ -15,10 +15,10 @@
.flex-truncate-child
= link_to [@project.namespace.becomes(Namespace), @project, deployment.deployable], class: 'build-link' do
#{deployment.deployable.name} (##{deployment.deployable.id})
- - if deployment.user
+ - if deployment.deployed_by
%div
by
- = user_avatar(user: deployment.user, size: 20, css_class: "mr-0 float-none")
+ = user_avatar(user: deployment.deployed_by, size: 20, css_class: "mr-0 float-none")
.table-section.section-15{ role: 'gridcell' }
.table-mobile-header{ role: 'rowheader' }= _("Created")
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..53bb3c7487d 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -21,7 +21,7 @@
.icon-container
= sprite_icon('flag')
- if @pipeline.latest?
- %span.js-pipeline-url-latest.badge.badge-success.has-tooltip{ title: _("Latest pipeline for this branch") }
+ %span.js-pipeline-url-latest.badge.badge-success.has-tooltip{ title: _("Latest pipeline for the most recent commit on this branch") }
latest
- if @pipeline.has_yaml_errors?
%span.js-pipeline-url-yaml.badge.badge-danger.has-tooltip{ title: @pipeline.yaml_errors }
@@ -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/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index c04f076a3ab..56995ffbcee 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -1,7 +1,7 @@
.tabs-holder
%ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator.nav.nav-tabs
%li.js-pipeline-tab-link
- = link_to project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
+ = link_to @pipeline_path, data: { target: '#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
= _('Pipeline')
%li.js-builds-tab-link
= link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
diff --git a/app/views/projects/serverless/functions/index.html.haml b/app/views/projects/serverless/functions/index.html.haml
index 9c69aedfbfc..bac6c76684b 100644
--- a/app/views/projects/serverless/functions/index.html.haml
+++ b/app/views/projects/serverless/functions/index.html.haml
@@ -14,5 +14,5 @@
.js-serverless-functions-notice
.flash-container
- .top-area.adjust
+ .top-area.adjust.d-flex.justify-content-center
.serverless-functions-table#js-serverless-functions
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..83d145444d8 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -1,6 +1,6 @@
%aside.right-sidebar.right-sidebar-expanded.wiki-sidebar.js-wiki-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix" } }
.sidebar-container
- .block.wiki-sidebar-header.append-bottom-default
+ .block.wiki-sidebar-header.append-bottom-default.w-100
%a.gutter-toggle.float-right.d-block.d-sm-block.d-md-none.js-sidebar-wiki-toggle{ href: "#" }
= icon('angle-double-right')
@@ -10,14 +10,12 @@
%span= _("Clone repository")
.blocks-container
- .block.block-first
+ .block.block-first.w-100
- if @sidebar_page
= render_wiki_content(@sidebar_page)
- else
%ul.wiki-pages
= render @sidebar_wiki_entries, context: 'sidebar'
- .block
+ .block.w-100
= 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..9ccf5acfefc 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -5,7 +5,7 @@
= wiki_page_errors(@error)
-.wiki-page-header.top-area.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
@@ -13,16 +13,13 @@
%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
+ .nav-controls.pb-md-3.pb-lg-0
- if @page.persisted?
= link_to project_wiki_history_path(@project, @page), class: "btn" do
= s_("Wiki|Page history")
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 009133be117..6972eda9bb7 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,15 +1,17 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title s_("WikiClone|Git Access"), _("Wiki")
-.wiki-page-header.top-area.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle.py-3.flex-column.flex-lg-row
%button.btn.btn-default.d-block.d-sm-block.d-md-none.float-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
- .git-access-header
- = _("Clone repository")
- %strong= @project_wiki.full_path
+ .git-access-header.w-100.d-flex.flex-column.justify-content-center
+ %span
+ = _("Clone repository")
+ %strong= @project_wiki.full_path
- = render "shared/clone_panel", project: @project_wiki
+ .pt-3.pt-lg-0.w-100
+ = render "shared/clone_panel", project: @project_wiki
.wiki-git-access
%h3= s_("WikiClone|Install Gollum")
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index f8468ef9a78..d3a55c53649 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,6 +1,6 @@
- page_title _("History"), @page.human_title, _("Wiki")
-.wiki-page-header.top-area.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index f7999c3f1bd..275dc5dbd23 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,13 +5,13 @@
- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class }
- .wiki-page-header.top-area
+ .wiki-page-header.top-area.flex-column.flex-lg-row
.nav-text.flex-fill
%h2.wiki-page-title
= s_("Wiki|Wiki Pages")
- .nav-controls
+ .nav-controls.pb-md-3.pb-lg-0
= link_to project_wikis_git_access_path(@project), class: 'btn' do
= icon('cloud-download')
= _("Clone repository")
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 1d649886331..c6197fe576e 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -4,7 +4,7 @@
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
-.wiki-page-header.top-area.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle.flex-column.flex-lg-row
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
@@ -15,7 +15,7 @@
= (_("Last edited by %{name}") % { name: "<strong>#{@page.last_version.author_name}</strong>" }).html_safe
#{time_ago_with_tooltip(@page.last_version.authored_date)}
- .nav-controls
+ .nav-controls.pb-md-3.pb-lg-0
= render 'main_links'
- if @page.historical?
diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml
index 1be230eedb9..4f0be117035 100644
--- a/app/views/shared/boards/_show.html.haml
+++ b/app/views/shared/boards/_show.html.haml
@@ -16,7 +16,7 @@
#board-app.boards-app.position-relative{ "v-cloak" => "true", data: board_data, ":class" => "{ 'is-compact': detailIssueVisible }" }
= render 'shared/issuable/search_bar', type: :boards, board: board
- .boards-list.w-100.py-3.px-2.text-nowrap
+ .boards-list.w-100.py-3.px-2.text-nowrap{ data: { qa_selector: "boards_list" } }
.boards-app-loading.w-100.text-center{ "v-if" => "loading" }
= icon("spinner spin 2x")
%board{ "v-cloak" => "true",
diff --git a/app/views/shared/boards/components/_board.html.haml b/app/views/shared/boards/components/_board.html.haml
index 6c0613605eb..ffa24d1c041 100644
--- a/app/views/shared/boards/components/_board.html.haml
+++ b/app/views/shared/boards/components/_board.html.haml
@@ -1,7 +1,7 @@
-.board.d-inline-block.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
- ":data-id" => "list.id" }
+.board.h-100.px-2.align-top.ws-normal{ ":class" => '{ "is-draggable": !list.preset, "is-expandable": list.isExpandable, "is-collapsed": !list.isExpanded, "board-type-assignee": list.type === "assignee" }',
+ ":data-id" => "list.id", data: { qa_selector: "board_list" } }
.board-inner.d-flex.flex-column.position-relative.h-100.rounded
- %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }" }
+ %header.board-header{ ":class" => '{ "has-border": list.label && list.label.color, "position-relative": list.isExpanded, "position-absolute position-top-0 position-left-0 w-100 h-100": !list.isExpanded }', ":style" => "{ borderTopColor: (list.label && list.label.color ? list.label.color : null) }", data: { qa_selector: "board_list_header" } }
%h3.board-title.m-0.d-flex.js-board-handle{ ":class" => '{ "user-can-drag": (!disabled && !list.preset), "border-bottom-0": !list.isExpanded }' }
.board-title-caret.no-drag{ "v-if": "list.isExpandable",
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 825088a58e7..837707707a9 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -38,7 +38,7 @@
= _('Milestone')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "milestone", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
- if milestone.present?
= link_to milestone[:title], milestone[:web_url], class: "bold has-tooltip", title: sidebar_milestone_remaining_days(milestone), data: { container: "body", html: 'true', boundary: 'viewport' }
@@ -66,7 +66,7 @@
= _('Due date')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right', data: { track_label: "right_sidebar", track_property: "due_date", track_event: "click_edit_button", track_value: "" }
.value.hide-collapsed
%span.value-content
- if issuable_sidebar[:due_date]
@@ -102,7 +102,7 @@
= _('Labels')
= icon('spinner spin', class: 'hidden block-loading', 'aria-hidden': 'true')
- if can_edit_issuable
- = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right'
+ = link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link qa-edit-link-labels float-right', data: { track_label: "right_sidebar", track_property: "labels", track_event: "click_edit_button", track_value: "" }
.value.issuable-show-labels.dont-hide.hide-collapsed.qa-labels-block{ class: ("has-labels" if selected_labels.any?) }
- if selected_labels.any?
- selected_labels.each do |label_hash|
@@ -139,7 +139,9 @@
- if signed_in
- if issuable_sidebar[:project_emails_disabled]
.block.js-emails-disabled
- = notification_description(:owner_disabled)
+ .sidebar-collapsed-icon.has-tooltip{ title: notification_description(:owner_disabled), data: { placement: "left", container: "body", boundary: 'viewport' } }
+ = notification_setting_icon
+ .hide-collapsed= notification_description(:owner_disabled)
- else
.js-sidebar-subscriptions-entry-point
@@ -160,7 +162,7 @@
= custom_icon('icon_arrow_right')
.dropdown.sidebar-move-issue-dropdown.hide-collapsed
%button.btn.btn-default.btn-block.js-sidebar-dropdown-toggle.js-move-issue{ type: 'button',
- data: { toggle: 'dropdown', display: 'static' } }
+ data: { toggle: 'dropdown', display: 'static', track_label: "right_sidebar", track_property: "move_issue", track_event: "click_button", track_value: "" } }
= _('Move issue')
.dropdown-menu.dropdown-menu-selectable.dropdown-extended-height
= dropdown_title(_('Move issue'))
diff --git a/app/views/shared/issuable/_sidebar_assignees.html.haml b/app/views/shared/issuable/_sidebar_assignees.html.haml
index ab01094ed6e..1dc538826dc 100644
--- a/app/views/shared/issuable/_sidebar_assignees.html.haml
+++ b/app/views/shared/issuable/_sidebar_assignees.html.haml
@@ -20,6 +20,8 @@
placeholder: _('Search users'),
data: { first_user: issuable_sidebar.dig(:current_user, :username),
current_user: true,
+ iid: issuable_sidebar[:iid],
+ issuable_type: issuable_type,
project_id: issuable_sidebar[:project_id],
author_id: issuable_sidebar[:author_id],
field_name: "#{issuable_type}[assignee_ids][]",
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/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index b7474d891dc..573ed36d7f4 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -89,7 +89,7 @@
- if pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
%span.icon-wrapper.pipeline-status
- = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), type: 'commit', tooltip_placement: 'top', path: pipeline_path
+ = render 'ci/status/icon', status: project.commit.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
.updated-note
%span
= _('Updated')
diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb
index 75e68d0233a..ef2da729705 100644
--- a/app/workers/ci/archive_traces_cron_worker.rb
+++ b/app/workers/ci/archive_traces_cron_worker.rb
@@ -10,7 +10,7 @@ module Ci
# Archive stale live traces which still resides in redis or database
# This could happen when ArchiveTraceWorker sidekiq jobs were lost by receiving SIGKILL
# More details in https://gitlab.com/gitlab-org/gitlab-ce/issues/36791
- Ci::Build.finished.with_live_trace.find_each(batch_size: 100) do |build|
+ Ci::Build.with_stale_live_trace.find_each(batch_size: 100) do |build|
Ci::ArchiveTraceService.new.execute(build, worker_name: self.class.name)
end
end
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 489d6215774..5499e12e49b 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -24,7 +24,7 @@ class GitGarbageCollectWorker
task = task.to_sym
- ::Projects::GitDeduplicationService.new(project).execute
+ ::Projects::GitDeduplicationService.new(project).execute if task == :gc
gitaly_call(task, project.repository.raw_repository)
diff --git a/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml b/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
deleted file mode 100644
index f249eff572c..00000000000
--- a/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust copy for adding additional members
-merge_request: 31726
-author:
-type: changed
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
deleted file mode 100644
index d93e7634ae5..00000000000
--- a/changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new table to store email domain per group
-merge_request: 31071
-author:
-type: added
diff --git a/changelogs/unreleased/11090-export-design-management-lfs-data.yml b/changelogs/unreleased/11090-export-design-management-lfs-data.yml
deleted file mode 100644
index 36b773124d7..00000000000
--- a/changelogs/unreleased/11090-export-design-management-lfs-data.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for exporting repository type data for LFS objects
-merge_request: 30830
-author:
-type: changed
diff --git a/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml b/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
deleted file mode 100644
index ccfd929b6ba..00000000000
--- a/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Track page views for cycle analytics show page
-merge_request: 31717
-author:
-type: added
diff --git a/changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml b/changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml
deleted file mode 100644
index 93936d441e7..00000000000
--- a/changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix inline rendering of relative paths to SVGs from the current repository
-merge_request: 31352
-author:
-type: fixed
diff --git a/changelogs/unreleased/19186-redirect-wiki-git-route-to-wiki.yml b/changelogs/unreleased/19186-redirect-wiki-git-route-to-wiki.yml
deleted file mode 100644
index 705621d06f7..00000000000
--- a/changelogs/unreleased/19186-redirect-wiki-git-route-to-wiki.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Redirect from a project wiki git route to the project wiki home
-merge_request: 31085
-author:
-type: added
diff --git a/changelogs/unreleased/20137-starrers.yml b/changelogs/unreleased/20137-starrers.yml
deleted file mode 100644
index d597b06f224..00000000000
--- a/changelogs/unreleased/20137-starrers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make starred projects and starrers of a project publicly visible
-merge_request: 24690
-author:
-type: added
diff --git a/changelogs/unreleased/21671-multiple-pipeline-status-api.yml b/changelogs/unreleased/21671-multiple-pipeline-status-api.yml
deleted file mode 100644
index b7b0f5fa0c7..00000000000
--- a/changelogs/unreleased/21671-multiple-pipeline-status-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Multiple pipeline support for Commit status
-merge_request: 30828
-author: Gaetan Semet
-type: changed
diff --git a/changelogs/unreleased/24705-multi-selection-for-delete-on-registry-page.yml b/changelogs/unreleased/24705-multi-selection-for-delete-on-registry-page.yml
deleted file mode 100644
index 5254bd36b9c..00000000000
--- a/changelogs/unreleased/24705-multi-selection-for-delete-on-registry-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added multi-select deletion of container registry images
-merge_request: 30837
-author:
-type: other
diff --git a/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml b/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml
deleted file mode 100644
index adbd7971a14..00000000000
--- a/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Add API endpoints to return container repositories and tags from the group
- level
-merge_request: 30817
-author:
-type: added
diff --git a/changelogs/unreleased/30974-issue-search-by-number.yml b/changelogs/unreleased/30974-issue-search-by-number.yml
deleted file mode 100644
index da50ee32c83..00000000000
--- a/changelogs/unreleased/30974-issue-search-by-number.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Search issuables by iids"
-merge_request: 28302
-author: Riccardo Padovani
-type: fixed
diff --git a/changelogs/unreleased/31434-make-issue-boards-importable.yml b/changelogs/unreleased/31434-make-issue-boards-importable.yml
deleted file mode 100644
index fd270a236dc..00000000000
--- a/changelogs/unreleased/31434-make-issue-boards-importable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make issue boards importable
-merge_request: 31434
-author: Jason Colyer
-type: changed
diff --git a/changelogs/unreleased/32032-html-code-shown-in-merge-request.yml b/changelogs/unreleased/32032-html-code-shown-in-merge-request.yml
new file mode 100644
index 00000000000..ffd58067784
--- /dev/null
+++ b/changelogs/unreleased/32032-html-code-shown-in-merge-request.yml
@@ -0,0 +1,5 @@
+---
+title: Fix HTML rendering for fast-forward rebases in merge request widget
+merge_request: 32032
+author:
+type: fixed
diff --git a/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml b/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml
deleted file mode 100644
index b7b39303c2e..00000000000
--- a/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve pipeline status Slack notifications
-merge_request: 27683
-author:
-type: added
diff --git a/changelogs/unreleased/34414-update-personal-access-token-scope-descriptions-to-reflect-registry-permissions.yml b/changelogs/unreleased/34414-update-personal-access-token-scope-descriptions-to-reflect-registry-permissions.yml
deleted file mode 100644
index f0cc7fe9b6d..00000000000
--- a/changelogs/unreleased/34414-update-personal-access-token-scope-descriptions-to-reflect-registry-permissions.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Updated the personal access token api scope description to reflect the permissions
- it grants
-merge_request: 31759
-author:
-type: other
diff --git a/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml b/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml
deleted file mode 100644
index e13e3e86a37..00000000000
--- a/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Kubernetes service integration page
-merge_request: 31365
-author:
-type: removed
diff --git a/changelogs/unreleased/43080-speed-up-deploy-keys.yml b/changelogs/unreleased/43080-speed-up-deploy-keys.yml
deleted file mode 100644
index 73c9a9e5f82..00000000000
--- a/changelogs/unreleased/43080-speed-up-deploy-keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Speed up loading and filtering deploy keys and their projects
-merge_request: 31384
-author:
-type: performance
diff --git a/changelogs/unreleased/44036-fix-someone-edited-the-issue-at-the-same-time-false-warning.yml b/changelogs/unreleased/44036-fix-someone-edited-the-issue-at-the-same-time-false-warning.yml
deleted file mode 100644
index 674d53286e6..00000000000
--- a/changelogs/unreleased/44036-fix-someone-edited-the-issue-at-the-same-time-false-warning.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix flashing conflict warning when editing issues
-merge_request: 31469
-author:
-type: fixed
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/47814-search-view-labels.yml b/changelogs/unreleased/47814-search-view-labels.yml
deleted file mode 100644
index b4f10150d13..00000000000
--- a/changelogs/unreleased/47814-search-view-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Moved labels out of fields on Search page
-merge_request: 31137
-author:
-type: fixed
diff --git a/changelogs/unreleased/48717-rate-limit-raw-controller-show.yml b/changelogs/unreleased/48717-rate-limit-raw-controller-show.yml
deleted file mode 100644
index 38ee95a7553..00000000000
--- a/changelogs/unreleased/48717-rate-limit-raw-controller-show.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Rate Request Limiter to RawController#show endpoint
-merge_request: 30635
-author:
-type: added
diff --git a/changelogs/unreleased/49392-exempt-jwt-auth-for-user-gitlab-ci-token-from-rate-limiting.yml b/changelogs/unreleased/49392-exempt-jwt-auth-for-user-gitlab-ci-token-from-rate-limiting.yml
new file mode 100644
index 00000000000..3ce96e64736
--- /dev/null
+++ b/changelogs/unreleased/49392-exempt-jwt-auth-for-user-gitlab-ci-token-from-rate-limiting.yml
@@ -0,0 +1,5 @@
+---
+title: Exempt user gitlab-ci-token from rate limiting
+merge_request: 31909
+author:
+type: fixed
diff --git a/changelogs/unreleased/50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml b/changelogs/unreleased/50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
deleted file mode 100644
index 9137e9339aa..00000000000
--- a/changelogs/unreleased/50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow email notifications to be disabled for all members of a group or project
-merge_request: 30755
-author: Dustin Spicuzza
-type: added
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
deleted file mode 100644
index a5fe7b1d18e..00000000000
--- a/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-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
deleted file mode 100644
index 03f1cec0f67..00000000000
--- a/changelogs/unreleased/50070-legacy-attachments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create rake tasks for migrating legacy uploads out of deprecated paths
-merge_request: 29409
-author:
-type: other
diff --git a/changelogs/unreleased/50130-cluster-cluster-details-update-automatically-after-cluster-is-created.yml b/changelogs/unreleased/50130-cluster-cluster-details-update-automatically-after-cluster-is-created.yml
deleted file mode 100644
index dc718572cfb..00000000000
--- a/changelogs/unreleased/50130-cluster-cluster-details-update-automatically-after-cluster-is-created.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update cluster page automatically when cluster is created
-merge_request: 27189
-author:
-type: changed
diff --git a/changelogs/unreleased/51470-webide-default-commit.yml b/changelogs/unreleased/51470-webide-default-commit.yml
new file mode 100644
index 00000000000..d64111e7ec3
--- /dev/null
+++ b/changelogs/unreleased/51470-webide-default-commit.yml
@@ -0,0 +1,5 @@
+---
+title: Updated WebIDE default commit options
+merge_request: 31449
+author:
+type: changed
diff --git a/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml b/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml
deleted file mode 100644
index 645c92127a3..00000000000
--- a/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use separate Kubernetes namespaces per environment
-merge_request: 30711
-author:
-type: added
diff --git a/changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml b/changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml
new file mode 100644
index 00000000000..91a1fb5e6c9
--- /dev/null
+++ b/changelogs/unreleased/55360-redundant-index-in-the-releases-table_v2.yml
@@ -0,0 +1,5 @@
+---
+title: Removed redundant index on releases table
+merge_request: 31487
+author:
+type: removed
diff --git a/changelogs/unreleased/55564-remove-if-in-before-after-action.yml b/changelogs/unreleased/55564-remove-if-in-before-after-action.yml
deleted file mode 100644
index a787faa8a9c..00000000000
--- a/changelogs/unreleased/55564-remove-if-in-before-after-action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rewrite `if:` argument in before_action and alike when `only:` is also used
-merge_request: 24412
-author: George Thomas @thegeorgeous
-type: other
diff --git a/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml b/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml
new file mode 100644
index 00000000000..a937614be38
--- /dev/null
+++ b/changelogs/unreleased/55999-misleading-pipeline-tooltip-messages-and-misleading-ci-status-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Remove "Commit" from pipeline status tooltips
+merge_request: 31861
+author:
+type: fixed
diff --git a/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml b/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml
deleted file mode 100644
index a2fa07c6ed2..00000000000
--- a/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make quick action commands applied banner more useful
-merge_request: 26672
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/56130-deployed_at.yml b/changelogs/unreleased/56130-deployed_at.yml
new file mode 100644
index 00000000000..c53658de752
--- /dev/null
+++ b/changelogs/unreleased/56130-deployed_at.yml
@@ -0,0 +1,5 @@
+---
+title: Replace finished_at with deployed_at for the internal API Deployment entity
+merge_request: 32000
+author:
+type: other
diff --git a/changelogs/unreleased/56130-deployment-date.yml b/changelogs/unreleased/56130-deployment-date.yml
deleted file mode 100644
index 7d1e84bbaa4..00000000000
--- a/changelogs/unreleased/56130-deployment-date.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add finished_at to the internal API Deployment entity
-merge_request: 31808
-author:
-type: other
diff --git a/changelogs/unreleased/56883-migration.yml b/changelogs/unreleased/56883-migration.yml
new file mode 100644
index 00000000000..d6eb49e62e1
--- /dev/null
+++ b/changelogs/unreleased/56883-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Create a project for self-monitoring the GitLab instance
+merge_request: 31389
+author:
+type: added
diff --git a/changelogs/unreleased/57402-upate-issues-list-sort-options.yml b/changelogs/unreleased/57402-upate-issues-list-sort-options.yml
new file mode 100644
index 00000000000..900e71e3204
--- /dev/null
+++ b/changelogs/unreleased/57402-upate-issues-list-sort-options.yml
@@ -0,0 +1,5 @@
+---
+title: Updates issues REST API to allow extended sort options
+merge_request: 31849
+author:
+type: changed
diff --git a/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml b/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml
deleted file mode 100644
index f634c0cd98a..00000000000
--- a/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix suggestion on lines that are not part of an MR
-merge_request: 30606
-author:
-type: fixed
diff --git a/changelogs/unreleased/58035-expand-mr-diff.yml b/changelogs/unreleased/58035-expand-mr-diff.yml
deleted file mode 100644
index 7163cce29f2..00000000000
--- a/changelogs/unreleased/58035-expand-mr-diff.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new expansion options for merge request diffs
-merge_request: 30927
-author:
-type: added
diff --git a/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml b/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml
deleted file mode 100644
index f719338b9cb..00000000000
--- a/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Incorrect empty state message on Explore projects
-merge_request: 25578
-author:
-type: fixed
diff --git a/changelogs/unreleased/59053-no-oauth-for-cicd-github-fe.yml b/changelogs/unreleased/59053-no-oauth-for-cicd-github-fe.yml
new file mode 100644
index 00000000000..221e8408dad
--- /dev/null
+++ b/changelogs/unreleased/59053-no-oauth-for-cicd-github-fe.yml
@@ -0,0 +1,5 @@
+---
+title: Remove oauth form from GitHub CI/CD only import authentication
+merge_request: 31488
+author:
+type: changed
diff --git a/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml b/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml
deleted file mode 100644
index 38cfa0f273e..00000000000
--- a/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'fix: updates to include units for the y axis label'
-merge_request: 30330
-author:
-type: fixed
diff --git a/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml b/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml
deleted file mode 100644
index 4c93a108f2b..00000000000
--- a/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove blank block from job sidebar
-merge_request: 30754
-author:
-type: fixed
diff --git a/changelogs/unreleased/59590-keyboard-shortcut-for-jump-to-next-unresolved-discussion.yml b/changelogs/unreleased/59590-keyboard-shortcut-for-jump-to-next-unresolved-discussion.yml
deleted file mode 100644
index 02e81c7fc87..00000000000
--- a/changelogs/unreleased/59590-keyboard-shortcut-for-jump-to-next-unresolved-discussion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Keyboard shortcut for jump to NEXT unresolved discussion
-merge_request: 30144
-author:
-type: added
diff --git a/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml
deleted file mode 100644
index 964962cb817..00000000000
--- a/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add branch/tags/commits dropdown filter on the search page for searching codes
-merge_request: 28282
-author: minghuan lei
-type: changed
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/59829-fix-style-lint-wiki.yml b/changelogs/unreleased/59829-fix-style-lint-wiki.yml
deleted file mode 100644
index 48242a77c6b..00000000000
--- a/changelogs/unreleased/59829-fix-style-lint-wiki.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the style-lint errors and warnings for `app/assets/stylesheets/pages/wiki.scss`
-merge_request: 31656
-author:
-type: other
diff --git a/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml b/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml
deleted file mode 100644
index ef11e8743f6..00000000000
--- a/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Batch processing of commit refs in markdown processing
-merge_request: 31037
-author:
-type: performance
diff --git a/changelogs/unreleased/60516-uninstall-tiller.yml b/changelogs/unreleased/60516-uninstall-tiller.yml
deleted file mode 100644
index db25e7b3338..00000000000
--- a/changelogs/unreleased/60516-uninstall-tiller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Helm to be uninstalled from the UI
-merge_request: 27359
-author:
-type: added
diff --git a/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml b/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml
deleted file mode 100644
index efc3ec241e2..00000000000
--- a/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Cert-Manager to be uninstalled
-merge_request: 31166
-author:
-type: added
diff --git a/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml b/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml
deleted file mode 100644
index c33dc0f50cd..00000000000
--- a/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Knative to be uninstalled from the UI
-merge_request: 30458
-author:
-type: added
diff --git a/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml b/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml
deleted file mode 100644
index 17763b4c69e..00000000000
--- a/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display group id on group admin page
-merge_request: 29735
-author: Zsolt Kovari
-type: added
diff --git a/changelogs/unreleased/60949-display-projectid-on-project-admin-page.yml b/changelogs/unreleased/60949-display-projectid-on-project-admin-page.yml
deleted file mode 100644
index 3ff83ede2fa..00000000000
--- a/changelogs/unreleased/60949-display-projectid-on-project-admin-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display project id on project admin page
-merge_request: 29734
-author: Zsolt Kovari
-type: added
diff --git a/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml b/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml
deleted file mode 100644
index 99fc817d703..00000000000
--- a/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Adjusted the clickable area of collapsed sidebar elements"
-merge_request: 30974
-author: Michel Engelen
-type: changed
diff --git a/changelogs/unreleased/61332-web-ide-mr-branch-dropdown-closes-unexpectedly.yml b/changelogs/unreleased/61332-web-ide-mr-branch-dropdown-closes-unexpectedly.yml
deleted file mode 100644
index 1f5e507d48d..00000000000
--- a/changelogs/unreleased/61332-web-ide-mr-branch-dropdown-closes-unexpectedly.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix an issue where clicking outside the MR/branch search box in WebIDE closed the dropdown.
-merge_request: 31523
-author:
-type: fixed
diff --git a/changelogs/unreleased/61335-fix-file-icon-status.yml b/changelogs/unreleased/61335-fix-file-icon-status.yml
deleted file mode 100644
index d524d91b246..00000000000
--- a/changelogs/unreleased/61335-fix-file-icon-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix IDE new files icon in tree
-merge_request: 31560
-author:
-type: fixed
diff --git a/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml b/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml
deleted file mode 100644
index 58e29212462..00000000000
--- a/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Prevent discussion filter from persisting to `Show all activity` when opening
- links to notes
-merge_request: 31229
-author:
-type: fixed
diff --git a/changelogs/unreleased/61776-fixing-the-U2F-warning-message-text-colour.yml b/changelogs/unreleased/61776-fixing-the-U2F-warning-message-text-colour.yml
deleted file mode 100644
index 988eb77db12..00000000000
--- a/changelogs/unreleased/61776-fixing-the-U2F-warning-message-text-colour.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove the warning style from the U2F device message in user settings > account
-merge_request: 30119
-author: matejlatin
-type: other
diff --git a/changelogs/unreleased/61787-broadcast-messages-colour-selector-provide-default-options-with-descriptive-labels.yml b/changelogs/unreleased/61787-broadcast-messages-colour-selector-provide-default-options-with-descriptive-labels.yml
deleted file mode 100644
index ec6e9c5aff8..00000000000
--- a/changelogs/unreleased/61787-broadcast-messages-colour-selector-provide-default-options-with-descriptive-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add color selector to broadcast messages form
-merge_request: 30988
-author:
-type: other
diff --git a/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml
deleted file mode 100644
index ccc3195e6ae..00000000000
--- a/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updated the detached pipeline badge tooltip text to offer a better explanation
-merge_request: 31626
-author:
-type: other
diff --git a/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml b/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml
deleted file mode 100644
index 10f2b7eaed5..00000000000
--- a/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Harmonize selections in user settings
-merge_request: 31110
-author: Marc Schwede
-type: other
diff --git a/changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml b/changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml
new file mode 100644
index 00000000000..7e1eeddef32
--- /dev/null
+++ b/changelogs/unreleased/62322-add-optional-id-to-label-api-put-delete-pd.yml
@@ -0,0 +1,5 @@
+---
+title: Add optional label_id parameter to label API for PUT and DELETE
+merge_request: 31804
+author:
+type: changed
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/62609-test-summary-loading-spinner-is-too-large.yml b/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml
deleted file mode 100644
index f8e4a26dad8..00000000000
--- a/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust size and align MR-widget loading icon
-merge_request: 31503
-author:
-type: fixed
diff --git a/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml b/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml
deleted file mode 100644
index b6bc03f4003..00000000000
--- a/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Embed specific metrics chart in issue
-merge_request: 31644
-author:
-type: added
diff --git a/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml
deleted file mode 100644
index aaf0ddfa48d..00000000000
--- a/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow links to metrics dashboard at a specific time
-merge_request: 31283
-author:
-type: added
diff --git a/changelogs/unreleased/63181-collapsible-line.yml b/changelogs/unreleased/63181-collapsible-line.yml
deleted file mode 100644
index c13d4eeab6c..00000000000
--- a/changelogs/unreleased/63181-collapsible-line.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Makes collapsible title clickable in job log
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml b/changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml
deleted file mode 100644
index 815010e15ae..00000000000
--- a/changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Personal access tokens are accepted using OAuth2 header format
-merge_request: 30277
-author:
-type: added
diff --git a/changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml b/changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml
deleted file mode 100644
index c3ee3108216..00000000000
--- a/changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pipeline emails not respecting group notification email setting
-merge_request: 30907
-author:
-type: fixed
diff --git a/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml b/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml
deleted file mode 100644
index 387c01dc135..00000000000
--- a/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add system notes for when a Zoom call was added/removed from an issue
-merge_request: 30857
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/63568-access-email-notifications-custom-email.yml b/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
deleted file mode 100644
index ece6442d7cf..00000000000
--- a/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect group notification email when sending group access notifications
-merge_request: 31089
-author:
-type: fixed
diff --git a/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml b/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml
deleted file mode 100644
index 7943d9573f3..00000000000
--- a/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix GC::Profiler metrics fetching
-merge_request: 31331
-author:
-type: fixed
diff --git a/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml b/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml
deleted file mode 100644
index ab116674ced..00000000000
--- a/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove extra padding from disabled comment box
-merge_request: 31603
-author:
-type: fixed
diff --git a/changelogs/unreleased/63730-fix-500-status-labels-pd.yml b/changelogs/unreleased/63730-fix-500-status-labels-pd.yml
deleted file mode 100644
index a1e2ae0e5df..00000000000
--- a/changelogs/unreleased/63730-fix-500-status-labels-pd.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix admin labels page when there are invalid records
-merge_request: 30885
-author:
-type: fixed
diff --git a/changelogs/unreleased/63833-fix-jira-issues-url.yml b/changelogs/unreleased/63833-fix-jira-issues-url.yml
deleted file mode 100644
index 24d6bca3842..00000000000
--- a/changelogs/unreleased/63833-fix-jira-issues-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Handle trailing slashes when generating Jira issue URLs
-merge_request: 30911
-author:
-type: fixed
diff --git a/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml b/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml
deleted file mode 100644
index 1a5a552b120..00000000000
--- a/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Count snippet creation, update and comment events
-merge_request: 30930
-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/63942-remove-config-action_dispatch-use_authenticated_cookie_encryption-configuration.yml b/changelogs/unreleased/63942-remove-config-action_dispatch-use_authenticated_cookie_encryption-configuration.yml
deleted file mode 100644
index 741763403a5..00000000000
--- a/changelogs/unreleased/63942-remove-config-action_dispatch-use_authenticated_cookie_encryption-configuration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable authenticated cookie encryption
-merge_request: 31463
-author:
-type: other
diff --git a/changelogs/unreleased/64081-override-helm-release-name.yml b/changelogs/unreleased/64081-override-helm-release-name.yml
deleted file mode 100644
index 2bf39b17c03..00000000000
--- a/changelogs/unreleased/64081-override-helm-release-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow multiple Auto DevOps projects to deploy to a single namespace within a k8s cluster
-merge_request: 30360
-author: James Keogh
-type: added
diff --git a/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml b/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml
deleted file mode 100644
index 272c830a914..00000000000
--- a/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enables storage statistics for root namespaces on database
-merge_request: 31392
-author:
-type: other
diff --git a/changelogs/unreleased/64160-fix-duplicate-buttons.yml b/changelogs/unreleased/64160-fix-duplicate-buttons.yml
deleted file mode 100644
index 12416a611ed..00000000000
--- a/changelogs/unreleased/64160-fix-duplicate-buttons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate buttons in diff discussion
-merge_request: 30757
-author:
-type: fixed
diff --git a/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml b/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml
deleted file mode 100644
index f86c63a15b6..00000000000
--- a/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve MembersFinder query performance using UNION
-merge_request: 30451
-author: Jacopo Beschi @jacopo-beschi
-type: performance
diff --git a/changelogs/unreleased/64190-add-mr-form.yml b/changelogs/unreleased/64190-add-mr-form.yml
deleted file mode 100644
index 08340d01fd8..00000000000
--- a/changelogs/unreleased/64190-add-mr-form.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add MR form to Visual Review (EE) runtime configuration
-merge_request: 30481
-author:
-type: changed
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/64257-active_session_lookup_key_cleanup.yml b/changelogs/unreleased/64257-active_session_lookup_key_cleanup.yml
deleted file mode 100644
index df3cd98830e..00000000000
--- a/changelogs/unreleased/64257-active_session_lookup_key_cleanup.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rake task to cleanup expired ActiveSession lookup keys
-merge_request: 30668
-author:
-type: performance
diff --git a/changelogs/unreleased/64257-warden_set_user_fix.yml b/changelogs/unreleased/64257-warden_set_user_fix.yml
deleted file mode 100644
index 7b6818876fb..00000000000
--- a/changelogs/unreleased/64257-warden_set_user_fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure Warden triggers after_authentication callback
-merge_request: 31138
-author:
-type: fixed
diff --git a/changelogs/unreleased/64265-center-loading-icon.yml b/changelogs/unreleased/64265-center-loading-icon.yml
deleted file mode 100644
index cd4253b63c6..00000000000
--- a/changelogs/unreleased/64265-center-loading-icon.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Center loading icon in CI action component
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/64269-pipeline-api-fails-with-401.yml b/changelogs/unreleased/64269-pipeline-api-fails-with-401.yml
new file mode 100644
index 00000000000..582339901ae
--- /dev/null
+++ b/changelogs/unreleased/64269-pipeline-api-fails-with-401.yml
@@ -0,0 +1,5 @@
+---
+title: Read pipelines from public projects through API without an access token
+merge_request: 31816
+author:
+type: fixed
diff --git a/changelogs/unreleased/64295-predictable-environment-slugs.yml b/changelogs/unreleased/64295-predictable-environment-slugs.yml
deleted file mode 100644
index de581b2b8e1..00000000000
--- a/changelogs/unreleased/64295-predictable-environment-slugs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use predictable environment slugs
-merge_request: 30551
-author:
-type: added
diff --git a/changelogs/unreleased/64341-user-callout-deferred-link-support.yml b/changelogs/unreleased/64341-user-callout-deferred-link-support.yml
deleted file mode 100644
index 05230ddc124..00000000000
--- a/changelogs/unreleased/64341-user-callout-deferred-link-support.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for deferred links in persistent user callouts.
-merge_request: 30818
-author:
-type: added
diff --git a/changelogs/unreleased/64383-pattern-matching-with-variables-causes-gitlabs-ci-lint-to-throw-500.yml b/changelogs/unreleased/64383-pattern-matching-with-variables-causes-gitlabs-ci-lint-to-throw-500.yml
new file mode 100644
index 00000000000..ba4bd614170
--- /dev/null
+++ b/changelogs/unreleased/64383-pattern-matching-with-variables-causes-gitlabs-ci-lint-to-throw-500.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors caused by pattern matching with variables in CI Lint
+merge_request: 31719
+author:
+type: fixed
diff --git a/changelogs/unreleased/64385-charts-scroll-handle-icon-has-disappeared.yml b/changelogs/unreleased/64385-charts-scroll-handle-icon-has-disappeared.yml
new file mode 100644
index 00000000000..93ed2ff4619
--- /dev/null
+++ b/changelogs/unreleased/64385-charts-scroll-handle-icon-has-disappeared.yml
@@ -0,0 +1,5 @@
+---
+title: fix charts scroll handle icon to use gitlab svg
+merge_request: 31825
+author:
+type: fixed
diff --git a/changelogs/unreleased/64608-double-tooltips.yml b/changelogs/unreleased/64608-double-tooltips.yml
deleted file mode 100644
index f6cb1944d26..00000000000
--- a/changelogs/unreleased/64608-double-tooltips.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevents showing 2 tooltips in pipelines table
-merge_request:
-author:
-type: fixed
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/64675-Dashboard-URL-legend-border.yml b/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml
deleted file mode 100644
index f35261fcd6c..00000000000
--- a/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removed extrenal dashboard legend border
-merge_request: 31407
-author:
-type: fixed
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/64697-markdown-issues-checkbox-inside-blockquote-status-won-t-be-saved.yml b/changelogs/unreleased/64697-markdown-issues-checkbox-inside-blockquote-status-won-t-be-saved.yml
deleted file mode 100644
index 00664d64050..00000000000
--- a/changelogs/unreleased/64697-markdown-issues-checkbox-inside-blockquote-status-won-t-be-saved.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Better support clickable tasklists inside blockquotes
-merge_request: 30952
-author:
-type: fixed
diff --git a/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml b/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml
deleted file mode 100644
index 0d2fbaf01ed..00000000000
--- a/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure visibility icons in group/project listings are grey
-merge_request: 30858
-author:
-type: fixed
diff --git a/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml
deleted file mode 100644
index c564b98bb41..00000000000
--- a/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve layout of dropdowns in the metrics dashboard page
-merge_request: 31239
-author:
-type: fixed
diff --git a/changelogs/unreleased/64746-Commit-authors-avatar-sretched-in-commit-view-if-no-image-is-loaded.yml b/changelogs/unreleased/64746-Commit-authors-avatar-sretched-in-commit-view-if-no-image-is-loaded.yml
deleted file mode 100644
index fb0f4cedc62..00000000000
--- a/changelogs/unreleased/64746-Commit-authors-avatar-sretched-in-commit-view-if-no-image-is-loaded.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed distorted avatars when resource not reachable
-merge_request: 30904
-author: Marc Schwede
-type: other
diff --git a/changelogs/unreleased/64763-fix-tags-page-layout.yml b/changelogs/unreleased/64763-fix-tags-page-layout.yml
deleted file mode 100644
index db6b1f31506..00000000000
--- a/changelogs/unreleased/64763-fix-tags-page-layout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix tag page layout
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/64764-fix-serverless-layout.yml b/changelogs/unreleased/64764-fix-serverless-layout.yml
new file mode 100644
index 00000000000..9717062df93
--- /dev/null
+++ b/changelogs/unreleased/64764-fix-serverless-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Fix serverless entry page layout
+merge_request: 32029
+author:
+type: fixed
diff --git a/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml b/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml
deleted file mode 100644
index 2a45eec78ef..00000000000
--- a/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add space to "merged by" widget
-merge_request: 30972
-author:
-type: fixed
diff --git a/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml b/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml
deleted file mode 100644
index 21771c76873..00000000000
--- a/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'feat: adds a download to csv functionality to the dropdown in prometheus metrics'
-merge_request: 31679
-author:
-type: changed
diff --git a/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml b/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml
deleted file mode 100644
index f80111f0bd9..00000000000
--- a/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pid discovery for Unicorn processes in `PidProvider`
-merge_request: 31056
-author:
-type: fixed
diff --git a/changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml b/changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml
deleted file mode 100644
index 4fa3b7783c5..00000000000
--- a/changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove :livesum from RubySampler metrics
-merge_request: 31047
-author:
-type: fixed
diff --git a/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml b/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml
deleted file mode 100644
index dd74b8443bc..00000000000
--- a/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix incorrect use of message interpolation
-merge_request: 31121
-author:
-type: fixed
diff --git a/changelogs/unreleased/65263-manual-action.yml b/changelogs/unreleased/65263-manual-action.yml
deleted file mode 100644
index 47b2a2ed329..00000000000
--- a/changelogs/unreleased/65263-manual-action.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hides loading spinner in pipelines actions after request has been fullfiled
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/65278-fix-puma-master-counter-wipe.yml b/changelogs/unreleased/65278-fix-puma-master-counter-wipe.yml
deleted file mode 100644
index fb9d6fa251d..00000000000
--- a/changelogs/unreleased/65278-fix-puma-master-counter-wipe.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix active metric files being wiped after the app starts
-merge_request: 31668
-author:
-type: fixed
diff --git a/changelogs/unreleased/65412-add-support-for-line-charts.yml b/changelogs/unreleased/65412-add-support-for-line-charts.yml
new file mode 100644
index 00000000000..cb9043596b7
--- /dev/null
+++ b/changelogs/unreleased/65412-add-support-for-line-charts.yml
@@ -0,0 +1,5 @@
+---
+title: Create component to display area and line charts in monitor dashboards
+merge_request: 31639
+author:
+type: added
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/65483-add-a-resend-confirmation-link.yml b/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml
deleted file mode 100644
index a5f62dbcd56..00000000000
--- a/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow users to resend a confirmation link when the grace period has expired
-merge_request: 31476
-author:
-type: changed
diff --git a/changelogs/unreleased/65530-add-externalization-and-fix-regression-in-shortcuts-helper-modal.yml b/changelogs/unreleased/65530-add-externalization-and-fix-regression-in-shortcuts-helper-modal.yml
deleted file mode 100644
index fc29a514c42..00000000000
--- a/changelogs/unreleased/65530-add-externalization-and-fix-regression-in-shortcuts-helper-modal.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fixed display of some sections and externalized all text in the shortcuts modal
- overlay
-merge_request: 31594
-author:
-type: fixed
diff --git a/changelogs/unreleased/65660-update-karma-to-4-2-0.yml b/changelogs/unreleased/65660-update-karma-to-4-2-0.yml
deleted file mode 100644
index c0cb40ce169..00000000000
--- a/changelogs/unreleased/65660-update-karma-to-4-2-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update karma to 4.2.0
-merge_request: 31495
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/65671-update-mini_magick-to-4-9-5.yml b/changelogs/unreleased/65671-update-mini_magick-to-4-9-5.yml
deleted file mode 100644
index a6f8576ae0b..00000000000
--- a/changelogs/unreleased/65671-update-mini_magick-to-4-9-5.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update mini_magick to 4.9.5
-merge_request: 31505
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml b/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml
deleted file mode 100644
index bacc3f5fc11..00000000000
--- a/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add max_replication_slots to PG HA documentation
-merge_request: 31534
-author:
-type: other
diff --git a/changelogs/unreleased/65705-two-buttons.yml b/changelogs/unreleased/65705-two-buttons.yml
deleted file mode 100644
index b92e28f9d68..00000000000
--- a/changelogs/unreleased/65705-two-buttons.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent duplicated trigger action button
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/65790-highlight.yml b/changelogs/unreleased/65790-highlight.yml
deleted file mode 100644
index 2531a3730ed..00000000000
--- a/changelogs/unreleased/65790-highlight.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds highlight to the collapsible section
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/65803-invalidate-branches-cache-on-refresh.yml b/changelogs/unreleased/65803-invalidate-branches-cache-on-refresh.yml
deleted file mode 100644
index 217db8aa05a..00000000000
--- a/changelogs/unreleased/65803-invalidate-branches-cache-on-refresh.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Invalidate branches cache on PostReceive
-merge_request: 31653
-author:
-type: fixed
diff --git a/changelogs/unreleased/66008-fix-project-image-in-slack-notifications.yml b/changelogs/unreleased/66008-fix-project-image-in-slack-notifications.yml
deleted file mode 100644
index df0ac649ac1..00000000000
--- a/changelogs/unreleased/66008-fix-project-image-in-slack-notifications.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project avatar image in Slack pipeline notifications
-merge_request: 31788
-author:
-type: fixed
diff --git a/changelogs/unreleased/66022-git-clone-url-box-on-wiki-git-access-page-is-broken.yml b/changelogs/unreleased/66022-git-clone-url-box-on-wiki-git-access-page-is-broken.yml
new file mode 100644
index 00000000000..931e947f8ed
--- /dev/null
+++ b/changelogs/unreleased/66022-git-clone-url-box-on-wiki-git-access-page-is-broken.yml
@@ -0,0 +1,5 @@
+---
+title: Fix broken git clone box on wiki git access page
+merge_request: 31898
+author:
+type: fixed
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
deleted file mode 100644
index 1caa5fa84ce..00000000000
--- a/changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix starrers counts after searching
-merge_request: 31823
-author:
-type: fixed
diff --git a/changelogs/unreleased/66037-deployment-user.yml b/changelogs/unreleased/66037-deployment-user.yml
new file mode 100644
index 00000000000..8a61b8145af
--- /dev/null
+++ b/changelogs/unreleased/66037-deployment-user.yml
@@ -0,0 +1,5 @@
+---
+title: Return correct user for manual deployments
+merge_request: 32004
+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/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml b/changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml
new file mode 100644
index 00000000000..8d7af96a7d8
--- /dev/null
+++ b/changelogs/unreleased/66073-use-time-series-chart-instead-of-area-chart-in-panel_types.yml
@@ -0,0 +1,5 @@
+---
+title: Enable line charts in dashbaord panels and embedded charts
+merge_request: 31920
+author:
+type: added
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/66402-use-visual-review-tools-npm-package.yml b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml
new file mode 100644
index 00000000000..46773e12002
--- /dev/null
+++ b/changelogs/unreleased/66402-use-visual-review-tools-npm-package.yml
@@ -0,0 +1,5 @@
+---
+title: Move visual review toolbar code to NPM
+merge_request: 32159
+author:
+type: fixed
diff --git a/changelogs/unreleased/FixLocaleEN.yml b/changelogs/unreleased/FixLocaleEN.yml
deleted file mode 100644
index 49738a6d127..00000000000
--- a/changelogs/unreleased/FixLocaleEN.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicated mapping key in config/locales/en.yml
-merge_request: 30980
-author: Peter Dave Hello
-type: fixed
diff --git a/changelogs/unreleased/GL-12412.yml b/changelogs/unreleased/GL-12412.yml
deleted file mode 100644
index 304bd63d150..00000000000
--- a/changelogs/unreleased/GL-12412.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add DS_PIP_DEPENDENCY_PATH option to configure Dependency Scanning for projects using pip.
-merge_request: 30762
-author:
-type: changed
diff --git a/changelogs/unreleased/GL-12757.yml b/changelogs/unreleased/GL-12757.yml
deleted file mode 100644
index e58ecf9259f..00000000000
--- a/changelogs/unreleased/GL-12757.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update the container scanning CI template to use v12 of the clair scanner.
-merge_request: 30809
-author:
-type: changed
diff --git a/changelogs/unreleased/ab-add-index-on-environments.yml b/changelogs/unreleased/ab-add-index-on-environments.yml
deleted file mode 100644
index 6c7641912f4..00000000000
--- a/changelogs/unreleased/ab-add-index-on-environments.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create index on environments by state
-merge_request: 31231
-author:
-type: performance
diff --git a/changelogs/unreleased/ab-count-strategies.yml b/changelogs/unreleased/ab-count-strategies.yml
deleted file mode 100644
index bd95ff45d6f..00000000000
--- a/changelogs/unreleased/ab-count-strategies.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use tablesample approximate counting by default.
-merge_request: 31048
-author:
-type: performance
diff --git a/changelogs/unreleased/ac-graphql-root-namespace-stats.yml b/changelogs/unreleased/ac-graphql-root-namespace-stats.yml
new file mode 100644
index 00000000000..9784605ab2e
--- /dev/null
+++ b/changelogs/unreleased/ac-graphql-root-namespace-stats.yml
@@ -0,0 +1,5 @@
+---
+title: Expose namespace storage statistics with GraphQL
+merge_request: 32012
+author:
+type: added
diff --git a/changelogs/unreleased/add-caching-to-archive-endpoint.yml b/changelogs/unreleased/add-caching-to-archive-endpoint.yml
deleted file mode 100644
index 770ec16e804..00000000000
--- a/changelogs/unreleased/add-caching-to-archive-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return an ETag header for the archive endpoint
-merge_request: 30581
-author:
-type: added
diff --git a/changelogs/unreleased/add-git-blame-api.yml b/changelogs/unreleased/add-git-blame-api.yml
deleted file mode 100644
index cdb77041433..00000000000
--- a/changelogs/unreleased/add-git-blame-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add git blame to GitLab API
-merge_request: 30675
-author: Oleg Zubchenko
-type: added
diff --git a/changelogs/unreleased/add-outbound-requests-whitelist-for-local-networks.yml b/changelogs/unreleased/add-outbound-requests-whitelist-for-local-networks.yml
deleted file mode 100644
index 9b50175f536..00000000000
--- a/changelogs/unreleased/add-outbound-requests-whitelist-for-local-networks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Outbound requests whitelist for local networks
-merge_request: 30350
-author: Istvan Szalai
-type: added
diff --git a/changelogs/unreleased/add-release-to-github-importer.yml b/changelogs/unreleased/add-release-to-github-importer.yml
deleted file mode 100644
index d11e7c725f7..00000000000
--- a/changelogs/unreleased/add-release-to-github-importer.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a field for released_at to GH importer
-merge_request: 31496
-author:
-type: fixed
diff --git a/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml b/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml
deleted file mode 100644
index f810c2c5ada..00000000000
--- a/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for start_sha to commits API
-merge_request: 29598
-author:
-type: changed
diff --git a/changelogs/unreleased/add_links_to_latest_pipelines.yml b/changelogs/unreleased/add_links_to_latest_pipelines.yml
new file mode 100644
index 00000000000..333cd6c92b4
--- /dev/null
+++ b/changelogs/unreleased/add_links_to_latest_pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: 'Add links for latest pipelines'
+merge_request: 20865
+author: Alex Ives
+type: added
diff --git a/changelogs/unreleased/adjust-group-level-analytics-to-accept-multiple-ids.yml b/changelogs/unreleased/adjust-group-level-analytics-to-accept-multiple-ids.yml
deleted file mode 100644
index 5e138e1059c..00000000000
--- a/changelogs/unreleased/adjust-group-level-analytics-to-accept-multiple-ids.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust group level analytics to accept multiple ids
-merge_request: 30744
-author:
-type: added
diff --git a/changelogs/unreleased/alipniagov-fix-wiki_can_not_be_created_total-counter.yml b/changelogs/unreleased/alipniagov-fix-wiki_can_not_be_created_total-counter.yml
deleted file mode 100644
index 58f969ed742..00000000000
--- a/changelogs/unreleased/alipniagov-fix-wiki_can_not_be_created_total-counter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix :wiki_can_not_be_created_total counter
-merge_request: 31673
-author:
-type: fixed
diff --git a/changelogs/unreleased/allow-all-users-to-see-history.yml b/changelogs/unreleased/allow-all-users-to-see-history.yml
deleted file mode 100644
index 7423fa079cc..00000000000
--- a/changelogs/unreleased/allow-all-users-to-see-history.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Align access permissions for wiki history to those of wiki pages
-merge_request: 30470
-type: fixed
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/an-sidekiq-chaos.yml b/changelogs/unreleased/an-sidekiq-chaos.yml
deleted file mode 100644
index cede35c95cc..00000000000
--- a/changelogs/unreleased/an-sidekiq-chaos.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds chaos endpoints to Sidekiq
-merge_request: 30814
-author:
-type: other
diff --git a/changelogs/unreleased/an-sidekiq-scheduling_latency.yml b/changelogs/unreleased/an-sidekiq-scheduling_latency.yml
deleted file mode 100644
index 2d6f462745e..00000000000
--- a/changelogs/unreleased/an-sidekiq-scheduling_latency.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds Sidekiq scheduling latency structured logging field
-merge_request: 30784
-author:
-type: other
diff --git a/changelogs/unreleased/avoid-race-condition-of-archive-trace-cron-worker.yml b/changelogs/unreleased/avoid-race-condition-of-archive-trace-cron-worker.yml
new file mode 100644
index 00000000000..b3f84ec32d2
--- /dev/null
+++ b/changelogs/unreleased/avoid-race-condition-of-archive-trace-cron-worker.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid conflicts between ArchiveTracesCronWorker and ArchiveTraceWorker
+merge_request: 31376
+author:
+type: fixed
diff --git a/changelogs/unreleased/bjk-64064_cache_metrics.yml b/changelogs/unreleased/bjk-64064_cache_metrics.yml
deleted file mode 100644
index c9baff7cb7b..00000000000
--- a/changelogs/unreleased/bjk-64064_cache_metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adjust redis cache metrics
-merge_request: 30572
-author:
-type: changed
diff --git a/changelogs/unreleased/bjk-usage_ping.yml b/changelogs/unreleased/bjk-usage_ping.yml
deleted file mode 100644
index dee6c1ad291..00000000000
--- a/changelogs/unreleased/bjk-usage_ping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update usage ping cron behavior
-merge_request: 30842
-author:
-type: performance
diff --git a/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml b/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml
deleted file mode 100644
index 48dfe662206..00000000000
--- a/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring scoped environment variables to core
-merge_request: 30779
-author:
-type: changed
diff --git a/changelogs/unreleased/bump_helm_kubectl_gitlab.yml b/changelogs/unreleased/bump_helm_kubectl_gitlab.yml
deleted file mode 100644
index d768462e130..00000000000
--- a/changelogs/unreleased/bump_helm_kubectl_gitlab.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Helm to 2.14.3 and kubectl to 1.11.10 for Kubernetes integration
-merge_request: 31716
-author:
-type: other
diff --git a/changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml b/changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml
deleted file mode 100644
index 1db0a4952b2..00000000000
--- a/changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Mark push mirrors as failed after 1 hour
-merge_request: 30999
-author:
-type: changed
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/bvl-remote-mirror-exception-handling.yml b/changelogs/unreleased/bvl-remote-mirror-exception-handling.yml
deleted file mode 100644
index 962376086b0..00000000000
--- a/changelogs/unreleased/bvl-remote-mirror-exception-handling.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Retry push mirrors faster when running concurrently, improve error handling
- when push mirrors fail
-merge_request: 31247
-author:
-type: changed
diff --git a/changelogs/unreleased/bw-add-index-for-relative-position.yml b/changelogs/unreleased/bw-add-index-for-relative-position.yml
deleted file mode 100644
index 80ca20992e6..00000000000
--- a/changelogs/unreleased/bw-add-index-for-relative-position.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add index for issues on relative position, project, and state for manual sorting
-merge_request: 30542
-author:
-type: fixed
diff --git a/changelogs/unreleased/ce-22058-improve-ux-multi-assignees-in-mr.yml b/changelogs/unreleased/ce-22058-improve-ux-multi-assignees-in-mr.yml
new file mode 100644
index 00000000000..e7453a2b9bd
--- /dev/null
+++ b/changelogs/unreleased/ce-22058-improve-ux-multi-assignees-in-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Update assignee (cannot merge) style
+merge_request: 31545
+author:
+type: changed
diff --git a/changelogs/unreleased/ce-xanf-add-links-to-admin-area.yml b/changelogs/unreleased/ce-xanf-add-links-to-admin-area.yml
deleted file mode 100644
index 9eb692c948b..00000000000
--- a/changelogs/unreleased/ce-xanf-add-links-to-admin-area.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add links to relevant configuration areas in admin area overview
-merge_request: 29306
-author:
-type: added
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/dblessing-fix-admin-user-radio-labels.yml b/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml
deleted file mode 100644
index 4f119d46a1f..00000000000
--- a/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix admin area user access level radio button labels
-merge_request: 31154
-author:
-type: fixed
diff --git a/changelogs/unreleased/dblessing-fix-public-project-ssh-only-ci-failure.yml b/changelogs/unreleased/dblessing-fix-public-project-ssh-only-ci-failure.yml
deleted file mode 100644
index 615a1571e95..00000000000
--- a/changelogs/unreleased/dblessing-fix-public-project-ssh-only-ci-failure.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow CI to clone public projects when HTTP protocol is disabled
-merge_request: 31632
-author:
-type: fixed
diff --git a/changelogs/unreleased/delete-designs-v2.yml b/changelogs/unreleased/delete-designs-v2.yml
deleted file mode 100644
index a678e4f93b9..00000000000
--- a/changelogs/unreleased/delete-designs-v2.yml
+++ /dev/null
@@ -1,4 +0,0 @@
----
-title: Adds event enum column to DesignsVersions join table
-merge_request: 30745
-type: added
diff --git a/changelogs/unreleased/dm-process-commit-worker-n-1.yml b/changelogs/unreleased/dm-process-commit-worker-n-1.yml
deleted file mode 100644
index 0bd7de6730a..00000000000
--- a/changelogs/unreleased/dm-process-commit-worker-n-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Look up upstream commits once before queuing ProcessCommitWorkers
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/double-slash-64592.yml b/changelogs/unreleased/double-slash-64592.yml
deleted file mode 100644
index e3b5b197ac5..00000000000
--- a/changelogs/unreleased/double-slash-64592.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent double slash in review apps path
-merge_request: 31212
-author:
-type: fixed
diff --git a/changelogs/unreleased/ee-2502-refactor-ee-app-assets-javascripts-approvals-components-approvers_select-vue-to-remove-approverusers.yml b/changelogs/unreleased/ee-2502-refactor-ee-app-assets-javascripts-approvals-components-approvers_select-vue-to-remove-approverusers.yml
new file mode 100644
index 00000000000..dfa0f0cb593
--- /dev/null
+++ b/changelogs/unreleased/ee-2502-refactor-ee-app-assets-javascripts-approvals-components-approvers_select-vue-to-remove-approverusers.yml
@@ -0,0 +1,5 @@
+---
+title: 'Add new API method in Api.js: projectUsers'
+merge_request: 31801
+author:
+type: other
diff --git a/changelogs/unreleased/enable-specific-embeds.yml b/changelogs/unreleased/enable-specific-embeds.yml
deleted file mode 100644
index f2e591621a8..00000000000
--- a/changelogs/unreleased/enable-specific-embeds.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable embedding of specific metrics charts in GFM
-merge_request: 31304
-author:
-type: added
diff --git a/changelogs/unreleased/extract_auto_deploy_into_base_image.yml b/changelogs/unreleased/extract_auto_deploy_into_base_image.yml
deleted file mode 100644
index ff0d1f3bd71..00000000000
--- a/changelogs/unreleased/extract_auto_deploy_into_base_image.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extract Auto DevOps deploy functions into a base image
-merge_request: 30404
-author:
-type: changed
diff --git a/changelogs/unreleased/fe-delete-old-boardservice.yml b/changelogs/unreleased/fe-delete-old-boardservice.yml
deleted file mode 100644
index bb06bfe80d5..00000000000
--- a/changelogs/unreleased/fe-delete-old-boardservice.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Change BoardService in favor of boardsStore on board blank state of the component
- board
-merge_request: 30546
-author: eduarmreyes
-type: other
diff --git a/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml b/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml
new file mode 100644
index 00000000000..736e12ff694
--- /dev/null
+++ b/changelogs/unreleased/fe-fix-issuable-sidebar-icon-of-notification-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Fix issuable sidebar icon on notification disabled
+merge_request: 32134
+author:
+type: fixed
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/feat-add-support-page-link-in-help-menu.yml b/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml
deleted file mode 100644
index 2cddd52212e..00000000000
--- a/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add admin-configurable "Support page URL" link to top Help dropdown menu
-merge_request: 30459
-author: Diego Louzán
-type: added
diff --git a/changelogs/unreleased/feat-smime-signed-notification-emails.yml b/changelogs/unreleased/feat-smime-signed-notification-emails.yml
new file mode 100644
index 00000000000..9672d0d964c
--- /dev/null
+++ b/changelogs/unreleased/feat-smime-signed-notification-emails.yml
@@ -0,0 +1,5 @@
+---
+title: Notification emails can be signed with SMIME
+merge_request: 30644
+author: Diego Louzán
+type: added
diff --git a/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml b/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
deleted file mode 100644
index bd9001bd671..00000000000
--- a/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Deploy serverless apps with gitlabktl
-merge_request: 30740
-author:
-type: added
diff --git a/changelogs/unreleased/filter-title-description-and-body-from-logs.yml b/changelogs/unreleased/filter-title-description-and-body-from-logs.yml
deleted file mode 100644
index 8b592790629..00000000000
--- a/changelogs/unreleased/filter-title-description-and-body-from-logs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Filter title, description, and body parameters from logs
-merge_request:
-author:
-type: changed
diff --git a/changelogs/unreleased/fix-alignment-on-security-reports.yml b/changelogs/unreleased/fix-alignment-on-security-reports.yml
deleted file mode 100644
index 5339b6d764d..00000000000
--- a/changelogs/unreleased/fix-alignment-on-security-reports.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes alignment issues with reports
-merge_request: 30839
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml b/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml
deleted file mode 100644
index a0564369b02..00000000000
--- a/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make `bin/web_puma` consider RAILS_ENV
-merge_request: 31378
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-commits-api-empty-refname.yml b/changelogs/unreleased/fix-commits-api-empty-refname.yml
deleted file mode 100644
index efdb950e45d..00000000000
--- a/changelogs/unreleased/fix-commits-api-empty-refname.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-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/fix-job-log-formatting.yml b/changelogs/unreleased/fix-job-log-formatting.yml
deleted file mode 100644
index 0dd545aaecc..00000000000
--- a/changelogs/unreleased/fix-job-log-formatting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix job logs where style changes were broken down into separate lines
-merge_request: 31674
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml b/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml
deleted file mode 100644
index 7d171c2cf5b..00000000000
--- a/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken issue links and possible 500 error on cycle analytics page when project name and path are different
-merge_request: 31471
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-avoid-incresaing-usage-ping-when-not-enabled.yml b/changelogs/unreleased/fj-avoid-incresaing-usage-ping-when-not-enabled.yml
deleted file mode 100644
index f1077f2d56d..00000000000
--- a/changelogs/unreleased/fj-avoid-incresaing-usage-ping-when-not-enabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid increasing redis counters when usage_ping is disabled
-merge_request: 30949
-author:
-type: changed
diff --git a/changelogs/unreleased/fj-count-web-ide-merge-requests.yml b/changelogs/unreleased/fj-count-web-ide-merge-requests.yml
deleted file mode 100644
index adcd0af37e8..00000000000
--- a/changelogs/unreleased/fj-count-web-ide-merge-requests.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Web IDE Usage Ping for Create SMAU
-merge_request: 30800
-author:
-type: changed
diff --git a/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml b/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml
deleted file mode 100644
index ab7c1697fd6..00000000000
--- a/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added navbar searches usage ping counter
-merge_request: 30953
-author:
-type: changed
diff --git a/changelogs/unreleased/georgekoltsov-18720-persistent-dashboard-sort.yml b/changelogs/unreleased/georgekoltsov-18720-persistent-dashboard-sort.yml
new file mode 100644
index 00000000000..7eed8550acd
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-18720-persistent-dashboard-sort.yml
@@ -0,0 +1,5 @@
+---
+title: Add persistance to last choice of projects sorting on projects dashboard page
+merge_request: 31669
+author:
+type: added
diff --git a/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml b/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml
deleted file mode 100644
index e28dbd6f0c4..00000000000
--- a/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix empty error flash message on profile:account page when updating username
- with username that has already been taken
-merge_request: 31809
-author:
-type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-51260-add-filtering-to-bitbucket-server-import.yml b/changelogs/unreleased/georgekoltsov-51260-add-filtering-to-bitbucket-server-import.yml
deleted file mode 100644
index c455b4cf642..00000000000
--- a/changelogs/unreleased/georgekoltsov-51260-add-filtering-to-bitbucket-server-import.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add BitBucketServer project import filtering
-merge_request: 31420
-author:
-type: added
diff --git a/changelogs/unreleased/georgekoltsov-55474-outbound-setting-system-hooks.yml b/changelogs/unreleased/georgekoltsov-55474-outbound-setting-system-hooks.yml
deleted file mode 100644
index fb1acb1e9f5..00000000000
--- a/changelogs/unreleased/georgekoltsov-55474-outbound-setting-system-hooks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new outbound network requests application setting for system hooks
-merge_request: 31177
-author:
-type: added
diff --git a/changelogs/unreleased/georgekoltsov-63408-user-mapping.yml b/changelogs/unreleased/georgekoltsov-63408-user-mapping.yml
deleted file mode 100644
index 451aac9c2e3..00000000000
--- a/changelogs/unreleased/georgekoltsov-63408-user-mapping.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix missing author line (`Created by: <user>`) in MRs/issues/comments of imported Bitbucket Cloud project'
-merge_request: 31579
-author:
-type: fixed
diff --git a/changelogs/unreleased/georgekoltsov-64311-set-visibility-private-if-internal-restricted.yml b/changelogs/unreleased/georgekoltsov-64311-set-visibility-private-if-internal-restricted.yml
deleted file mode 100644
index 18af16e5216..00000000000
--- a/changelogs/unreleased/georgekoltsov-64311-set-visibility-private-if-internal-restricted.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set visibility level 'Private' for restricted 'Internal' imported projects when 'Internal' visibility setting is restricted in admin settings
-merge_request: 30522
-author:
-type: other
diff --git a/changelogs/unreleased/georgekoltsov-64377-add-better-log-msg-to-members-mapper.yml b/changelogs/unreleased/georgekoltsov-64377-add-better-log-msg-to-members-mapper.yml
deleted file mode 100644
index 9557e633f76..00000000000
--- a/changelogs/unreleased/georgekoltsov-64377-add-better-log-msg-to-members-mapper.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: When GitLab import fails during importer user mapping step, add an explicit
- error message mentioning importer
-merge_request: 30838
-author:
-type: other
diff --git a/changelogs/unreleased/gitaly-version-v1.57.0.yml b/changelogs/unreleased/gitaly-version-v1.57.0.yml
deleted file mode 100644
index 65596199e16..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.57.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.57.0
-merge_request: 31568
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.59.0.yml b/changelogs/unreleased/gitaly-version-v1.59.0.yml
deleted file mode 100644
index d103f6b248e..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.59.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.59.0
-merge_request: 31743
-author:
-type: changed
diff --git a/changelogs/unreleased/group-milestones-dashboard-blunceford.yml b/changelogs/unreleased/group-milestones-dashboard-blunceford.yml
deleted file mode 100644
index a6ded27cb8c..00000000000
--- a/changelogs/unreleased/group-milestones-dashboard-blunceford.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug in dashboard display of closed milestones
-merge_request: 30820
-author:
-type: fixed
diff --git a/changelogs/unreleased/id-mr-widget-etag-caching.yml b/changelogs/unreleased/id-mr-widget-etag-caching.yml
deleted file mode 100644
index 69bf89991d4..00000000000
--- a/changelogs/unreleased/id-mr-widget-etag-caching.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Split MR widget into etag-cached and non-cached serializers
-merge_request: 31354
-author:
-type: performance
diff --git a/changelogs/unreleased/id-source-code-smau.yml b/changelogs/unreleased/id-source-code-smau.yml
deleted file mode 100644
index 6ba5068544e..00000000000
--- a/changelogs/unreleased/id-source-code-smau.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add usage pings for source code pushes
-merge_request: 31734
-author:
-type: added
diff --git a/changelogs/unreleased/implement-dag.yml b/changelogs/unreleased/implement-dag.yml
deleted file mode 100644
index 72f3f9a510c..00000000000
--- a/changelogs/unreleased/implement-dag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Support creating DAGs in CI config through the `needs` key"
-merge_request: 31328
-author:
-type: added
diff --git a/changelogs/unreleased/improve-quick-action-messages.yml b/changelogs/unreleased/improve-quick-action-messages.yml
deleted file mode 100644
index 86f8c251860..00000000000
--- a/changelogs/unreleased/improve-quick-action-messages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve quick action error messages
-merge_request: 31451
-author:
-type: other
diff --git a/changelogs/unreleased/issue-61873-no-error-message-for-general-settings.yml b/changelogs/unreleased/issue-61873-no-error-message-for-general-settings.yml
deleted file mode 100644
index 606c60721b8..00000000000
--- a/changelogs/unreleased/issue-61873-no-error-message-for-general-settings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: error message for general settings
-merge_request: 31636
-author: Mesut Güneş
-type: fixed
diff --git a/changelogs/unreleased/issue_58494.yml b/changelogs/unreleased/issue_58494.yml
deleted file mode 100644
index c74768fc020..00000000000
--- a/changelogs/unreleased/issue_58494.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent turning plain links into embedded when moving issues
-merge_request: 31489
-author:
-type: fixed
diff --git a/changelogs/unreleased/je-separate-namespace-fe.yml b/changelogs/unreleased/je-separate-namespace-fe.yml
deleted file mode 100644
index fb6be071eb6..00000000000
--- a/changelogs/unreleased/je-separate-namespace-fe.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update namespace label for GitLab-managed clusters
-merge_request: 30935
-author:
-type: added
diff --git a/changelogs/unreleased/jivanvl-add-chart-empty-state.yml b/changelogs/unreleased/jivanvl-add-chart-empty-state.yml
deleted file mode 100644
index 7b81ee82582..00000000000
--- a/changelogs/unreleased/jivanvl-add-chart-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add empty chart component
-merge_request: 30682
-author:
-type: fixed
diff --git a/changelogs/unreleased/jprovazn-fix-positioning.yml b/changelogs/unreleased/jprovazn-fix-positioning.yml
deleted file mode 100644
index 5d703008bba..00000000000
--- a/changelogs/unreleased/jprovazn-fix-positioning.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Optimize relative re-positioning when moving issues.
-merge_request: 30938
-author:
-type: fixed
diff --git a/changelogs/unreleased/jprovazn-project-search.yml b/changelogs/unreleased/jprovazn-project-search.yml
deleted file mode 100644
index f76d4858924..00000000000
--- a/changelogs/unreleased/jprovazn-project-search.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Order projects in 'Move issue' dropdown by name.
-merge_request: 30778
-author:
-type: fixed
diff --git a/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml b/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml
deleted file mode 100644
index 4c049dffc58..00000000000
--- a/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix mirroring help text
-merge_request: 31348
-author: jramsay
-type: other
diff --git a/changelogs/unreleased/jupyter-fixes-v1.yml b/changelogs/unreleased/jupyter-fixes-v1.yml
deleted file mode 100644
index 7a34f273c90..00000000000
--- a/changelogs/unreleased/jupyter-fixes-v1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Jupyter fixes
-merge_request: 31332
-author: Amit Rathi
-type: fixed
diff --git a/changelogs/unreleased/khair1-master-patch-79459.yml b/changelogs/unreleased/khair1-master-patch-79459.yml
deleted file mode 100644
index 22b0877336d..00000000000
--- a/changelogs/unreleased/khair1-master-patch-79459.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Packer.gitlab-ci.yml to use latest image
-merge_request:
-author: Kelly Hair
-type: other
diff --git a/changelogs/unreleased/label-descr-push-opts.yml b/changelogs/unreleased/label-descr-push-opts.yml
deleted file mode 100644
index 9b01cfdaed2..00000000000
--- a/changelogs/unreleased/label-descr-push-opts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support setting of merge request title and description using git push options
-merge_request: 31068
-author:
-type: added
diff --git a/changelogs/unreleased/labkit-cache-tracing.yml b/changelogs/unreleased/labkit-cache-tracing.yml
new file mode 100644
index 00000000000..7e58e1b88dd
--- /dev/null
+++ b/changelogs/unreleased/labkit-cache-tracing.yml
@@ -0,0 +1,5 @@
+---
+title: Add Redis interceptor tracing
+merge_request: 30238
+author:
+type: other
diff --git a/changelogs/unreleased/latest-pipeline.yml b/changelogs/unreleased/latest-pipeline.yml
new file mode 100644
index 00000000000..1177bb57121
--- /dev/null
+++ b/changelogs/unreleased/latest-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Updated latest pipeline tag tooltip to be more descriptive
+merge_request: 31624
+author:
+type: changed
diff --git a/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
deleted file mode 100644
index 59f12fca1f1..00000000000
--- a/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Export and download CSV from metrics charts
-merge_request: 30760
-author:
-type: added
diff --git a/changelogs/unreleased/load-search-counts-async.yml b/changelogs/unreleased/load-search-counts-async.yml
deleted file mode 100644
index 1f466450e76..00000000000
--- a/changelogs/unreleased/load-search-counts-async.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Load search result counts asynchronously
-merge_request: 31663
-author:
-type: changed
diff --git a/changelogs/unreleased/maintainers-can-create-subgroup.yml b/changelogs/unreleased/maintainers-can-create-subgroup.yml
deleted file mode 100644
index 180f0f7247f..00000000000
--- a/changelogs/unreleased/maintainers-can-create-subgroup.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Maintainers can create subgroups
-merge_request: 29718
-author: Fabio Papa
-type: changed
diff --git a/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml b/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml
deleted file mode 100644
index 9b8eff8e043..00000000000
--- a/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Allows masking @ and : characters.'
-merge_request: 31065
-author:
-type: changed
diff --git a/changelogs/unreleased/mc-feature-manual-job-variables.yml b/changelogs/unreleased/mc-feature-manual-job-variables.yml
deleted file mode 100644
index a71cabfe303..00000000000
--- a/changelogs/unreleased/mc-feature-manual-job-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow specifying variables when running manual jobs
-merge_request: 30485
-author:
-type: added
diff --git a/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml b/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml
deleted file mode 100644
index d56a07fe569..00000000000
--- a/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create database tables for the new cycle analytics backend
-merge_request: 31621
-author:
-type: other
diff --git a/changelogs/unreleased/optimise-build-queue-service.yml b/changelogs/unreleased/optimise-build-queue-service.yml
new file mode 100644
index 00000000000..8ffaa3bfbdc
--- /dev/null
+++ b/changelogs/unreleased/optimise-build-queue-service.yml
@@ -0,0 +1,5 @@
+---
+title: Optimise UpdateBuildQueueService
+merge_request: 32095
+author:
+type: performance
diff --git a/changelogs/unreleased/optimize-note-indexes.yml b/changelogs/unreleased/optimize-note-indexes.yml
deleted file mode 100644
index bfb84779abf..00000000000
--- a/changelogs/unreleased/optimize-note-indexes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-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/post-migrate-private-profile.yml b/changelogs/unreleased/post-migrate-private-profile.yml
deleted file mode 100644
index 53a55661aa0..00000000000
--- a/changelogs/unreleased/post-migrate-private-profile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Migrate remaining users with null private_profile
-merge_request: 31708
-author:
-type: other
diff --git a/changelogs/unreleased/rails-template-update.yml b/changelogs/unreleased/rails-template-update.yml
deleted file mode 100644
index 53eb57c84ff..00000000000
--- a/changelogs/unreleased/rails-template-update.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update 'Ruby on Rails' project template
-merge_request: 31310
-author:
-type: other
diff --git a/changelogs/unreleased/remove-line-profile-from-performance-bar.yml b/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
deleted file mode 100644
index c1c7450fbbd..00000000000
--- a/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove line profiler from performance bar
-merge_request:
-author:
-type: removed
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/remove-peek-gc.yml b/changelogs/unreleased/remove-peek-gc.yml
deleted file mode 100644
index 9412cd7c9a6..00000000000
--- a/changelogs/unreleased/remove-peek-gc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove GC metrics from performance bar
-merge_request:
-author:
-type: removed
diff --git a/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml b/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml
deleted file mode 100644
index d32cbd1d8e0..00000000000
--- a/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Remove incorrect fallback when determining which cluster to use when retrieving
- MR performance metrics
-merge_request: 31126
-author:
-type: changed
diff --git a/changelogs/unreleased/report-missing-job-dependency.yml b/changelogs/unreleased/report-missing-job-dependency.yml
deleted file mode 100644
index 660cdfc856e..00000000000
--- a/changelogs/unreleased/report-missing-job-dependency.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Default dependency job stage index to Infinity, and correctly report it as
- undefined in prior stages
-merge_request: 31116
-author:
-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
deleted file mode 100644
index f412ba11b91..00000000000
--- a/changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Security Dashboard feature flag
-merge_request: 31820
-author:
-type: other
diff --git a/changelogs/unreleased/rm-src-branch.yml b/changelogs/unreleased/rm-src-branch.yml
deleted file mode 100644
index 03b91d0c7db..00000000000
--- a/changelogs/unreleased/rm-src-branch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support remove source branch on merge w/ push options
-merge_request: 30728
-author:
-type: added
diff --git a/changelogs/unreleased/safe-archiving-for-traces.yml b/changelogs/unreleased/safe-archiving-for-traces.yml
deleted file mode 100644
index 2b9070bacfe..00000000000
--- a/changelogs/unreleased/safe-archiving-for-traces.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extra logging for new live trace architecture
-merge_request: 30892
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml
deleted file mode 100644
index cd31fe0f35c..00000000000
--- a/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Restrict slash commands to users who can log in
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-60551-fix-upload-scope.yml b/changelogs/unreleased/security-60551-fix-upload-scope.yml
deleted file mode 100644
index 7d7096833a7..00000000000
--- a/changelogs/unreleased/security-60551-fix-upload-scope.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Queries for Upload should be scoped by model
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml b/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
deleted file mode 100644
index f4686484e33..00000000000
--- a/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make pdf.js render CJK characters
-merge_request: 31220
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml b/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml
deleted file mode 100644
index d2143e83045..00000000000
--- a/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Gitaly and Rugged call timing in Sidekiq logs
-merge_request: 31651
-author:
-type: other
diff --git a/changelogs/unreleased/sh-add-index-extern-uid.yml b/changelogs/unreleased/sh-add-index-extern-uid.yml
deleted file mode 100644
index 531770237a8..00000000000
--- a/changelogs/unreleased/sh-add-index-extern-uid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add partial index on identities table to speed up LDAP lookups
-merge_request: 26710
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-add-missing-csp-report-uri.yml b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml
deleted file mode 100644
index 656eb8e9c37..00000000000
--- a/changelogs/unreleased/sh-add-missing-csp-report-uri.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add missing report-uri to CSP config
-merge_request: 31593
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-add-rugged-logs.yml b/changelogs/unreleased/sh-add-rugged-logs.yml
deleted file mode 100644
index 1f464dd92ff..00000000000
--- a/changelogs/unreleased/sh-add-rugged-logs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Rugged calls and duration to API and Rails logs
-merge_request: 30871
-author:
-type: other
diff --git a/changelogs/unreleased/sh-add-rugged-to-peek.yml b/changelogs/unreleased/sh-add-rugged-to-peek.yml
deleted file mode 100644
index 8a030f3daf2..00000000000
--- a/changelogs/unreleased/sh-add-rugged-to-peek.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Rugged calls to performance bar
-merge_request: 30983
-author:
-type: other
diff --git a/changelogs/unreleased/sh-break-out-invited-group-members.yml b/changelogs/unreleased/sh-break-out-invited-group-members.yml
deleted file mode 100644
index 091f1d48843..00000000000
--- a/changelogs/unreleased/sh-break-out-invited-group-members.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make it easier to find invited group members
-merge_request: 28436
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-disable-redis-peek.yml b/changelogs/unreleased/sh-disable-redis-peek.yml
deleted file mode 100644
index de86c0031c7..00000000000
--- a/changelogs/unreleased/sh-disable-redis-peek.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only track Redis calls if Peek is enabled
-merge_request: 31438
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-disable-registry-delete.yml b/changelogs/unreleased/sh-disable-registry-delete.yml
deleted file mode 100644
index 180b983e07c..00000000000
--- a/changelogs/unreleased/sh-disable-registry-delete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't attempt to contact registry if it is disabled
-merge_request: 31553
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml b/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml
new file mode 100644
index 00000000000..00523f53dd7
--- /dev/null
+++ b/changelogs/unreleased/sh-eliminate-gitaly-nplus-one-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate Gitaly N+1 queries with notes API
+merge_request: 32089
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-enable-bootsnap.yml b/changelogs/unreleased/sh-enable-bootsnap.yml
deleted file mode 100644
index 674a900ee01..00000000000
--- a/changelogs/unreleased/sh-enable-bootsnap.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make Bootsnap available via ENABLE_BOOTSNAP=1
-merge_request: 30963
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-fix-discussions-api-perf.yml b/changelogs/unreleased/sh-fix-discussions-api-perf.yml
deleted file mode 100644
index 8cdbbf03dab..00000000000
--- a/changelogs/unreleased/sh-fix-discussions-api-perf.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Eliminate many Gitaly calls in discussions API
-merge_request: 31834
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-fix-import-export-suggestions.yml b/changelogs/unreleased/sh-fix-import-export-suggestions.yml
deleted file mode 100644
index 4b15fc3858f..00000000000
--- a/changelogs/unreleased/sh-fix-import-export-suggestions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Properly save suggestions in project exports
-merge_request: 31690
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-special-role-error-500.yml b/changelogs/unreleased/sh-fix-special-role-error-500.yml
deleted file mode 100644
index 9aed0710da3..00000000000
--- a/changelogs/unreleased/sh-fix-special-role-error-500.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix first-time contributor notes not rendering
-merge_request: 31340
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml b/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml
deleted file mode 100644
index f5e2147f00e..00000000000
--- a/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ignore Gitaly errors if cache flushing fails on project destruction
-merge_request: 31164
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-make-githost-json.yml b/changelogs/unreleased/sh-make-githost-json.yml
deleted file mode 100644
index f4113a693cc..00000000000
--- a/changelogs/unreleased/sh-make-githost-json.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Convert githost.log to JSON format
-merge_request: 30967
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-only-flush-tags-once-per-push.yml b/changelogs/unreleased/sh-only-flush-tags-once-per-push.yml
deleted file mode 100644
index 502fc22ebbd..00000000000
--- a/changelogs/unreleased/sh-only-flush-tags-once-per-push.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only expire tag cache once per push
-merge_request: 31641
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml b/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml
deleted file mode 100644
index cd63b9bf425..00000000000
--- a/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Reduce Gitaly calls in PostReceive
-merge_request: 31741
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-post-receive-cache-clear-once.yml b/changelogs/unreleased/sh-post-receive-cache-clear-once.yml
deleted file mode 100644
index b677adf78d9..00000000000
--- a/changelogs/unreleased/sh-post-receive-cache-clear-once.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expire project caches once per push instead of once per ref
-merge_request: 31876
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml b/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
deleted file mode 100644
index a00ceedf3f4..00000000000
--- a/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove pdf.js deprecation warnings
-merge_request: 31253
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-rename-githost-to-gitjson.yml b/changelogs/unreleased/sh-rename-githost-to-gitjson.yml
deleted file mode 100644
index 24fcd1e9781..00000000000
--- a/changelogs/unreleased/sh-rename-githost-to-gitjson.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Rename githost.log -> git_json.log
-merge_request: 31634
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-support-csp-nonce.yml b/changelogs/unreleased/sh-support-csp-nonce.yml
deleted file mode 100644
index 3e6ac1e4a32..00000000000
--- a/changelogs/unreleased/sh-support-csp-nonce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for Content-Security-Policy
-merge_request: 31402
-author:
-type: added
diff --git a/changelogs/unreleased/sh-update-mermaid.yml b/changelogs/unreleased/sh-update-mermaid.yml
deleted file mode 100644
index 58d94ec6235..00000000000
--- a/changelogs/unreleased/sh-update-mermaid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update Mermaid to v8.2.3
-merge_request: 30985
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-update-rouge-3-7-0.yml b/changelogs/unreleased/sh-update-rouge-3-7-0.yml
deleted file mode 100644
index 6828f48863c..00000000000
--- a/changelogs/unreleased/sh-update-rouge-3-7-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update rouge to v3.7.0
-merge_request: 31254
-author:
-type: other
diff --git a/changelogs/unreleased/sh-update-rugged-0-28-3.yml b/changelogs/unreleased/sh-update-rugged-0-28-3.yml
deleted file mode 100644
index 86446564e12..00000000000
--- a/changelogs/unreleased/sh-update-rugged-0-28-3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Rugged to 0.28.3
-merge_request: 31794
-author:
-type: security
diff --git a/changelogs/unreleased/sh-use-redis-caching-store.yml b/changelogs/unreleased/sh-use-redis-caching-store.yml
deleted file mode 100644
index e61bdb490bc..00000000000
--- a/changelogs/unreleased/sh-use-redis-caching-store.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Rails 5.2 Redis caching store
-merge_request: 30966
-author:
-type: other
diff --git a/changelogs/unreleased/sh-use-shared-state-cluster-pubsub.yml b/changelogs/unreleased/sh-use-shared-state-cluster-pubsub.yml
deleted file mode 100644
index 5e72f23d7ad..00000000000
--- a/changelogs/unreleased/sh-use-shared-state-cluster-pubsub.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use persistent Redis cluster for Workhorse pub/sub notifications
-merge_request: 30990
-author:
-type: fixed
diff --git a/changelogs/unreleased/snowplow-ee-to-ce.yml b/changelogs/unreleased/snowplow-ee-to-ce.yml
deleted file mode 100644
index 85c959f0510..00000000000
--- a/changelogs/unreleased/snowplow-ee-to-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Moves snowplow tracking from ee to ce
-merge_request: 31160
-author: jejacks0n
-type: added
diff --git a/changelogs/unreleased/speed-up-labels-api.yml b/changelogs/unreleased/speed-up-labels-api.yml
deleted file mode 100644
index d5ac4313414..00000000000
--- a/changelogs/unreleased/speed-up-labels-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove counts from default labels API responses
-merge_request: 31543
-author:
-type: changed
diff --git a/changelogs/unreleased/tr-embed-metric-links.yml b/changelogs/unreleased/tr-embed-metric-links.yml
deleted file mode 100644
index 6918114a4ae..00000000000
--- a/changelogs/unreleased/tr-embed-metric-links.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Generate shareable link for specific metric charts
-merge_request: 31339
-author:
-type: added
diff --git a/changelogs/unreleased/tr-remove-embed-metrics-flag.yml b/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
deleted file mode 100644
index a327a6868d3..00000000000
--- a/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Link and embed metrics in GitLab Flavored Markdown
-merge_request: 31106
-author:
-type: added
diff --git a/changelogs/unreleased/uncomment_commit_signatures_feature_flag.yml b/changelogs/unreleased/uncomment_commit_signatures_feature_flag.yml
new file mode 100644
index 00000000000..8f6c89e7dd3
--- /dev/null
+++ b/changelogs/unreleased/uncomment_commit_signatures_feature_flag.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly 1.60.0
+merge_request: 31981
+author:
+type: changed
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/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-7-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-7-0.yml
deleted file mode 100644
index ab1e7d77520..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-7-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.7.0
-merge_request: 30950
-author:
-type: other
diff --git a/changelogs/unreleased/update-graphicsmagick-to-1-3-33.yml b/changelogs/unreleased/update-graphicsmagick-to-1-3-33.yml
deleted file mode 100644
index 898fdef830a..00000000000
--- a/changelogs/unreleased/update-graphicsmagick-to-1-3-33.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GraphicsMagick from 1.3.29 to 1.3.33 for CI tests
-merge_request: 31692
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml b/changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml
deleted file mode 100644
index 8c1a033dd29..00000000000
--- a/changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enhance style of the shared runners limit
-merge_request: 31386
-author:
-type: other
diff --git a/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml b/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml
deleted file mode 100644
index 4c9b048aaa3..00000000000
--- a/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix visual review app storage keys
-merge_request: 31427
-author:
-type: fixed
diff --git a/changelogs/unreleased/wiki-usage-pings.yml b/changelogs/unreleased/wiki-usage-pings.yml
deleted file mode 100644
index c3d084228c3..00000000000
--- a/changelogs/unreleased/wiki-usage-pings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Count wiki creation, update and delete events
-merge_request: 30864
-author:
-type: added
diff --git a/changelogs/unreleased/winh-deduplicate-board-headers.yml b/changelogs/unreleased/winh-deduplicate-board-headers.yml
new file mode 100644
index 00000000000..009ce59b6bc
--- /dev/null
+++ b/changelogs/unreleased/winh-deduplicate-board-headers.yml
@@ -0,0 +1,5 @@
+---
+title: Hide duplicate board list while dragging
+merge_request: 32099
+author:
+type: fixed
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/application.rb b/config/application.rb
index 2554dd8cca2..294ed470298 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -241,10 +241,7 @@ module Gitlab
end
# Use caching across all environments
- # Full list of options:
- # https://api.rubyonrails.org/classes/ActiveSupport/Cache/RedisCacheStore.html#method-c-new
caching_config_hash = Gitlab::Redis::Cache.params
- caching_config_hash[:compress] = false
caching_config_hash[:namespace] = Gitlab::Redis::Cache::CACHE_NAMESPACE
caching_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
if Sidekiq.server? # threaded context
@@ -252,7 +249,7 @@ module Gitlab
caching_config_hash[:pool_timeout] = 1
end
- config.cache_store = :redis_cache_store, caching_config_hash
+ config.cache_store = :redis_store, caching_config_hash
config.active_job.queue_adapter = :sidekiq
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 226f2ec3722..973c2747838 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -50,24 +50,24 @@ production: &base
# Content Security Policy
# See https://guides.rubyonrails.org/security.html#content-security-policy
content_security_policy:
- enabled: false
+ enabled: true
report_only: false
directives:
base_uri:
child_src:
- connect_src: "'self' http://localhost:3808 ws://localhost:3808 wss://localhost:3000"
+ connect_src: "'self' http://localhost:* ws://localhost:* wss://localhost:*"
default_src: "'self'"
font_src:
form_action:
frame_ancestors: "'self'"
frame_src: "'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com"
- img_src: "* data: blob"
+ img_src: "* data: blob:"
manifest_src:
media_src:
- object_src: "'self' http://localhost:3808 'unsafe-inline' 'unsafe-eval' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://www.gstatic.com/recaptcha/ https://apis.google.com"
- script_src:
+ object_src: "'none'"
+ script_src: "'self' 'unsafe-eval' http://localhost:* https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://www.gstatic.com/recaptcha/ https://apis.google.com"
style_src: "'self' 'unsafe-inline'"
- worker_src: "http://localhost:3000 blob:"
+ worker_src: "'self' blob:"
report_uri:
# Trusted Proxies
@@ -95,6 +95,15 @@ production: &base
email_display_name: GitLab
email_reply_to: noreply@example.com
email_subject_suffix: ''
+ email_smime:
+ # Uncomment and set to true if you need to enable email S/MIME signing (default: false)
+ # enabled: false
+ # S/MIME private key file in PEM format, unencrypted
+ # Default is '.gitlab_smime_key' relative to Rails.root (i.e. root of the GitLab app).
+ # key_file: /home/git/gitlab/.gitlab_smime_key
+ # S/MIME public certificate key in PEM format, will be attached to signed messages
+ # Default is '.gitlab_smime_cert' relative to Rails.root (i.e. root of the GitLab app).
+ # cert_file: /home/git/gitlab/.gitlab_smime_cert
# Email server smtp settings are in config/initializers/smtp_settings.rb.sample
@@ -1090,6 +1099,27 @@ test:
host: localhost
port: 80
+ content_security_policy:
+ enabled: true
+ report_only: false
+ directives:
+ base_uri:
+ child_src:
+ connect_src:
+ default_src: "'self'"
+ font_src:
+ form_action:
+ frame_ancestors: "'self'"
+ frame_src: "'self' https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://content.googleapis.com https://content-compute.googleapis.com https://content-cloudbilling.googleapis.com https://content-cloudresourcemanager.googleapis.com"
+ img_src: "* data: blob:"
+ manifest_src:
+ media_src:
+ object_src: "'none'"
+ script_src: "'self' 'unsafe-eval' http://localhost:* https://www.google.com/recaptcha/ https://www.recaptcha.net/ https://www.gstatic.com/recaptcha/ https://apis.google.com"
+ style_src: "'self' 'unsafe-inline'"
+ worker_src: "'self' blob:"
+ report_uri:
+
# When you run tests we clone and set up gitlab-shell
# In order to set it up correctly you need to specify
# your system username you use to run GitLab
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/1_settings.rb b/config/initializers/1_settings.rb
index 828732126b6..fdc6b0a05ab 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -1,5 +1,6 @@
require_relative '../settings'
require_relative '../object_store_settings'
+require_relative '../smime_signature_settings'
# Default settings
Settings['ldap'] ||= Settingslogic.new({})
@@ -171,6 +172,7 @@ Settings.gitlab['email_from'] ||= ENV['GITLAB_EMAIL_FROM'] || "gitlab@#{Settings
Settings.gitlab['email_display_name'] ||= ENV['GITLAB_EMAIL_DISPLAY_NAME'] || 'GitLab'
Settings.gitlab['email_reply_to'] ||= ENV['GITLAB_EMAIL_REPLY_TO'] || "noreply@#{Settings.gitlab.host}"
Settings.gitlab['email_subject_suffix'] ||= ENV['GITLAB_EMAIL_SUBJECT_SUFFIX'] || ""
+Settings.gitlab['email_smime'] = SmimeSignatureSettings.parse(Settings.gitlab['email_smime'])
Settings.gitlab['base_url'] ||= Settings.__send__(:build_base_gitlab_url)
Settings.gitlab['url'] ||= Settings.__send__(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
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/action_mailer_hooks.rb b/config/initializers/action_mailer_hooks.rb
index f1b3c1f8ae8..02ca6ef13bf 100644
--- a/config/initializers/action_mailer_hooks.rb
+++ b/config/initializers/action_mailer_hooks.rb
@@ -10,3 +10,8 @@ ActionMailer::Base.register_interceptors(
)
ActionMailer::Base.register_observer(::Gitlab::Email::Hook::DeliveryMetricsObserver)
+
+if Gitlab.config.gitlab.email_enabled && Gitlab.config.gitlab.email_smime.enabled
+ ActionMailer::Base.register_interceptor(::Gitlab::Email::Hook::SmimeSignatureInterceptor)
+ Gitlab::AppLogger.debug "S/MIME signing of outgoing emails enabled"
+end
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/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 7217f098fd9..9f3e104bc2b 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -28,11 +28,13 @@ if Rails.env.development?
end
enable_json_logs = Gitlab.config.sidekiq.log_format == 'json'
+enable_sidekiq_monitor = ENV.fetch("SIDEKIQ_MONITOR_WORKER", 0).to_i.nonzero?
Sidekiq.configure_server do |config|
config.redis = queues_config_hash
config.server_middleware do |chain|
+ chain.add Gitlab::SidekiqMiddleware::Monitor if enable_sidekiq_monitor
chain.add Gitlab::SidekiqMiddleware::Metrics if Settings.monitoring.sidekiq_exporter
chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] && !enable_json_logs
chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS']
@@ -57,6 +59,8 @@ Sidekiq.configure_server do |config|
# Clear any connections that might have been obtained before starting
# Sidekiq (e.g. in an initializer).
ActiveRecord::Base.clear_all_connections!
+
+ Gitlab::SidekiqMonitor.instance.start if enable_sidekiq_monitor
end
if enable_reliable_fetch?
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
index 3c8779f238f..5b55a06692e 100644
--- a/config/initializers/tracing.rb
+++ b/config/initializers/tracing.rb
@@ -21,9 +21,13 @@ if Labkit::Tracing.enabled?
end
end
+ # Instrument Redis
+ Labkit::Tracing::Redis.instrument
+
# Instrument Rails
Labkit::Tracing::Rails::ActiveRecordSubscriber.instrument
Labkit::Tracing::Rails::ActionViewSubscriber.instrument
+ Labkit::Tracing::Rails::ActiveSupportSubscriber.instrument
# In multi-processed clustered architectures (puma, unicorn) don't
# start tracing until the worker processes are spawned. This works
diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index b005fdf159b..af4aec7b355 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -100,11 +100,7 @@ def instrument_classes(instrumentation)
instrumentation.instrument_instance_methods(Gitlab::Elastic::SnippetSearchResults)
instrumentation.instrument_methods(Gitlab::Elastic::Helper)
- instrumentation.instrument_instance_methods(Elastic::ApplicationSearch)
- instrumentation.instrument_instance_methods(Elastic::IssuesSearch)
- instrumentation.instrument_instance_methods(Elastic::MergeRequestsSearch)
- instrumentation.instrument_instance_methods(Elastic::MilestonesSearch)
- instrumentation.instrument_instance_methods(Elastic::NotesSearch)
+ instrumentation.instrument_instance_methods(Elastic::ApplicationVersionedSearch)
instrumentation.instrument_instance_methods(Elastic::ProjectsSearch)
instrumentation.instrument_instance_methods(Elastic::RepositoriesSearch)
instrumentation.instrument_instance_methods(Elastic::SnippetsSearch)
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index 32475ef8380..08504d6f7d5 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -166,7 +166,7 @@ panel_groups:
label: Total (cores)
unit: "cores"
- title: "Memory Usage (Pod average)"
- type: "area-chart"
+ type: "line-chart"
y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
@@ -175,7 +175,7 @@ panel_groups:
label: Pod average (MB)
unit: MB
- title: "Canary: Memory Usage (Pod Average)"
- type: "area-chart"
+ type: "line-chart"
y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
@@ -185,7 +185,7 @@ panel_groups:
unit: MB
track: canary
- title: "Core Usage (Pod Average)"
- type: "area-chart"
+ type: "line-chart"
y_label: "Cores per Pod"
weight: 1
metrics:
@@ -194,7 +194,7 @@ panel_groups:
label: Pod average (cores)
unit: "cores"
- title: "Canary: Core Usage (Pod Average)"
- type: "area-chart"
+ type: "line-chart"
y_label: "Cores per Pod"
weight: 1
metrics:
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/project.rb b/config/routes/project.rb
index 9a453d101a1..29e462f904d 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -359,6 +359,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
collection do
resource :pipelines_settings, path: 'settings', only: [:show, :update]
get :charts
+ scope '(*ref)', constraints: { ref: Gitlab::PathRegex.git_reference_regex } do
+ get :latest, action: :show, defaults: { latest: true }
+ end
end
member 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/config/smime_signature_settings.rb b/config/smime_signature_settings.rb
new file mode 100644
index 00000000000..3d19db84c19
--- /dev/null
+++ b/config/smime_signature_settings.rb
@@ -0,0 +1,11 @@
+# Set default values for email_smime settings
+class SmimeSignatureSettings
+ def self.parse(email_smime)
+ email_smime ||= Settingslogic.new({})
+ email_smime['enabled'] = false unless email_smime['enabled']
+ email_smime['key_file'] ||= Rails.root.join('.gitlab_smime_key')
+ email_smime['cert_file'] ||= Rails.root.join('.gitlab_smime_cert')
+
+ email_smime
+ end
+end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 442b4b4c21e..969a84e85dd 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -298,6 +298,13 @@ module.exports = {
from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'),
},
+ {
+ from: path.join(
+ ROOT_PATH,
+ 'node_modules/@gitlab/visual-review-tools/dist/visual_review_toolbar.js',
+ ),
+ to: path.join(ROOT_PATH, 'public/assets/webpack'),
+ },
]),
// compression can require a lot of compute time and is disabled in CI
diff --git a/config/webpack.config.review_toolbar.js b/config/webpack.config.review_toolbar.js
deleted file mode 100644
index baaba7ed387..00000000000
--- a/config/webpack.config.review_toolbar.js
+++ /dev/null
@@ -1,58 +0,0 @@
-const path = require('path');
-const CompressionPlugin = require('compression-webpack-plugin');
-
-const ROOT_PATH = path.resolve(__dirname, '..');
-const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
-const NO_SOURCEMAPS = process.env.NO_SOURCEMAPS;
-const IS_PRODUCTION = process.env.NODE_ENV === 'production';
-
-const devtool = IS_PRODUCTION ? 'source-map' : 'cheap-module-eval-source-map';
-
-const alias = {
- vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- spec: path.join(ROOT_PATH, 'spec/javascripts'),
-};
-
-module.exports = {
- mode: IS_PRODUCTION ? 'production' : 'development',
-
- context: path.join(ROOT_PATH, 'app/assets/javascripts'),
-
- name: 'visual_review_toolbar',
-
- entry: './visual_review_toolbar',
-
- output: {
- path: path.join(ROOT_PATH, 'public/assets/webpack'),
- filename: 'visual_review_toolbar.js',
- library: 'VisualReviewToolbar',
- libraryTarget: 'var',
- },
-
- resolve: {
- alias,
- },
-
- module: {
- rules: [
- {
- test: /\.js$/,
- loader: 'babel-loader',
- options: {
- cacheDirectory: path.join(CACHE_PATH, 'babel-loader'),
- },
- },
- {
- test: /\.css$/,
- use: ['style-loader', 'css-loader'],
- },
- ],
- },
-
- plugins: [
- // compression can require a lot of compute time and is disabled in CI
- new CompressionPlugin(),
- ].filter(Boolean),
-
- devtool: NO_SOURCEMAPS ? false : devtool,
-};
diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile
index 63b2f6f5c5c..2d1ca64a9e8 100644
--- a/danger/changelog/Dangerfile
+++ b/danger/changelog/Dangerfile
@@ -1,19 +1,18 @@
+# frozen_string_literal: true
# rubocop:disable Style/SignalException
require 'yaml'
-NO_CHANGELOG_LABELS = %w[backstage ci-build Documentation meta QA test].freeze
-SEE_DOC = "See [the documentation](https://docs.gitlab.com/ce/development/changelog.html).".freeze
-CREATE_CHANGELOG_MESSAGE = <<~MSG.freeze
+NO_CHANGELOG_LABELS = %w[backstage ci-build meta].freeze
+SEE_DOC = "See [the documentation](https://docs.gitlab.com/ce/development/changelog.html)."
+CREATE_CHANGELOG_MESSAGE = <<~MSG
You can create one with:
```
bin/changelog -m %<mr_iid>s "%<mr_title>s"
```
-If your merge request doesn't warrant a CHANGELOG entry,
-consider adding any of the %<labels>s labels.
-#{SEE_DOC}
+Note: Merge requests with %<labels>s do not trigger this check.
MSG
def ee_changelog?(changelog_path)
@@ -60,7 +59,7 @@ if changelog_needed
if changelog_found
check_changelog(changelog_found)
else
- warn "**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html).**\n\n" +
+ message "**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html)**: If this merge request [doesn't need a CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html#what-warrants-a-changelog-entry), feel free to ignore this message.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
end
end
diff --git a/danger/only_documentation/Dangerfile b/danger/only_documentation/Dangerfile
index ff65f8713d2..dad12c0d29c 100644
--- a/danger/only_documentation/Dangerfile
+++ b/danger/only_documentation/Dangerfile
@@ -1,7 +1,7 @@
# rubocop:disable Style/SignalException
# frozen_string_literal: true
-has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml', '.mdlrc') }
+has_only_docs_changes = helper.all_changed_files.all? { |file| file.start_with?('doc/', '.gitlab/ci/docs.gitlab-ci.yml', '.mdlrc') || file.end_with?('.md') }
is_docs_only_branch = gitlab.branch_for_head =~ /(^docs[\/-].*|.*-docs$)/
if is_docs_only_branch && !has_only_docs_changes
diff --git a/db/fixtures/development/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/fixtures/development/15_award_emoji.rb b/db/fixtures/development/15_award_emoji.rb
index 137a036edaf..a9dcc048586 100644
--- a/db/fixtures/development/15_award_emoji.rb
+++ b/db/fixtures/development/15_award_emoji.rb
@@ -1,35 +1,22 @@
require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
- emoji = Gitlab::Emoji.emojis.keys
+ EMOJI = Gitlab::Emoji.emojis.keys
- Issue.order(Gitlab::Database.random).limit(Issue.count / 2).each do |issue|
- project = issue.project
+ def seed_award_emoji(klass)
+ klass.order(Gitlab::Database.random).limit(klass.count / 2).each do |awardable|
+ awardable.project.authorized_users.where('project_authorizations.access_level > ?', Gitlab::Access::GUEST).sample(2).each do |user|
+ AwardEmojis::AddService.new(awardable, EMOJI.sample, user).execute
- project.team.users.sample(2).each do |user|
- issue.create_award_emoji(emoji.sample, user)
+ awardable.notes.user.sample(2).each do |note|
+ AwardEmojis::AddService.new(note, EMOJI.sample, user).execute
+ end
- issue.notes.sample(2).each do |note|
- next if note.system?
- note.create_award_emoji(emoji.sample, user)
+ print '.'
end
-
- print '.'
end
end
- MergeRequest.order(Gitlab::Database.random).limit(MergeRequest.count / 2).each do |mr|
- project = mr.project
-
- project.team.users.sample(2).each do |user|
- mr.create_award_emoji(emoji.sample, user)
-
- mr.notes.sample(2).each do |note|
- next if note.system?
- note.create_award_emoji(emoji.sample, user)
- end
-
- print '.'
- end
- end
+ seed_award_emoji(Issue)
+ seed_award_emoji(MergeRequest)
end
diff --git a/db/migrate/20171230123729_init_schema.rb b/db/migrate/20171230123729_init_schema.rb
index fa90b37954f..a474ea2f925 100644
--- a/db/migrate/20171230123729_init_schema.rb
+++ b/db/migrate/20171230123729_init_schema.rb
@@ -5,6 +5,7 @@
# rubocop:disable Metrics/AbcSize
# rubocop:disable Migration/AddConcurrentForeignKey
# rubocop:disable Style/WordArray
+# rubocop:disable Migration/AddLimitToStringColumns
class InitSchema < ActiveRecord::Migration[4.2]
DOWNTIME = false
@@ -1852,3 +1853,4 @@ class InitSchema < ActiveRecord::Migration[4.2]
raise ActiveRecord::IrreversibleMigration, "The initial migration is not revertable"
end
end
+# rubocop:enable Migration/AddLimitToStringColumns
diff --git a/db/migrate/20180101160629_create_prometheus_metrics.rb b/db/migrate/20180101160629_create_prometheus_metrics.rb
index e3b1ed710d6..a098b999a0a 100644
--- a/db/migrate/20180101160629_create_prometheus_metrics.rb
+++ b/db/migrate/20180101160629_create_prometheus_metrics.rb
@@ -4,6 +4,7 @@ class CreatePrometheusMetrics < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :prometheus_metrics do |t|
t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false
t.string :title, null: false
@@ -14,5 +15,6 @@ class CreatePrometheusMetrics < ActiveRecord::Migration[4.2]
t.integer :group, null: false, index: true
t.timestamps_with_timezone null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
index c76dc5b3a68..eb446ad0d72 100644
--- a/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
+++ b/db/migrate/20180122162010_add_auto_devops_domain_to_application_settings.rb
@@ -8,6 +8,6 @@ class AddAutoDevopsDomainToApplicationSettings < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :application_settings, :auto_devops_domain, :string
+ add_column :application_settings, :auto_devops_domain, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180129193323_add_uploads_builder_context.rb b/db/migrate/20180129193323_add_uploads_builder_context.rb
index c7227bf0f1e..710fa7b3ba8 100644
--- a/db/migrate/20180129193323_add_uploads_builder_context.rb
+++ b/db/migrate/20180129193323_add_uploads_builder_context.rb
@@ -8,7 +8,9 @@ class AddUploadsBuilderContext < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :uploads, :mount_point, :string
add_column :uploads, :secret, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb b/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
index e2a9a68b1ad..78aa2014601 100644
--- a/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
+++ b/db/migrate/20180212030105_add_external_ip_to_clusters_applications_ingress.rb
@@ -4,6 +4,6 @@ class AddExternalIpToClustersApplicationsIngress < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :clusters_applications_ingress, :external_ip, :string
+ add_column :clusters_applications_ingress, :external_ip, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180214093516_create_badges.rb b/db/migrate/20180214093516_create_badges.rb
index 66e017b115a..fe27d465571 100644
--- a/db/migrate/20180214093516_create_badges.rb
+++ b/db/migrate/20180214093516_create_badges.rb
@@ -2,6 +2,7 @@ class CreateBadges < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :badges do |t|
t.string :link_url, null: false
t.string :image_url, null: false
@@ -11,6 +12,7 @@ class CreateBadges < ActiveRecord::Migration[4.2]
t.timestamps_with_timezone null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
# rubocop:disable Migration/AddConcurrentForeignKey
add_foreign_key :badges, :namespaces, column: :group_id, on_delete: :cascade
diff --git a/db/migrate/20180214155405_create_clusters_applications_runners.rb b/db/migrate/20180214155405_create_clusters_applications_runners.rb
index ce594c91890..f611fefbb0d 100644
--- a/db/migrate/20180214155405_create_clusters_applications_runners.rb
+++ b/db/migrate/20180214155405_create_clusters_applications_runners.rb
@@ -13,7 +13,7 @@ class CreateClustersApplicationsRunners < ActiveRecord::Migration[4.2]
t.index :cluster_id, unique: true
t.integer :status, null: false
t.timestamps_with_timezone null: false
- t.string :version, null: false
+ t.string :version, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.text :status_reason
end
diff --git a/db/migrate/20180216120000_add_pages_domain_verification.rb b/db/migrate/20180216120000_add_pages_domain_verification.rb
index f709f5a5809..b2f19f2e1a9 100644
--- a/db/migrate/20180216120000_add_pages_domain_verification.rb
+++ b/db/migrate/20180216120000_add_pages_domain_verification.rb
@@ -3,6 +3,6 @@ class AddPagesDomainVerification < ActiveRecord::Migration[4.2]
def change
add_column :pages_domains, :verified_at, :datetime_with_timezone
- add_column :pages_domains, :verification_code, :string
+ add_column :pages_domains, :verification_code, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180222043024_add_ip_address_to_runner.rb b/db/migrate/20180222043024_add_ip_address_to_runner.rb
index b52366c0be1..08fb0bd900c 100644
--- a/db/migrate/20180222043024_add_ip_address_to_runner.rb
+++ b/db/migrate/20180222043024_add_ip_address_to_runner.rb
@@ -4,6 +4,6 @@ class AddIpAddressToRunner < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :ci_runners, :ip_address, :string
+ add_column :ci_runners, :ip_address, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
index 5e4bf96f86f..9bdd44baf58 100644
--- a/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
+++ b/db/migrate/20180308125206_add_user_internal_regex_to_application_setting.rb
@@ -4,7 +4,7 @@ class AddUserInternalRegexToApplicationSetting < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
- add_column :application_settings, :user_default_internal_regex, :string, null: true
+ add_column :application_settings, :user_default_internal_regex, :string, null: true # rubocop:disable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
index ee3d1078f5e..c379d207ff0 100644
--- a/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
+++ b/db/migrate/20180315160435_add_external_auth_mutual_tls_fields_to_project_settings.rb
@@ -2,6 +2,7 @@ class AddExternalAuthMutualTlsFieldsToProjectSettings < ActiveRecord::Migration[
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :application_settings,
:external_auth_client_cert, :text
add_column :application_settings,
@@ -12,5 +13,6 @@ class AddExternalAuthMutualTlsFieldsToProjectSettings < ActiveRecord::Migration[
:encrypted_external_auth_client_key_pass, :string
add_column :application_settings,
:encrypted_external_auth_client_key_pass_iv, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180319190020_create_deploy_tokens.rb b/db/migrate/20180319190020_create_deploy_tokens.rb
index a4d797679c5..f444521b3ae 100644
--- a/db/migrate/20180319190020_create_deploy_tokens.rb
+++ b/db/migrate/20180319190020_create_deploy_tokens.rb
@@ -2,6 +2,7 @@ class CreateDeployTokens < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :deploy_tokens do |t|
t.boolean :revoked, default: false
t.boolean :read_repository, null: false, default: false
@@ -15,5 +16,6 @@ class CreateDeployTokens < ActiveRecord::Migration[4.2]
t.index [:token, :expires_at, :id], where: "(revoked IS FALSE)"
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180502122856_create_project_mirror_data.rb b/db/migrate/20180502122856_create_project_mirror_data.rb
index 9781815a97b..04367e1c98b 100644
--- a/db/migrate/20180502122856_create_project_mirror_data.rb
+++ b/db/migrate/20180502122856_create_project_mirror_data.rb
@@ -3,6 +3,7 @@ class CreateProjectMirrorData < ActiveRecord::Migration[4.2]
DOWNTIME = false
+ # rubocop:disable Migration/AddLimitToStringColumns
def up
if table_exists?(:project_mirror_data)
add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status)
@@ -17,6 +18,7 @@ class CreateProjectMirrorData < ActiveRecord::Migration[4.2]
end
end
end
+ # rubocop:enable Migration/AddLimitToStringColumns
def down
remove_column :project_mirror_data, :status
diff --git a/db/migrate/20180503131624_create_remote_mirrors.rb b/db/migrate/20180503131624_create_remote_mirrors.rb
index 288ae365f0f..a079c1b3328 100644
--- a/db/migrate/20180503131624_create_remote_mirrors.rb
+++ b/db/migrate/20180503131624_create_remote_mirrors.rb
@@ -5,6 +5,7 @@ class CreateRemoteMirrors < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
+ # rubocop:disable Migration/AddLimitToStringColumns
def up
return if table_exists?(:remote_mirrors)
@@ -27,6 +28,7 @@ class CreateRemoteMirrors < ActiveRecord::Migration[4.2]
t.timestamps null: false
end
end
+ # rubocop:enable Migration/AddLimitToStringColumns
def down
# ee/db/migrate/20160321161032_create_remote_mirrors_ee.rb will remove the table
diff --git a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
index 3775b3a08c9..f00493ed515 100644
--- a/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
+++ b/db/migrate/20180503175053_ensure_missing_columns_to_project_mirror_data.rb
@@ -4,8 +4,8 @@ class EnsureMissingColumnsToProjectMirrorData < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
- add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status)
- add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid)
+ add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status) # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid) # rubocop:disable Migration/AddLimitToStringColumns
add_column :project_mirror_data, :last_error, :text unless column_exists?(:project_mirror_data, :last_error)
end
diff --git a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
index 749aeeb4792..4633d930e2d 100644
--- a/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
+++ b/db/migrate/20180511131058_create_clusters_applications_jupyter.rb
@@ -7,17 +7,19 @@ class CreateClustersApplicationsJupyter < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_applications_jupyter do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
t.references :oauth_application, foreign_key: { on_delete: :nullify }
t.integer :status, null: false
- t.string :version, null: false
- t.string :hostname
+ t.string :version, null: false # rubocop:disable Migration/AddLimitToStringColumns
+ t.string :hostname # rubocop:disable Migration/AddLimitToStringColumns
t.timestamps_with_timezone null: false
t.text :status_reason
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180515121227_create_notes_diff_files.rb b/db/migrate/20180515121227_create_notes_diff_files.rb
index e50324d8599..5f6dba11ff9 100644
--- a/db/migrate/20180515121227_create_notes_diff_files.rb
+++ b/db/migrate/20180515121227_create_notes_diff_files.rb
@@ -4,6 +4,7 @@ class CreateNotesDiffFiles < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :note_diff_files do |t|
t.references :diff_note, references: :notes, null: false, index: { unique: true }
t.text :diff, null: false
@@ -18,5 +19,6 @@ class CreateNotesDiffFiles < ActiveRecord::Migration[4.2]
# rubocop:disable Migration/AddConcurrentForeignKey
add_foreign_key :note_diff_files, :notes, column: :diff_note_id, on_delete: :cascade
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180529093006_ensure_remote_mirror_columns.rb b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
index 207e1f089fb..a0a1150f022 100644
--- a/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
+++ b/db/migrate/20180529093006_ensure_remote_mirror_columns.rb
@@ -8,7 +8,7 @@ class EnsureRemoteMirrorColumns < ActiveRecord::Migration[4.2]
def up
# rubocop:disable Migration/Datetime
add_column :remote_mirrors, :last_update_started_at, :datetime unless column_exists?(:remote_mirrors, :last_update_started_at)
- add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name)
+ add_column :remote_mirrors, :remote_name, :string unless column_exists?(:remote_mirrors, :remote_name) # rubocop:disable Migration/AddLimitToStringColumns
unless column_exists?(:remote_mirrors, :only_protected_branches)
add_column_with_default(:remote_mirrors,
diff --git a/db/migrate/20180531185349_add_repository_languages.rb b/db/migrate/20180531185349_add_repository_languages.rb
index 26a01c3bb26..d517c21c26c 100644
--- a/db/migrate/20180531185349_add_repository_languages.rb
+++ b/db/migrate/20180531185349_add_repository_languages.rb
@@ -4,6 +4,7 @@ class AddRepositoryLanguages < ActiveRecord::Migration[4.2]
DOWNTIME = false
def up
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table(:programming_languages) do |t|
t.string :name, null: false
t.string :color, null: false
@@ -19,6 +20,7 @@ class AddRepositoryLanguages < ActiveRecord::Migration[4.2]
add_index :programming_languages, :name, unique: true
add_index :repository_languages, [:project_id, :programming_language_id],
unique: true, name: "index_repository_languages_on_project_and_languages_id"
+ # rubocop:enable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180613081317_create_ci_builds_runner_session.rb b/db/migrate/20180613081317_create_ci_builds_runner_session.rb
index eb41f76b105..68af38834d2 100644
--- a/db/migrate/20180613081317_create_ci_builds_runner_session.rb
+++ b/db/migrate/20180613081317_create_ci_builds_runner_session.rb
@@ -8,6 +8,7 @@ class CreateCiBuildsRunnerSession < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :ci_builds_runner_session, id: :bigserial do |t|
t.integer :build_id, null: false
t.string :url, null: false
@@ -17,5 +18,6 @@ class CreateCiBuildsRunnerSession < ActiveRecord::Migration[4.2]
t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
t.index :build_id, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180713092803_create_user_statuses.rb b/db/migrate/20180713092803_create_user_statuses.rb
index 43b96805c1e..3abab4e45a9 100644
--- a/db/migrate/20180713092803_create_user_statuses.rb
+++ b/db/migrate/20180713092803_create_user_statuses.rb
@@ -6,6 +6,7 @@ class CreateUserStatuses < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :user_statuses, id: false, primary_key: :user_id do |t|
t.references :user,
foreign_key: { on_delete: :cascade },
@@ -16,5 +17,6 @@ class CreateUserStatuses < ActiveRecord::Migration[4.2]
t.string :message, limit: 100
t.string :message_html
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180814153625_add_commit_email_to_users.rb b/db/migrate/20180814153625_add_commit_email_to_users.rb
index 4d9217ea504..98bafc14a03 100644
--- a/db/migrate/20180814153625_add_commit_email_to_users.rb
+++ b/db/migrate/20180814153625_add_commit_email_to_users.rb
@@ -28,6 +28,6 @@ class AddCommitEmailToUsers < ActiveRecord::Migration[4.2]
# disable_ddl_transaction!
def change
- add_column :users, :commit_email, :string
+ add_column :users, :commit_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
index 7aa5950249c..8f30363c310 100644
--- a/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
+++ b/db/migrate/20180831164908_add_identifier_to_prometheus_metric.rb
@@ -6,6 +6,6 @@ class AddIdentifierToPrometheusMetric < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :prometheus_metrics, :identifier, :string
+ add_column :prometheus_metrics, :identifier, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
index ca8dbdba2bb..9757f7fdc79 100644
--- a/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
+++ b/db/migrate/20180910115836_add_attr_encrypted_columns_to_web_hook.rb
@@ -6,10 +6,12 @@ class AddAttrEncryptedColumnsToWebHook < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column :web_hooks, :encrypted_token, :string
add_column :web_hooks, :encrypted_token_iv, :string
add_column :web_hooks, :encrypted_url, :string
add_column :web_hooks, :encrypted_url_iv, :string
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
index 142e454832f..52923f52499 100644
--- a/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
+++ b/db/migrate/20180910153412_add_token_digest_to_personal_access_tokens.rb
@@ -8,7 +8,7 @@ class AddTokenDigestToPersonalAccessTokens < ActiveRecord::Migration[4.2]
def up
change_column :personal_access_tokens, :token, :string, null: true
- add_column :personal_access_tokens, :token_digest, :string
+ add_column :personal_access_tokens, :token_digest, :string # rubocop:disable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20180912111628_add_knative_application.rb b/db/migrate/20180912111628_add_knative_application.rb
index 86d9100d2e7..7c55de02d1c 100644
--- a/db/migrate/20180912111628_add_knative_application.rb
+++ b/db/migrate/20180912111628_add_knative_application.rb
@@ -6,6 +6,7 @@ class AddKnativeApplication < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table "clusters_applications_knative" do |t|
t.references :cluster, null: false, unique: true, foreign_key: { on_delete: :cascade }
@@ -16,5 +17,6 @@ class AddKnativeApplication < ActiveRecord::Migration[4.2]
t.string "hostname"
t.text "status_reason"
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
index 62ad6c63d0a..b6ffb2866aa 100644
--- a/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
+++ b/db/migrate/20181009190428_create_clusters_kubernetes_namespaces.rb
@@ -5,6 +5,7 @@ class CreateClustersKubernetesNamespaces < ActiveRecord::Migration[4.2]
INDEX_NAME = 'kubernetes_namespaces_cluster_and_namespace'
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_kubernetes_namespaces, id: :bigserial do |t|
t.references :cluster, null: false, index: true, foreign_key: { on_delete: :cascade }
t.references :project, index: true, foreign_key: { on_delete: :nullify }
@@ -20,5 +21,6 @@ class CreateClustersKubernetesNamespaces < ActiveRecord::Migration[4.2]
t.index [:cluster_id, :namespace], name: INDEX_NAME, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181019032400_add_shards_table.rb b/db/migrate/20181019032400_add_shards_table.rb
index e31af97cc94..82287e5c3b5 100644
--- a/db/migrate/20181019032400_add_shards_table.rb
+++ b/db/migrate/20181019032400_add_shards_table.rb
@@ -5,7 +5,7 @@ class AddShardsTable < ActiveRecord::Migration[4.2]
def change
create_table :shards do |t|
- t.string :name, null: false, index: { unique: true }
+ t.string :name, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
end
end
end
diff --git a/db/migrate/20181019032408_add_repositories_table.rb b/db/migrate/20181019032408_add_repositories_table.rb
index 2153c1c9fc6..dd510b44084 100644
--- a/db/migrate/20181019032408_add_repositories_table.rb
+++ b/db/migrate/20181019032408_add_repositories_table.rb
@@ -6,7 +6,7 @@ class AddRepositoriesTable < ActiveRecord::Migration[4.2]
def change
create_table :repositories, id: :bigserial do |t|
t.references :shard, null: false, index: true, foreign_key: { on_delete: :restrict }
- t.string :disk_path, null: false, index: { unique: true }
+ t.string :disk_path, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
end
add_column :projects, :pool_repository_id, :bigint
diff --git a/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb b/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
index 052a344f182..c0e4897b8d7 100644
--- a/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
+++ b/db/migrate/20181025115728_add_private_commit_email_hostname_to_application_settings.rb
@@ -6,6 +6,6 @@ class AddPrivateCommitEmailHostnameToApplicationSettings < ActiveRecord::Migrati
DOWNTIME = false
def change
- add_column(:application_settings, :commit_email_hostname, :string, null: true)
+ add_column(:application_settings, :commit_email_hostname, :string, null: true) # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181031190559_drop_gcp_clusters_table.rb b/db/migrate/20181031190559_drop_gcp_clusters_table.rb
index 597fe49f4c8..850fa93c75a 100644
--- a/db/migrate/20181031190559_drop_gcp_clusters_table.rb
+++ b/db/migrate/20181031190559_drop_gcp_clusters_table.rb
@@ -10,6 +10,7 @@ class DropGcpClustersTable < ActiveRecord::Migration[4.2]
end
def down
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :gcp_clusters do |t|
# Order columns by best align scheme
t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
@@ -49,5 +50,6 @@ class DropGcpClustersTable < ActiveRecord::Migration[4.2]
t.text :encrypted_gcp_token
t.string :encrypted_gcp_token_iv
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
index 0b6155356d9..3bc20046311 100644
--- a/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
+++ b/db/migrate/20181101191341_create_clusters_applications_cert_manager.rb
@@ -6,6 +6,7 @@ class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :clusters_applications_cert_managers do |t|
t.references :cluster, null: false, index: false, foreign_key: { on_delete: :cascade }
t.integer :status, null: false
@@ -15,5 +16,6 @@ class CreateClustersApplicationsCertManager < ActiveRecord::Migration[4.2]
t.text :status_reason
t.index :cluster_id, unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
index 5b2bb4f6b08..124eedf7933 100644
--- a/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
+++ b/db/migrate/20181115140140_add_encrypted_runners_token_to_settings.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToSettings < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :application_settings, :runners_registration_token_encrypted, :string
+ add_column :application_settings, :runners_registration_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116050532_knative_external_ip.rb b/db/migrate/20181116050532_knative_external_ip.rb
index 5645b040a23..4634b411108 100644
--- a/db/migrate/20181116050532_knative_external_ip.rb
+++ b/db/migrate/20181116050532_knative_external_ip.rb
@@ -9,6 +9,6 @@ class KnativeExternalIp < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :clusters_applications_knative, :external_ip, :string
+ add_column :clusters_applications_knative, :external_ip, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
index dcf565cd6c0..0a8ed912891 100644
--- a/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
+++ b/db/migrate/20181116141415_add_encrypted_runners_token_to_namespaces.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToNamespaces < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :namespaces, :runners_token_encrypted, :string
+ add_column :namespaces, :runners_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
index 13cd80e5c8b..3083dff49b8 100644
--- a/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
+++ b/db/migrate/20181116141504_add_encrypted_runners_token_to_projects.rb
@@ -6,6 +6,6 @@ class AddEncryptedRunnersTokenToProjects < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :projects, :runners_token_encrypted, :string
+ add_column :projects, :runners_token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
index 8b990451adc..2270246dfb4 100644
--- a/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
+++ b/db/migrate/20181120151656_add_token_encrypted_to_ci_runners.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToCiRunners < ActiveRecord::Migration[4.2]
DOWNTIME = false
def change
- add_column :ci_runners, :token_encrypted, :string
+ add_column :ci_runners, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181122160027_create_project_repositories.rb b/db/migrate/20181122160027_create_project_repositories.rb
index e42cef9b1c6..3f123daa150 100644
--- a/db/migrate/20181122160027_create_project_repositories.rb
+++ b/db/migrate/20181122160027_create_project_repositories.rb
@@ -11,7 +11,7 @@ class CreateProjectRepositories < ActiveRecord::Migration[5.0]
def change
create_table :project_repositories, id: :bigserial do |t|
t.references :shard, null: false, index: true, foreign_key: { on_delete: :restrict }
- t.string :disk_path, null: false, index: { unique: true }
+ t.string :disk_path, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
t.references :project, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
end
end
diff --git a/db/migrate/20181123144235_create_suggestions.rb b/db/migrate/20181123144235_create_suggestions.rb
index 1723f6de7eb..78888517db5 100644
--- a/db/migrate/20181123144235_create_suggestions.rb
+++ b/db/migrate/20181123144235_create_suggestions.rb
@@ -8,7 +8,7 @@ class CreateSuggestions < ActiveRecord::Migration[5.0]
t.references :note, foreign_key: { on_delete: :cascade }, null: false
t.integer :relative_order, null: false, limit: 2
t.boolean :applied, null: false, default: false
- t.string :commit_id
+ t.string :commit_id # rubocop:disable Migration/AddLimitToStringColumns
t.text :from_content, null: false
t.text :to_content, null: false
diff --git a/db/migrate/20181128123704_add_state_to_pool_repository.rb b/db/migrate/20181128123704_add_state_to_pool_repository.rb
index 714232ede56..04bbcf24f62 100644
--- a/db/migrate/20181128123704_add_state_to_pool_repository.rb
+++ b/db/migrate/20181128123704_add_state_to_pool_repository.rb
@@ -9,7 +9,7 @@ class AddStateToPoolRepository < ActiveRecord::Migration[5.0]
# the transactions don't have to be disabled
# rubocop: disable Migration/AddConcurrentForeignKey, Migration/AddIndex
def change
- add_column(:pool_repositories, :state, :string, null: true)
+ add_column(:pool_repositories, :state, :string, null: true) # rubocop:disable Migration/AddLimitToStringColumns
add_column :pool_repositories, :source_project_id, :integer
add_index :pool_repositories, :source_project_id, unique: true
diff --git a/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
index 11b98203793..62a7421eae0 100644
--- a/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
+++ b/db/migrate/20181129104854_add_token_encrypted_to_ci_builds.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToCiBuilds < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :ci_builds, :token_encrypted, :string
+ add_column :ci_builds, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181203002526_add_project_bfg_object_map_column.rb b/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
index 8b42cd6f941..5e6d416895c 100644
--- a/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
+++ b/db/migrate/20181203002526_add_project_bfg_object_map_column.rb
@@ -4,6 +4,6 @@ class AddProjectBfgObjectMapColumn < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :projects, :bfg_object_map, :string
+ add_column :projects, :bfg_object_map, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
index 60815e0c31a..3ab808ba667 100644
--- a/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
+++ b/db/migrate/20181211092510_add_name_author_id_and_sha_to_releases.rb
@@ -7,7 +7,7 @@ class AddNameAuthorIdAndShaToReleases < ActiveRecord::Migration[5.0]
def change
add_column :releases, :author_id, :integer
- add_column :releases, :name, :string
- add_column :releases, :sha, :string
+ add_column :releases, :name, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :releases, :sha, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181212171634_create_error_tracking_settings.rb b/db/migrate/20181212171634_create_error_tracking_settings.rb
index 18c38bd2c47..950b9a88005 100644
--- a/db/migrate/20181212171634_create_error_tracking_settings.rb
+++ b/db/migrate/20181212171634_create_error_tracking_settings.rb
@@ -6,6 +6,7 @@ class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :project_error_tracking_settings, id: :int, primary_key: :project_id, default: nil do |t|
t.boolean :enabled, null: false, default: true
t.string :api_url, null: false
@@ -13,5 +14,6 @@ class CreateErrorTrackingSettings < ActiveRecord::Migration[5.0]
t.string :encrypted_token_iv
t.foreign_key :projects, column: :project_id, on_delete: :cascade
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20181228175414_create_releases_link_table.rb b/db/migrate/20181228175414_create_releases_link_table.rb
index 03558202137..168c4722cc1 100644
--- a/db/migrate/20181228175414_create_releases_link_table.rb
+++ b/db/migrate/20181228175414_create_releases_link_table.rb
@@ -6,6 +6,7 @@ class CreateReleasesLinkTable < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :release_links, id: :bigserial do |t|
t.references :release, null: false, index: false, foreign_key: { on_delete: :cascade }
t.string :url, null: false
@@ -15,5 +16,6 @@ class CreateReleasesLinkTable < ActiveRecord::Migration[5.0]
t.index [:release_id, :url], unique: true
t.index [:release_id, :name], unique: true
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190109153125_add_merge_request_external_diffs.rb b/db/migrate/20190109153125_add_merge_request_external_diffs.rb
index c67903c7f67..a680856a3d8 100644
--- a/db/migrate/20190109153125_add_merge_request_external_diffs.rb
+++ b/db/migrate/20190109153125_add_merge_request_external_diffs.rb
@@ -11,7 +11,7 @@ class AddMergeRequestExternalDiffs < ActiveRecord::Migration[5.0]
def change
# Allow the merge request diff to store details about an external file
- add_column :merge_request_diffs, :external_diff, :string
+ add_column :merge_request_diffs, :external_diff, :string # rubocop:disable Migration/AddLimitToStringColumns
add_column :merge_request_diffs, :external_diff_store, :integer
add_column :merge_request_diffs, :stored_externally, :boolean
diff --git a/db/migrate/20190114172110_add_domain_to_cluster.rb b/db/migrate/20190114172110_add_domain_to_cluster.rb
index 58d7664b8c0..d8f10af9cad 100644
--- a/db/migrate/20190114172110_add_domain_to_cluster.rb
+++ b/db/migrate/20190114172110_add_domain_to_cluster.rb
@@ -4,6 +4,6 @@ class AddDomainToCluster < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :clusters, :domain, :string
+ add_column :clusters, :domain, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb b/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
index 190b6f958fd..afed929cce4 100644
--- a/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
+++ b/db/migrate/20190115092821_add_columns_project_error_tracking_settings.rb
@@ -6,8 +6,8 @@ class AddColumnsProjectErrorTrackingSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :project_error_tracking_settings, :project_name, :string
- add_column :project_error_tracking_settings, :organization_name, :string
+ add_column :project_error_tracking_settings, :project_name, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :project_error_tracking_settings, :organization_name, :string # rubocop:disable Migration/AddLimitToStringColumns
change_column_default :project_error_tracking_settings, :enabled, from: true, to: false
diff --git a/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb b/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
index 7bf581fe9b0..39aab600546 100644
--- a/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
+++ b/db/migrate/20190116234221_add_sorting_fields_to_user_preference.rb
@@ -10,8 +10,8 @@ class AddSortingFieldsToUserPreference < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column :user_preferences, :issues_sort, :string
- add_column :user_preferences, :merge_requests_sort, :string
+ add_column :user_preferences, :issues_sort, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :user_preferences, :merge_requests_sort, :string # rubocop:disable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
index 2c3a54b12a9..37ba1090cf0 100644
--- a/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
+++ b/db/migrate/20190301182457_add_external_hostname_to_ingress_and_knative.rb
@@ -4,7 +4,7 @@ class AddExternalHostnameToIngressAndKnative < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :clusters_applications_ingress, :external_hostname, :string
- add_column :clusters_applications_knative, :external_hostname, :string
+ add_column :clusters_applications_ingress, :external_hostname, :string # rubocop:disable Migration/AddLimitToStringColumns
+ add_column :clusters_applications_knative, :external_hostname, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
index e9cf2af84a5..aeabf4e3cb4 100644
--- a/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
+++ b/db/migrate/20190320174702_add_lets_encrypt_notification_email_to_application_settings.rb
@@ -10,6 +10,6 @@ class AddLetsEncryptNotificationEmailToApplicationSettings < ActiveRecord::Migra
DOWNTIME = false
def change
- add_column :application_settings, :lets_encrypt_notification_email, :string
+ add_column :application_settings, :lets_encrypt_notification_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190325105715_add_fields_to_user_preferences.rb b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
index 9ea3b4f9cd8..4da5c496147 100644
--- a/db/migrate/20190325105715_add_fields_to_user_preferences.rb
+++ b/db/migrate/20190325105715_add_fields_to_user_preferences.rb
@@ -11,7 +11,7 @@ class AddFieldsToUserPreferences < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column(:user_preferences, :timezone, :string)
+ add_column(:user_preferences, :timezone, :string) # rubocop:disable Migration/AddLimitToStringColumns
add_column(:user_preferences, :time_display_relative, :boolean)
add_column(:user_preferences, :time_format_in_24h, :boolean)
end
diff --git a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
index 2f3069032a1..d912f922510 100644
--- a/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
+++ b/db/migrate/20190327163904_add_notification_email_to_notification_settings.rb
@@ -6,6 +6,6 @@ class AddNotificationEmailToNotificationSettings < ActiveRecord::Migration[5.0]
DOWNTIME = false
def change
- add_column :notification_settings, :notification_email, :string
+ add_column :notification_settings, :notification_email, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb
index 8762cc53ed7..3f13b68c2f3 100644
--- a/db/migrate/20190402150158_backport_enterprise_schema.rb
+++ b/db/migrate/20190402150158_backport_enterprise_schema.rb
@@ -2,6 +2,7 @@
# rubocop: disable Metrics/AbcSize
# rubocop: disable Migration/Datetime
+# rubocop: disable Migration/AddLimitToStringColumns
class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
@@ -2190,3 +2191,4 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0]
end
# rubocop: enable Metrics/AbcSize
# rubocop: enable Migration/Datetime
+# rubocop: enable Migration/AddLimitToStringColumns
diff --git a/db/migrate/20190409224933_add_name_to_geo_nodes.rb b/db/migrate/20190409224933_add_name_to_geo_nodes.rb
index 2dff81b429c..65c01683995 100644
--- a/db/migrate/20190409224933_add_name_to_geo_nodes.rb
+++ b/db/migrate/20190409224933_add_name_to_geo_nodes.rb
@@ -10,7 +10,7 @@ class AddNameToGeoNodes < ActiveRecord::Migration[5.0]
DOWNTIME = false
def up
- add_column :geo_nodes, :name, :string
+ add_column :geo_nodes, :name, :string # rubocop:disable Migration/AddLimitToStringColumns
# url is also unique, and its type and size is identical to the name column,
# so this is safe.
diff --git a/db/migrate/20190422082247_create_project_metrics_settings.rb b/db/migrate/20190422082247_create_project_metrics_settings.rb
index 3e21dd0a934..a0a2ed64820 100644
--- a/db/migrate/20190422082247_create_project_metrics_settings.rb
+++ b/db/migrate/20190422082247_create_project_metrics_settings.rb
@@ -7,7 +7,7 @@ class CreateProjectMetricsSettings < ActiveRecord::Migration[5.0]
def change
create_table :project_metrics_settings, id: :int, primary_key: :project_id, default: nil do |t|
- t.string :external_dashboard_url, null: false
+ t.string :external_dashboard_url, null: false # rubocop:disable Migration/AddLimitToStringColumns
t.foreign_key :projects, column: :project_id, on_delete: :cascade
end
end
diff --git a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
index af811e83518..ca1796d054c 100644
--- a/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
+++ b/db/migrate/20190429082448_create_pages_domain_acme_orders.rb
@@ -10,6 +10,7 @@ class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :pages_domain_acme_orders do |t|
t.references :pages_domain, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
@@ -24,5 +25,6 @@ class CreatePagesDomainAcmeOrders < ActiveRecord::Migration[5.1]
t.text :encrypted_private_key, null: false
t.text :encrypted_private_key_iv, null: false
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190430131225_create_issue_tracker_data.rb b/db/migrate/20190430131225_create_issue_tracker_data.rb
index 7859bea9c22..d2134ad82c7 100644
--- a/db/migrate/20190430131225_create_issue_tracker_data.rb
+++ b/db/migrate/20190430131225_create_issue_tracker_data.rb
@@ -9,6 +9,7 @@ class CreateIssueTrackerData < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :issue_tracker_data do |t|
t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false
t.timestamps_with_timezone
@@ -19,5 +20,6 @@ class CreateIssueTrackerData < ActiveRecord::Migration[5.1]
t.string :encrypted_new_issue_url
t.string :encrypted_new_issue_url_iv
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190430142025_create_jira_tracker_data.rb b/db/migrate/20190430142025_create_jira_tracker_data.rb
index d328ad63854..5e53e5a701a 100644
--- a/db/migrate/20190430142025_create_jira_tracker_data.rb
+++ b/db/migrate/20190430142025_create_jira_tracker_data.rb
@@ -9,6 +9,7 @@ class CreateJiraTrackerData < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :jira_tracker_data do |t|
t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false
t.timestamps_with_timezone
@@ -22,5 +23,6 @@ class CreateJiraTrackerData < ActiveRecord::Migration[5.1]
t.string :encrypted_password_iv
t.string :jira_issue_transition_id
end
+ # rubocop:enable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190514105711_create_ip_restriction.rb b/db/migrate/20190514105711_create_ip_restriction.rb
index dfafbe32ad1..69f8c1b8c4e 100644
--- a/db/migrate/20190514105711_create_ip_restriction.rb
+++ b/db/migrate/20190514105711_create_ip_restriction.rb
@@ -12,7 +12,7 @@ class CreateIpRestriction < ActiveRecord::Migration[5.1]
type: :integer,
null: false,
index: true
- t.string :range, null: false
+ t.string :range, null: false # rubocop:disable Migration/AddLimitToStringColumns
end
add_foreign_key(:ip_restrictions, :namespaces, column: :group_id, on_delete: :cascade) # rubocop: disable Migration/AddConcurrentForeignKey
diff --git a/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb b/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
index 6cbac0ed507..5c47e6f33c2 100644
--- a/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
+++ b/db/migrate/20190527011309_add_required_template_name_to_application_settings.rb
@@ -10,6 +10,6 @@ class AddRequiredTemplateNameToApplicationSettings < ActiveRecord::Migration[5.1
DOWNTIME = false
def change
- add_column :application_settings, :required_instance_ci_template, :string, null: true
+ add_column :application_settings, :required_instance_ci_template, :string, null: true # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb b/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
index 024b5bd2ba5..2db4dc85750 100644
--- a/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
+++ b/db/migrate/20190606054742_add_token_encrypted_to_operations_feature_flags_clients.rb
@@ -6,6 +6,6 @@ class AddTokenEncryptedToOperationsFeatureFlagsClients < ActiveRecord::Migration
DOWNTIME = false
def change
- add_column :operations_feature_flags_clients, :token_encrypted, :string
+ add_column :operations_feature_flags_clients, :token_encrypted, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190613044655_add_username_to_deploy_tokens.rb b/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
index 793553afe35..a0acb02013b 100644
--- a/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
+++ b/db/migrate/20190613044655_add_username_to_deploy_tokens.rb
@@ -4,6 +4,6 @@ class AddUsernameToDeployTokens < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
- add_column :deploy_tokens, :username, :string
+ add_column :deploy_tokens, :username, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190613073003_create_project_aliases.rb b/db/migrate/20190613073003_create_project_aliases.rb
index 5a2c2ba0cf2..896d3ca5813 100644
--- a/db/migrate/20190613073003_create_project_aliases.rb
+++ b/db/migrate/20190613073003_create_project_aliases.rb
@@ -8,7 +8,7 @@ class CreateProjectAliases < ActiveRecord::Migration[5.1]
def change
create_table :project_aliases do |t|
t.references :project, null: false, index: true, foreign_key: { on_delete: :cascade }, type: :integer
- t.string :name, null: false, index: { unique: true }
+ t.string :name, null: false, index: { unique: true } # rubocop:disable Migration/AddLimitToStringColumns
t.timestamps_with_timezone null: false
end
diff --git a/db/migrate/20190621151636_add_merge_request_rebase_jid.rb b/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
index 1fed5690ead..6c1081732e8 100644
--- a/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
+++ b/db/migrate/20190621151636_add_merge_request_rebase_jid.rb
@@ -4,6 +4,6 @@ class AddMergeRequestRebaseJid < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
- add_column :merge_requests, :rebase_jid, :string
+ add_column :merge_requests, :rebase_jid, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/migrate/20190624123615_add_grafana_url_to_settings.rb b/db/migrate/20190624123615_add_grafana_url_to_settings.rb
index 61efe64a7a1..835ec4e9094 100644
--- a/db/migrate/20190624123615_add_grafana_url_to_settings.rb
+++ b/db/migrate/20190624123615_add_grafana_url_to_settings.rb
@@ -8,8 +8,10 @@ class AddGrafanaUrlToSettings < ActiveRecord::Migration[5.1]
DOWNTIME = false
def up
+ # rubocop:disable Migration/AddLimitToStringColumns
add_column_with_default(:application_settings, :grafana_url, :string,
default: '/-/grafana', allow_null: false)
+ # rubocop:enable Migration/AddLimitToStringColumns
end
def down
diff --git a/db/migrate/20190711124721_create_job_variables.rb b/db/migrate/20190711124721_create_job_variables.rb
index a860522f39e..4ff4b031d8f 100644
--- a/db/migrate/20190711124721_create_job_variables.rb
+++ b/db/migrate/20190711124721_create_job_variables.rb
@@ -10,6 +10,7 @@ class CreateJobVariables < ActiveRecord::Migration[5.1]
DOWNTIME = false
def change
+ # rubocop:disable Migration/AddLimitToStringColumns
create_table :ci_job_variables do |t|
t.string :key, null: false
t.text :encrypted_value
@@ -17,6 +18,7 @@ class CreateJobVariables < ActiveRecord::Migration[5.1]
t.references :job, null: false, index: true, foreign_key: { to_table: :ci_builds, on_delete: :cascade }
t.integer :variable_type, null: false, limit: 2, default: 1
end
+ # rubocop:enable Migration/AddLimitToStringColumns
add_index :ci_job_variables, [:key, :job_id], unique: true
end
diff --git a/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
new file mode 100644
index 00000000000..fc4bc1a423b
--- /dev/null
+++ b/db/migrate/20190805140353_remove_rendundant_index_from_releases.rb
@@ -0,0 +1,21 @@
+# 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 RemoveRendundantIndexFromReleases < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :releases, :project_id
+ end
+
+ def down
+ add_concurrent_index :releases, :project_id
+ end
+end
diff --git a/db/migrate/20190808152507_add_projects_sorting_field_to_user_preferences.rb b/db/migrate/20190808152507_add_projects_sorting_field_to_user_preferences.rb
new file mode 100644
index 00000000000..941fead655e
--- /dev/null
+++ b/db/migrate/20190808152507_add_projects_sorting_field_to_user_preferences.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddProjectsSortingFieldToUserPreferences < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_column :user_preferences, :projects_sort, :string, limit: 64
+ end
+
+ def down
+ remove_column :user_preferences, :projects_sort
+ end
+end
diff --git a/db/migrate/20190814205640_import_common_metrics_line_charts.rb b/db/migrate/20190814205640_import_common_metrics_line_charts.rb
new file mode 100644
index 00000000000..1c28d686a42
--- /dev/null
+++ b/db/migrate/20190814205640_import_common_metrics_line_charts.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ImportCommonMetricsLineCharts < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
index 550ad94f4ab..b6e5473e896 100644
--- a/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
+++ b/db/post_migrate/20181013005024_remove_koding_from_application_settings.rb
@@ -12,6 +12,6 @@ class RemoveKodingFromApplicationSettings < ActiveRecord::Migration[4.2]
def down
add_column :application_settings, :koding_enabled, :boolean # rubocop:disable Migration/SaferBooleanColumn
- add_column :application_settings, :koding_url, :string
+ add_column :application_settings, :koding_url, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
index 785ceb2fb28..8e7ef0ec54f 100644
--- a/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
+++ b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb
@@ -16,6 +16,6 @@ class RemoveAlternateUrlFromGeoNodes < ActiveRecord::Migration[5.0]
end
def down
- add_column :geo_nodes, :alternate_url, :string
+ add_column :geo_nodes, :alternate_url, :string # rubocop:disable Migration/AddLimitToStringColumns
end
end
diff --git a/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
index 427df343193..9d71bfafffb 100644
--- a/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
+++ b/db/post_migrate/20190625184066_remove_sentry_from_application_settings.rb
@@ -32,7 +32,7 @@ class RemoveSentryFromApplicationSettings < ActiveRecord::Migration[5.0]
end
SENTRY_DSN_COLUMNS.each do |column|
- add_column(:application_settings, column, :string) unless column_exists?(:application_settings, column)
+ add_column(:application_settings, column, :string) unless column_exists?(:application_settings, column) # rubocop:disable Migration/AddLimitToStringColumns
end
end
end
diff --git a/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
new file mode 100644
index 00000000000..8b2cf7b3d76
--- /dev/null
+++ b/db/post_migrate/20190801072937_add_gitlab_instance_administration_project.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGitlabInstanceAdministrationProject < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService.new.execute!
+ end
+
+ def down
+ ApplicationSetting.current_without_cache
+ &.instance_administration_project
+ &.owner
+ &.destroy!
+ end
+end
diff --git a/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
new file mode 100644
index 00000000000..0c4faebc548
--- /dev/null
+++ b/db/post_migrate/20190809072552_set_self_monitoring_project_alerting_token.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+class SetSelfMonitoringProjectAlertingToken < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ module Migratable
+ module Alerting
+ class ProjectAlertingSetting < ApplicationRecord
+ self.table_name = 'project_alerting_settings'
+
+ belongs_to :project
+
+ validates :token, presence: true
+
+ attr_encrypted :token,
+ mode: :per_attribute_iv,
+ key: Settings.attr_encrypted_db_key_base_truncated,
+ algorithm: 'aes-256-gcm'
+
+ before_validation :ensure_token
+
+ private
+
+ def ensure_token
+ self.token ||= generate_token
+ end
+
+ def generate_token
+ SecureRandom.hex
+ end
+ end
+ end
+
+ class Project < ApplicationRecord
+ has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting'
+ end
+
+ class ApplicationSetting < ApplicationRecord
+ self.table_name = 'application_settings'
+
+ belongs_to :instance_administration_project, class_name: 'Project'
+
+ def self.current_without_cache
+ last
+ end
+ end
+ end
+
+ def setup_alertmanager_token(project)
+ return unless License.feature_available?(:prometheus_alerts)
+
+ project.create_alerting_setting!
+ end
+
+ def up
+ Gitlab.ee do
+ project = Migratable::ApplicationSetting.current_without_cache&.instance_administration_project
+
+ if project
+ setup_alertmanager_token(project)
+ end
+ end
+ end
+
+ def down
+ Gitlab.ee do
+ Migratable::ApplicationSetting.current_without_cache
+ &.instance_administration_project
+ &.alerting_setting
+ &.destroy!
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index ce5fd38129a..944d23e5365 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3015,7 +3015,6 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.datetime_with_timezone "released_at", null: false
t.index ["author_id"], name: "index_releases_on_author_id"
t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag"
- t.index ["project_id"], name: "index_releases_on_project_id"
end
create_table "remote_mirrors", id: :serial, force: :cascade do |t|
@@ -3411,6 +3410,7 @@ ActiveRecord::Schema.define(version: 2019_08_15_093949) do
t.string "epics_sort"
t.integer "roadmap_epics_state"
t.string "roadmaps_sort"
+ t.string "projects_sort", limit: 64
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true
end
diff --git a/doc/README.md b/doc/README.md
index 8ce5d2e240a..f12c06199c2 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -358,7 +358,7 @@ The following documentation relates to the DevOps **Secure** stage:
| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
-| [License Management](user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
+| [License Compliance](user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
| [Project Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View the latest security reports for your project. |
| [Static Application Security Testing (SAST)](user/application_security/sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index 02de2caf558..8075a40cae7 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -75,6 +75,8 @@ From there, you can see the following actions:
- User was removed from project
- Project export was downloaded
- Project repository was downloaded
+- Project was archived
+- Project was unarchived
### Instance events **(PREMIUM ONLY)**
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index d0adeb89543..f5d58db0133 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -137,6 +137,15 @@ otherwise you will run into conflicts.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Validate using:
+
+ ```sh
+ openssl s_client -showcerts -servername gitlab.example.com -connect gitlab.example.com:443 > cacert.pem
+ ```
+
+NOTE: **Note:**
+If your certificate provider provides the CA Bundle certificates, append them to the TLS certificate file.
+
**Installations from source**
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
@@ -163,9 +172,13 @@ docker login gitlab.example.com:4567
If the Registry is configured to use its own domain, you will need a TLS
certificate for that specific domain (e.g., `registry.example.com`) or maybe
-a wildcard certificate if hosted under a subdomain of your existing GitLab
+a wildcard certificate if hosted under a subdomain of your existing GitLab
domain (e.g., `registry.gitlab.example.com`).
+NOTE: **Note:**
+As well as manually generated SSL certificates (explained here), certificates automatically
+generated by Let's Encrypt are also [supported in Omnibus installs](https://docs.gitlab.com/omnibus/settings/ssl.html#host-services).
+
Let's assume that you want the container Registry to be accessible at
`https://registry.gitlab.example.com`.
@@ -458,7 +471,7 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
```
1. A certificate keypair is required for GitLab and the Container Registry to
- communicate securely. By default omnibus-gitlab will generate one keypair,
+ communicate securely. By default Omnibus GitLab will generate one keypair,
which is saved to `/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key`.
When using a non-bundled Container Registry, you will need to supply a
custom certificate key. To do that, add the following to
@@ -474,7 +487,7 @@ You can use GitLab as an auth endpoint and use a non-bundled Container Registry.
**Note:** The file specified at `registry_key_path` gets populated with the
content specified by `internal_key`, each time reconfigure is executed. If
- no file is specified, omnibus-gitlab will default it to
+ no file is specified, Omnibus GitLab will default it to
`/var/opt/gitlab/gitlab-rails/etc/gitlab-registry.key` and will populate
it.
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 32462a95a1a..7238d08ab09 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -12,17 +12,17 @@ NOTE: **Note:**
Custom Git hooks won't be replicated to secondary nodes if you use [GitLab Geo](geo/replication/index.md)
Git natively supports hooks that are executed on different actions.
-Examples of server-side git hooks include pre-receive, post-receive, and update.
+Examples of server-side Git hooks include pre-receive, post-receive, and update.
See [Git SCM Server-Side Hooks][hooks] for more information about each hook type.
-As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
-administrators can add custom git hooks to any GitLab project.
+As of GitLab Shell version 2.2.0 (which requires GitLab 7.5+), GitLab
+administrators can add custom Git hooks to any GitLab project.
## Create a custom Git hook for a repository
Server-side Git hooks are typically placed in the repository's `hooks`
-subdirectory. In GitLab, hook directories are symlinked to the gitlab-shell
-`hooks` directory for ease of maintenance between gitlab-shell upgrades.
+subdirectory. In GitLab, hook directories are symlinked to the GitLab Shell
+`hooks` directory for ease of maintenance between GitLab Shell upgrades.
Custom hooks are implemented differently, but the behavior is exactly the same
once the hook is created. Follow the steps below to set up a custom hook for a
repository:
@@ -36,7 +36,7 @@ repository:
1. Inside the new `custom_hooks` directory, create a file with a name matching
the hook type. For a pre-receive hook the file name should be `pre-receive`
with no extension.
-1. Make the hook file executable and make sure it's owned by git.
+1. Make the hook file executable and make sure it's owned by Git.
1. Write the code to make the Git hook function as expected. Hooks can be
in any language. Ensure the 'shebang' at the top properly reflects the language
type. For example, if the script is in Ruby the shebang will probably be
@@ -49,17 +49,17 @@ as appropriate.
To create a Git hook that applies to all of your repositories in
your instance, set a global Git hook. Since all the repositories' `hooks`
-directories are symlinked to gitlab-shell's `hooks` directory, adding any hook
-to the gitlab-shell `hooks` directory will also apply it to all repositories. Follow
+directories are symlinked to GitLab Shell's `hooks` directory, adding any hook
+to the GitLab Shell `hooks` directory will also apply it to all repositories. Follow
the steps below to properly set up a custom hook for all repositories:
1. On the GitLab server, navigate to the configured custom hook directory. The
- default is in the gitlab-shell directory. The gitlab-shell `hook` directory
+ default is in the GitLab Shell directory. The GitLab Shell `hook` directory
for an installation from source the path is usually
`/home/git/gitlab-shell/hooks`. For Omnibus installs the path is usually
`/opt/gitlab/embedded/service/gitlab-shell/hooks`.
To look in a different directory for the global custom hooks,
- set `custom_hooks_dir` in the gitlab-shell config. For
+ set `custom_hooks_dir` in the GitLab Shell config. For
Omnibus installations, this can be set in `gitlab.rb`; and in source
installations, this can be set in `gitlab-shell/config.yml`.
1. Create a new directory in this location. Depending on your hook, it will be
diff --git a/doc/administration/dependency_proxy.md b/doc/administration/dependency_proxy.md
index d2c52b67e67..5153666705f 100644
--- a/doc/administration/dependency_proxy.md
+++ b/doc/administration/dependency_proxy.md
@@ -48,7 +48,7 @@ local location or even use object storage.
The dependency proxy files for Omnibus GitLab installations are stored under
`/var/opt/gitlab/gitlab-rails/shared/dependency_proxy/` and for source
-installations under `shared/dependency_proxy/` (relative to the git home directory).
+installations under `shared/dependency_proxy/` (relative to the Git home directory).
To change the local storage path:
**Omnibus GitLab installations**
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/git_protocol.md b/doc/administration/git_protocol.md
index 1780e1babe9..9d653d4e09e 100644
--- a/doc/administration/git_protocol.md
+++ b/doc/administration/git_protocol.md
@@ -53,7 +53,7 @@ sudo service ssh restart
## Instructions
In order to use the new protocol, clients need to either pass the configuration
-`-c protocol.version=2` to the git command, or set it globally:
+`-c protocol.version=2` to the Git command, or set it globally:
```sh
git config --global protocol.version 2
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 8e10de3d592..1ee8dcd21fd 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -64,6 +64,7 @@ The following list depicts what the network architecture of Gitaly is:
topology.
- A `(Gitaly address, Gitaly token)` corresponds to a Gitaly server.
- A Gitaly server hosts one or more storages.
+- A GitLab server can use one or more Gitaly servers.
- Gitaly addresses must be specified in such a way that they resolve
correctly for ALL Gitaly clients.
- Gitaly clients are: Unicorn, Sidekiq, gitlab-workhorse,
@@ -77,14 +78,16 @@ The following list depicts what the network architecture of Gitaly is:
- Authentication is done through a static token which is shared among the Gitaly
and GitLab Rails nodes.
-Below we describe how to configure a Gitaly server at address
-`gitaly.internal:8075` with secret token `abc123secret`. We assume
-your GitLab installation has two repository storages, `default` and
-`storage1`.
+Below we describe how to configure two Gitaly servers one at
+`gitaly1.internal` and the other at `gitaly2.internal`
+with secret token `abc123secret`. We assume
+your GitLab installation has three repository storages: `default`,
+`storage1` and `storage2`.
### 1. Installation
-First install Gitaly using either Omnibus GitLab or install it from source:
+First install Gitaly on each Gitaly server using either
+Omnibus GitLab or install it from source:
- For Omnibus GitLab: [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
package you want using **steps 1 and 2** from the GitLab downloads page but
@@ -119,7 +122,7 @@ Configure a token on the instance that runs the GitLab Rails application.
### 3. Gitaly server configuration
-Next, on the Gitaly server, you need to configure storage paths, enable
+Next, on the Gitaly servers, you need to configure storage paths, enable
the network listener and configure the token.
NOTE: **Note:** if you want to reduce the risk of downtime when you enable
@@ -175,15 +178,29 @@ Check the directory layout on your Gitaly server to be sure.
gitaly['listen_addr'] = "0.0.0.0:8075"
gitaly['auth_token'] = 'abc123secret'
+ # To use TLS for Gitaly you need to add
+ gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+ gitaly['certificate_path'] = "path/to/cert.pem"
+ gitaly['key_path'] = "path/to/key.pem"
+ ```
+
+1. Append the following to `/etc/gitlab/gitlab.rb` for each respective server:
+
+ For `gitaly1.internal`:
+
+ ```
gitaly['storage'] = [
{ 'name' => 'default' },
{ 'name' => 'storage1' },
]
+ ```
+
+ For `gitaly2.internal`:
- # To use TLS for Gitaly you need to add
- gitaly['tls_listen_addr'] = "0.0.0.0:9999"
- gitaly['certificate_path'] = "path/to/cert.pem"
- gitaly['key_path'] = "path/to/key.pem"
+ ```
+ gitaly['storage'] = [
+ { 'name' => 'storage2' },
+ ]
```
NOTE: **Note:**
@@ -206,13 +223,26 @@ Check the directory layout on your Gitaly server to be sure.
[auth]
token = 'abc123secret'
+ ```
+
+1. Append the following to `/home/git/gitaly/config.toml` for each respective server:
+
+ For `gitaly1.internal`:
+ ```toml
[[storage]]
name = 'default'
[[storage]]
name = 'storage1'
```
+
+ For `gitaly2.internal`:
+
+ ```toml
+ [[storage]]
+ name = 'storage2'
+ ```
NOTE: **Note:**
In some cases, you'll have to set `path` for each `[[storage]]` in the
@@ -231,9 +261,13 @@ then all Gitaly requests will fail.
Additionally, you need to
[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
-We assume that your Gitaly server can be reached at
-`gitaly.internal:8075` from your GitLab server, and that Gitaly can read and
-write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1` respectively.
+We assume that your `gitaly1.internal` Gitaly server can be reached at
+`gitaly1.internal:8075` from your GitLab server, and that Gitaly server
+can read and write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1`.
+
+We assume also that your `gitaly2.internal` Gitaly server can be reached at
+`gitaly2.internal:8075` from your GitLab server, and that Gitaly server
+can read and write to `/mnt/gitlab/storage2`.
**For Omnibus GitLab**
@@ -241,8 +275,9 @@ write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1` respectively.
```ruby
git_data_dirs({
- 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
- 'storage1' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
})
gitlab_rails['gitaly_token'] = 'abc123secret'
@@ -264,10 +299,13 @@ write to `/mnt/gitlab/default` and `/mnt/gitlab/storage1` respectively.
repositories:
storages:
default:
- gitaly_address: tcp://gitaly.internal:8075
+ gitaly_address: tcp://gitaly1.internal:8075
path: /some/dummy/path
storage1:
- gitaly_address: tcp://gitaly.internal:8075
+ gitaly_address: tcp://gitaly1.internal:8075
+ path: /some/dummy/path
+ storage2:
+ gitaly_address: tcp://gitaly2.internal:8075
path: /some/dummy/path
gitaly:
@@ -349,8 +387,9 @@ To configure Gitaly with TLS:
```ruby
git_data_dirs({
- 'default' => { 'gitaly_address' => 'tls://gitaly.internal:9999' },
- 'storage1' => { 'gitaly_address' => 'tls://gitaly.internal:9999' },
+ 'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
})
gitlab_rails['gitaly_token'] = 'abc123secret'
@@ -376,11 +415,14 @@ To configure Gitaly with TLS:
repositories:
storages:
default:
+ gitaly_address: tls://gitaly1.internal:9999
path: /some/dummy/path
- gitaly_address: tls://gitaly.internal:9999
storage1:
+ gitaly_address: tls://gitaly1.internal:9999
+ path: /some/dummy/path
+ storage2:
+ gitaly_address: tls://gitaly2.internal:9999
path: /some/dummy/path
- gitaly_address: tls://gitaly.internal:9999
gitaly:
token: 'abc123secret'
@@ -617,3 +659,33 @@ To fix this problem, confirm that your [`gitlab-secrets.json` file](#3-gitaly-se
on the Gitaly node matches the one on all other nodes. If it doesn't match,
update the secrets file on the Gitaly node to match the others, then
[reconfigure the node](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+### Command line tools cannot connect to Gitaly
+
+If you are having trouble connecting to a Gitaly node with command line (CLI) tools, and certain actions result in a `14: Connect Failed` error message, it means that gRPC cannot reach your Gitaly node.
+
+Verify that you can reach Gitaly via TCP:
+
+```bash
+sudo gitlab-rake gitlab:tcp_check[GITALY_SERVER_IP,GITALY_LISTEN_PORT]
+```
+
+If the TCP connection fails, check your network settings and your firewall rules. If the TCP connection succeeds, your networking and firewall rules are correct.
+
+If you use proxy servers in your command line environment, such as Bash, these can interfere with your gRPC traffic.
+
+If you use Bash or a compatible command line environment, run the following commands to determine whether you have proxy servers configured:
+
+```bash
+echo $http_proxy
+echo $https_proxy
+```
+
+If either of these variables have a value, your Gitaly CLI connections may be getting routed through a proxy which cannot connect to Gitaly.
+
+To remove the proxy setting, run the following commands (depending on which variables had values):
+
+```bash
+unset http_proxy
+unset https_proxy
+``` \ No newline at end of file
diff --git a/doc/administration/high_availability/load_balancer.md b/doc/administration/high_availability/load_balancer.md
index 9e9f604317a..f11d27487d1 100644
--- a/doc/administration/high_availability/load_balancer.md
+++ b/doc/administration/high_availability/load_balancer.md
@@ -60,11 +60,21 @@ for details on managing SSL certificates and configuring Nginx.
### Basic ports
-| LB Port | Backend Port | Protocol |
-| ------- | ------------ | --------------- |
-| 80 | 80 | HTTP [^1] |
-| 443 | 443 | TCP or HTTPS [^1] [^2] |
-| 22 | 22 | TCP |
+| LB Port | Backend Port | Protocol |
+| ------- | ------------ | ------------------------ |
+| 80 | 80 | HTTP (*1*) |
+| 443 | 443 | TCP or HTTPS (*1*) (*2*) |
+| 22 | 22 | TCP |
+
+- (*1*): [Web terminal](../../ci/environments.md#web-terminals) support requires
+ your load balancer to correctly handle WebSocket connections. When using
+ HTTP or HTTPS proxying, this means your load balancer must be configured
+ to pass through the `Connection` and `Upgrade` hop-by-hop headers. See the
+ [web terminal](../integration/terminal.md) integration guide for
+ more details.
+- (*2*): When using HTTPS protocol for port 443, you will need to add an SSL
+ certificate to the load balancers. If you wish to terminate SSL at the
+ GitLab application server instead, use TCP protocol.
### GitLab Pages Ports
@@ -72,12 +82,19 @@ If you're using GitLab Pages with custom domain support you will need some
additional port configurations.
GitLab Pages requires a separate virtual IP address. Configure DNS to point the
`pages_external_url` from `/etc/gitlab/gitlab.rb` at the new virtual IP address. See the
-[GitLab Pages documentation][gitlab-pages] for more information.
+[GitLab Pages documentation](../pages/index.md) for more information.
-| LB Port | Backend Port | Protocol |
-| ------- | ------------ | -------- |
-| 80 | Varies [^3] | HTTP |
-| 443 | Varies [^3] | TCP [^4] |
+| LB Port | Backend Port | Protocol |
+| ------- | ------------- | --------- |
+| 80 | Varies (*1*) | HTTP |
+| 443 | Varies (*1*) | TCP (*2*) |
+
+- (*1*): The backend port for GitLab Pages depends on the
+ `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
+ setting. See [GitLab Pages documentation](../pages/index.md) for more details.
+- (*2*): Port 443 for GitLab Pages should always use the TCP protocol. Users can
+ configure custom domains with custom SSL, which would not be possible
+ if SSL was terminated at the load balancer.
### Alternate SSH Port
@@ -86,7 +103,7 @@ it may be helpful to configure an alternate SSH hostname that allows users
to use SSH on port 443. An alternate SSH hostname will require a new virtual IP address
compared to the other GitLab HTTP configuration above.
-Configure DNS for an alternate SSH hostname such as altssh.gitlab.example.com.
+Configure DNS for an alternate SSH hostname such as `altssh.gitlab.example.com`.
| LB Port | Backend Port | Protocol |
| ------- | ------------ | -------- |
@@ -101,24 +118,6 @@ Read more on high-availability configuration:
1. [Configure NFS](nfs.md)
1. [Configure the GitLab application servers](gitlab.md)
-[^1]: [Web terminal](../../ci/environments.md#web-terminals) support requires
- your load balancer to correctly handle WebSocket connections. When using
- HTTP or HTTPS proxying, this means your load balancer must be configured
- to pass through the `Connection` and `Upgrade` hop-by-hop headers. See the
- [web terminal](../integration/terminal.md) integration guide for
- more details.
-[^2]: When using HTTPS protocol for port 443, you will need to add an SSL
- certificate to the load balancers. If you wish to terminate SSL at the
- GitLab application server instead, use TCP protocol.
-[^3]: The backend port for GitLab Pages depends on the
- `gitlab_pages['external_http']` and `gitlab_pages['external_https']`
- setting. See [GitLab Pages documentation][gitlab-pages] for more details.
-[^4]: Port 443 for GitLab Pages should always use the TCP protocol. Users can
- configure custom domains with custom SSL, which would not be possible
- if SSL was terminated at the load balancer.
-
-[gitlab-pages]: ../pages/index.md
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/administration/incoming_email.md b/doc/administration/incoming_email.md
index 73a39a6dd35..29915cb3a99 100644
--- a/doc/administration/incoming_email.md
+++ b/doc/administration/incoming_email.md
@@ -151,7 +151,7 @@ Reply by email should now be working.
#### Postfix
-Example configuration for Postfix mail server. Assumes mailbox incoming@gitlab.example.com.
+Example configuration for Postfix mail server. Assumes mailbox `incoming@gitlab.example.com`.
Example for Omnibus installs:
@@ -218,7 +218,7 @@ incoming_email:
#### Gmail
-Example configuration for Gmail/G Suite. Assumes mailbox gitlab-incoming@gmail.com.
+Example configuration for Gmail/G Suite. Assumes mailbox `gitlab-incoming@gmail.com`.
Example for Omnibus installs:
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 2b25e8efd23..650cb10a64a 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -64,6 +64,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [External Classification Policy Authorization](../user/admin_area/settings/external_authorization.md) **(PREMIUM ONLY)**
- [Upload a license](../user/admin_area/license.md): Upload a license to unlock features that are in paid tiers of GitLab. **(STARTER ONLY)**
- [Admin Area](../user/admin_area/index.md): for self-managed instance-wide configuration and maintenance.
+- [S/MIME Signing](smime_signing_email.md): how to sign all outgoing notification emails with S/MIME
#### Customizing GitLab's appearance
@@ -192,6 +193,6 @@ Learn how to install, configure, update, and maintain your GitLab instance.
for troubleshooting Kubernetes-related issues.
- Useful links from the Support Team:
- [GitLab Developer Docs](https://docs.gitlab.com/ee/development/README.html).
- - [Repairing and recovering broken git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html).
+ - [Repairing and recovering broken Git repositories](https://git.seveas.net/repairing-and-recovering-broken-git-repositories.html).
- [Testing with OpenSSL](https://www.feistyduck.com/library/openssl-cookbook/online/ch-testing-with-openssl.html).
- [Strace zine](https://wizardzines.com/zines/strace/).
diff --git a/doc/administration/instance_review.md b/doc/administration/instance_review.md
index ab6a4646a71..825435deff9 100644
--- a/doc/administration/instance_review.md
+++ b/doc/administration/instance_review.md
@@ -14,4 +14,3 @@ Additionally you will be contacted by our team for further review which should h
[6995]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6995
[ee]: https://about.gitlab.com/pricing/
-
diff --git a/doc/administration/issue_closing_pattern.md b/doc/administration/issue_closing_pattern.md
index e1bbabb2878..66ab3ef8d81 100644
--- a/doc/administration/issue_closing_pattern.md
+++ b/doc/administration/issue_closing_pattern.md
@@ -13,7 +13,7 @@ in the project's default branch.
In order to change the pattern you need to have access to the server that GitLab
is installed on.
-The default pattern can be located in [gitlab.yml.example] under the
+The default pattern can be located in [`gitlab.yml.example`] under the
"Automatic issue closing" section.
> **Tip:**
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 2b624f8de77..350cd5b7992 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -94,7 +94,7 @@ If you're enabling S3 in [GitLab HA](high_availability/README.md), you will need
### Object Storage Settings
-For source installations the following settings are nested under `artifacts:` and then `object_store:`. On omnibus installs they are prefixed by `artifacts_object_store_`.
+For source installations the following settings are nested under `artifacts:` and then `object_store:`. On Omnibus GitLab installs they are prefixed by `artifacts_object_store_`.
| Setting | Description | Default |
|---------|-------------|---------|
@@ -115,7 +115,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `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) |
diff --git a/doc/administration/logs.md b/doc/administration/logs.md
index 306d611f6bf..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.
@@ -334,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/merge_request_diffs.md b/doc/administration/merge_request_diffs.md
index 0b065082ded..d52d865cec5 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -90,7 +90,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `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) |
diff --git a/doc/administration/monitoring/gitlab_instance_administration_project/index.md b/doc/administration/monitoring/gitlab_instance_administration_project/index.md
index 8e33cea6217..d445b68721d 100644
--- a/doc/administration/monitoring/gitlab_instance_administration_project/index.md
+++ b/doc/administration/monitoring/gitlab_instance_administration_project/index.md
@@ -27,7 +27,7 @@ If that's not the case or if you have an external Prometheus instance or an HA s
you should
[configure it manually](../../../user/project/integrations/prometheus.md#manual-configuration-of-prometheus).
-## Taking action on Prometheus alerts **[ULTIMATE]**
+## Taking action on Prometheus alerts **(ULTIMATE)**
You can [add a webhook](../../../user/project/integrations/prometheus.md#external-prometheus-instances)
to the Prometheus config in order for GitLab to receive notifications of any alerts.
diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md
index 9f671e0db11..c32edb60f9d 100644
--- a/doc/administration/monitoring/performance/request_profiling.md
+++ b/doc/administration/monitoring/performance/request_profiling.md
@@ -5,7 +5,7 @@
1. Grab the profiling token from **Monitoring > Requests Profiles** admin page
(highlighted in a blue in the image below).
![Profile token](img/request_profiling_token.png)
-1. Pass the header `X-Profile-Token: <token>` and `X-Profile-Mode: <mode>`(where <mode> can be `execution` or `memory`) to the request you want to profile. You can use:
+1. Pass the header `X-Profile-Token: <token>` and `X-Profile-Mode: <mode>`(where `<mode>` can be `execution` or `memory`) to the request you want to profile. You can use:
- Browser extensions. For example, [ModHeader](https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj) Chrome extension.
- `curl`. For example, `curl --header 'X-Profile-Token: <token>' --header 'X-Profile-Mode: <mode>' https://gitlab.example.com/group/project`.
1. Once request is finished (which will take a little longer than usual), you can
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index ec26c0b2e7e..0605fb76e2f 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -19,38 +19,106 @@ it, the client IP needs to be [included in a whitelist][whitelist].
For Omnibus and Chart installations, these metrics are automatically enabled and collected as of [GitLab 9.4](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests/1702). For source installations or earlier versions, these metrics will need to be enabled manually and collected by a Prometheus server.
-## Unicorn Metrics available
+## Metrics available
The following metrics are available:
-| Metric | Type | Since | Description |
-|:--------------------------------- |:--------- |:----- |:----------- |
-| db_ping_timeout | Gauge | 9.4 | Whether or not the last database ping timed out |
-| db_ping_success | Gauge | 9.4 | Whether or not the last database ping succeeded |
-| db_ping_latency_seconds | Gauge | 9.4 | Round trip time of the database ping |
-| filesystem_access_latency_seconds | Gauge | 9.4 | Latency in accessing a specific filesystem |
-| filesystem_accessible | Gauge | 9.4 | Whether or not a specific filesystem is accessible |
-| filesystem_write_latency_seconds | Gauge | 9.4 | Write latency of a specific filesystem |
-| filesystem_writable | Gauge | 9.4 | Whether or not the filesystem is writable |
-| filesystem_read_latency_seconds | Gauge | 9.4 | Read latency of a specific filesystem |
-| filesystem_readable | Gauge | 9.4 | Whether or not the filesystem is readable |
-| gitlab_cache_misses_total | Counter | 10.2 | Cache read miss |
-| gitlab_cache_operation_duration_seconds | Histogram | 10.2 | Cache access time |
-| gitlab_cache_operations_total | Counter | 12.2 | Cache operations by controller/action |
-| http_requests_total | Counter | 9.4 | Rack request count |
-| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware |
-| pipelines_created_total | Counter | 9.4 | Counter of pipelines created |
-| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count |
-| redis_ping_timeout | Gauge | 9.4 | Whether or not the last redis ping timed out |
-| redis_ping_success | Gauge | 9.4 | Whether or not the last redis ping succeeded |
-| redis_ping_latency_seconds | Gauge | 9.4 | Round trip time of the redis ping |
-| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in |
-| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
-| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
-| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
-| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
-| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
-| unicorn_workers | Gauge | 12.0 | The number of Unicorn workers |
+| Metric | Type | Since | Description | Labels |
+|:-------------------------------------------------------------|:----------|-----------------------:|:----------------------------------------------------------------------------------------------------|:----------------------------------------------------|
+| gitlab_banzai_cached_render_real_duration_seconds | Histogram | 9.4 | Duration of rendering markdown into HTML when cached output exists | controller, action |
+| gitlab_banzai_cacheless_render_real_duration_seconds | Histogram | 9.4 | Duration of rendering markdown into HTML when cached outupt does not exist | controller, action |
+| gitlab_cache_misses_total | Counter | 10.2 | Cache read miss | controller, action |
+| gitlab_cache_operation_duration_seconds | Histogram | 10.2 | Cache access time | |
+| gitlab_cache_operations_total | Counter | 12.2 | Cache operations by controller/action | controller, action, operation |
+| gitlab_database_transaction_seconds | Histogram | 12.1 | Time spent in database transactions, in seconds | |
+| gitlab_method_call_duration_seconds | Histogram | 10.2 | Method calls real duration | controller, action, module, method |
+| gitlab_rails_queue_duration_seconds | Histogram | 9.4 | Measures latency between gitlab-workhorse forwarding a request to Rails | |
+| gitlab_sql_duration_seconds | Histogram | 10.2 | SQL execution time, excluding SCHEMA operations and BEGIN / COMMIT | |
+| gitlab_transaction_allocated_memory_bytes | Histogram | 10.2 | Allocated memory for all transactions (gitlab_transaction_* metrics) | |
+| gitlab_transaction_cache_<key>_count_total | Counter | 10.2 | Counter for total Rails cache calls (per key) | |
+| gitlab_transaction_cache_<key>_duration_total | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (per key) | |
+| gitlab_transaction_cache_count_total | Counter | 10.2 | Counter for total Rails cache calls (aggregate) | |
+| gitlab_transaction_cache_duration_total | Counter | 10.2 | Counter for total time (seconds) spent in Rails cache calls (aggregate) | |
+| gitlab_transaction_cache_read_hit_count_total | Counter | 10.2 | Counter for cache hits for Rails cache calls | controller, action |
+| gitlab_transaction_cache_read_miss_count_total | Counter | 10.2 | Counter for cache misses for Rails cache calls | controller, action |
+| gitlab_transaction_duration_seconds | Histogram | 10.2 | Duration for all transactions (gitlab_transaction_* metrics) | controller, action |
+| gitlab_transaction_event_build_found_total | Counter | 9.4 | Counter for build found for api /jobs/request | |
+| gitlab_transaction_event_build_invalid_total | Counter | 9.4 | Counter for build invalid due to concurrency conflict for api /jobs/request | |
+| gitlab_transaction_event_build_not_found_cached_total | Counter | 9.4 | Counter for cached response of build not found for api /jobs/request | |
+| gitlab_transaction_event_build_not_found_total | Counter | 9.4 | Counter for build not found for api /jobs/request | |
+| gitlab_transaction_event_change_default_branch_total | Counter | 9.4 | Counter when default branch is changed for any repository | |
+| gitlab_transaction_event_create_repository_total | Counter | 9.4 | Counter when any repository is created | |
+| gitlab_transaction_event_etag_caching_cache_hit_total | Counter | 9.4 | Counter for etag cache hit. | endpoint |
+| gitlab_transaction_event_etag_caching_header_missing_total | Counter | 9.4 | Counter for etag cache miss - header missing | endpoint |
+| gitlab_transaction_event_etag_caching_key_not_found_total | Counter | 9.4 | Counter for etag cache miss - key not found | endpoint |
+| gitlab_transaction_event_etag_caching_middleware_used_total | Counter | 9.4 | Counter for etag middleware accessed | endpoint |
+| gitlab_transaction_event_etag_caching_resource_changed_total | Counter | 9.4 | Counter for etag cache miss - resource changed | endpoint |
+| gitlab_transaction_event_fork_repository_total | Counter | 9.4 | Counter for repository forks (RepositoryForkWorker). Only incremented when source repository exists | |
+| gitlab_transaction_event_import_repository_total | Counter | 9.4 | Counter for repository imports (RepositoryImportWorker) | |
+| gitlab_transaction_event_push_branch_total | Counter | 9.4 | Counter for all branch pushes | |
+| gitlab_transaction_event_push_commit_total | Counter | 9.4 | Counter for commits | branch |
+| gitlab_transaction_event_push_tag_total | Counter | 9.4 | Counter for tag pushes | |
+| gitlab_transaction_event_rails_exception_total | Counter | 9.4 | Counter for number of rails exceptions | |
+| gitlab_transaction_event_receive_email_total | Counter | 9.4 | Counter for recieved emails | handler |
+| gitlab_transaction_event_remote_mirrors_failed_total | Counter | 10.8 | Counter for failed remote mirrors | |
+| gitlab_transaction_event_remote_mirrors_finished_total | Counter | 10.8 | Counter for finished remote mirrors | |
+| gitlab_transaction_event_remote_mirrors_running_total | Counter | 10.8 | Counter for running remote mirrors | |
+| gitlab_transaction_event_remove_branch_total | Counter | 9.4 | Counter when a branch is removed for any repository | |
+| gitlab_transaction_event_remove_repository_total | Counter | 9.4 | Counter when a repository is removed | |
+| gitlab_transaction_event_remove_tag_total | Counter | 9.4 | Counter when a tag is remove for any repository | |
+| gitlab_transaction_event_sidekiq_exception_total | Counter | 9.4 | Counter of sidekiq exceptions | |
+| gitlab_transaction_event_stuck_import_jobs_total | Counter | 9.4 | Count of stuck import jobs | projects_without_jid_count, projects_with_jid_count |
+| gitlab_transaction_event_update_build_total | Counter | 9.4 | Counter for update build for api /jobs/request/:id | |
+| gitlab_transaction_new_redis_connections_total | Counter | 9.4 | Counter for new redis connections | |
+| gitlab_transaction_queue_duration_total | Counter | 9.4 | Duration jobs were enqueued before processing | |
+| gitlab_transaction_rails_queue_duration_total | Counter | 9.4 | Measures latency between gitlab-workhorse forwarding a request to Rails | controller, action |
+| gitlab_transaction_view_duration_total | Counter | 9.4 | Duration for views | controller, action, view |
+| gitlab_view_rendering_duration_seconds | Histogram | 10.2 | Duration for views (histogram) | controller, action, view |
+| http_requests_total | Counter | 9.4 | Rack request count | method |
+| http_request_duration_seconds | Histogram | 9.4 | HTTP response time from rack middleware | method, status |
+| pipelines_created_total | Counter | 9.4 | Counter of pipelines created | |
+| rack_uncaught_errors_total | Counter | 9.4 | Rack connections handling uncaught errors count | |
+| user_session_logins_total | Counter | 9.4 | Counter of how many users have logged in | |
+| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file | |
+| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login | |
+| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login | |
+
+## Metrics controlled by a feature flag
+
+The following metrics can be controlled by feature flags:
+
+| Metric | Feature Flag |
+|:-------------------------------------------------------------|:-----------------------------------------------------------------|
+| gitlab_method_call_duration_seconds | prometheus_metrics_method_instrumentation |
+| gitlab_transaction_allocated_memory_bytes | prometheus_metrics_transaction_allocated_memory |
+| gitlab_transaction_event_build_found_total | prometheus_transaction_event_build_found_total |
+| gitlab_transaction_event_build_invalid_total | prometheus_transaction_event_build_invalid_total |
+| gitlab_transaction_event_build_not_found_cached_total | prometheus_transaction_event_build_not_found_cached_total |
+| gitlab_transaction_event_build_not_found_total | prometheus_transaction_event_build_not_found_total |
+| gitlab_transaction_event_change_default_branch_total | prometheus_transaction_event_change_default_branch_total |
+| gitlab_transaction_event_create_repository_total | prometheus_transaction_event_create_repository_total |
+| gitlab_transaction_event_etag_caching_cache_hit_total | prometheus_transaction_event_etag_caching_cache_hit_total |
+| gitlab_transaction_event_etag_caching_header_missing_total | prometheus_transaction_event_etag_caching_header_missing_total |
+| gitlab_transaction_event_etag_caching_key_not_found_total | prometheus_transaction_event_etag_caching_key_not_found_total |
+| gitlab_transaction_event_etag_caching_middleware_used_total | prometheus_transaction_event_etag_caching_middleware_used_total |
+| gitlab_transaction_event_etag_caching_resource_changed_total | prometheus_transaction_event_etag_caching_resource_changed_total |
+| gitlab_transaction_event_fork_repository_total | prometheus_transaction_event_fork_repository_total |
+| gitlab_transaction_event_import_repository_total | prometheus_transaction_event_import_repository_total |
+| gitlab_transaction_event_push_branch_total | prometheus_transaction_event_push_branch_total |
+| gitlab_transaction_event_push_commit_total | prometheus_transaction_event_push_commit_total |
+| gitlab_transaction_event_push_tag_total | prometheus_transaction_event_push_tag_total |
+| gitlab_transaction_event_rails_exception_total | prometheus_transaction_event_rails_exception_total |
+| gitlab_transaction_event_receive_email_total | prometheus_transaction_event_receive_email_total |
+| gitlab_transaction_event_remote_mirrors_failed_total | prometheus_transaction_event_remote_mirrors_failed_total |
+| gitlab_transaction_event_remote_mirrors_finished_total | prometheus_transaction_event_remote_mirrors_finished_total |
+| gitlab_transaction_event_remote_mirrors_running_total | prometheus_transaction_event_remote_mirrors_running_total |
+| gitlab_transaction_event_remove_branch_total | prometheus_transaction_event_remove_branch_total |
+| gitlab_transaction_event_remove_repository_total | prometheus_transaction_event_remove_repository_total |
+| gitlab_transaction_event_remove_tag_total | prometheus_transaction_event_remove_tag_total |
+| gitlab_transaction_event_sidekiq_exception_total | prometheus_transaction_event_sidekiq_exception_total |
+| gitlab_transaction_event_stuck_import_jobs_total | prometheus_transaction_event_stuck_import_jobs_total |
+| gitlab_transaction_event_update_build_total | prometheus_transaction_event_update_build_total |
+| gitlab_view_rendering_duration_seconds | prometheus_metrics_view_instrumentation |
## Sidekiq Metrics available for Geo **(PREMIUM)**
@@ -99,17 +167,27 @@ Some basic Ruby runtime metrics are available:
| Metric | Type | Since | Description |
|:-------------------------------------- |:--------- |:----- |:----------- |
-| ruby_gc_duration_seconds_total | Counter | 11.1 | Time spent by Ruby in GC |
+| ruby_gc_duration_seconds | Counter | 11.1 | Time spent by Ruby in GC |
| ruby_gc_stat_... | Gauge | 11.1 | Various metrics from [GC.stat] |
| ruby_file_descriptors | Gauge | 11.1 | File descriptors per process |
| ruby_memory_bytes | Gauge | 11.1 | Memory usage by process |
-| ruby_sampler_duration_seconds_total | Counter | 11.1 | Time spent collecting stats |
+| ruby_sampler_duration_seconds | Counter | 11.1 | Time spent collecting stats |
| ruby_process_cpu_seconds_total | Gauge | 12.0 | Total amount of CPU time per process |
| ruby_process_max_fds | Gauge | 12.0 | Maximum number of open file descriptors per process |
| ruby_process_resident_memory_bytes | Gauge | 12.0 | Memory usage by process, measured in bytes |
| ruby_process_start_time_seconds | Gauge | 12.0 | UNIX timestamp of process start time |
-[GC.stat]: https://ruby-doc.org/core-2.3.0/GC.html#method-c-stat
+[GC.stat]: https://ruby-doc.org/core-2.6.3/GC.html#method-c-stat
+
+## Unicorn Metrics
+
+Unicorn specific metrics, when Unicorn is used.
+
+| Metric | Type | Since | Description |
+|:---------------------------|:------|:------|:---------------------------------------------------|
+| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
+| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
+| unicorn_workers | Gauge | 12.0 | The number of Unicorn workers |
## Puma Metrics **(EXPERIMENTAL)**
@@ -126,7 +204,6 @@ When Puma is used instead of Unicorn, following metrics are available:
| puma_pool_capacity | Gauge | 12.0 | Number of requests the worker is capable of taking right now |
| puma_max_threads | Gauge | 12.0 | Maximum number of worker threads |
| puma_idle_threads | Gauge | 12.0 | Number of spawned threads which are not processing a request |
-| rack_state_total | Gauge | 12.0 | Number of requests in a given rack state |
| puma_killer_terminations_total | Gauge | 12.0 | Number of workers terminated by PumaWorkerKiller |
## Metrics shared directory
diff --git a/doc/administration/packages.md b/doc/administration/packages.md
index c0f8777a8c0..1628b6d6f91 100644
--- a/doc/administration/packages.md
+++ b/doc/administration/packages.md
@@ -55,7 +55,7 @@ local location or even use object storage.
The packages for Omnibus GitLab installations are stored under
`/var/opt/gitlab/gitlab-rails/shared/packages/` and for source
-installations under `shared/packages/` (relative to the git homedir).
+installations under `shared/packages/` (relative to the Git homedir).
To change the local storage path:
**Omnibus GitLab installations**
diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md
index 4cf3c607dae..92a4d56ca63 100644
--- a/doc/administration/plugins.md
+++ b/doc/administration/plugins.md
@@ -39,7 +39,7 @@ Follow the steps below to set up a custom hook:
1. Inside the `plugins` directory, create a file with a name of your choice,
without spaces or special characters.
-1. Make the hook file executable and make sure it's owned by the git user.
+1. Make the hook file executable and make sure it's owned by the Git user.
1. Write the code to make the plugin function as expected. That can be
in any language, and ensure the 'shebang' at the top properly reflects the
language type. For example, if the script is in Ruby the shebang will
@@ -112,4 +112,4 @@ Validating plugins from /plugins directory
[system hooks]: ../system_hooks/system_hooks.md
[webhooks]: ../user/project/integrations/webhooks.md
-[highly available]: ./high_availability/README.md \ No newline at end of file
+[highly available]: ./high_availability/README.md
diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md
index 406f7e8a034..56cd23b2eb8 100644
--- a/doc/administration/reply_by_email_postfix_setup.md
+++ b/doc/administration/reply_by_email_postfix_setup.md
@@ -18,7 +18,7 @@ The instructions make the assumption that you will be using the email address `i
sudo apt-get install postfix
```
- When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches gitlab.example.com`.
+ When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`.
1. Install the `mailutils` package.
@@ -331,7 +331,7 @@ Courier, which we will install later to add IMAP authentication, requires mailbo
a logout
```
-## Done!
+## Done
If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [incoming email] guide to configure GitLab.
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 05a7cb006a3..ab911c1cf0e 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -3,7 +3,7 @@
> [Introduced][ce-3232] in GitLab 8.7. It is OFF by default because it still
causes too many false alarms.
-Git has a built-in mechanism, [git fsck][git-fsck], to verify the
+Git has a built-in mechanism, [`git fsck`][git-fsck], to verify the
integrity of all data committed to a repository. GitLab administrators
can trigger such a check for a project via the project page under the
admin panel. The checks run asynchronously so it may take a few minutes
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 1669f8b128c..142bcc65561 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -118,7 +118,7 @@ If you do have any existing integration, you may want to do a small rollout firs
to validate. You can do so by specifying a range with the operation.
This is an example of how to limit the rollout to Project IDs 50 to 100, running in
-an Omnibus Gitlab installation:
+an Omnibus GitLab installation:
```bash
sudo gitlab-rake gitlab:storage:migrate_to_hashed ID_FROM=50 ID_TO=100
@@ -139,7 +139,7 @@ To schedule a complete rollback, see the
[rake task documentation for storage rollback](raketasks/storage.md#rollback-from-hashed-storage-to-legacy-storage) for instructions.
The rollback task also supports specifying a range of Project IDs. Here is an example
-of limiting the rollout to Project IDs 50 to 100, in an Omnibus Gitlab installation:
+of limiting the rollout to Project IDs 50 to 100, in an Omnibus GitLab installation:
```bash
sudo gitlab-rake gitlab:storage:rollback_to_legacy ID_FROM=50 ID_TO=100
@@ -185,7 +185,7 @@ CI Artifacts are S3 compatible since **9.4** (GitLab Premium), and available in
##### LFS Objects
-LFS Objects implements a similar storage pattern using 2 chars, 2 level folders, following git own implementation:
+LFS Objects implements a similar storage pattern using 2 chars, 2 level folders, following Git own implementation:
```ruby
"shared/lfs-objects/#{oid[0..1}/#{oid[2..3]}/#{oid[4..-1]}"
diff --git a/doc/administration/smime_signing_email.md b/doc/administration/smime_signing_email.md
new file mode 100644
index 00000000000..9f719088f25
--- /dev/null
+++ b/doc/administration/smime_signing_email.md
@@ -0,0 +1,49 @@
+# Signing outgoing email with S/MIME
+
+Notification emails sent by Gitlab can be signed with S/MIME for improved
+security.
+
+> **Note:**
+Please be aware that S/MIME certificates and TLS/SSL certificates are not the
+same and are used for different purposes: TLS creates a secure channel, whereas
+S/MIME signs and/or encrypts the message itself
+
+## Enable S/MIME signing
+
+This setting must be explicitly enabled and a single pair of key and certificate
+files must be provided in `gitlab.rb` or `gitlab.yml` if you are using Omnibus
+GitLab or installed GitLab from source respectively:
+
+```yaml
+email_smime:
+ enabled: true
+ key_file: /etc/pki/smime/private/gitlab.key
+ cert_file: /etc/pki/smime/certs/gitlab.crt
+```
+
+- Both files must be provided PEM-encoded.
+- The key file must be unencrypted so that Gitlab can read it without user
+ intervention.
+
+NOTE: **Note:** Be mindful of the access levels for your private keys and visibility to
+third parties.
+
+### How to convert S/MIME PKCS#12 / PFX format to PEM encoding
+
+Typically S/MIME certificates are handled in binary PKCS#12 format (`.pfx` or `.p12`
+extensions), which contain the following in a single encrypted file:
+
+- Server certificate
+- Intermediate certificates (if any)
+- Private key
+
+In order to export the required files in PEM encoding from the PKCS#12 file,
+the `openssl` command can be used:
+
+```bash
+#-- Extract private key in PEM encoding (no password, unencrypted)
+$ openssl pkcs12 -in gitlab.p12 -nocerts -nodes -out gitlab.key
+
+#-- Extract certificates in PEM encoding (full certs chain including CA)
+$ openssl pkcs12 -in gitlab.p12 -nokeys -out gitlab.crt
+```
diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
index 95cdb1508fa..260af333e8e 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
@@ -72,7 +72,7 @@ and they will assist you with any issues you are having.
This is the principle of Kubernetes, read [Twelve-factor app](https://12factor.net/)
for details.
-## Gitlab-specific kubernetes information
+## GitLab-specific kubernetes information
- Minimal config that can be used to test a Kubernetes helm chart can be found
[here](https://gitlab.com/charts/gitlab/issues/620).
@@ -83,12 +83,22 @@ 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`,
- but a number of third-party tools can be used to do it:
+- Tail and follow all pods that share a label (in this case, `unicorn`):
- - [Kubetail](https://github.com/johanhaleby/kubetail)
- - [kail: kubernetes tail](https://github.com/boz/kail)
- - [stern](https://github.com/wercker/stern)
+ ```bash
+ # all containers in the unicorn pods
+ kubectl logs -f -l app=unicorn --all-containers=true --max-log-requests=50
+
+ # only the unicorn containers in all unicorn pods
+ kubectl logs -f -l app=unicorn -c unicorn --max-log-requests=50
+ ```
+
+- One can stream logs from all containers at once, similar to the Omnibus
+ command `gitlab-ctl tail`:
+
+ ```bash
+ kubectl logs -f -l release=gitlab --all-containers=true --max-log-requests=100
+ ```
- Check all events in the `gitlab` namespace (the namespace name can be different if you
specified a different one when deploying the helm chart):
@@ -131,7 +141,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 +152,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 +189,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 +216,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 +229,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/administration/troubleshooting/sidekiq.md b/doc/administration/troubleshooting/sidekiq.md
index 7067958ecb4..9b016c64e29 100644
--- a/doc/administration/troubleshooting/sidekiq.md
+++ b/doc/administration/troubleshooting/sidekiq.md
@@ -169,3 +169,121 @@ The PostgreSQL wiki has details on the query you can run to see blocking
queries. The query is different based on PostgreSQL version. See
[Lock Monitoring](https://wiki.postgresql.org/wiki/Lock_Monitoring) for
the query details.
+
+## Managing Sidekiq queues
+
+It is possible to use [Sidekiq API](https://github.com/mperham/sidekiq/wiki/API)
+to perform a number of troubleshoting on Sidekiq.
+
+These are the administrative commands and it should only be used if currently
+admin interface is not suitable due to scale of installation.
+
+All this commands should be run using `gitlab-rails console`.
+
+### View the queue size
+
+```ruby
+Sidekiq::Queue.new("pipeline_processing:build_queue").size
+```
+
+### Enumerate all enqueued jobs
+
+```ruby
+queue = Sidekiq::Queue.new("chaos:chaos_sleep")
+queue.each do |job|
+ # job.klass # => 'MyWorker'
+ # job.args # => [1, 2, 3]
+ # job.jid # => jid
+ # job.queue # => chaos:chaos_sleep
+ # job["retry"] # => 3
+ # job.item # => {
+ # "class"=>"Chaos::SleepWorker",
+ # "args"=>[1000],
+ # "retry"=>3,
+ # "queue"=>"chaos:chaos_sleep",
+ # "backtrace"=>true,
+ # "queue_namespace"=>"chaos",
+ # "jid"=>"39bc482b823cceaf07213523",
+ # "created_at"=>1566317076.266069,
+ # "correlation_id"=>"c323b832-a857-4858-b695-672de6f0e1af",
+ # "enqueued_at"=>1566317076.26761},
+ # }
+
+ # job.delete if job.jid == 'abcdef1234567890'
+end
+```
+
+### Enumerate currently running jobs
+
+```ruby
+workers = Sidekiq::Workers.new
+workers.each do |process_id, thread_id, work|
+ # process_id is a unique identifier per Sidekiq process
+ # thread_id is a unique identifier per thread
+ # work is a Hash which looks like:
+ # {"queue"=>"chaos:chaos_sleep",
+ # "payload"=>
+ # { "class"=>"Chaos::SleepWorker",
+ # "args"=>[1000],
+ # "retry"=>3,
+ # "queue"=>"chaos:chaos_sleep",
+ # "backtrace"=>true,
+ # "queue_namespace"=>"chaos",
+ # "jid"=>"b2a31e3eac7b1a99ff235869",
+ # "created_at"=>1566316974.9215662,
+ # "correlation_id"=>"e484fb26-7576-45f9-bf21-b99389e1c53c",
+ # "enqueued_at"=>1566316974.9229589},
+ # "run_at"=>1566316974}],
+end
+```
+
+### Remove sidekiq jobs for given parameters (destructive)
+
+```ruby
+# for jobs like this:
+# RepositoryImportWorker.new.perform_async(100)
+id_list = [100]
+
+queue = Sidekiq::Queue.new('repository_import')
+queue.each do |job|
+ job.delete if id_list.include?(job.args[0])
+end
+```
+
+### Remove specific job ID (destructive)
+
+```ruby
+queue = Sidekiq::Queue.new('repository_import')
+queue.each do |job|
+ job.delete if job.jid == 'my-job-id'
+end
+```
+
+## Canceling running jobs (destructive)
+
+> Introduced in GitLab 12.3.
+
+This is highly risky operation and use it as last resort.
+Doing that might result in data corruption, as the job
+is interrupted mid-execution and it is not guaranteed
+that proper rollback of transactions is implemented.
+
+```ruby
+Gitlab::SidekiqMonitor.cancel_job('job-id')
+```
+
+> This requires the Sidekiq to be run with `SIDEKIQ_MONITOR_WORKER=1`
+> environment variable.
+
+To perform of the interrupt we use `Thread.raise` which
+has number of drawbacks, as mentioned in [Why Ruby’s Timeout is dangerous (and Thread.raise is terrifying)](https://jvns.ca/blog/2015/11/27/why-rubys-timeout-is-dangerous-and-thread-dot-raise-is-terrifying/):
+
+> This is where the implications get interesting, and terrifying. This means that an exception can get raised:
+>
+> * during a network request (ok, as long as the surrounding code is prepared to catch Timeout::Error)
+> * during the cleanup for the network request
+> * during a rescue block
+> * while creating an object to save to the database afterwards
+> * in any of your code, regardless of whether it could have possibly raised an exception before
+>
+> Nobody writes code to defend against an exception being raised on literally any line. That’s not even possible. So Thread.raise is basically like a sneak attack on your code that could result in almost anything. It would probably be okay if it were pure-functional code that did not modify any state. But this is Ruby, so that’s unlikely :)
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 99f1c386183..a4bed72b965 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -57,7 +57,7 @@ This configuration relies on valid AWS credentials to be configured already.
## Object Storage Settings
-For source installations the following settings are nested under `uploads:` and then `object_store:`. On omnibus installs they are prefixed by `uploads_object_store_`.
+For source installations the following settings are nested under `uploads:` and then `object_store:`. On Omnibus GitLab installs they are prefixed by `uploads_object_store_`.
| Setting | Description | Default |
|---------|-------------|---------|
@@ -78,7 +78,7 @@ The connection settings match those provided by [Fog](https://github.com/fog), a
| `aws_access_key_id` | AWS credentials, or compatible | |
| `aws_secret_access_key` | AWS credentials, or compatible | |
| `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) |
@@ -220,7 +220,7 @@ _The uploads are stored by default in
openstack_temp_url_key: OPENSTACK_TEMP_URL_KEY
openstack_auth_url: 'https://auth.cloud.ovh.net/v2.0/'
openstack_region: DE1
- openstack_tenant: 'TENANT_ID'
+ openstack_tenant: 'TENANT_ID'
```
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
diff --git a/doc/api/README.md b/doc/api/README.md
index b7ee710b87a..33394d46a2d 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/api_resources.md b/doc/api/api_resources.md
index b32f11464ef..9af5430f1c8 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -67,7 +67,7 @@ The following API resources are available in the project context:
| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
| [Services](services.md) | `/projects/:id/services` |
| [Tags](tags.md) | `/projects/:id/repository/tags` |
-| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities` (also available for groups) |
+| [Vulnerabilities](vulnerabilities.md) **(ULTIMATE)** | `/projects/:id/vulnerabilities`
| [Wikis](wikis.md) | `/projects/:id/wikis` |
## Group resources
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/dependencies.md b/doc/api/dependencies.md
index 015ffbe60f6..5296d4e316f 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -5,20 +5,21 @@ This API is in an alpha stage and considered unstable.
The response payload may be subject to change or breakage
across GitLab releases.
-Every call to this endpoint requires authentication. To perform this call, user should be authorized to read
-[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard).
+Every call to this endpoint requires authentication. To perform this call, user should be authorized to read repository.
+To see vulnerabilities in response, user should be authorized to read
+[Project Security Dashboard](../user/application_security/security_dashboard/index.md#project-security-dashboard).
## List project dependencies
-Get a list of project dependencies. This API partially mirroring
+Get a list of project dependencies. This API partially mirroring
[Dependency List](../user/application_security/dependency_list/index.md) feature.
This list can be generated only for [languages and package managers](../user/application_security/dependency_scanning/index.md#supported-languages-and-package-managers)
-supported by Gemnasium.
+supported by Gemnasium.
```
GET /projects/:id/dependencies
-GET /projects/:id/vulnerabilities?package_manager=maven
-GET /projects/:id/vulnerabilities?package_manager=yarn,bundler
+GET /projects/:id/dependencies?package_manager=maven
+GET /projects/:id/dependencies?package_manager=yarn,bundler
```
| Attribute | Type | Required | Description |
@@ -38,13 +39,18 @@ Example response:
"name": "rails",
"version": "5.0.1",
"package_manager": "bundler",
- "dependency_file_path": "Gemfile.lock"
+ "dependency_file_path": "Gemfile.lock",
+ "vulnerabilities": [{
+ "name": "DDoS",
+ "severity": "unknown"
+ }]
},
{
"name": "hanami",
"version": "1.3.1",
"package_manager": "bundler",
- "dependency_file_path": "Gemfile.lock"
+ "dependency_file_path": "Gemfile.lock",
+ "vulnerabilities": []
}
]
```
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 94351e1a300..df8cf06fc4a 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -203,6 +203,7 @@ Example response:
"created_at" : "2015-08-29T12:44:31.550Z"
}
```
+
## Adding deploy keys to multiple projects
If you want to easily add the same deploy key to multiple projects in the same
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index b4a2d0b15f6..12dbba78291 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -160,7 +160,7 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab
Adds a new note to the thread. This can also [create a thread from a single comment](../user/discussions/#start-a-thread-by-replying-to-a-standard-comment).
**WARNING**
-Notes can be added to other items than comments (system notes, etc.) making them threads.
+Notes can be added to other items than comments (system notes, etc.) making them threads.
```
POST /projects/:id/issues/:issue_iid/discussions/:discussion_id/notes
diff --git a/doc/api/epics.md b/doc/api/epics.md
index 3036b3c2364..87ae0c48199 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -65,6 +65,8 @@ Example response:
"title": "Accusamus iste et ullam ratione voluptatem omnis debitis dolor est.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened",
+ "web_edit_url": "http://localhost:3001/groups/test/-/epics/4",
+ "reference": "&4",
"author": {
"id": 10,
"name": "Lu Mayer",
@@ -118,6 +120,8 @@ Example response:
"title": "Ea cupiditate dolores ut vero consequatur quasi veniam voluptatem et non.",
"description": "Molestias dolorem eos vitae expedita impedit necessitatibus quo voluptatum.",
"state": "opened",
+ "web_edit_url": "http://localhost:3001/groups/test/-/epics/5",
+ "reference": "&5",
"author":{
"id": 7,
"name": "Pamella Huel",
@@ -182,6 +186,8 @@ Example response:
"title": "Epic",
"description": "Epic description",
"state": "opened",
+ "web_edit_url": "http://localhost:3001/groups/test/-/epics/6",
+ "reference": "&6",
"author": {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
@@ -247,6 +253,8 @@ Example response:
"title": "New Title",
"description": "Epic description",
"state": "opened",
+ "web_edit_url": "http://localhost:3001/groups/test/-/epics/6",
+ "reference": "&6",
"author": {
"name" : "Alexandra Bashirian",
"avatar_url" : null,
diff --git a/doc/api/features.md b/doc/api/features.md
index 6ecd4ec14b9..e8d0c7c942b 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -60,8 +60,8 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
-| `group` | string | no | A GitLab group's path, for example 'gitlab-org' |
-| `project` | string | no | A projects path, for example 'gitlab-org/gitlab-ce' |
+| `group` | string | no | A GitLab group's path, for example `gitlab-org` |
+| `project` | string | no | A projects path, for example `gitlab-org/gitlab-ce` |
Note that you can enable or disable a feature for a `feature_group`, a `user`,
a `group`, and a `project` in a single API call.
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index d0b33ab467f..5eba7f038ed 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -111,7 +111,7 @@ PUT /geo_nodes/:id
|-----------------------------|---------|-----------|---------------------------------------------------------------------------|
| `id` | integer | yes | The ID of the Geo node. |
| `enabled` | boolean | no | Flag indicating if the Geo node is enabled. |
-| `name` | string | yes | The unique identifier for the Geo node. Must match `geo_node_name` if it is set in gitlab.rb, otherwise it must match `external_url`. |
+| `name` | string | yes | The unique identifier for the Geo node. Must match `geo_node_name` if it is set in `gitlab.rb`, otherwise it must match `external_url`. |
| `url` | string | yes | The user-facing URL of the Geo node. |
| `internal_url` | string | no | The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set.|
| `files_max_capacity` | integer | no | Control the maximum concurrency of LFS/attachment backfill for this secondary node. |
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 2d3bec4ff67..d99a4c37d72 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -109,6 +109,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `visibility` | String | |
| `lfsEnabled` | Boolean | |
| `requestAccessEnabled` | Boolean | |
+| `rootStorageStatistics` | RootStorageStatistics | The aggregated storage statistics. Only available if the namespace has no parent |
| `userPermissions` | GroupPermissions! | Permissions for the current user on the resource |
| `webUrl` | String! | |
| `avatarUrl` | String | |
@@ -453,6 +454,17 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
| `exists` | Boolean! | |
| `tree` | Tree | |
+### RootStorageStatistics
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `storageSize` | Int! | The total storage in Bytes |
+| `repositorySize` | Int! | The git repository size in Bytes |
+| `lfsObjectsSize` | Int! | The LFS objects size in Bytes |
+| `buildArtifactsSize` | Int! | The CI artifacts size in Bytes |
+| `packagesSize` | Int! | The packages size in Bytes |
+| `wikiSize` | Int! | The wiki size in Bytes |
+
### Submodule
| Name | Type | Description |
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..8313dd2c3bd 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -49,7 +49,7 @@ GET /issues?confidential=true
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
| `iids[]` | integer array | no | Return only the issues having the given `iid` |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search issues against their `title` and `description` |
| `in` | string | no | Modify the scope of the `search` attribute. `title`, `description`, or a string joining them with comma. Default is `title,description` |
@@ -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
@@ -199,7 +198,7 @@ GET /groups/:id/issues?confidential=true
| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search group issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
@@ -348,7 +347,7 @@ GET /projects/:id/issues?confidential=true
| `assignee_username` | string array | no | Return issues assigned to the given `username`. Similar to `assignee_id` and mutually exclusive with `assignee_id`. In CE version `assignee_username` array should only contain a single value or an invalid param error will be returned otherwise. |
| `my_reaction_emoji` | string | no | Return issues reacted by the authenticated user by the given `emoji`. `None` returns issues not given a reaction. `Any` returns issues given at least one reaction. _([Introduced][ce-14016] in GitLab 10.0)_ |
| `weight` **(STARTER)** | integer | no | Return issues with the specified `weight`. `None` returns issues with no weight assigned. `Any` returns issues with a weight assigned. |
-| `order_by` | string | no | Return issues ordered by `created_at` or `updated_at` fields. Default is `created_at` |
+| `order_by` | string | no | Return issues ordered by `created_at`, `updated_at`, `priority`, `due_date`, `relative_position`, `label_priority`, `milestone_due`, `popularity`, `weight` fields. Default is `created_at` |
| `sort` | string | no | Return issues sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Search project issues against their `title` and `description` |
| `created_after` | datetime | no | Return issues created on or after the given time |
@@ -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/labels.md b/doc/api/labels.md
index 5db0edcf14d..fde1d861cf6 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -137,8 +137,9 @@ DELETE /projects/:id/labels
| Attribute | Type | Required | Description |
| --------- | ------- | -------- | --------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the label |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `label_id` | integer | yes (or `name`) | The id of the existing label |
+| `name` | string | yes (or `label_id`) | The name of the existing label |
```bash
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/labels?name=bug"
@@ -156,7 +157,8 @@ PUT /projects/:id/labels
| Attribute | Type | Required | Description |
| --------------- | ------- | --------------------------------- | ------------------------------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `name` | string | yes | The name of the existing label |
+| `label_id` | integer | yes (or `name`) | The id of the existing label |
+| `name` | string | yes (or `label_id`) | The name of the existing label |
| `new_name` | string | yes if `color` is not provided | The new name of the label |
| `color` | string | yes if `new_name` is not provided | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
| `description` | string | no | The new description of the label |
diff --git a/doc/api/lint.md b/doc/api/lint.md
index 79f5e629c7f..dacd3f4c493 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -1,4 +1,4 @@
-# Validate the .gitlab-ci.yml (API)
+# Validate the `.gitlab-ci.yml` (API)
> [Introduced][ce-5953] in GitLab 8.12.
@@ -10,7 +10,7 @@ POST /ci/lint
| Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
-| `content` | string | yes | the .gitlab-ci.yaml content|
+| `content` | string | yes | the `.gitlab-ci.yaml` content|
```bash
curl --header "Content-Type: application/json" https://gitlab.example.com/api/v4/ci/lint --data '{"content": "{ \"image\": \"ruby:2.6\", \"services\": [\"postgres\"], \"before_script\": [\"bundle install\", \"bundle exec rake db:create\"], \"variables\": {\"DB_NAME\": \"postgres\"}, \"types\": [\"test\", \"deploy\", \"notify\"], \"rspec\": { \"script\": \"rake spec\", \"tags\": [\"ruby\", \"postgres\"], \"only\": [\"branches\"]}}"}'
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/api/projects.md b/doc/api/projects.md
index 70df44ec0fd..9f392418153 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -855,6 +855,7 @@ GET /projects/:id/users
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `search` | string | no | Search for specific users |
+| `skip_users` | array[int] | no | Filter out users with the specified IDs |
```json
[
@@ -2036,13 +2037,13 @@ Read more in the [Project Badges](project_badges.md) documentation.
The non-default [issue and merge request description templates](../user/project/description_templates.md) are managed inside the project's repository. So you can manage them via the API through the [Repositories API](repositories.md) and the [Repository Files API](repository_files.md).
-## Download snapshot of a git repository
+## Download snapshot of a Git repository
> Introduced in GitLab 10.7
This endpoint may only be accessed by an administrative user.
-Download a snapshot of the project (or wiki, if requested) git repository. This
+Download a snapshot of the project (or wiki, if requested) Git repository. This
snapshot is always in uncompressed [tar](https://en.wikipedia.org/wiki/Tar_(computing))
format.
diff --git a/doc/api/protected_branches.md b/doc/api/protected_branches.md
index 9309306ba05..ffb4a70168b 100644
--- a/doc/api/protected_branches.md
+++ b/doc/api/protected_branches.md
@@ -185,6 +185,7 @@ Example response:
{
"access_level": 30,
"access_level_description": "Developers + Maintainers"
+ }
],
"unprotect_access_levels": [
{
@@ -217,6 +218,7 @@ Example response:
"user_id": null,
"group_id": null,
"access_level_description": "Developers + Maintainers"
+ }
],
"unprotect_access_levels": [
{
@@ -232,7 +234,7 @@ Example response:
### Example with user / group level access **(STARTER)**
Elements in the `allowed_to_push` / `allowed_to_merge` / `allowed_to_unprotect` array should take the
-form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`. Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md). These access levels allow [more granular control over protected branch access](../user/project/protected_branches.md#restricting-push-and-merge-access-to-certain-users-starter) and were [added to the API in ][ee-3516] in GitLab 10.3 EE.
+form `{user_id: integer}`, `{group_id: integer}` or `{access_level: integer}`. Each user must have access to the project and each group must [have this project shared](../user/project/members/share_project_with_groups.md). These access levels allow [more granular control over protected branch access](../user/project/protected_branches.md#restricting-push-and-merge-access-to-certain-users-starter) and were [added to the API in](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3516) in GitLab 10.3 EE.
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" 'https://gitlab.example.com/api/v4/projects/5/protected_branches?name=*-stable&allowed_to_push%5B%5D%5Buser_id%5D=1'
@@ -242,29 +244,29 @@ Example response:
```json
{
- "name":"*-stable",
+ "name": "*-stable",
"push_access_levels": [
{
- "access_level":null,
- "user_id":1,
- "group_id":null,
- "access_level_description":"Administrator"
+ "access_level": null,
+ "user_id": 1,
+ "group_id": null,
+ "access_level_description": "Administrator"
}
],
"merge_access_levels": [
{
- "access_level":40,
- "user_id":null,
- "group_id":null,
- "access_level_description":"Maintainers"
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
}
],
"unprotect_access_levels": [
{
- "access_level":40,
- "user_id":null,
- "group_id":null,
- "access_level_description":"Maintainers"
+ "access_level": 40,
+ "user_id": null,
+ "group_id": null,
+ "access_level_description": "Maintainers"
}
]
}
@@ -286,5 +288,3 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" 'https://git
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
| `name` | string | yes | The name of the branch |
-
-[ee-3516]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3516 "ProtectedBranches API handles per user/group granularity"
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index b292c9dd7de..513dc996c91 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -246,7 +246,7 @@ error message. Possible causes for a failed commit include:
user tried to make an empty commit;
- the branch was updated by a Git push while the file edit was in progress.
-Currently gitlab-shell has a boolean return code, preventing GitLab from specifying the error.
+Currently GitLab Shell has a boolean return code, preventing GitLab from specifying the error.
## Delete existing file in repository
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 248d19461f6..710b63c9a2f 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -232,7 +232,7 @@ are listed in the descriptions of the relevant settings.
| `file_template_project_id` | integer | no | **(PREMIUM)** The ID of a project to load custom file templates from |
| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday, `1` for Monday, and `6` for Saturday. |
| `geo_status_timeout` | integer | no | **(PREMIUM)** The amount of seconds after which a request to get a secondary node status will time out. |
-| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
+| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for Git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
| `gravatar_enabled` | boolean | no | Enable Gravatar. |
@@ -244,7 +244,7 @@ are listed in the descriptions of the relevant settings.
| `hide_third_party_offers` | boolean | no | Do not display offers from third parties within GitLab. |
| `home_page_url` | string | no | Redirect to this URL when not logged in. |
| `housekeeping_bitmaps_enabled` | boolean | required by: `housekeeping_enabled` | Enable Git pack file bitmap creation. |
-| `housekeeping_enabled` | boolean | no | (**If enabled, requires:** `housekeeping_bitmaps_enabled`, `housekeeping_full_repack_period`, `housekeeping_gc_period`, and `housekeeping_incremental_repack_period`) Enable or disable git housekeeping. |
+| `housekeeping_enabled` | boolean | no | (**If enabled, requires:** `housekeeping_bitmaps_enabled`, `housekeeping_full_repack_period`, `housekeeping_gc_period`, and `housekeeping_incremental_repack_period`) Enable or disable Git housekeeping. |
| `housekeeping_full_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
| `housekeeping_gc_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which `git gc` is run. |
| `housekeeping_incremental_repack_period` | integer | required by: `housekeeping_enabled` | Number of Git pushes after which an incremental `git repack` is run. |
@@ -322,7 +322,7 @@ are listed in the descriptions of the relevant settings.
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
| `snowplow_enabled` | boolean | no | Enable snowplow tracking. |
-| `snowplow_collector_hostname` | string | required by: `snowplow_enabled` | The Snowplow collector hostname. (e.g. `snowplow.trx.gitlab.net`) |
+| `snowplow_collector_hostname` | string | required by: `snowplow_enabled` | The Snowplow collector hostname. (e.g. `snowplow.trx.gitlab.net`) |
| `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) |
| `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) |
| `geo_node_allowed_ips` | string | yes | **(PREMIUM)** Comma-separated list of IPs and CIDRs of allowed secondary nodes. For example, `1.1.1.1, 2.2.2.0/24`. |
diff --git a/doc/api/tags.md b/doc/api/tags.md
index af86ba961f4..1d874fea1f8 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -112,7 +112,7 @@ Parameters:
- `tag_name` (required) - The name of a tag
- `ref` (required) - Create tag using commit SHA, another tag name, or branch name.
- `message` (optional) - Creates annotated tag.
-- `release_description` (optional) - Add release notes to the git tag and store it in the GitLab database.
+- `release_description` (optional) - Add release notes to the Git tag and store it in the GitLab database.
```json
{
@@ -166,7 +166,7 @@ Parameters:
## Create a new release
-Add release notes to the existing git tag. If there
+Add release notes to the existing Git tag. If there
already exists a release for the given tag, status code `409` is returned.
```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index ca9d0aa61bd..4be13204227 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -131,7 +131,7 @@ Its feature set is listed on the table below according to DevOps stages.
| **Secure** ||
| [Container Scanning](../user/application_security/container_scanning/index.md) **(ULTIMATE)** | Check your Docker containers for known vulnerabilities.|
| [Dependency Scanning](../user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
-| [License Management](../user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
+| [License Compliance](../user/application_security/license_management/index.md) **(ULTIMATE)** | Search your project dependencies for their licenses. |
| [Security Test reports](../user/project/merge_requests/index.md#security-reports-ultimate) **(ULTIMATE)** | Check for app vulnerabilities. |
## Examples
@@ -174,7 +174,7 @@ been necessary. These are:
#### 12.0
-- [Use refspec to clone/fetch git
+- [Use refspec to clone/fetch Git
repository](https://gitlab.com/gitlab-org/gitlab-runner/issues/4069).
- [Old cache
configuration](https://gitlab.com/gitlab-org/gitlab-runner/issues/4070).
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
index e54be9f3bd9..1c38c08b7cb 100644
--- a/doc/ci/directed_acyclic_graph/index.md
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -65,7 +65,7 @@ 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
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 cb8d383f7d9..61f260cb70d 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -205,5 +205,5 @@ Some features are not implemented yet. For example, support for environments.
- `stage`
- `allow_failure`
- `only` and `except`
-- `when`
+- `when` (only with `on_success`, `on_failure`, and `always` values)
- `extends`
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index ed8d0e3bc35..eaa6efc526d 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -377,6 +377,10 @@ This functionality is only available:
- For users with at least Developer access.
- If the the stage contains [manual actions](#manual-actions-from-pipeline-graphs).
+## Most Recent Pipeline
+
+There's a link to the latest pipeline for the last commit of a given branch at `/project/pipelines/[branch]/latest`. Also, `/project/pipelines/latest` will redirect you to the latest pipeline for the last commit on the project's default branch.
+
## Security on protected branches
A strict security model is enforced when pipelines are executed on
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..89a61b2a9e3 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".
+
+With `only`, 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.
@@ -1568,7 +1582,7 @@ dashboards.
The `license_management` report collects [Licenses](../../user/project/merge_requests/license_management.md)
as artifacts.
-The collected License Management report will be uploaded to GitLab as an artifact and will
+The collected License Compliance report will be uploaded to GitLab as an artifact and will
be automatically shown in merge requests, pipeline view and provide data for security
dashboards.
@@ -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/customization/libravatar.md b/doc/customization/libravatar.md
index 1c3bf877fa1..6d96528f760 100644
--- a/doc/customization/libravatar.md
+++ b/doc/customization/libravatar.md
@@ -14,7 +14,7 @@ server.
## Configuration
-In the [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122), set
+In the [`gitlab.yml` gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122), set
the configuration options as follows:
### For HTTP
@@ -46,7 +46,7 @@ For example, you host a service on `http://libravatar.example.com` and the
`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
-### Omnibus-gitlab example
+### Omnibus GitLab example
In `/etc/gitlab/gitlab.rb`:
diff --git a/doc/customization/system_header_and_footer_messages.md b/doc/customization/system_header_and_footer_messages.md
index 15830be4e8a..bd2de3e201c 100644
--- a/doc/customization/system_header_and_footer_messages.md
+++ b/doc/customization/system_header_and_footer_messages.md
@@ -8,7 +8,7 @@ Navigate to the **Admin** area and go to the **Appearance** page.
Under **System header and footer** insert your header message and/or footer message.
Both background and font color of the header and footer are customizable.
-You can also apply the header and footer messages to gitlab emails,
+You can also apply the header and footer messages to GitLab emails,
by checking the **Enable header and footer in emails** checkbox.
Note that color settings will only be applied within the app interface and not to emails
diff --git a/doc/development/README.md b/doc/development/README.md
index 6281bb809ff..3912a828dec 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -5,7 +5,7 @@ description: 'Learn how to contribute to GitLab.'
# Contributor and Development Docs
-## Get started!
+## Get started
- Set up GitLab's development environment with [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/README.md)
- [GitLab contributing guide](contributing/index.md)
@@ -65,6 +65,7 @@ description: 'Learn how to contribute to GitLab.'
- [Repository mirroring](repository_mirroring.md)
- [Git LFS](lfs.md)
- [Developing against interacting components or features](interacting_components.md)
+- [File uploads](uploads.md)
## Performance guides
@@ -116,6 +117,7 @@ description: 'Learn how to contribute to GitLab.'
## Case studies
- [Database case study: Filtering by label](filtering_by_label.md)
+- [Database case study: Namespaces storage statistics](namespaces_storage_statistics.md)
## Integration guides
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index 0866d3baeeb..61576236c96 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -51,7 +51,7 @@ allowed.
– <https://github.com/ruby-grape/grape#declared>
-### Exclude params from parent namespaces!
+### Exclude params from parent namespaces
> By default `declared(params)`includes parameters that were defined in all
parent namespaces.
@@ -64,7 +64,7 @@ In most cases you will want to exclude params from the parent namespaces:
declared(params, include_parent_namespaces: false)
```
-### When to use `declared(params)`?
+### When to use `declared(params)`
You should always use `declared(params)` when you pass the params hash as
arguments to a method call.
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..158606aa6a2 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -173,13 +173,13 @@ Now, every time you create an MR for CE and EE:
## How we run the Automatic CE->EE merge at GitLab
-At GitLab, we use the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
-project to keep our [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee)
-repository updated with commits from
+At GitLab, we use the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
+project to keep our [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee)
+repository updated with commits from
[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce).
We have a mirror of the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
-project [configured](https://ops.gitlab.net/gitlab-org/merge-train) to run an
+project [configured](https://ops.gitlab.net/gitlab-org/merge-train) to run an
automatic CE->EE merge job every twenty minutes as a scheduled CI job. The
[configured](https://ops.gitlab.net/gitlab-org/merge-train) Merge Train project
is only accessible to authorized GitLab staff.
@@ -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/distributed_tracing.md b/doc/development/distributed_tracing.md
index bfce7488a8d..4776c8348d4 100644
--- a/doc/development/distributed_tracing.md
+++ b/doc/development/distributed_tracing.md
@@ -179,4 +179,3 @@ By default, the Jaeger search UI is available at <http://localhost:16686/search>
TIP: **Tip:**
Don't forget that you will need to generate traces by using the application before
they appear in the Jaeger UI.
-
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index ac93ada5a4b..00c76fe0f1b 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -69,7 +69,7 @@ To follow a consistent workflow every month, documentation changes
involve the Product Managers, the developer who shipped the feature,
and the technical writer for the DevOps stage. Each role is described below.
-The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/template-improvements-for-documentation/.gitlab/issue_templates/Feature%20proposal.md)
+The Documentation items in the GitLab CE/EE [Feature Proposal issue template](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/.gitlab/issue_templates/Feature%20proposal.md)
and default merge request template will assist you with following this process.
### Product Manager role
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 2217dedccd3..a732a94b7c4 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -945,7 +945,7 @@ export default {
- Since we [can't async load a mixin](https://github.com/vuejs/vue-loader/issues/418#issuecomment-254032223) we will use the [`ee_else_ce`](../development/ee_features.md#javascript-code-in-assetsjavascripts) alias we already have for webpack.
- This means all the EE specific props, computed properties, methods, etc that are EE only should be in a mixin in the `ee/` folder and we need to create a CE counterpart of the mixin
-##### Example:
+##### Example
```javascript
import mixin from 'ee_else_ce/path/mixin';
@@ -976,7 +976,7 @@ For regular JS files, the approach is similar.
1. An EE file should be created with the EE only code, and it should extend the CE counterpart.
1. For code inside functions that can't be extended, the code should be moved into a new file and we should use `ee_else_ce` helper:
-#### Example:
+#### Example
```javascript
import eeCode from 'ee_else_ce/ee_code';
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 635895051bc..090e5235619 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -148,26 +148,49 @@ Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/
- Searches can have their own analyzers. Remember to check when editing analyzers
- `Character` filters (as opposed to token filters) always replace the original character, so they're not a good choice as they can hinder exact searches
-## Architecture
+## Zero downtime reindexing with multiple indices
-GitLab uses `elasticsearch-rails` for handling communication with Elasticsearch server. However, in order to achieve zero-downtime deployment during schema changes, an extra abstraction layer is built to allow:
+Currently GitLab can only handle a single version of setting. Any setting/schema changes would require reindexing everything from scratch. Since reindexing can take a long time, this can cause search functionality downtime.
-* Indexing (writes) to multiple indexes, with different mappings
-* Switching to different index for searches (reads) on the fly
+To avoid downtime, GitLab is working to support multiple indices that
+can function at the same time. Whenever the schema changes, the admin
+will be able to create a new index and reindex to it, while searches
+continue to go to the older, stable index. Any data updates will be
+forwarded to both indices. Once the new index is ready, an admin can
+mark it active, which will direct all searches to it, and remove the old
+index.
-Currently we are on the process of migrating models to this new design (e.g. `Snippet`), and it is hardwired to work with a single version for now.
+This is also helpful for migrating to new servers, e.g. moving to/from AWS.
-Traditionally, `elasticsearch-rails` provides class and instance level `__elasticsearch__` proxy methods. If you call `Issue.__elasticsearch__`, you will get an instance of `Elasticsearch::Model::Proxy::ClassMethodsProxy`, and if you call `Issue.first.__elasticsearch__`, you will get an instance of `Elasticsearch::Model::Proxy::InstanceMethodsProxy`. These proxy objects would talk to Elasticsearch server directly.
+Currently we are on the process of migrating to this new design. Everything is hardwired to work with one single version for now.
-In the new design, `__elasticsearch__` instead represents one extra layer of proxy. It would keep multiple versions of the actual proxy objects, and it would forward read and write calls to the proxy of the intended version.
+### Architecture
-The `elasticsearch-rails`'s way of specifying each model's mappings and other settings is to create a module for the model to include. However in the new design, each model would have its own corresponding subclassed proxy object, where the settings reside in. For example, snippet related setting in the past reside in `SnippetsSearch` module, but in the new design would reside in `SnippetClassProxy` (which is a subclass of `Elasticsearch::Model::Proxy::ClassMethodsProxy`). This reduces namespace pollution in model classes.
+The traditional setup, provided by `elasticsearch-rails`, is to communicate through its internal proxy classes. Developers would write model-specific logic in a module for the model to include in (e.g. `SnippetsSearch`). The `__elasticsearch__` methods would return a proxy object, e.g.:
+
+- `Issue.__elasticsearch__` returns an instance of `Elasticsearch::Model::Proxy::ClassMethodsProxy`
+- `Issue.first.__elasticsearch__` returns an instance of `Elasticsearch::Model::Proxy::InstanceMethodsProxy`.
+
+These proxy objects would talk to Elasticsearch server directly (see top half of the diagram).
+
+![Elasticsearch Architecture](img/elasticsearch_architecture.svg)
+
+In the planned new design, each model would have a pair of corresponding subclassed proxy objects, in which model-specific logic is located. For example, `Snippet` would have `SnippetClassProxy` and `SnippetInstanceProxy` (being subclass of `Elasticsearch::Model::Proxy::ClassMethodsProxy` and `Elasticsearch::Model::Proxy::InstanceMethodsProxy`, respectively).
+
+`__elasticsearch__` would represent another layer of proxy object, keeping track of multiple actual proxy objects. It would forward method calls to the appropriate index. For example:
+
+- `model.__elasticsearch__.search` would be forwarded to the one stable index, since it is a read operation.
+- `model.__elasticsearch__.update_document` would be forwarded to all indices, to keep all indices up-to-date.
The global configurations per version are now in the `Elastic::(Version)::Config` class. You can change mappings there.
### Creating new version of schema
-Currently GitLab would still work with a single version of setting. Once it is implemented, multiple versions of setting can exists in different folders (e.g. `ee/lib/elastic/v12p1` and `ee/lib/elastic/v12p3`). To keep a continuous git history, the latest version lives under the `/latest` folder, but is aliased as the latest version.
+NOTE: **Note:** this is not applicable yet as multiple indices functionality is not fully implemented.
+
+Folders like `ee/lib/elastic/v12p1` contain snapshots of search logic from different versions. To keep a continuous git history, the latest version lives under `ee/lib/elastic/latest`, but its classes are aliased under an actual version (e.g. `ee/lib/elastic/v12p3`). When referencing these classes, never use the `Latest` namespace directly, but use the actual version (e.g. `V12p3`).
+
+The version name basically follows GitLab's release version. If setting is changed in 12.3, we will create a new namespace called `V12p3` (p stands for "point"). Raise an issue if there is a need to name a version differently.
If the current version is `v12p1`, and we need to create a new version for `v12p3`, the steps are as follows:
@@ -176,7 +199,7 @@ If the current version is `v12p1`, and we need to create a new version for `v12p
1. Delete `v12p1` folder
1. Copy the entire folder of `latest` as `v12p1`
1. Change the namespace for files under `v12p1` folder from `Latest` to `V12p1`
-1. Make changes to `Latest` as needed
+1. Make changes to files under the `latest` folder as needed
## Troubleshooting
diff --git a/doc/development/emails.md b/doc/development/emails.md
index e6af075a282..edec0f86989 100644
--- a/doc/development/emails.md
+++ b/doc/development/emails.md
@@ -5,6 +5,10 @@
To view rendered emails "sent" in your development instance, visit
[`/rails/letter_opener`](http://localhost:3000/rails/letter_opener).
+Please note that [S/MIME signed](../administration/smime_signing_email.md) emails
+[cannot be currently previewed](https://github.com/fgrehm/letter_opener_web/issues/96) with
+`letter_opener`.
+
## Mailer previews
Rails provides a way to preview our mailer templates in HTML and plaintext using
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/file_storage.md b/doc/development/file_storage.md
index 475d1c1611e..44af2b020a4 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -2,6 +2,8 @@
We use the [CarrierWave] gem to handle file upload, store and retrieval.
+File uploads should be accelerated by workhorse, for details please refer to [uploads development documentation](uploads.md).
+
There are many places where file uploading is used, according to contexts:
- System
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/img/elasticsearch_architecture.svg b/doc/development/img/elasticsearch_architecture.svg
new file mode 100644
index 00000000000..2f38f9b04ee
--- /dev/null
+++ b/doc/development/img/elasticsearch_architecture.svg
@@ -0,0 +1 @@
+<svg version="1.2" width="210mm" height="297mm" viewBox="0 0 21000 29700" preserveAspectRatio="xMidYMid" fill-rule="evenodd" stroke-width="28.222" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><defs class="ClipPathGroup"><clipPath id="a" clipPathUnits="userSpaceOnUse"><path d="M0 0h21000v29700H0z"/></clipPath></defs><g class="SlideGroup"><g class="Slide" clip-path="url(#a)"><g class="Page"><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1975 5575h3051v1651H1975z"/><path fill="#FFF" d="M3500 7200H2000V5600h3000v1600H3500z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3500 7200H2000V5600h3000v1600H3500z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="2778" y="6311"><tspan>Snippet</tspan></tspan><tspan class="TextPosition" x="2099" y="6785"><tspan>(ActiveRecord)</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1475 3975h4051v3551H1475z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3500 7500H1500V4000h4000v3500H3500z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="1788" y="5048"><tspan>ApplicationSearch</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M5975 4675h8051v701H5975z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M6000 5350h4000v-650h4000"/></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M5975 5325h8051v1101H5975z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M6000 5350h4000v1050h4000"/></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1075 2875h4951v4951H1075z"/><path fill="none" stroke="#F33" stroke-width="50" d="M3550 7800H1100V2900h4900v4900H3550z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="1946" y="3514"><tspan fill="#C9211E">SnippetsSearch</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1975 12175h3051v1651H1975z"/><path fill="#FFF" d="M3500 13800H2000v-1600h3000v1600H3500z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3500 13800H2000v-1600h3000v1600H3500z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="2778" y="12911"><tspan>Snippet</tspan></tspan><tspan class="TextPosition" x="2099" y="13385"><tspan>(ActiveRecord)</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1075 10775h4951v3251H1075z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3550 14000H1100v-3200h4900v3200H3550z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="2511" y="11461"><tspan>Application</tspan></tspan><tspan class="TextPosition" x="1933" y="11935"><tspan>VersionedSearch</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M3525 13975h4501v7451H3525z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3550 14000v7400h4450"/></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M14008 14075h4985v851h-4985z"/><path fill="none" stroke="#999" stroke-width="50" d="M16500 14900h-2467v-800h4934v800h-2467z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14720" y="14648"><tspan fill="gray">ClassMethodProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13375 13075h6251v2151h-6251z"/><path fill="none" stroke="#F33" stroke-width="50" d="M16500 15200h-3100v-2100h6200v2100h-3100z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="13799" y="13731"><tspan fill="#C9211E">V12p1::SnippetClassProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M7975 14575h3051v1851H7975z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M9500 16400H8000v-1800h3000v1800H9500z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="8277" y="15411"><tspan>MultiVersion-</tspan></tspan><tspan class="TextPosition" x="8429" y="15885"><tspan>ClassProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M14008 16875h4985v851h-4985z"/><path fill="none" stroke="#999" stroke-width="50" d="M16500 17700h-2467v-800h4934v800h-2467z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14720" y="17448"><tspan fill="gray">ClassMethodProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13375 15875h6251v2151h-6251z"/><path fill="none" stroke="#F33" stroke-width="50" d="M16500 18000h-3100v-2100h6200v2100h-3100z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="13799" y="16531"><tspan fill="#C9211E">V12p2::SnippetClassProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10975 14125h2451v1401h-2451z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M11000 15500h1463v-1350h937"/></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10975 15475h2451v1501h-2451z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M11000 15500h1463v1450h937"/></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M3525 13975h4501v1551H3525z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M3550 14000v1500h4450"/></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M14008 19975h4985v851h-4985z"/><path fill="none" stroke="#999" stroke-width="50" d="M16500 20800h-2467v-800h4934v800h-2467z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14445" y="20548"><tspan fill="gray">InstanceMethodProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13375 18975h6251v2151h-6251z"/><path fill="none" stroke="#F33" stroke-width="50" d="M16500 21100h-3100v-2100h6200v2100h-3100z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="13505" y="19631"><tspan fill="#C9211E">V12p1::SnippetInstanceProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M7975 20275h3051v2251H7975z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M9500 22500H8000v-2200h3000v2200H9500z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="8277" y="21311"><tspan>MultiVersion-</tspan></tspan><tspan class="TextPosition" x="8154" y="21785"><tspan>InstanceProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M14008 22775h4985v851h-4985z"/><path fill="none" stroke="#999" stroke-width="50" d="M16500 23600h-2467v-800h4934v800h-2467z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14445" y="23348"><tspan fill="gray">InstanceMethodProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13375 21775h6251v2151h-6251z"/><path fill="none" stroke="#F33" stroke-width="50" d="M16500 23900h-3100v-2100h6200v2100h-3100z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="13505" y="22431"><tspan fill="#C9211E">V12p2::SnippetInstanceProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10975 20025h2451v1401h-2451z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M11000 21400h1463v-1350h937"/></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10975 21375h2451v1501h-2451z"/><path fill="none" stroke="#3465A4" stroke-width="50" d="M11000 21400h1463v1450h937"/></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M900 1600h10697v879H900z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="564" font-weight="400"><tspan class="TextPosition" x="1150" y="2233"><tspan>Standard elasticsearch-rails setup</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M900 9300h7683v879H900z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="564" font-weight="400"><tspan class="TextPosition" x="1150" y="9933"><tspan>GitLab multi-indices setup</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M3400 21300h4821v1197H3400z"/><text class="TextShape"><tspan class="TextParagraph" font-size="388" font-weight="400"><tspan class="TextPosition" x="4250" y="21840"><tspan fill="gray">(instance method)</tspan></tspan><tspan class="TextPosition" x="3651" y="22264"><tspan font-family="Courier" font-size="423">__elasticsearch__</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M3380 15400h4821v1197H3380z"/><text class="TextShape"><tspan class="TextParagraph" font-size="388" font-weight="400"><tspan class="TextPosition" x="4512" y="15940"><tspan fill="gray">(class method)</tspan></tspan><tspan class="TextPosition" x="3631" y="16364"><tspan font-family="Courier" font-size="423">__elasticsearch__</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M9000 3500h4821v1197H9000z"/><text class="TextShape"><tspan class="TextParagraph" font-size="388" font-weight="400"><tspan class="TextPosition" x="10132" y="4040"><tspan fill="gray">(class method)</tspan></tspan><tspan class="TextPosition" x="9251" y="4464"><tspan font-family="Courier" font-size="423">__elasticsearch__</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M9000 6400h4821v1197H9000z"/><text class="TextShape"><tspan class="TextParagraph" font-size="388" font-weight="400"><tspan class="TextPosition" x="9850" y="6940"><tspan fill="gray">(instance method)</tspan></tspan><tspan class="TextPosition" x="9251" y="7364"><tspan font-family="Courier" font-size="423">__elasticsearch__</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1975 25175h2051v851H1975z"/><path fill="none" stroke="#999" stroke-width="50" d="M3000 26000H2000v-800h2000v800H3000z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="2634" y="25748"><tspan fill="gray">Foo</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M4400 25200h7101v726H4400z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="4650" y="25710"><tspan>elasticsearch-rails’ internal class</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M4400 26400h8601v1200H4400z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="4650" y="26910"><tspan>where model-specific logic is</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M1975 26275h2051v851H1975z"/><path fill="none" stroke="#F33" stroke-width="50" d="M3000 27100H2000v-800h2000v800H3000z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="700"><tspan class="TextPosition" x="2613" y="26848"><tspan fill="#C9211E">Foo</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M4900 17289h5901v2312H4900z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="370" font-weight="400"><tspan class="TextPosition" x="7236" y="17748"><tspan fill="gray">Write operations like </tspan></tspan><tspan class="TextPosition" x="5323" y="18159"><tspan fill="gray">indexing/updating are forwarded </tspan></tspan><tspan class="TextPosition" x="8024" y="18570"><tspan fill="gray">to all instances.</tspan></tspan></tspan><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="370" font-weight="400"><tspan class="TextPosition" x="5501" y="18981"><tspan fill="gray">Read operations are forwarded </tspan></tspan><tspan class="TextPosition" x="7126" y="19392"><tspan fill="gray">to specified instance.</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10785 15769h1422v2691h-1422z"/><path fill="none" stroke="#999" stroke-width="30" d="M10800 18444c1429 0 934-1618 1119-2337"/><path fill="#999" d="M12206 15769l-460 293 267 217 193-510z"/></g><g class="com.sun.star.drawing.ConnectorShape"><path class="BoundingBox" fill="none" d="M10785 18429h1528v2862h-1528z"/><path fill="none" stroke="#999" stroke-width="30" d="M10800 18444c1509 0 970 1782 1200 2526"/><path fill="#999" d="M12312 21290l-227-496-252 235 479 261z"/></g><g class="com.sun.star.drawing.TextShape"><path class="BoundingBox" fill="none" d="M1800 24000h7101v807H1800z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="494" font-weight="700"><tspan class="TextPosition" x="2050" y="24574"><tspan>Legend</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13975 4275h5085v851h-5085z"/><path fill="none" stroke="#999" stroke-width="50" d="M16517 5100h-2517v-800h5034v800h-2517z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14737" y="4848"><tspan fill="gray">ClassMethodProxy</tspan></tspan></tspan></text></g><g class="com.sun.star.drawing.CustomShape"><path class="BoundingBox" fill="none" d="M13975 5975h5085v851h-5085z"/><path fill="none" stroke="#999" stroke-width="50" d="M16517 6800h-2517v-800h5034v800h-2517z"/><text class="TextShape"><tspan class="TextParagraph" font-family="Arial, sans-serif" font-size="423" font-weight="400"><tspan class="TextPosition" x="14462" y="6548"><tspan fill="gray">InstanceMethodProxy</tspan></tspan></tspan></text></g></g></g></g></svg> \ No newline at end of file
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/lfs.md b/doc/development/lfs.md
index 8c3408eb6e2..cb4c2d8967b 100644
--- a/doc/development/lfs.md
+++ b/doc/development/lfs.md
@@ -8,4 +8,4 @@ In April 2019, Francisco Javier López hosted a [Deep Dive] on GitLab's [Git LFS
[Git LFS]: ../workflow/lfs/manage_large_binaries_with_git_lfs.html
[recording on YouTube]: https://www.youtube.com/watch?v=Yyxwcksr0Qc
[Google Slides]: https://docs.google.com/presentation/d/1E-aw6-z0rYd0346YhIWE7E9A65zISL9iIMAOq2zaw9E/edit
-[PDF]: https://gitlab.com/gitlab-org/create-stage/uploads/07a89257a140db067bdfb484aecd35e1/Git_LFS_Deep_Dive__Create_.pdf \ No newline at end of file
+[PDF]: https://gitlab.com/gitlab-org/create-stage/uploads/07a89257a140db067bdfb484aecd35e1/Git_LFS_Deep_Dive__Create_.pdf
diff --git a/doc/development/namespaces_storage_statistics.md b/doc/development/namespaces_storage_statistics.md
new file mode 100644
index 00000000000..b3285de4705
--- /dev/null
+++ b/doc/development/namespaces_storage_statistics.md
@@ -0,0 +1,178 @@
+# Database case study: Namespaces storage statistics
+
+## Introduction
+
+On [Storage and limits management for groups](https://gitlab.com/groups/gitlab-org/-/epics/886),
+we want to facilitate a method for easily viewing the amount of
+storage consumed by a group, and allow easy management.
+
+## Proposal
+
+1. Create a new ActiveRecord model to hold the namespaces' statistics in an aggregated form (only for root namespaces).
+1. Refresh the statistics in this model every time a project belonging to this namespace is changed.
+
+## Problem
+
+In GitLab, we update the project storage statistics through a
+[callback](https://gitlab.com/gitlab-org/gitlab-ce/blob/v12.2.0.pre/app/models/project.rb#L90)
+every time the project is saved.
+
+The summary of those statistics per namespace is then retrieved
+by [`Namespaces#with_statistics`](https://gitlab.com/gitlab-org/gitlab-ce/blob/v12.2.0.pre/app/models/namespace.rb#L70) scope. Analyzing this query we noticed that:
+
+* It takes up to `1.2` seconds for namespaces with over `15k` projects.
+* It can't be analyzed with [ChatOps](chatops_on_gitlabcom.md), as it times out.
+
+Additionally, the pattern that is currently used to update the project statistics
+(the callback) doesn't scale adequately. It is currently one of the largest
+[database queries transactions on production](https://gitlab.com/gitlab-org/gitlab-ce/issues/62488)
+that takes the most time overall. We can't add one more query to it as
+it will increase the transaction's length.
+
+Because of all of the above, we can't apply the same pattern to store
+and update the namespaces statistics, as the `namespaces` table is one
+of the largest tables on GitLab.com. Therefore we needed to find a performant and
+alternative method.
+
+## Attempts
+
+### Attempt A: PostgreSQL materialized view
+
+Model can be updated through a refresh strategy based on a project routes SQL and a [materialized view](https://www.postgresql.org/docs/9.6/rules-materializedviews.html):
+
+```sql
+SELECT split_part("rs".path, '/', 1) as root_path,
+ COALESCE(SUM(ps.storage_size), 0) AS storage_size,
+ COALESCE(SUM(ps.repository_size), 0) AS repository_size,
+ COALESCE(SUM(ps.wiki_size), 0) AS wiki_size,
+ COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size,
+ COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size,
+ COALESCE(SUM(ps.packages_size), 0) AS packages_size
+FROM "projects"
+ INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'
+ INNER JOIN project_statistics ps ON ps.project_id = projects.id
+GROUP BY root_path
+```
+
+We could then execute the query with:
+
+```sql
+REFRESH MATERIALIZED VIEW root_namespace_storage_statistics;
+```
+
+While this implied a single query update (and probably a fast one), it has some downsides:
+
+* Materialized views syntax varies from PostgreSQL and MySQL. While this feature was worked on, MySQL was still supported by GitLab.
+* Rails does not have native support for materialized views. We'd need to use a specialized gem to take care of the management of the database views, which implies additional work.
+
+### Attempt B: An update through a CTE
+
+Similar to Attempt A: Model update done through a refresh strategy with a [Common Table Expression](https://www.postgresql.org/docs/9.1/queries-with.html)
+
+```sql
+WITH refresh AS (
+ SELECT split_part("rs".path, '/', 1) as root_path,
+ COALESCE(SUM(ps.storage_size), 0) AS storage_size,
+ COALESCE(SUM(ps.repository_size), 0) AS repository_size,
+ COALESCE(SUM(ps.wiki_size), 0) AS wiki_size,
+ COALESCE(SUM(ps.lfs_objects_size), 0) AS lfs_objects_size,
+ COALESCE(SUM(ps.build_artifacts_size), 0) AS build_artifacts_size,
+ COALESCE(SUM(ps.packages_size), 0) AS packages_size
+ FROM "projects"
+ INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'
+ INNER JOIN project_statistics ps ON ps.project_id = projects.id
+ GROUP BY root_path)
+UPDATE namespace_storage_statistics
+SET storage_size = refresh.storage_size,
+ repository_size = refresh.repository_size,
+ wiki_size = refresh.wiki_size,
+ lfs_objects_size = refresh.lfs_objects_size,
+ build_artifacts_size = refresh.build_artifacts_size,
+ packages_size = refresh.packages_size
+FROM refresh
+ INNER JOIN routes rs ON rs.path = refresh.root_path AND rs.source_type = 'Namespace'
+WHERE namespace_storage_statistics.namespace_id = rs.source_id
+```
+
+Same benefits and downsides as attempt A.
+
+### Attempt C: Get rid of the model and store the statistics on Redis
+
+We could get rid of the model that stores the statistics in aggregated form and instead use a Redis Set.
+This would be the [boring solution](https://about.gitlab.com/handbook/values/#boring-solutions) and the fastest one
+to implement, as GitLab already includes Redis as part of its [Architecture](architecture.md#redis).
+
+The downside of this approach is that Redis does not provide the same persistence/consistency guarantees as PostgreSQL,
+and this is information we can't afford to lose in a Redis failure.
+
+### Attempt D: Tag the root namespace and its child namespaces
+
+Directly relate the root namespace to its child namespaces, so
+whenever a namespace is created without a parent, this one is tagged
+with the root namespace ID:
+
+| id | root_id | parent_id
+|:---|:--------|:----------
+| 1 | 1 | NULL
+| 2 | 1 | 1
+| 3 | 1 | 2
+
+To aggregate the statistics inside a namespace we'd execute something like:
+
+```sql
+SELECT COUNT(...)
+FROM projects
+WHERE namespace_id IN (
+ SELECT id
+ FROM namespaces
+ WHERE root_id = X
+)
+```
+
+Even though this approach would make aggregating much easier, it has some major downsides:
+
+* We'd have to migrate **all namespaces** by adding and filling a new column. Because of the size of the table, dealing with time/cost will not be great. The background migration will take approximately `153h`, see <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29772>.
+* Background migration has to be shipped one release before, delaying the functionality by another milestone.
+
+### Attempt E (final): Update the namespace storage statistics in async way
+
+This approach consists of keep using the incremental statistics updates we currently already have,
+but we refresh them through Sidekiq jobs and in different transactions:
+
+1. Create a second table (`namespace_aggregation_schedules`) with two columns `id` and `namespace_id`.
+1. Whenever the statistics of a project changes, insert a row into `namespace_aggregation_schedules`
+ - We don't insert a new row if there's already one related to the root namespace.
+ - Keeping in mind the length of the transaction that involves updating `project_statistics`(<https://gitlab.com/gitlab-org/gitlab-ce/issues/62488>), the insertion should be done in a different transaction and through a Sidekiq Job.
+1. After inserting the row, we schedule another worker to be executed async at two different moments:
+ - One enqueued for immediate execution and another one scheduled in `1.5h` hours.
+ - We only schedule the jobs, if we can obtain a `1.5h` lease on Redis on a key based on the root namespace ID.
+ - If we can't obtain the lease, it indicates there's another aggregation already in progress, or scheduled in no more than `1.5h`.
+1. This worker will:
+ - Update the root namespace storage statistics by querying all the namespaces through a service.
+ - Delete the related `namespace_aggregation_schedules` after the update.
+1. Another Sidekiq job is also included to traverse any remaining rows on the `namespace_aggregation_schedules` table and schedule jobs for every pending row.
+ - This job is scheduled with cron to run every night (UTC).
+
+This implementation has the following benefits:
+
+* All the updates are done async, so we're not increasing the length of the transactions for `project_statistics`.
+* We're doing the update in a single SQL query.
+* It is compatible with PostgreSQL and MySQL.
+* No background migration required.
+
+The only downside of this approach is that namespaces' statistics are updated up to `1.5` hours after the change is done,
+which means there's a time window in which the statistics are inaccurate. Because we're still not
+[enforcing storage limits](https://gitlab.com/gitlab-org/gitlab-ce/issues/30421), this is not a major problem.
+
+## Conclusion
+
+Updating the storage statistics asynchronously, was the less problematic and
+performant approach of aggregating the root namespaces.
+
+All the details regarding this use case can be found on:
+
+* <https://gitlab.com/gitlab-org/gitlab-ce/issues/62214>
+* Merge Request with the implementation: <https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28996>
+
+Performance of the namespace storage statistics were measured in staging and production (GitLab.com). All results were posted
+on <https://gitlab.com/gitlab-org/gitlab-ce/issues/64092>: No problem has been reported so far.
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 67f36eb1ab4..e9d6cfe00b2 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,4 +216,3 @@ 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/repository_mirroring.md b/doc/development/repository_mirroring.md
index f8c33ff2b85..dc51bf80e92 100644
--- a/doc/development/repository_mirroring.md
+++ b/doc/development/repository_mirroring.md
@@ -8,4 +8,4 @@ In December 2018, Tiago Botelho hosted a [Deep Dive] on GitLab's [Pull Repositor
[Pull Repository Mirroring functionality]: ../workflow/repository_mirroring.md#pulling-from-a-remote-repository-starter
[recording on YouTube]: https://www.youtube.com/watch?v=sSZq0fpdY-Y
[Google Slides]: https://docs.google.com/presentation/d/17BTT6M6RyNRckV4wTt-dr07nIfBvD325_xVBoLtSoPM/edit?usp=sharing
-[PDF]: https://gitlab.com/gitlab-org/create-stage/uploads/8693404888a941fd851f8a8ecdec9675/Gitlab_Create_-_Pull_Mirroring_Deep_Dive.pdf \ No newline at end of file
+[PDF]: https://gitlab.com/gitlab-org/create-stage/uploads/8693404888a941fd851f8a8ecdec9675/Gitlab_Create_-_Pull_Mirroring_Deep_Dive.pdf
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/end_to_end/index.md b/doc/development/testing_guide/end_to_end/index.md
index d6b944a3e74..3ae3ce183d9 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -45,11 +45,11 @@ Results are reported in the `#qa-staging` Slack channel.
### Testing code in merge requests
-#### Using the `package-and-qa` job
+#### Using the `package-and-qa-manual` job
It is possible to run end-to-end tests for a merge request, eventually being run in
a pipeline in the [`gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/) project,
-by triggering the `package-and-qa` manual action in the `test` stage (not
+by triggering the `package-and-qa-manual` manual action in the `test` stage (not
available for forks).
**This runs end-to-end tests against a custom Omnibus package built from your
@@ -71,7 +71,7 @@ graph LR
B2[`Trigger-qa` stage<br>`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
subgraph "gitlab-ce/ee pipeline"
- A1[`test` stage<br>`package-and-qa` job]
+ A1[`test` stage<br>`package-and-qa-manual` job]
end
subgraph "omnibus-gitlab pipeline"
@@ -79,7 +79,7 @@ subgraph "omnibus-gitlab pipeline"
end
subgraph "gitlab-qa pipeline"
- A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br>and post the result on the original commit tested| A1
+ A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa-manual` job<br>and post the result on the original commit tested| A1
end
```
diff --git a/doc/development/testing_guide/end_to_end/page_objects.md b/doc/development/testing_guide/end_to_end/page_objects.md
index 47e58a425fd..850ea6b60ac 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -40,7 +40,7 @@ the time it would take to build packages and test everything.
That is why when someone changes `t.text_field :login` to
`t.text_field :username` in the _new session_ view we won't know about this
change until our GitLab QA nightly pipeline fails, or until someone triggers
-`package-and-qa` action in their merge request.
+`package-and-qa-manual` action in their merge request.
Obviously such a change would break all tests. We call this problem a _fragile
tests problem_.
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..7dc89a3fcdb 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,517 @@ 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.
+
+```mermaid
+graph RL
+ plain[Plain JavaScript];
+ Vue[Vue Components];
+ feature-flags[Feature Flags];
+ license-checks[License Checks];
+
+ plain---Vuex;
+ plain---GraphQL;
+ Vue---plain;
+ Vue---Vuex;
+ Vue---GraphQL;
+ browser---plain;
+ browser---Vue;
+ plain---backend;
+ Vuex---backend;
+ GraphQL---backend;
+ Vue---backend;
+ backend---database;
+ backend---feature-flags;
+ backend---license-checks;
+
+ class plain tested;
+ class Vuex tested;
+
+ classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090
+ classDef label stroke-width:0;
+ classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ subgraph " "
+ tested;
+ mocked;
+ class tested tested;
+ end
+```
+
+#### 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.
+
+```mermaid
+graph RL
+ plain[Plain JavaScript];
+ Vue[Vue Components];
+ feature-flags[Feature Flags];
+ license-checks[License Checks];
+
+ plain---Vuex;
+ plain---GraphQL;
+ Vue---plain;
+ Vue---Vuex;
+ Vue---GraphQL;
+ browser---plain;
+ browser---Vue;
+ plain---backend;
+ Vuex---backend;
+ GraphQL---backend;
+ Vue---backend;
+ backend---database;
+ backend---feature-flags;
+ backend---license-checks;
+
+ class Vue tested;
+
+ classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090
+ classDef label stroke-width:0;
+ classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ subgraph " "
+ tested;
+ mocked;
+ class tested tested;
+ end
+```
+
+#### 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.
+
+```mermaid
+graph RL
+ plain[Plain JavaScript];
+ Vue[Vue Components];
+ feature-flags[Feature Flags];
+ license-checks[License Checks];
+
+ plain---Vuex;
+ plain---GraphQL;
+ Vue---plain;
+ Vue---Vuex;
+ Vue---GraphQL;
+ browser---plain;
+ browser---Vue;
+ plain---backend;
+ Vuex---backend;
+ GraphQL---backend;
+ Vue---backend;
+ backend---database;
+ backend---feature-flags;
+ backend---license-checks;
+
+ class plain tested;
+ class Vue tested;
+ class Vuex tested;
+ class GraphQL tested;
+ class browser tested;
+ linkStyle 0,1,2,3,4,5,6 stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090
+ classDef label stroke-width:0;
+ classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ subgraph " "
+ tested;
+ mocked;
+ class tested tested;
+ end
+```
+
+#### 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).
+
+```mermaid
+graph RL
+ plain[Plain JavaScript];
+ Vue[Vue Components];
+ feature-flags[Feature Flags];
+ license-checks[License Checks];
+
+ plain---Vuex;
+ plain---GraphQL;
+ Vue---plain;
+ Vue---Vuex;
+ Vue---GraphQL;
+ browser---plain;
+ browser---Vue;
+ plain---backend;
+ Vuex---backend;
+ GraphQL---backend;
+ Vue---backend;
+ backend---database;
+ backend---feature-flags;
+ backend---license-checks;
+
+ class backend tested;
+ class plain tested;
+ class Vue tested;
+ class Vuex tested;
+ class GraphQL tested;
+ class browser tested;
+ linkStyle 0,1,2,3,4,5,6,7,8,9,10 stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ classDef node color:#909090,fill:#f0f0f0,stroke-width:2px,stroke:#909090
+ classDef label stroke-width:0;
+ classDef tested color:#000000,fill:#a0c0ff,stroke:#6666ff,stroke-width:2px,stroke-dasharray: 5, 5;
+
+ subgraph " "
+ tested;
+ mocked;
+ class tested tested;
+ end
+```
+
+#### 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/uploads.md b/doc/development/uploads.md
new file mode 100644
index 00000000000..234539bb673
--- /dev/null
+++ b/doc/development/uploads.md
@@ -0,0 +1,271 @@
+# Uploads development documentation
+
+[GitLab Workhorse](https://gitlab.com/gitlab-org/gitlab-workhorse) has special rules for handling uploads.
+To prevent occupying a ruby process on I/O operations, we process the upload in workhorse, where is cheaper.
+This process can also directly upload to object storage.
+
+## The problem description
+
+The following graph explains machine boundaries in a scalable GitLab installation. Without any workhorse optimization in place, we can expect incoming requests to follow the numbers on the arrows.
+
+```mermaid
+graph TB
+ subgraph "load balancers"
+ LB(HA Proxy)
+ end
+
+ subgraph "Shared storage"
+ nfs(NFS)
+ end
+
+ subgraph "redis cluster"
+ r(persisted redis)
+ end
+ LB-- 1 -->workhorse
+
+ subgraph "web or API fleet"
+ workhorse-- 2 -->rails
+ end
+ rails-- "3 (write files)" -->nfs
+ rails-- "4 (schedule a job)" -->r
+
+ subgraph sidekiq
+ s(sidekiq)
+ end
+ s-- "5 (fetch a job)" -->r
+ s-- "6 (read files)" -->nfs
+```
+
+We have three challenges here: performance, availability, and scalability.
+
+### Performance
+
+Rails process are expensive in terms of both CPU and memory. Ruby [global interpreter lock](https://en.wikipedia.org/wiki/Global_interpreter_lock) adds to cost too because the ruby process will spend time on I/O operations on step 3 causing incoming requests to pile up.
+
+In order to improve this, [workhorse disk acceleration](#workhorse-disk-acceleration) was implemented. With this, Rails no longer deals with writing uploaded files to disk.
+
+```mermaid
+graph TB
+ subgraph "load balancers"
+ LB(HA Proxy)
+ end
+
+ subgraph "Shared storage"
+ nfs(NFS)
+ end
+
+ subgraph "redis cluster"
+ r(persisted redis)
+ end
+ LB-- 1 -->workhorse
+
+ subgraph "web or API fleet"
+ workhorse-- "3 (without files)" -->rails
+ end
+ workhorse -- "2 (write files)" -->nfs
+ rails-- "4 (schedule a job)" -->r
+
+ subgraph sidekiq
+ s(sidekiq)
+ end
+ s-- "5 (fetch a job)" -->r
+ s-- "6 (read files)" -->nfs
+```
+
+### Availability
+
+There's also an availability problem in this setup, NFS is a [single point of failure](https://en.wikipedia.org/wiki/Single_point_of_failure).
+
+To address this problem an HA object storage can be used and it's supported by [workhorse object storage acceleration](#workhorse-object-storage-acceleration)
+
+### Scalability
+
+Scaling NFS is outside of our support scope, and NFS is not a part of cloud native installations.
+
+All features that require sidekiq and do not use object storage acceleration won't work without NFS. In Kubernetes, machine boundaries translate to PODs, and in this case the uploaded file will be written into the POD private disk. Since sidekiq POD cannot reach into other pods, the operation will fail to read it.
+
+## How to select the proper level of acceleration?
+
+Selecting the proper acceleration is a tradeoff between speed of development and operational costs.
+
+We can identify three major use-cases for an upload:
+
+1. **storage:** if we are uploading for storing a file (i.e. artifacts, packages, discussion attachments). In this case [object storage acceleration](#workhorse-object-storage-acceleration) is the proper level as it's the less resource-intensive operation. Additional information can be found on [File Storage in GitLab](file_storage.md).
+1. **in-controller/synchronous processing:** if we allow processing **small files** synchronously, using [disk acceleration](#workhorse-disk-acceleration) may speed up development.
+1. **sidekiq/asynchronous processing:** Async processing must implement [object storage acceleration](#workhorse-object-storage-acceleration), the reason being that it's the only way to support Cloud Native deployments without a shared NFS.
+
+For more details about currently broken feature see [epic &1802](https://gitlab.com/groups/gitlab-org/-/epics/1802).
+
+### Handling repository uploads
+
+Some features involves git repository uploads without using a regular git client.
+Some examples are uploading a repository file from the web interface and [design management](../user/project/issues/design_management.md).
+
+Those uploads requires the rails controller to act as a git client in lieu of the user.
+Those operation falls into _in-controller/synchronous processing_ category, but we have no warranties on the file size.
+
+In case of a LFS upload, the file pointer is committed synchronously, but file upload to object storage is performed asynchronously with sidekiq.
+
+## Upload encodings
+
+By upload encoding we mean how the file is included within the incoming request.
+
+We have three kinds of file encoding in our uploads:
+
+1. <i class="fa fa-check-circle"></i> **multipart**: `multipart/form-data` is the most common, a file is encoded as a part of a multipart encoded request.
+1. <i class="fa fa-check-circle"></i> **body**: some APIs uploads files as the whole request body.
+1. <i class="fa fa-times-circle"></i> **JSON**: some JSON API uploads files as base64 encoded strings. This requires [gitlab-workhorse#226](https://gitlab.com/gitlab-org/gitlab-workhorse/issues/226) to be implemented.
+
+## Uploading technologies
+
+By uploading technologies we mean how all the involved services interact with each other.
+
+GitLab supports 3 kinds of uploading technologies, here follows a brief description with a sequence diagram for each one. Diagrams are not meant to be exhaustive.
+
+### Regular rails upload
+
+This is the default kind of upload, and it's most expensive in terms of resources.
+
+In this case, workhorse is unaware of files being uploaded and acts as a regular proxy.
+
+When a multipart request reaches the rails application, `Rack::Multipart` leaves behind tempfiles in `/tmp` and uses valuable Ruby process time to copy files around.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+
+ activate c
+ c ->>+w: POST /some/url/upload
+ w->>+r: POST /some/url/upload
+
+ r->>r: save the incoming file on /tmp
+ r->>r: read the file for processing
+
+ r-->>-c: request result
+ deactivate c
+ deactivate w
+```
+
+### Workhorse disk acceleration
+
+This kind of upload avoids wasting resources caused by handling upload writes to `/tmp` in rails.
+
+This optimization is not active by default on REST API requests.
+
+When enabled, Workhorse looks for files in multipart MIME requests, uploading
+any it finds to a temporary file on shared storage. The MIME data in the request
+is replaced with the path to the corresponding file before it is forwarded to
+Rails.
+
+To prevent abuse of this feature, Workhorse signs the modified request with a
+special header, stating which entries it modified. Rails will ignore any
+unsigned path entries.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+ participant s as NFS
+
+ activate c
+ c ->>+w: POST /some/url/upload
+
+ w->>+s: save the incoming file on a temporary location
+ s-->>-w:
+
+ w->>+r: POST /some/url/upload
+ Note over w,r: file was replaced with its location<br>and other metadata
+
+ opt requires async processing
+ r->>+redis: schedule a job
+ redis-->>-r:
+ end
+
+ r-->>-c: request result
+ deactivate c
+ w->>-w: cleanup
+
+ opt requires async processing
+ activate sidekiq
+ sidekiq->>+redis: fetch a job
+ redis-->>-sidekiq: job
+
+ sidekiq->>+s: read file
+ s-->>-sidekiq: file
+
+ sidekiq->>sidekiq: process file
+
+ deactivate sidekiq
+ end
+```
+
+### Workhorse object storage acceleration
+
+This is the more advanced acceleration technique we have in place.
+
+Workhorse asks rails for temporary pre-signed object storage URLs and directly uploads to object storage.
+
+In this setup an extra rails route needs to be implemented in order to handle authorization,
+you can see an example of this in [`Projects::LfsStorageController`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/controllers/projects/lfs_storage_controller.rb)
+and [its routes](https://gitlab.com/gitlab-org/gitlab-ce/blob/v12.2.0/config/routes/git_http.rb#L31-32).
+
+**note:** this will fallback to _Workhorse disk acceleration_ when object storage is not enabled in the gitlab instance. The answer to the `/authorize` call will only contain a file system path.
+
+```mermaid
+sequenceDiagram
+ participant c as Client
+ participant w as Workhorse
+ participant r as Rails
+ participant os as Object Storage
+
+ activate c
+ c ->>+w: POST /some/url/upload
+
+ w ->>+r: POST /some/url/upload/authorize
+ Note over w,r: this request has an empty body
+ r-->>-w: presigned OS URL
+
+ w->>+os: PUT file
+ Note over w,os: file is stored on a temporary location. Rails select the destination
+ os-->>-w:
+
+ w->>+r: POST /some/url/upload
+ Note over w,r: file was replaced with its location<br>and other metadata
+
+ r->>+os: move object to final destination
+ os-->>-r:
+
+ opt requires async processing
+ r->>+redis: schedule a job
+ redis-->>-r:
+ end
+
+ r-->>-c: request result
+ deactivate c
+ w->>-w: cleanup
+
+ opt requires async processing
+ activate sidekiq
+ sidekiq->>+redis: fetch a job
+ redis-->>-sidekiq: job
+
+ sidekiq->>+os: get object
+ os-->>-sidekiq: file
+
+ sidekiq->>sidekiq: process file
+
+ deactivate sidekiq
+ end
+```
+
+## What does the `direct_upload` setting mean?
+
+[Object storage setting](../administration/uploads.md#object-storage-settings) allows instance administators to enable `direct_upload`, this in an option that only affects the behavior of [workhorse object storage acceleration](#workhorse-object-storage-acceleration).
+
+This option affect the response to the `/authorize` call. When not enabled, the API response will not contain presigned URLs and workhorse will write the file the shared disk, on the path is provided by rails, acting like object storage was disabled.
+
+Once the request reachs rails, it will schedule an object storage upload as a sidekiq job.
+
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/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 3e3f96fb31f..5de173abbff 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -63,8 +63,8 @@ git config --global user.email
You'll need to do this only once, since you are using the `--global` option. It tells
Git to always use this information for anything you do on that system. If you want
-to override this with a different username or email address for specific projects,
-you can run the command without the `--global` option when you’re in that project.
+to override this with a different username or email address for specific projects or repositories,
+you can run the command without the `--global` option when you’re in that project, and that will default to `--local`. You can read more on how Git manages configurations in the [Git Config](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration) documentation.
## Check your information
@@ -102,8 +102,7 @@ files to your local computer, automatically preserving the Git connection with t
remote repository.
You can either clone it via HTTPS or [SSH](../ssh/README.md). If you chose to clone
-it via HTTPS, you'll have to enter your credentials every time you pull and push.
-With SSH, you enter your credentials only once.
+it via HTTPS, you'll have to enter your credentials every time you pull and push. You can read more about credential storage in the [Git Credentials documentation](https://git-scm.com/book/en/v2/Git-Tools-Credential-Storage). With SSH, you enter your credentials only once.
You can find both paths (HTTPS and SSH) by navigating to your project's landing page
and clicking **Clone**. GitLab will prompt you with both paths, from which you can copy
@@ -152,13 +151,15 @@ to get the main branch code, or the branch name of the branch you are currently
in.
```bash
-git pull REMOTE <name-of-branch>
+git pull <REMOTE> <name-of-branch>
```
-When you first clone a repository, REMOTE is typically `origin`. This is where the
+When you clone a repository, `REMOTE` is typically `origin`. This is where the
repository was cloned from, and it indicates the SSH or HTTPS URL of the repository
on the remote server. `<name-of-branch>` is usually `master`, but it may be any existing
-branch.
+branch. You can create additional named remotes and branches as necessary.
+
+You can learn more on how Git manages remote repositories in the [Git Remote documentation](https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes).
### View your remote repositories
@@ -168,6 +169,8 @@ To view your remote repositories, type:
git remote -v
```
+The `-v` flag stands for verbose.
+
### Add a remote repository
To add a link to a remote repository:
@@ -186,7 +189,7 @@ following (spaces won't be recognized in the branch name, so you will need to us
hyphen or underscore):
```bash
-git checkout -b <name-of-branch>>
+git checkout -b <name-of-branch>
```
### Work on an existing branch
@@ -238,7 +241,7 @@ git commit -m "COMMENT TO DESCRIBE THE INTENTION OF THE COMMIT"
```
NOTE: **Note:**
-The `.` character typically means _all_ in Git.
+The `.` character means _all file changes in the current directory and all subdirectories_.
### Send changes to GitLab.com
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index 543a222bd25..c5939dc6856 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -407,7 +407,7 @@ on any cloud service you choose.
## Where to next?
-Check out our other [Technical Articles](../../articles/index.md) or browse the [GitLab Documentation][GitLab-Docs](../../README.md) to learn more about GitLab.
+Check out our other [Technical Articles](../../articles/index.md) or browse the [GitLab Documentation](../../README.md) to learn more about GitLab.
### Useful links
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 295d9804497..6d64b8a4936 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -789,7 +789,7 @@ nginx: configuration file /etc/nginx/nginx.conf test failed`
sudo service nginx restart
```
-## Done!
+## Post-install
### Double-check Application Status
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index 6755640d39c..d865f977799 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -35,7 +35,7 @@ and [Migrating from Jenkins to GitLab](https://www.youtube.com/watch?v=RlEVGOpYF
For a real use case, read the blog post [Continuous integration: From Jenkins to GitLab using Docker](https://about.gitlab.com/2017/07/27/docker-my-precious/).
-NOTE: **Moving from a traditional CI plug-in to a single application for the entire software development lifecycle can decrease hours spent on maintaining toolchains by 10% or more.**
+NOTE: **Moving from a traditional CI plug-in to a single application for the entire software development lifecycle can decrease hours spent on maintaining toolchains by 10% or more.**
Visit the ['GitLab vs. Jenkins' comparison page](https://about.gitlab.com/devops-tools/jenkins-vs-gitlab.html) to learn how our built-in CI compares to Jenkins.
## Requirements
diff --git a/doc/integration/oauth2_generic.md b/doc/integration/oauth2_generic.md
index f4119b1d1ce..ad59592c45f 100644
--- a/doc/integration/oauth2_generic.md
+++ b/doc/integration/oauth2_generic.md
@@ -12,7 +12,7 @@ This strategy is designed to allow configuration of the simple OmniAuth SSO proc
1. Strategy parses user information from the response, using a **configurable** format
1. GitLab finds or creates the returned user and logs them in
-## Limitations of this Strategy:
+## Limitations of this Strategy
- It can only be used for Single Sign on, and will not provide any other access granted by any OAuth provider
(importing projects or users, etc)
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 9947c19a667..2b8379141c3 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -227,7 +227,7 @@ sudo mv /path/to/12345_gitlab_ci_backup.tar /var/opt/gitlab/backups/
sudo mv /path/to/12345_gitlab_ci_backup.tar /home/git/gitlab/tmp/backups/
```
-### 5. Import the CI data into GitLab.
+### 5. Import the CI data into GitLab
This step will delete any existing CI data on your GitLab server. There should
be no CI data yet because you turned CI on the GitLab server off earlier.
diff --git a/doc/policy/maintenance.md b/doc/policy/maintenance.md
index 018c273c51a..f7ba7c16a9e 100644
--- a/doc/policy/maintenance.md
+++ b/doc/policy/maintenance.md
@@ -86,6 +86,7 @@ Please see the table below for some examples:
| 9.4.5 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.4.5` | `8.17.7` is the last version in version `8` |
| 10.1.4 | 8.13.4 | `8.13.4 -> 8.17.7 -> 9.5.10 -> 10.1.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9` |
| 11.3.4 | 8.13.4 | `8.13.4` -> `8.17.7` -> `9.5.10` -> `10.8.7` -> `11.3.4` | `8.17.7` is the last version in version `8`, `9.5.10` is the last version in version `9`, `10.8.7` is the last version in version `10` |
+| 12.0.2 | 11.3.4 | `11.3.4` -> `11.11.x` -> `12.0.2` | `11.11.x` is the last version in version `11`
More information about the release procedures can be found in our
[release documentation](https://gitlab.com/gitlab-org/release/docs). You may also want to read our
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index 0142e5075cc..dc6ee9b2503 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -6,7 +6,7 @@ type: reference
GitLab allows [Owners](../user/permissions.md) to set a projects' visibility as **public**, **internal**
or **private**. These visibility levels affect who can see the project in the
-public access directory (`/public` under your GitLab instance), like at [https://gitlab.com/public]().
+public access directory (`/public` under your GitLab instance), like at <https://gitlab.com/public>
## Visibility of projects
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 51f04df27c7..b4b7d711d5a 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -978,4 +978,3 @@ If this happens, check the following:
1. Confirm there is sufficent diskspace for the gzip operation.
1. If NFS is being used, check if the mount option `timeo` is set. The default is `600`, and changing this to smaller values have resulted in this error.
-
diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md
index 832078d23cb..f84d29cca9a 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -1,6 +1,10 @@
# Cleanup
-## Remove garbage from filesystem. Important! Data loss!
+## Remove garbage from filesystem
+
+DANGER: **Danger:**
+The commands below will remove data permanently from your GitLab instance. Only use
+these commands if you are 100% certain that it is safe to delete this data.
Remove namespaces(dirs) from all repository storage paths if they don't exist in GitLab database.
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 8f65fab366e..c76180a242e 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -11,7 +11,7 @@
## How to use
-### Create a new folder to import your Git repositories from.
+### Create a new folder to import your Git repositories from
The new folder needs to have git user ownership and read/write/execute access for git user and its group:
@@ -19,7 +19,7 @@ The new folder needs to have git user ownership and read/write/execute access fo
sudo -u git mkdir -p /var/opt/gitlab/git-data/repository-import-<date>/new_group
```
-### Copy your bare repositories inside this newly created folder:
+### Copy your bare repositories inside this newly created folder
- Any .git repositories found on any of the subfolders will be imported as projects
- Groups will be created as needed, these could be nested folders. Example:
@@ -38,7 +38,7 @@ sudo chown -R git:git /var/opt/gitlab/git-data/repository-import-<date>
If you are using an installation from source, replace `/var/opt/gitlab/` with `/home/git`.
-### Run the command below depending on your type of installation:
+### Run the command below depending on your type of installation
#### Omnibus Installation
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index cc1166a04cc..d9a042ec8fe 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -1,6 +1,6 @@
# Webhooks administration **(CORE ONLY)**
-## Add a webhook for **ALL** projects:
+## Add a webhook for **ALL** projects
```sh
# omnibus-gitlab
@@ -9,7 +9,7 @@ sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook"
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production
```
-## Add a webhook for projects in a given **NAMESPACE**:
+## Add a webhook for projects in a given **NAMESPACE**
```sh
# omnibus-gitlab
@@ -18,7 +18,7 @@ sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acm
bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
```
-## Remove a webhook from **ALL** projects using:
+## Remove a webhook from **ALL** projects using
```sh
# omnibus-gitlab
@@ -27,7 +27,7 @@ sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook"
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production
```
-## Remove a webhook from projects in a given **NAMESPACE**:
+## Remove a webhook from projects in a given **NAMESPACE**
```sh
# omnibus-gitlab
@@ -36,7 +36,7 @@ sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme
bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production
```
-## List **ALL** webhooks:
+## List **ALL** webhooks
```sh
# omnibus-gitlab
@@ -45,7 +45,7 @@ sudo gitlab-rake gitlab:web_hook:list
bundle exec rake gitlab:web_hook:list RAILS_ENV=production
```
-## List the webhooks from projects in a given **NAMESPACE**:
+## List the webhooks from projects in a given **NAMESPACE**
```sh
# omnibus-gitlab
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
index c80f2f264b2..80088da77a0 100644
--- a/doc/security/rate_limits.md
+++ b/doc/security/rate_limits.md
@@ -30,4 +30,3 @@ similarly mitigated by a rate limit.
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 9c1258fa1aa..26d5221acc5 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -65,7 +65,7 @@ project in a simple and automatic way:
1. [Auto Code Quality](#auto-code-quality-starter) **(STARTER)**
1. [Auto SAST (Static Application Security Testing)](#auto-sast-ultimate) **(ULTIMATE)**
1. [Auto Dependency Scanning](#auto-dependency-scanning-ultimate) **(ULTIMATE)**
-1. [Auto License Management](#auto-license-management-ultimate) **(ULTIMATE)**
+1. [Auto License Compliance](#auto-license-compliance-ultimate) **(ULTIMATE)**
1. [Auto Container Scanning](#auto-container-scanning-ultimate) **(ULTIMATE)**
1. [Auto Review Apps](#auto-review-apps)
1. [Auto DAST (Dynamic Application Security Testing)](#auto-dast-ultimate) **(ULTIMATE)**
@@ -401,13 +401,13 @@ check out.
Any security warnings are also shown in the merge request widget. Read more about
[Dependency Scanning](../../user/application_security/dependency_scanning/index.md).
-### Auto License Management **(ULTIMATE)**
+### Auto License Compliance **(ULTIMATE)**
> Introduced in [GitLab Ultimate][ee] 11.0.
-License Management uses the
-[License Management Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
-to search the project dependencies for their license. The Auto License Management stage
+License Compliance uses the
+[License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
+to search the project dependencies for their license. The Auto License Compliance stage
will be skipped on licenses other than Ultimate.
Once the
@@ -415,7 +415,7 @@ report is created, it's uploaded as an artifact which you can later download and
check out.
Any licenses are also shown in the merge request widget. Read more how
-[License Management works](../../user/application_security/license_management/index.md).
+[License Compliance works](../../user/application_security/license_management/index.md).
### Auto Container Scanning **(ULTIMATE)**
@@ -584,6 +584,55 @@ 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](https://github.com/mperham/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:
+
+- Maintain your own Redis instance.
+- 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 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 worker for your Rails application
+by adding a file named `helm-values.yaml` to your repository 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.
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 7ab59b80374..35a5aff6a60 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -167,7 +167,7 @@ In the **test** stage, GitLab runs various checks on the application:
- The `sast` job runs static analysis on the current code to check for potential
security issues and is allowed to fail([Auto SAST](index.md#auto-sast-ultimate)) **(ULTIMATE)**
- The `license_management` job searches the application's dependencies to determine each of their
- licenses and is allowed to fail ([Auto License Management](index.md#auto-license-management-ultimate)) **(ULTIMATE)**
+ licenses and is allowed to fail ([Auto License Compliance](index.md#auto-license-compliance-ultimate)) **(ULTIMATE)**
NOTE: **Note:**
As you might have noticed, all jobs except `test` are allowed to fail in the
diff --git a/doc/university/README.md b/doc/university/README.md
index b17f40b91a5..25f77906e68 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -57,7 +57,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
1. [Migrating from SVN](../user/project/import/svn.md)
1. [Migrating from Fogbugz](../user/project/import/fogbugz.md)
-### 1.6. GitLab Inc.
+### 1.6. The GitLab team
1. [About GitLab](https://about.gitlab.com/about/)
1. [GitLab Direction](https://about.gitlab.com/direction/)
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 0506d992d4b..6a41c6aec32 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -41,7 +41,7 @@ sudo -u git -H git checkout -- Gemfile.lock db/schema.rb locale
sudo -u git -H git checkout LATEST_TAG -b LATEST_TAG
```
-### 3. Install libs, migrations, etc.
+### 3. Install libs, migrations, etc
```bash
cd /home/git/gitlab
diff --git a/doc/update/upgrading_from_ce_to_ee.md b/doc/update/upgrading_from_ce_to_ee.md
index bea5bcd9dd7..49e2fb2568e 100644
--- a/doc/update/upgrading_from_ce_to_ee.md
+++ b/doc/update/upgrading_from_ce_to_ee.md
@@ -54,7 +54,7 @@ sudo -u git -H git remote add -f ee https://gitlab.com/gitlab-org/gitlab-ee.git
sudo -u git -H git checkout EE_BRANCH
```
-### 3. Install libs, migrations, etc.
+### 3. Install libs, migrations, etc
```sh
cd /home/git/gitlab
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index df35638cba2..f6b64dcf694 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -313,7 +313,7 @@ For Ubuntu 16.04.1 LTS:
sudo systemctl daemon-reload
```
-### 13. Install libs, migrations, etc.
+### 13. Install libs, migrations, etc
```bash
cd /home/git/gitlab
diff --git a/doc/user/admin_area/abuse_reports.md b/doc/user/admin_area/abuse_reports.md
index 0c5d2f81e25..cd8dc7bcbf6 100644
--- a/doc/user/admin_area/abuse_reports.md
+++ b/doc/user/admin_area/abuse_reports.md
@@ -2,7 +2,7 @@
type: reference, howto
---
-# Abuse reports
+# Abuse reports **(CORE ONLY)**
View and resolve abuse reports from GitLab users.
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
index 01b6558bdbe..b0491499f88 100644
--- a/doc/user/admin_area/broadcast_messages.md
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -2,7 +2,7 @@
type: reference, howto
---
-# Broadcast Messages
+# Broadcast Messages **(CORE ONLY)**
GitLab can display messages to all users of a GitLab instance in a banner that appears in the UI.
diff --git a/doc/user/admin_area/diff_limits.md b/doc/user/admin_area/diff_limits.md
index 4063c40a751..9fe4b50a991 100644
--- a/doc/user/admin_area/diff_limits.md
+++ b/doc/user/admin_area/diff_limits.md
@@ -2,7 +2,7 @@
type: reference
---
-# Diff limits administration
+# Diff limits administration **(CORE ONLY)**
You can set a maximum size for display of diff files (patches).
diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md
index 35e7b6fb541..a3f4bc774ce 100644
--- a/doc/user/admin_area/monitoring/health_check.md
+++ b/doc/user/admin_area/monitoring/health_check.md
@@ -2,7 +2,7 @@
type: concepts, howto
---
-# Health Check
+# Health Check **(CORE ONLY)**
> - Liveness and readiness probes were [introduced][ce-10416] in GitLab 9.1.
> - The `health_check` endpoint was [introduced][ce-3888] in GitLab 8.8 and was
@@ -21,28 +21,71 @@ traffic until the system is ready or restart the container as needed.
To access monitoring resources, the requesting client IP needs to be included in a whitelist.
For details, see [how to add IPs to a whitelist for the monitoring endpoints](../../../administration/monitoring/ip_whitelist.md).
-## Using the endpoints
+## Using the endpoints locally
With default whitelist settings, the probes can be accessed from localhost using the following URLs:
-- `http://localhost/-/health`
-- `http://localhost/-/readiness`
-- `http://localhost/-/liveness`
+```text
+GET http://localhost/-/health
+```
+```text
+GET http://localhost/-/readiness
+```
+```text
+GET http://localhost/-/liveness
+```text
+GET http://localhost/-/health
+```
+
+```text
+GET http://localhost/-/readiness
+```
-The first endpoint, `health`, only checks whether the application server is running. It does not verify the database or other services are running. A successful response will return a 200 status code with the following message:
+```text
+GET http://localhost/-/liveness
+```
+
+## Health
+
+Checks whether the application server is running. It does not verify the database or other services are running.
+
+```text
+GET /-/health
+```
+
+Example request:
+
+```sh
+curl 'https://gitlab.example.com/-/health'
+```
+
+Example response:
```text
GitLab OK
```
-The readiness and liveness probes will provide a report of system health in JSON format.
+## Readiness
+
+The readiness probe checks whether the Gitlab instance is ready to use. It checks the dependent services (Database, Redis, Gitaly etc.) and gives a status for each.
+
+```text
+GET /-/readiness
+```
+
+Example request:
+
+```sh
+curl 'https://gitlab.example.com/-/readiness'
+```
-`readiness` probe example output:
+Example response:
```json
{
"db_check":{
- "status":"ok"
+ "status":"failed",
+ "message": "unexpected Db check result: 0"
},
"redis_check":{
"status":"ok"
@@ -65,7 +108,23 @@ The readiness and liveness probes will provide a report of system health in JSON
}
```
-`liveness` probe example output:
+## Liveness
+
+The liveness probe checks whether the application server is alive. Unlike the [`health`](#health) check, this check hits the database.
+
+```text
+GET /-/liveness
+```
+
+Example request:
+
+```sh
+curl 'https://gitlab.example.com/-/liveness'
+```
+
+Example response:
+
+On success, the endpoint will return a valid successful HTTP status code, and a response like below.
```json
{
@@ -90,10 +149,7 @@ The readiness and liveness probes will provide a report of system health in JSON
}
```
-## Status
-
-On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint
-will return a valid successful HTTP status code, and a `success` message.
+On failure, the endpoint will return a `500` HTTP status code.
## Access token (Deprecated)
diff --git a/doc/user/admin_area/settings/account_and_limit_settings.md b/doc/user/admin_area/settings/account_and_limit_settings.md
index 6faab685b26..5e385b7216d 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -2,7 +2,7 @@
type: reference
---
-# Account and limit settings
+# Account and limit settings **(CORE ONLY)**
## Max attachment size
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 490163c1816..ddf989d0181 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -2,7 +2,7 @@
type: reference
---
-# Email
+# Email **(CORE ONLY)**
You can customize some of the content in emails sent from your GitLab instance.
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
index b2d56be154b..8e53a6995fb 100644
--- a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -9,7 +9,7 @@ type: reference
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.
+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)
@@ -18,3 +18,5 @@ 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/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index 1b1bcbcd6e8..aea717e806d 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -2,7 +2,7 @@
type: reference
---
-# Sign-up restrictions
+# Sign-up restrictions **(CORE ONLY)**
You can use sign-up restrictions to require user email confirmation, as well as
to blacklist or whitelist email addresses belonging to specific domains.
diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md
index a1bce5a6c69..baf219ac9c7 100644
--- a/doc/user/admin_area/settings/terms.md
+++ b/doc/user/admin_area/settings/terms.md
@@ -2,7 +2,7 @@
type: reference
---
-# Enforce accepting Terms of Service
+# Enforce accepting Terms of Service **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/18570)
> in [GitLab Core](https://about.gitlab.com/pricing/) 10.8
diff --git a/doc/user/admin_area/settings/third_party_offers.md b/doc/user/admin_area/settings/third_party_offers.md
index d3c9cf7d8ff..ca26147b287 100644
--- a/doc/user/admin_area/settings/third_party_offers.md
+++ b/doc/user/admin_area/settings/third_party_offers.md
@@ -2,7 +2,7 @@
type: reference
---
-# Third party offers
+# Third party offers **(CORE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20379)
> in [GitLab Core](https://about.gitlab.com/pricing/) 11.1
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index 516600c9d99..efac7e699f3 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -2,7 +2,7 @@
type: reference
---
-# Usage statistics
+# Usage statistics **(CORE ONLY)**
GitLab Inc. will periodically collect information about your instance in order
to perform various actions.
diff --git a/doc/user/admin_area/settings/user_and_ip_rate_limits.md b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
index e3a495750f2..b9d93bf3671 100644
--- a/doc/user/admin_area/settings/user_and_ip_rate_limits.md
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -2,7 +2,7 @@
type: reference
---
-# User and IP rate limits
+# User and IP rate limits **(CORE ONLY)**
Rate limiting is a common technique used to improve the security and durability
of a web application. For more details, see
diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md
index bf59f49b993..1e2f5705728 100644
--- a/doc/user/admin_area/settings/visibility_and_access_controls.md
+++ b/doc/user/admin_area/settings/visibility_and_access_controls.md
@@ -2,7 +2,7 @@
type: reference
---
-# Visibility and access controls
+# Visibility and access controls **(CORE ONLY)**
GitLab allows administrators to:
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/application_security/index.md b/doc/user/application_security/index.md
index 83ea0ea3386..fcd683ca2db 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -28,7 +28,7 @@ GitLab can scan and report any vulnerabilities found in your project.
| [Dependency List](dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
-| [License Management](license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
+| [License Compliance](license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
| [Security Dashboard](security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all your projects and groups. |
| [Static Application Security Testing (SAST)](sast/index.md) **(ULTIMATE)** | Analyze source code for known vulnerabilities. |
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index c324848c703..912f2f0e209 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -2,7 +2,7 @@
type: reference, howto
---
-# License Management **(ULTIMATE)**
+# License Compliance **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
@@ -10,18 +10,18 @@ in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.0.
## Overview
If you are using [GitLab CI/CD](../../../ci/README.md), you can search your project dependencies for their licenses
-using License Management.
+using License Compliance.
-You can take advantage of License Management by either [including the job](#configuration)
+You can take advantage of License Compliance by either [including the job](#configuration)
in your existing `.gitlab-ci.yml` file or by implicitly using
-[Auto License Management](../../../topics/autodevops/index.md#auto-license-management-ultimate)
+[Auto License Compliance](../../../topics/autodevops/index.md#auto-license-compliance-ultimate)
that is provided by [Auto DevOps](../../../topics/autodevops/index.md).
-GitLab checks the License Management report, compares the licenses between the
+GitLab checks the License Compliance report, compares the licenses between the
source and target branches, and shows the information right on the merge request.
Blacklisted licenses will be clearly visible with an `x` red icon next to them
as well as new licenses which need a decision from you. In addition, you can
-[manually approve or blacklist](#project-policies-for-license-management)
+[manually approve or blacklist](#project-policies-for-license-compliance)
licenses in your project's settings.
NOTE: **Note:**
@@ -31,7 +31,7 @@ will be displayed in the merge request area. That is the case when you add the
Consecutive merge requests will have something to compare to and the license
management report will be shown properly.
-![License Management Widget](img/license_management.png)
+![License Compliance Widget](img/license_management.png)
If you are a project or group Maintainer, you can click on a license to be given
the choice to approve it or blacklist it.
@@ -66,12 +66,12 @@ The following languages and package managers are supported.
## Requirements
-To run a License Management scanning job, you need GitLab Runner with the
+To run a License Compliance scanning job, you need GitLab Runner with the
[`docker` executor](https://docs.gitlab.com/runner/executors/docker.html).
## Configuration
-For GitLab 11.9 and later, to enable License Management, you must
+For GitLab 11.9 and later, to enable License Compliance, you must
[include](../../../ci/yaml/README.md#includetemplate) the
[`License-Management.gitlab-ci.yml` template](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/lib/gitlab/ci/templates/Security/License-Management.gitlab-ci.yml)
that's provided as a part of your GitLab installation.
@@ -89,14 +89,14 @@ The included template will create a `license_management` job in your CI/CD pipel
and scan your dependencies to find their licenses.
The results will be saved as a
-[License Management report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
+[License Compliance report artifact](../../../ci/yaml/README.md#artifactsreportslicense_management-ultimate)
that you can later download and analyze. Due to implementation limitations, we
-always take the latest License Management artifact available. Behind the scenes, the
-[GitLab License Management Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
+always take the latest License Compliance artifact available. Behind the scenes, the
+[GitLab License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/license-management)
is used to detect the languages/frameworks and in turn analyzes the licenses.
-The License Management settings can be changed through environment variables by using the
-[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Management documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
+The License Compliance settings can be changed through environment variables by using the
+[`variables`](../../../ci/yaml/README.md#variables) parameter in `.gitlab-ci.yml`. These variables are documented in the [License Compliance documentation](https://gitlab.com/gitlab-org/security-products/license-management#settings).
### Installing custom dependencies
@@ -143,7 +143,7 @@ license_management:
### Configuring Maven projects
-The License Management tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
+The License Compliance tool provides a `MAVEN_CLI_OPTS` environment variable which can hold
the command line arguments to pass to the `mvn install` command which is executed under the hood.
Feel free to use it for the customization of Maven execution. For example:
@@ -169,7 +169,7 @@ If you still need to run tests during `mvn install`, add `-DskipTests=false` to
> [Introduced](https://gitlab.com/gitlab-org/security-products/license-management/merge_requests/36) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
-License Management uses Python 2.7 and pip 10.0 by default.
+License Compliance uses Python 2.7 and pip 10.0 by default.
If your project requires Python 3, you can switch to Python 3.5 and pip 19.1
by setting the `LM_PYTHON_VERSION` environment variable to `3`.
@@ -182,7 +182,7 @@ license_management:
LM_PYTHON_VERSION: 3
```
-## Project policies for License Management
+## Project policies for License Compliance
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5940)
in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.4.
@@ -196,10 +196,10 @@ To approve or blacklist a license:
1. Either use the **Manage licenses** button in the merge request widget, or
navigate to the project's **Settings > CI/CD** and expand the
- **License Management** section.
+ **License Compliance** section.
1. Click the **Add a license** button.
- ![License Management Add License](img/license_management_add_license.png)
+ ![License Compliance Add License](img/license_management_add_license.png)
1. In the **License name** dropdown, either:
- Select one of the available licenses. You can search for licenses in the field
@@ -211,17 +211,17 @@ To approve or blacklist a license:
To modify an existing license:
-1. In the **License Management** list, click the **Approved/Declined** dropdown to change it to the desired status.
+1. In the **License Compliance** list, click the **Approved/Declined** dropdown to change it to the desired status.
- ![License Management Settings](img/license_management_settings.png)
+ ![License Compliance Settings](img/license_management_settings.png)
Searching for Licenses:
1. Use the **Search** box to search for a specific license.
- ![License Management Search](img/license_management_search.png)
+ ![License Compliance Search](img/license_management_search.png)
-## License Management report under pipelines
+## License Compliance report under pipelines
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5491)
in [GitLab Ultimate](https://about.gitlab.com/pricing/) 11.2.
@@ -230,7 +230,7 @@ From your project's left sidebar, navigate to **CI/CD > Pipelines** and click on
pipeline ID that has a `license_management` job to see the Licenses tab with the listed
licenses (if any).
-![License Management Pipeline Tab](img/license_management_pipeline_tab.png)
+![License Compliance Pipeline Tab](img/license_management_pipeline_tab.png)
<!-- ## Troubleshooting
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..af37cc896ad 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -43,13 +43,15 @@ Host gitlab.com
Below are the settings for [GitLab Pages].
-| Setting | GitLab.com | Default |
-| ----------------------- | ---------------- | ------------- |
-| Domain name | `gitlab.io` | - |
-| IP address | `35.185.44.232` | - |
-| Custom domains support | yes | no |
-| TLS certificates support| yes | no |
+| Setting | GitLab.com | Default |
+| --------------------------- | ---------------- | ------------- |
+| Domain name | `gitlab.io` | - |
+| IP address | `35.185.44.232` | - |
+| Custom domains support | yes | no |
+| TLS certificates support | yes | no |
+| Maximum size (uncompressed) | 1G | 100M |
+NOTE: **Note:**
The maximum size of your Pages site is regulated by the artifacts maximum size
which is part of [GitLab CI/CD](#gitlab-cicd).
@@ -59,7 +61,7 @@ Below are the current settings regarding [GitLab CI/CD](../../ci/README.md).
| Setting | GitLab.com | Default |
| ----------- | ----------------- | ------------- |
-| Artifacts maximum size | 1G | 100M |
+| Artifacts maximum size (uncompressed) | 1G | 100M |
| Artifacts [expiry time](../../ci/yaml/README.md#artifactsexpire_in) | kept forever | deleted after 30 days unless otherwise specified |
## Repository size limit
@@ -112,57 +114,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 +129,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 +158,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 d1d4f3740b0..864f1a397d5 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -342,7 +342,7 @@ underlying projects, issues, etc, by IP address. This can help ensure that
particular content doesn't leave the premises, while not blocking off access to
the entire instance.
-Add whitelisted IP subnet using CIDR notation to the group settings and anyone
+Add one or more whitelisted IP subnets using CIDR notation in comma separated format to the group settings and anyone
coming from a different IP address won't be able to access the restricted
content.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index e3f657af564..bd50367681e 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -39,6 +39,25 @@ However, users will not be prompted to log via SSO on each visit. GitLab will ch
We intend to add a similar SSO requirement for [Git and API activity](https://gitlab.com/gitlab-org/gitlab-ee/issues/9152) in the future.
+#### Group-managed accounts
+
+[Introduced in GitLab 12.1](https://gitlab.com/groups/gitlab-org/-/epics/709).
+
+When SSO is being enforced, groups can enable an additional level of protection by enforcing the creation of dedicated user accounts to access the group.
+
+Without group-managed accounts, users can link their SAML identity with any existing user on the instance. With group-managed accounts enabled, users are required to create a new, dedicated user linked to the group. The notification email address associated with the user is locked to the email address received from the configured identity provider.
+
+When this option is enabled:
+
+- All existing and new users in the group will be required to log in via the SSO URL associated with the group.
+- On successfully authenticating, GitLab will prompt the user to create a new, dedicated account using the email address received from the configured identity provider.
+- After the group managed account has been created, group activity will require the use of this user account.
+
+Since use of the group managed account requires the use of SSO, users of group managed accounts will lose access to these accounts when they are no longer able to authenticate with the connected identity provider. In the case of an offboarded employee who has been removed from your identity provider:
+
+- The user will be unable to access the group (their credentials will no longer work on the identity provider when prompted to SSO).
+- Contributions in the group (e.g. issues, merge requests) will remain intact.
+
### NameID
GitLab.com uses the SAML NameID to identify users. The NameID element:
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/markdown.md b/doc/user/markdown.md
index 6cfc8b6429b..17bbed2945d 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -57,12 +57,6 @@ render incorrectly:
- milk
```
-1. Chocolate
- - dark
- - milk
-
----
-
Simply add a space to each nested item to align the `-` with the first character of
the top list item (`C` in this case):
@@ -265,8 +259,7 @@ this font installed by default.
### Front matter
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331)
- in GitLab 11.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331) in GitLab 11.6.
Front matter is metadata included at the beginning of a markdown document, preceding
its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/),
@@ -866,18 +859,6 @@ or underscores
___
```
-Three or more hyphens,
-
----
-
-asterisks,
-
-***
-
-or underscores
-
-___
-
### Images
Examples:
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 4fd7c5abf78..80d1bf992ec 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -45,7 +45,7 @@ The following table depicts the various user permission levels in a project.
| Leave comments | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Insights charts **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View approved/blacklisted licenses **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
-| View license management reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View License Compliance reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Security reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View [Design Management](project/issues/design_management.md) pages **(PREMIUM)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
@@ -162,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
@@ -237,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/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index e2b1a20a605..f7ba921aa7d 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -48,6 +48,7 @@ To enable 2FA:
- [andOTP](https://github.com/andOTP/andOTP): feature rich open source app for Android which supports PGP encrypted backups.
- [FreeOTP](https://freeotp.github.io/): open source app for Android.
- [Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en): proprietary app for iOS and Android.
+ - [SailOTP](https://openrepos.net/content/seiichiro0185/sailotp): open source app for SailFish OS.
1. In the application, add a new entry in one of two ways:
- Scan the code presented in GitLab with your device's camera to add the
entry automatically.
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/index.md b/doc/user/project/index.md
index 30ff0e9ff07..932e7fd10b2 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -98,7 +98,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Maven packages](packages/maven_repository.md): your private Maven repository in GitLab. **(PREMIUM)**
- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **(PREMIUM)**
- [Code owners](code_owners.md): specify code owners for certain files **(STARTER)**
-- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
+- [License Compliance](../application_security/license_management/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
### Project integrations
diff --git a/doc/user/project/integrations/img/download_as_csv.png b/doc/user/project/integrations/img/download_as_csv.png
new file mode 100644
index 00000000000..0ed5ab8db89
--- /dev/null
+++ b/doc/user/project/integrations/img/download_as_csv.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..9fc0ade809e 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -269,6 +269,12 @@ Note the following properties:
![single stat panel type](img/prometheus_dashboard_single_stat_panel_type.png)
+### Downloading data as CSV
+
+Data from Prometheus charts on the metrics dashboard can be downloaded as CSV.
+
+![Downloading as CSV](img/download_as_csv.png)
+
### Setting up alerts for Prometheus metrics **(ULTIMATE)**
#### Managed Prometheus instances
@@ -354,7 +360,7 @@ Prometheus server.
![Merge Request with Performance Impact](img/merge_request_performance.png)
-## Embedding metric charts within Gitlab Flavored Markdown
+## Embedding metric charts within GitLab Flavored Markdown
> [Introduced][ce-29691] in GitLab 12.2.
> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
@@ -363,6 +369,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/members/img/access_requests_management.png b/doc/user/project/members/img/access_requests_management.png
index 8996d9564d7..9a1c9621e41 100644
--- a/doc/user/project/members/img/access_requests_management.png
+++ b/doc/user/project/members/img/access_requests_management.png
Binary files differ
diff --git a/doc/user/project/members/img/request_access_button.png b/doc/user/project/members/img/request_access_button.png
index e8b490b91b8..e693f9a9ac2 100644
--- a/doc/user/project/members/img/request_access_button.png
+++ b/doc/user/project/members/img/request_access_button.png
Binary files differ
diff --git a/doc/user/project/members/img/withdraw_access_request_button.png b/doc/user/project/members/img/withdraw_access_request_button.png
index 6a3172dfcdb..e5a8fe0b356 100644
--- a/doc/user/project/members/img/withdraw_access_request_button.png
+++ b/doc/user/project/members/img/withdraw_access_request_button.png
Binary files differ
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/approvals_premium_project_edit.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
deleted file mode 100644
index 6a09412533f..00000000000
--- a/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
new file mode 100644
index 00000000000..32b9a3b9ce4
--- /dev/null
+++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit_v12_3.png
Binary files differ
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 8a82b163481..04db54872d3 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -41,7 +41,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- View the deployment process across projects with [Multi-Project Pipelines](../../../ci/multi_project_pipelines.md) **(PREMIUM)**
- Request [approvals](merge_request_approvals.md) from your managers **(STARTER)**
- Analyze the impact of your changes with [Code Quality reports](code_quality.md) **(STARTER)**
-- Manage the licenses of your dependencies with [License Management](../../application_security/license_management/index.md) **(ULTIMATE)**
+- Manage the licenses of your dependencies with [License Compliance](../../application_security/license_management/index.md) **(ULTIMATE)**
- Analyze your source code for vulnerabilities with [Static Application Security Testing](../../application_security/sast/index.md) **(ULTIMATE)**
- Analyze your running web applications for vulnerabilities with [Dynamic Application Security Testing](../../application_security/dast/index.md) **(ULTIMATE)**
- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **(ULTIMATE)**
@@ -57,7 +57,7 @@ A. Consider you are a software developer working in a team:
1. You gather feedback from your team
1. You work on the implementation optimizing code with [Code Quality reports](code_quality.md) **(STARTER)**
1. You verify your changes with [JUnit test reports](../../../ci/junit_test_reports.md) in GitLab CI/CD
-1. You avoid using dependencies whose license is not compatible with your project with [License Management reports](license_management.md) **(ULTIMATE)**
+1. You avoid using dependencies whose license is not compatible with your project with [License Compliance reports](license_management.md) **(ULTIMATE)**
1. You request the [approval](#merge-request-approvals-starter) from your manager
1. Your manager pushes a commit with their final review, [approves the merge request](merge_request_approvals.md), and set it to [merge when pipeline succeeds](#merge-when-pipeline-succeeds) (Merge Request Approvals are available in GitLab Starter)
1. Your changes get deployed to production with [manual actions](../../../ci/yaml/README.md#whenmanual) for GitLab CI/CD
@@ -497,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_approvals.md b/doc/user/project/merge_requests/merge_request_approvals.md
index 5161b25de99..a0432414bcf 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -83,7 +83,7 @@ request approval rules:
1. Give the approval rule a name that describes the set of approvers selected.
1. Click **Add approvers** to submit the new rule.
- ![Approvals premium project edit](img/approvals_premium_project_edit.png)
+ ![Approvals premium project edit](img/approvals_premium_project_edit_v12_3.png)
## Multiple approval rules **(PREMIUM)**
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/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/git_annex.md b/doc/workflow/git_annex.md
index 9543859f86f..cdc38a5b7eb 100644
--- a/doc/workflow/git_annex.md
+++ b/doc/workflow/git_annex.md
@@ -200,7 +200,7 @@ searching for your distribution.
Although there is no general guide for `git-annex` errors, there are a few tips
on how to go around the warnings.
-### git-annex-shell: Not a git-annex or gcrypt repository.
+### git-annex-shell: Not a git-annex or gcrypt repository
This warning can appear on the initial `git annex sync --content` and is caused
by differences in `git-annex-shell`. You can read more about it
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 ccb8844aea3..5abae929355 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -53,7 +53,7 @@ These settings can be configured on group page under the name of the group. It w
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.
+and the notification button will be disabled with an explanatory tooltip.
### Project Settings
@@ -66,7 +66,7 @@ These settings can be configured on project page under the name of the project.
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.
+button will be disabled with an explanatory tooltip.
## Notification events
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/award_emoji.rb b/lib/api/award_emoji.rb
index a1851ba3627..89b7e5c5e4b 100644
--- a/lib/api/award_emoji.rb
+++ b/lib/api/award_emoji.rb
@@ -69,12 +69,12 @@ module API
post endpoint do
not_found!('Award Emoji') unless can_read_awardable? && can_award_awardable?
- award = awardable.create_award_emoji(params[:name], current_user)
+ service = AwardEmojis::AddService.new(awardable, params[:name], current_user).execute
- if award.persisted?
- present award, with: Entities::AwardEmoji
+ if service[:status] == :success
+ present service[:award], with: Entities::AwardEmoji
else
- not_found!("Award Emoji #{award.errors.messages}")
+ not_found!("Award Emoji #{service[:message]}")
end
end
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/helpers/issues_helpers.rb b/lib/api/helpers/issues_helpers.rb
index 5b7199fddb0..a8480bb9339 100644
--- a/lib/api/helpers/issues_helpers.rb
+++ b/lib/api/helpers/issues_helpers.rb
@@ -27,6 +27,10 @@ module API
]
end
+ def self.sort_options
+ %w[created_at updated_at priority due_date relative_position label_priority milestone_due popularity]
+ end
+
def issue_finder(args = {})
args = declared_params.merge(args)
@@ -34,15 +38,14 @@ module API
args[:milestone_title] ||= args.delete(:milestone)
args[:label_name] ||= args.delete(:labels)
args[:scope] = args[:scope].underscore if args[:scope]
+ args[:sort] = "#{args[:order_by]}_#{args[:sort]}"
IssuesFinder.new(current_user, args)
end
def find_issues(args = {})
finder = issue_finder(args)
- issues = finder.execute.with_api_entity_associations
-
- issues.reorder(order_options_with_tie_breaker) # rubocop: disable CodeReuse/ActiveRecord
+ finder.execute.with_api_entity_associations
end
def issues_statistics(args = {})
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index 896b0aba52b..ec5b688dd1c 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -11,9 +11,9 @@ module API
optional :description, type: String, desc: 'The description of label to be created'
end
- def find_label(parent, id, include_ancestor_groups: true)
+ def find_label(parent, id_or_title, include_ancestor_groups: true)
labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
- label = labels.find_by_id(id) || labels.find_by_title(id)
+ label = labels.find_by_id(id_or_title) || labels.find_by_title(id_or_title)
label || not_found!('Label')
end
@@ -35,12 +35,7 @@ module API
priority = params.delete(:priority)
label_params = declared_params(include_missing: false)
- label =
- if parent.is_a?(Project)
- ::Labels::CreateService.new(label_params).execute(project: parent)
- else
- ::Labels::CreateService.new(label_params).execute(group: parent)
- end
+ label = ::Labels::CreateService.new(label_params).execute(create_service_params(parent))
if label.persisted?
if parent.is_a?(Project)
@@ -56,10 +51,13 @@ module API
def update_label(parent, entity)
authorize! :admin_label, parent
- label = find_label(parent, params[:name], include_ancestor_groups: false)
+ label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
update_priority = params.key?(:priority)
priority = params.delete(:priority)
+ # params is used to update the label so we need to remove this field here
+ params.delete(:label_id)
+
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
render_validation_error!(label) unless label.valid?
@@ -77,10 +75,24 @@ module API
def delete_label(parent)
authorize! :admin_label, parent
- label = find_label(parent, params[:name], include_ancestor_groups: false)
+ label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
destroy_conditionally!(label)
end
+
+ def params_id_or_title
+ @params_id_or_title ||= params[:label_id] || params[:name]
+ end
+
+ def create_service_params(parent)
+ if parent.is_a?(Project)
+ { project: parent }
+ elsif parent.is_a?(Group)
+ { group: parent }
+ else
+ raise TypeError, 'Parent type is not supported'
+ end
+ end
end
end
end
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 6bf9057fad7..b2bf6bf7417 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -3,6 +3,8 @@
module API
module Helpers
module NotesHelpers
+ include ::RendersNotes
+
def self.noteable_types
# This is a method instead of a constant, allowing EE to more easily
# extend it.
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index d687acf3423..e16eeef202c 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -44,7 +44,7 @@ module API
optional :with_labels_details, type: Boolean, desc: 'Return more label data than just lable title', default: false
optional :state, type: String, values: %w[opened closed all], default: 'all',
desc: 'Return opened, closed, or all issues'
- optional :order_by, type: String, values: %w[created_at updated_at], default: 'created_at',
+ optional :order_by, type: String, values: Helpers::IssuesHelpers.sort_options, default: 'created_at',
desc: 'Return issues ordered by `created_at` or `updated_at` fields.'
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return issues sorted in `asc` or `desc` order.'
@@ -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/api/labels.rb b/lib/api/labels.rb
index c183198d3c6..83d645ca07a 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -38,11 +38,13 @@ module API
success Entities::ProjectLabel
end
params do
- requires :name, type: String, desc: 'The name of the label to be updated'
+ optional :label_id, type: Integer, desc: 'The id of the label to be updated'
+ optional :name, type: String, desc: 'The name of the label to be updated'
optional :new_name, type: String, desc: 'The new name of the label'
optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
optional :description, type: String, desc: 'The new description of label'
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
+ exactly_one_of :label_id, :name
at_least_one_of :new_name, :color, :description, :priority
end
put ':id/labels' do
@@ -53,7 +55,9 @@ module API
success Entities::ProjectLabel
end
params do
- requires :name, type: String, desc: 'The name of the label to be deleted'
+ optional :label_id, type: Integer, desc: 'The id of the label to be deleted'
+ optional :name, type: String, desc: 'The name of the label to be deleted'
+ exactly_one_of :label_id, :name
end
delete ':id/labels' do
delete_label(user_project)
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index 9381f045144..84563d66ee8 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -36,12 +36,13 @@ module API
# page can have less elements than :per_page even if
# there's more than one page.
raw_notes = noteable.notes.with_metadata.reorder(order_options_with_tie_breaker)
- notes =
- # paginate() only works with a relation. This could lead to a
- # mismatch between the pagination headers info and the actual notes
- # array returned, but this is really a edge-case.
- paginate(raw_notes)
- .reject { |n| n.cross_reference_not_visible_for?(current_user) }
+
+ # paginate() only works with a relation. This could lead to a
+ # mismatch between the pagination headers info and the actual notes
+ # array returned, but this is really a edge-case.
+ notes = paginate(raw_notes)
+ notes = prepare_notes_for_rendering(notes)
+ notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
present notes, with: Entities::Note
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/pipelines.rb b/lib/api/pipelines.rb
index 667bf1ec801..9e888368e7b 100644
--- a/lib/api/pipelines.rb
+++ b/lib/api/pipelines.rb
@@ -4,7 +4,7 @@ module API
class Pipelines < Grape::API
include PaginationParams
- before { authenticate! }
+ before { authenticate_non_get! }
params do
requires :id, type: String, desc: 'The project ID'
@@ -32,6 +32,7 @@ module API
end
get ':id/pipelines' do
authorize! :read_pipeline, user_project
+ authorize! :read_build, user_project
pipelines = PipelinesFinder.new(user_project, current_user, params).execute
present paginate(pipelines), with: Entities::PipelineBasic
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 996205d4b7b..3073c14b341 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -489,11 +489,13 @@ module API
end
params do
optional :search, type: String, desc: 'Return list of users matching the search criteria'
+ optional :skip_users, type: Array[Integer], desc: 'Filter out users with the specified IDs'
use :pagination
end
get ':id/users' do
users = DeclarativePolicy.subject_scope { user_project.team.users }
users = users.search(params[:search]) if params[:search].present?
+ users = users.where_not_in(params[:skip_users]) if params[:skip_users].present?
present paginate(users), with: Entities::UserBasic
end
diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb
index 9ded1aed4e3..656becbffd3 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -6,8 +6,11 @@ class Feature
class Gitaly
# Server feature flags should use '_' to separate words.
SERVER_FEATURE_FLAGS =
- [
- # 'get_commit_signatures'.freeze
+ %w[
+ get_commit_signatures
+ cache_invalidator
+ inforef_uploadpack_cache
+ get_all_lfs_pointers_go
].freeze
DEFAULT_ON_FLAGS = Set.new([]).freeze
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/analytics/cycle_analytics/default_stages.rb b/lib/gitlab/analytics/cycle_analytics/default_stages.rb
new file mode 100644
index 00000000000..286c393005f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/default_stages.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+# This module represents the default Cycle Analytics stages that are currently provided by CE
+# Each method returns a hash that can be used to build a new stage object.
+#
+# Example:
+#
+# params = Gitlab::Analytics::CycleAnalytics::DefaultStages.params_for_issue_stage
+# Analytics::CycleAnalytics::ProjectStage.new(params)
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module DefaultStages
+ def self.all
+ [
+ params_for_issue_stage,
+ params_for_plan_stage,
+ params_for_code_stage,
+ params_for_test_stage,
+ params_for_review_stage,
+ params_for_staging_stage,
+ params_for_production_stage
+ ]
+ end
+
+ def self.params_for_issue_stage
+ {
+ name: 'issue',
+ custom: false, # this stage won't be customizable, we provide it as it is
+ relative_position: 1, # when opening the CycleAnalytics page in CE, this stage will be the first item
+ start_event_identifier: :issue_created, # IssueCreated class is used as start event
+ end_event_identifier: :issue_stage_end # IssueStageEnd class is used as end event
+ }
+ end
+
+ def self.params_for_plan_stage
+ {
+ name: 'plan',
+ custom: false,
+ relative_position: 2,
+ start_event_identifier: :plan_stage_start,
+ end_event_identifier: :issue_first_mentioned_in_commit
+ }
+ end
+
+ def self.params_for_code_stage
+ {
+ name: 'code',
+ custom: false,
+ relative_position: 3,
+ start_event_identifier: :code_stage_start,
+ end_event_identifier: :merge_request_created
+ }
+ end
+
+ def self.params_for_test_stage
+ {
+ name: 'test',
+ custom: false,
+ relative_position: 4,
+ start_event_identifier: :merge_request_last_build_started,
+ end_event_identifier: :merge_request_last_build_finished
+ }
+ end
+
+ def self.params_for_review_stage
+ {
+ name: 'review',
+ custom: false,
+ relative_position: 5,
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged
+ }
+ end
+
+ def self.params_for_staging_stage
+ {
+ name: 'staging',
+ custom: false,
+ relative_position: 6,
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_first_deployed_to_production
+ }
+ end
+
+ def self.params_for_production_stage
+ {
+ name: 'production',
+ custom: false,
+ relative_position: 7,
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_first_deployed_to_production
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events.rb b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
new file mode 100644
index 00000000000..d21f344f483
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Convention:
+ # Issue: < 100
+ # MergeRequest: >= 100 && < 1000
+ # Custom events for default stages: >= 1000 (legacy)
+ ENUM_MAPPING = {
+ StageEvents::IssueCreated => 1,
+ StageEvents::IssueFirstMentionedInCommit => 2,
+ StageEvents::MergeRequestCreated => 100,
+ StageEvents::MergeRequestFirstDeployedToProduction => 101,
+ StageEvents::MergeRequestLastBuildFinished => 102,
+ StageEvents::MergeRequestLastBuildStarted => 103,
+ StageEvents::MergeRequestMerged => 104,
+ StageEvents::CodeStageStart => 1_000,
+ StageEvents::IssueStageEnd => 1_001,
+ StageEvents::PlanStageStart => 1_002
+ }.freeze
+
+ EVENTS = ENUM_MAPPING.keys.freeze
+
+ # Defines which start_event and end_event pairs are allowed
+ PAIRING_RULES = {
+ StageEvents::PlanStageStart => [
+ StageEvents::IssueFirstMentionedInCommit
+ ],
+ StageEvents::CodeStageStart => [
+ StageEvents::MergeRequestCreated
+ ],
+ StageEvents::IssueCreated => [
+ StageEvents::IssueStageEnd
+ ],
+ StageEvents::MergeRequestCreated => [
+ StageEvents::MergeRequestMerged
+ ],
+ StageEvents::MergeRequestLastBuildStarted => [
+ StageEvents::MergeRequestLastBuildFinished
+ ],
+ StageEvents::MergeRequestMerged => [
+ StageEvents::MergeRequestFirstDeployedToProduction
+ ]
+ }.freeze
+
+ def [](identifier)
+ events.find { |e| e.identifier.to_s.eql?(identifier.to_s) } || raise(KeyError)
+ end
+
+ # hash for defining ActiveRecord enum: identifier => number
+ def to_enum
+ ENUM_MAPPING.each_with_object({}) { |(k, v), hash| hash[k.identifier] = v }
+ end
+
+ # will be overridden in EE with custom events
+ def pairing_rules
+ PAIRING_RULES
+ end
+
+ # will be overridden in EE with custom events
+ def events
+ EVENTS
+ end
+
+ module_function :[], :to_enum, :pairing_rules, :events
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
new file mode 100644
index 00000000000..ff9c8a79225
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/code_stage_start.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class CodeStageStart < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
+ end
+
+ def self.identifier
+ :code_stage_start
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb
new file mode 100644
index 00000000000..a601c9797f8
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_created.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueCreated < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue created")
+ end
+
+ def self.identifier
+ :issue_created
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb
new file mode 100644
index 00000000000..7424043ef7b
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_first_mentioned_in_commit.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueFirstMentionedInCommit < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first mentioned in a commit")
+ end
+
+ def self.identifier
+ :issue_first_mentioned_in_commit
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
new file mode 100644
index 00000000000..ceb229c552f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/issue_stage_end.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class IssueStageEnd < SimpleStageEvent
+ def self.name
+ PlanStageStart.name
+ end
+
+ def self.identifier
+ :issue_stage_end
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb
new file mode 100644
index 00000000000..8be00831b4f
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_created.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestCreated < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request created")
+ end
+
+ def self.identifier
+ :merge_request_created
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb
new file mode 100644
index 00000000000..6d7a2c023ff
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_first_deployed_to_production.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestFirstDeployedToProduction < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request first deployed to production")
+ end
+
+ def self.identifier
+ :merge_request_first_deployed_to_production
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb
new file mode 100644
index 00000000000..12d82fe2c62
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_finished.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestLastBuildFinished < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request last build finish time")
+ end
+
+ def self.identifier
+ :merge_request_last_build_finished
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb
new file mode 100644
index 00000000000..9e749b0fdfa
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_last_build_started.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestLastBuildStarted < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request last build start time")
+ end
+
+ def self.identifier
+ :merge_request_last_build_started
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb
new file mode 100644
index 00000000000..bbfb5d12992
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/merge_request_merged.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class MergeRequestMerged < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Merge request merged")
+ end
+
+ def self.identifier
+ :merge_request_merged
+ end
+
+ def object_type
+ MergeRequest
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
new file mode 100644
index 00000000000..803317d8b55
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/plan_stage_start.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ class PlanStageStart < SimpleStageEvent
+ def self.name
+ s_("CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board")
+ end
+
+ def self.identifier
+ :plan_stage_start
+ end
+
+ def object_type
+ Issue
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb
new file mode 100644
index 00000000000..253c489d822
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/simple_stage_event.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Represents a simple event that usually refers to one database column and does not require additional user input
+ class SimpleStageEvent < StageEvent
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
new file mode 100644
index 00000000000..a55eee048c2
--- /dev/null
+++ b/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Analytics
+ module CycleAnalytics
+ module StageEvents
+ # Base class for expressing an event that can be used for a stage.
+ class StageEvent
+ def initialize(params)
+ @params = params
+ end
+
+ def self.name
+ raise NotImplementedError
+ end
+
+ def self.identifier
+ raise NotImplementedError
+ end
+
+ def object_type
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 82e0c7ceeaa..e17a096ef19 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -46,7 +46,7 @@ module Gitlab
user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new
- rate_limit!(ip, success: result.success?, login: login)
+ rate_limit!(ip, success: result.success?, login: login) unless skip_rate_limit?(login: login)
Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
return result if result.success? || authenticate_using_internal_or_ldap_password?
@@ -119,6 +119,10 @@ module Gitlab
private
+ def skip_rate_limit?(login:)
+ ::Ci::Build::CI_REGISTRY_USER == login
+ end
+
def authenticate_using_internal_or_ldap_password?
Gitlab::CurrentSettings.password_authentication_enabled_for_git? || Gitlab::Auth::LDAP::Config.enabled?
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/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
index 942e4e55323..f7b0720d4a9 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb
@@ -11,8 +11,9 @@ module Gitlab
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
+ return false unless regexp
- regexp.scan(text.to_s).any?
+ regexp.scan(text.to_s).present?
end
def self.build(_value, behind, ahead)
diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
index 831c27fa0ea..02479ed28a4 100644
--- a/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
+++ b/lib/gitlab/ci/pipeline/expression/lexeme/not_matches.rb
@@ -11,8 +11,9 @@ module Gitlab
def evaluate(variables = {})
text = @left.evaluate(variables)
regexp = @right.evaluate(variables)
+ return true unless regexp
- regexp.scan(text.to_s).none?
+ regexp.scan(text.to_s).empty?
end
def self.build(_value, behind, ahead)
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index 7ec03d132c0..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,13 @@ 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_hash = @attributes.to_h
+ 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?)
@@ -73,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?
@@ -109,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/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/daemon.rb b/lib/gitlab/daemon.rb
index 6d5fc4219fb..2f4ae010e74 100644
--- a/lib/gitlab/daemon.rb
+++ b/lib/gitlab/daemon.rb
@@ -46,7 +46,10 @@ module Gitlab
if thread
thread.wakeup if thread.alive?
- thread.join unless Thread.current == thread
+ begin
+ thread.join unless Thread.current == thread
+ rescue Exception # rubocop:disable Lint/RescueException
+ end
@thread = nil
end
end
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/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 9bba4f6ce1e..8bc45f6e78c 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -747,6 +747,11 @@ module Gitlab
EOF
execute <<-EOF.strip_heredoc
+ DROP TRIGGER IF EXISTS #{trigger}
+ ON #{table}
+ EOF
+
+ execute <<-EOF.strip_heredoc
CREATE TRIGGER #{trigger}
BEFORE INSERT OR UPDATE
ON #{table}
diff --git a/lib/gitlab/database_importers/self_monitoring/project/create_service.rb b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
new file mode 100644
index 00000000000..164854e1e1a
--- /dev/null
+++ b/lib/gitlab/database_importers/self_monitoring/project/create_service.rb
@@ -0,0 +1,251 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DatabaseImporters
+ module SelfMonitoring
+ module Project
+ include Stepable
+
+ class CreateService < ::BaseService
+ include Stepable
+
+ STEPS_ALLOWED_TO_FAIL = [
+ :validate_application_settings, :validate_project_created, :validate_admins
+ ].freeze
+
+ VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
+ PROJECT_NAME = 'GitLab Instance Administration'
+
+ steps :validate_application_settings,
+ :validate_project_created,
+ :validate_admins,
+ :create_group,
+ :create_project,
+ :save_project_id,
+ :add_group_members,
+ :add_to_whitelist,
+ :add_prometheus_manual_configuration
+
+ def initialize
+ super(nil)
+ end
+
+ def execute!
+ result = execute_steps
+
+ if result[:status] == :success
+ result
+ elsif STEPS_ALLOWED_TO_FAIL.include?(result[:failed_step])
+ success
+ else
+ raise StandardError, result[:message]
+ end
+ end
+
+ private
+
+ def validate_application_settings
+ return success if application_settings
+
+ log_error(_('No application_settings found'))
+ error(_('No application_settings found'))
+ end
+
+ def validate_project_created
+ return success unless project_created?
+
+ log_error(_('Project already created'))
+ error(_('Project already created'))
+ end
+
+ def validate_admins
+ unless instance_admins.any?
+ log_error(_('No active admin user found'))
+ return error(_('No active admin user found'))
+ end
+
+ success
+ end
+
+ def create_group
+ if project_created?
+ log_info(_('Instance administrators group already exists'))
+ @group = application_settings.instance_administration_project.owner
+ return success(group: @group)
+ end
+
+ @group = ::Groups::CreateService.new(group_owner, create_group_params).execute
+
+ if @group.persisted?
+ success(group: @group)
+ else
+ error(_('Could not create group'))
+ end
+ end
+
+ def create_project
+ if project_created?
+ log_info(_('Instance administration project already exists'))
+ @project = application_settings.instance_administration_project
+ return success(project: project)
+ end
+
+ @project = ::Projects::CreateService.new(group_owner, create_project_params).execute
+
+ if project.persisted?
+ success(project: project)
+ else
+ log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
+ error(_('Could not create project'))
+ end
+ end
+
+ def save_project_id
+ return success if project_created?
+
+ result = application_settings.update(instance_administration_project_id: @project.id)
+
+ if result
+ success
+ else
+ log_error(_("Could not save instance administration project ID, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+ error(_('Could not save project ID'))
+ end
+ end
+
+ def add_group_members
+ members = @group.add_users(members_to_add, Gitlab::Access::MAINTAINER)
+ errors = members.flat_map { |member| member.errors.full_messages }
+
+ if errors.any?
+ log_error(_('Could not add admins as members to self-monitoring project. Errors: %{errors}') % { errors: errors })
+ error(_('Could not add admins as members'))
+ else
+ success
+ end
+ end
+
+ def add_to_whitelist
+ return success unless prometheus_enabled?
+ return success unless prometheus_listen_address.present?
+
+ uri = parse_url(internal_prometheus_listen_address_uri)
+ return error(_('Prometheus listen_address is not a valid URI')) unless uri
+
+ application_settings.add_to_outbound_local_requests_whitelist([uri.normalized_host])
+ result = application_settings.save
+
+ if result
+ # Expire the Gitlab::CurrentSettings cache after updating the whitelist.
+ # This happens automatically in an after_commit hook, but in migrations,
+ # the after_commit hook only runs at the end of the migration.
+ Gitlab::CurrentSettings.expire_current_application_settings
+ success
+ else
+ log_error(_("Could not add prometheus URL to whitelist, errors: %{errors}") % { errors: application_settings.errors.full_messages })
+ error(_('Could not add prometheus URL to whitelist'))
+ end
+ end
+
+ def add_prometheus_manual_configuration
+ return success unless prometheus_enabled?
+ return success unless prometheus_listen_address.present?
+
+ service = project.find_or_initialize_service('prometheus')
+
+ unless service.update(prometheus_service_attributes)
+ log_error(_('Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}') % { errors: service.errors.full_messages })
+ return error(_('Could not save prometheus manual configuration'))
+ end
+
+ success
+ end
+
+ def application_settings
+ @application_settings ||= ApplicationSetting.current_without_cache
+ end
+
+ def project_created?
+ application_settings.instance_administration_project.present?
+ end
+
+ def parse_url(uri_string)
+ Addressable::URI.parse(uri_string)
+ rescue Addressable::URI::InvalidURIError, TypeError
+ end
+
+ def prometheus_enabled?
+ Gitlab.config.prometheus.enable
+ rescue Settingslogic::MissingSetting
+ log_error(_('prometheus.enable is not present in gitlab.yml'))
+
+ false
+ end
+
+ def prometheus_listen_address
+ Gitlab.config.prometheus.listen_address
+ rescue Settingslogic::MissingSetting
+ log_error(_('prometheus.listen_address is not present in gitlab.yml'))
+
+ nil
+ end
+
+ def instance_admins
+ @instance_admins ||= User.admins.active
+ end
+
+ def group_owner
+ instance_admins.first
+ end
+
+ def members_to_add
+ # Exclude admins who are already members of group because
+ # `@group.add_users(users)` returns an error if the users parameter contains
+ # users who are already members of the group.
+ instance_admins - @group.members.collect(&:user)
+ end
+
+ def create_group_params
+ {
+ name: 'GitLab Instance Administrators',
+ path: "gitlab-instance-administrators-#{SecureRandom.hex(4)}",
+ visibility_level: VISIBILITY_LEVEL
+ }
+ end
+
+ def docs_path
+ Rails.application.routes.url_helpers.help_page_path(
+ 'administration/monitoring/gitlab_instance_administration_project/index'
+ )
+ end
+
+ def create_project_params
+ {
+ initialize_with_readme: true,
+ visibility_level: VISIBILITY_LEVEL,
+ name: PROJECT_NAME,
+ description: "This project is automatically generated and will be used to help monitor this GitLab instance. [More information](#{docs_path})",
+ namespace_id: @group.id
+ }
+ end
+
+ def internal_prometheus_listen_address_uri
+ if prometheus_listen_address.starts_with?('http')
+ prometheus_listen_address
+ else
+ 'http://' + prometheus_listen_address
+ end
+ end
+
+ def prometheus_service_attributes
+ {
+ api_url: internal_prometheus_listen_address_uri,
+ manual_configuration: true,
+ active: true
+ }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/hook/smime_signature_interceptor.rb b/lib/gitlab/email/hook/smime_signature_interceptor.rb
new file mode 100644
index 00000000000..e48041d9218
--- /dev/null
+++ b/lib/gitlab/email/hook/smime_signature_interceptor.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Email
+ module Hook
+ class SmimeSignatureInterceptor
+ # Sign emails with SMIME if enabled
+ class << self
+ def delivering_email(message)
+ signed_message = Gitlab::Email::Smime::Signer.sign(
+ cert: certificate.cert,
+ key: certificate.key,
+ data: message.encoded)
+ signed_email = Mail.new(signed_message)
+
+ overwrite_body(message, signed_email)
+ overwrite_headers(message, signed_email)
+ end
+
+ private
+
+ def certificate
+ @certificate ||= Gitlab::Email::Smime::Certificate.from_files(key_path, cert_path)
+ end
+
+ def key_path
+ Gitlab.config.gitlab.email_smime.key_file
+ end
+
+ def cert_path
+ Gitlab.config.gitlab.email_smime.cert_file
+ end
+
+ def overwrite_body(message, signed_email)
+ # since this is a multipart email, assignment to nil is important,
+ # otherwise Message#body will add a new mail part
+ message.body = nil
+ message.body = signed_email.body.encoded
+ end
+
+ def overwrite_headers(message, signed_email)
+ message.content_disposition = signed_email.content_disposition
+ message.content_transfer_encoding = signed_email.content_transfer_encoding
+ message.content_type = signed_email.content_type
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/smime/certificate.rb b/lib/gitlab/email/smime/certificate.rb
new file mode 100644
index 00000000000..b331c4ca19c
--- /dev/null
+++ b/lib/gitlab/email/smime/certificate.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Email
+ module Smime
+ class Certificate
+ include OpenSSL
+
+ attr_reader :key, :cert
+
+ def key_string
+ @key.to_s
+ end
+
+ def cert_string
+ @cert.to_pem
+ end
+
+ def self.from_strings(key_string, cert_string)
+ key = PKey::RSA.new(key_string)
+ cert = X509::Certificate.new(cert_string)
+ new(key, cert)
+ end
+
+ def self.from_files(key_path, cert_path)
+ from_strings(File.read(key_path), File.read(cert_path))
+ end
+
+ def initialize(key, cert)
+ @key = key
+ @cert = cert
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/email/smime/signer.rb b/lib/gitlab/email/smime/signer.rb
new file mode 100644
index 00000000000..2fa83014003
--- /dev/null
+++ b/lib/gitlab/email/smime/signer.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'openssl'
+
+module Gitlab
+ module Email
+ module Smime
+ # Tooling for signing and verifying data with SMIME
+ class Signer
+ include OpenSSL
+
+ def self.sign(cert:, key:, data:)
+ signed_data = PKCS7.sign(cert, key, data, nil, PKCS7::DETACHED)
+ PKCS7.write_smime(signed_data)
+ end
+
+ # return nil if data cannot be verified, otherwise the signed content data
+ def self.verify_signature(cert:, ca_cert: nil, signed_data:)
+ store = X509::Store.new
+ store.set_default_paths
+ store.add_cert(ca_cert) if ca_cert
+
+ signed_smime = PKCS7.read_smime(signed_data)
+ signed_smime if signed_smime.verify([cert], store)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index e6cbfb00f60..201db9fec26 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -406,7 +406,8 @@ module Gitlab
def self.filesystem_id(storage)
response = Gitlab::GitalyClient::ServerService.new(storage).info
storage_status = response.storage_statuses.find { |status| status.storage_name == storage }
- storage_status.filesystem_id
+
+ storage_status&.filesystem_id
end
def self.filesystem_id_from_disk(storage)
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/graphql/loaders/batch_root_storage_statistics_loader.rb b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb
new file mode 100644
index 00000000000..a0312366d66
--- /dev/null
+++ b/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Loaders
+ class BatchRootStorageStatisticsLoader
+ attr_reader :namespace_id
+
+ def initialize(namespace_id)
+ @namespace_id = namespace_id
+ end
+
+ def find
+ BatchLoader.for(namespace_id).batch do |namespace_ids, loader|
+ Namespace::RootStorageStatistics.for_namespace_ids(namespace_ids).each do |statistics|
+ loader.call(statistics.namespace_id, statistics)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index ec7671f9a8b..425c30d67fe 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -97,7 +97,7 @@ module Gitlab
attr_reader :load_times_by_model, :private_token
def debug(message, *)
- message.gsub!(private_token, FILTERED_STRING) if private_token
+ message = message.gsub(private_token, FILTERED_STRING) if private_token
_, type, time = *message.match(/(\w+) Load \(([0-9.]+)ms\)/)
diff --git a/lib/gitlab/quick_actions/substitution_definition.rb b/lib/gitlab/quick_actions/substitution_definition.rb
index 2f78ea05cf0..0fda056a4fe 100644
--- a/lib/gitlab/quick_actions/substitution_definition.rb
+++ b/lib/gitlab/quick_actions/substitution_definition.rb
@@ -17,8 +17,9 @@ module Gitlab
return unless content
all_names.each do |a_name|
- content.gsub!(%r{/#{a_name} ?(.*)$}i, execute_block(action_block, context, '\1'))
+ content = content.gsub(%r{/#{a_name} ?(.*)$}i, execute_block(action_block, context, '\1'))
end
+
content
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_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index 60782306ade..48b1524f9c7 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -8,16 +8,16 @@ module Gitlab
MAXIMUM_JOB_ARGUMENTS_LENGTH = 10.kilobytes
def call(job, queue)
- started_at = current_time
+ started_time = get_time
base_payload = parse_job(job)
- Sidekiq.logger.info log_job_start(started_at, base_payload)
+ Sidekiq.logger.info log_job_start(base_payload)
yield
- Sidekiq.logger.info log_job_done(job, started_at, base_payload)
+ Sidekiq.logger.info log_job_done(job, started_time, base_payload)
rescue => job_exception
- Sidekiq.logger.warn log_job_done(job, started_at, base_payload, job_exception)
+ Sidekiq.logger.warn log_job_done(job, started_time, base_payload, job_exception)
raise
end
@@ -32,7 +32,7 @@ module Gitlab
output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS))
end
- def log_job_start(started_at, payload)
+ def log_job_start(payload)
payload['message'] = "#{base_message(payload)}: start"
payload['job_status'] = 'start'
@@ -45,11 +45,12 @@ module Gitlab
payload
end
- def log_job_done(job, started_at, payload, job_exception = nil)
+ def log_job_done(job, started_time, payload, job_exception = nil)
payload = payload.dup
add_instrumentation_keys!(job, payload)
- payload['duration'] = elapsed(started_at)
- payload['completed_at'] = Time.now.utc
+
+ elapsed_time = elapsed(started_time)
+ add_time_keys!(elapsed_time, payload)
message = base_message(payload)
@@ -69,6 +70,14 @@ module Gitlab
payload
end
+ def add_time_keys!(time, payload)
+ payload['duration'] = time[:duration].round(3)
+ payload['system_s'] = time[:stime].round(3)
+ payload['user_s'] = time[:utime].round(3)
+ payload['child_s'] = time[:ctime].round(3) if time[:ctime] > 0
+ payload['completed_at'] = Time.now.utc
+ end
+
def parse_job(job)
job = job.dup
@@ -93,8 +102,25 @@ module Gitlab
(Time.now.utc - start).to_f.round(3)
end
- def elapsed(start)
- (current_time - start).round(3)
+ def elapsed(t0)
+ t1 = get_time
+ {
+ duration: t1[:now] - t0[:now],
+ stime: t1[:times][:stime] - t0[:times][:stime],
+ utime: t1[:times][:utime] - t0[:times][:utime],
+ ctime: ctime(t1[:times]) - ctime(t0[:times])
+ }
+ end
+
+ def get_time
+ {
+ now: current_time,
+ times: Process.times
+ }
+ end
+
+ def ctime(times)
+ times[:cstime] + times[:cutime]
end
def current_time
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/sidekiq_middleware/monitor.rb b/lib/gitlab/sidekiq_middleware/monitor.rb
new file mode 100644
index 00000000000..53a6132edac
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/monitor.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class Monitor
+ def call(worker, job, queue)
+ Gitlab::SidekiqMonitor.instance.within_job(job['jid'], queue) do
+ yield
+ end
+ rescue Gitlab::SidekiqMonitor::CancelledError
+ # push job to DeadSet
+ payload = ::Sidekiq.dump_json(job)
+ ::Sidekiq::DeadSet.new.kill(payload, notify_failure: false)
+
+ # ignore retries
+ raise ::Sidekiq::JobRetry::Skip
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_monitor.rb b/lib/gitlab/sidekiq_monitor.rb
new file mode 100644
index 00000000000..9842f1f53f7
--- /dev/null
+++ b/lib/gitlab/sidekiq_monitor.rb
@@ -0,0 +1,182 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class SidekiqMonitor < Daemon
+ include ::Gitlab::Utils::StrongMemoize
+
+ NOTIFICATION_CHANNEL = 'sidekiq:cancel:notifications'.freeze
+ CANCEL_DEADLINE = 24.hours.seconds
+ RECONNECT_TIME = 3.seconds
+
+ # We use exception derived from `Exception`
+ # to consider this as an very low-level exception
+ # that should not be caught by application
+ CancelledError = Class.new(Exception) # rubocop:disable Lint/InheritException
+
+ attr_reader :jobs_thread
+ attr_reader :jobs_mutex
+
+ def initialize
+ super
+
+ @jobs_thread = {}
+ @jobs_mutex = Mutex.new
+ end
+
+ def within_job(jid, queue)
+ jobs_mutex.synchronize do
+ jobs_thread[jid] = Thread.current
+ end
+
+ if cancelled?(jid)
+ Sidekiq.logger.warn(
+ class: self.class.to_s,
+ action: 'run',
+ queue: queue,
+ jid: jid,
+ canceled: true
+ )
+ raise CancelledError
+ end
+
+ yield
+ ensure
+ jobs_mutex.synchronize do
+ jobs_thread.delete(jid)
+ end
+ end
+
+ def self.cancel_job(jid)
+ payload = {
+ action: 'cancel',
+ jid: jid
+ }.to_json
+
+ ::Gitlab::Redis::SharedState.with do |redis|
+ redis.setex(cancel_job_key(jid), CANCEL_DEADLINE, 1)
+ redis.publish(NOTIFICATION_CHANNEL, payload)
+ end
+ end
+
+ private
+
+ def start_working
+ Sidekiq.logger.info(
+ class: self.class.to_s,
+ action: 'start',
+ message: 'Starting Monitor Daemon'
+ )
+
+ while enabled?
+ process_messages
+ sleep(RECONNECT_TIME)
+ end
+
+ ensure
+ Sidekiq.logger.warn(
+ class: self.class.to_s,
+ action: 'stop',
+ message: 'Stopping Monitor Daemon'
+ )
+ end
+
+ def stop_working
+ thread.raise(Interrupt) if thread.alive?
+ end
+
+ def process_messages
+ ::Gitlab::Redis::SharedState.with do |redis|
+ redis.subscribe(NOTIFICATION_CHANNEL) do |on|
+ on.message do |channel, message|
+ process_message(message)
+ end
+ end
+ end
+ rescue Exception => e # rubocop:disable Lint/RescueException
+ Sidekiq.logger.warn(
+ class: self.class.to_s,
+ action: 'exception',
+ message: e.message
+ )
+
+ # we re-raise system exceptions
+ raise e unless e.is_a?(StandardError)
+ end
+
+ def process_message(message)
+ Sidekiq.logger.info(
+ class: self.class.to_s,
+ channel: NOTIFICATION_CHANNEL,
+ message: 'Received payload on channel',
+ payload: message
+ )
+
+ message = safe_parse(message)
+ return unless message
+
+ case message['action']
+ when 'cancel'
+ process_job_cancel(message['jid'])
+ else
+ # unknown message
+ end
+ end
+
+ def safe_parse(message)
+ JSON.parse(message)
+ rescue JSON::ParserError
+ end
+
+ def process_job_cancel(jid)
+ return unless jid
+
+ # try to find thread without lock
+ return unless find_thread_unsafe(jid)
+
+ Thread.new do
+ # try to find a thread, but with guaranteed
+ # that handle for thread corresponds to actually
+ # running job
+ find_thread_with_lock(jid) do |thread|
+ Sidekiq.logger.warn(
+ class: self.class.to_s,
+ action: 'cancel',
+ message: 'Canceling thread with CancelledError',
+ jid: jid,
+ thread_id: thread.object_id
+ )
+
+ thread&.raise(CancelledError)
+ end
+ end
+ end
+
+ # This method needs to be thread-safe
+ # This is why it passes thread in block,
+ # to ensure that we do process this thread
+ def find_thread_unsafe(jid)
+ jobs_thread[jid]
+ end
+
+ def find_thread_with_lock(jid)
+ # don't try to lock if we cannot find the thread
+ return unless find_thread_unsafe(jid)
+
+ jobs_mutex.synchronize do
+ find_thread_unsafe(jid).tap do |thread|
+ yield(thread) if thread
+ end
+ end
+ end
+
+ def cancelled?(jid)
+ ::Gitlab::Redis::SharedState.with do |redis|
+ redis.exists(self.class.cancel_job_key(jid))
+ end
+ end
+
+ def self.cancel_job_key(jid)
+ "sidekiq:cancel:#{jid}"
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 1542905d2ce..353298e67b3 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -188,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/assets.rake b/lib/tasks/gitlab/assets.rake
index a07ae3a418a..7a42e4e92a0 100644
--- a/lib/tasks/gitlab/assets.rake
+++ b/lib/tasks/gitlab/assets.rake
@@ -10,15 +10,9 @@ namespace :gitlab do
rake:assets:precompile
webpack:compile
gitlab:assets:fix_urls
- gitlab:assets:compile_vrt
].each(&Gitlab::TaskHelpers.method(:invoke_and_time_task))
end
- desc 'GitLab | Assets | Compile visual review toolbar'
- task :compile_vrt do
- system 'yarn', 'webpack-vrt'
- end
-
desc 'GitLab | Assets | Clean up old compiled frontend assets'
task clean: ['rake:assets:clean']
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index e8d27360395..61642fbbd59 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -128,9 +128,6 @@ msgstr[1] ""
msgid "%{actionText} & %{openOrClose} %{noteable}"
msgstr ""
-msgid "%{canMergeCount}/%{assigneesCount} can merge"
-msgstr ""
-
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
@@ -202,6 +199,9 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
+msgid "%{mergeLength}/%{usersLength} can merge"
+msgstr ""
+
msgid "%{mrText}, this issue will be closed automatically."
msgstr ""
@@ -279,6 +279,9 @@ msgstr ""
msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what information is shared with GitLab Inc."
msgstr ""
+msgid "%{userName} (cannot merge)"
+msgstr ""
+
msgid "%{userName}'s avatar"
msgstr ""
@@ -306,6 +309,9 @@ msgstr ""
msgid "(external source)"
msgstr ""
+msgid "+ %{amount} more"
+msgstr ""
+
msgid "+ %{count} more"
msgstr ""
@@ -469,6 +475,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 ""
@@ -987,9 +996,6 @@ msgstr ""
msgid "Alternate support URL for help page and help dropdown"
msgstr ""
-msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
-msgstr ""
-
msgid "Amount of time (in hours) that users are allowed to skip forced configuration of two-factor authentication"
msgstr ""
@@ -1426,6 +1432,12 @@ msgstr ""
msgid "August"
msgstr ""
+msgid "Authenticate"
+msgstr ""
+
+msgid "Authenticate with GitHub"
+msgstr ""
+
msgid "Authentication Log"
msgstr ""
@@ -3341,6 +3353,12 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not add admins as members"
+msgstr ""
+
+msgid "Could not add admins as members to self-monitoring project. Errors: %{errors}"
+msgstr ""
+
msgid "Could not add prometheus URL to whitelist"
msgstr ""
@@ -3359,6 +3377,9 @@ msgstr ""
msgid "Could not create Wiki Repository at this time. Please try again later."
msgstr ""
+msgid "Could not create group"
+msgstr ""
+
msgid "Could not create instance administration project. Errors: %{errors}"
msgstr ""
@@ -3386,6 +3407,12 @@ msgstr ""
msgid "Could not save project ID"
msgstr ""
+msgid "Could not save prometheus manual configuration"
+msgstr ""
+
+msgid "Could not save prometheus manual configuration for self-monitoring project. Errors: %{errors}"
+msgstr ""
+
msgid "Coverage"
msgstr ""
@@ -3422,6 +3449,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create and provide your GitHub %{link_start}Personal Access Token%{link_end}. You will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
+msgstr ""
+
msgid "Create board"
msgstr ""
@@ -3587,6 +3617,30 @@ msgstr ""
msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
+msgid "CycleAnalyticsEvent|Issue created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first associated with a milestone or issue first added to a board"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Issue first mentioned in a commit"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request created"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request first deployed to production"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build finish time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request last build start time"
+msgstr ""
+
+msgid "CycleAnalyticsEvent|Merge request merged"
+msgstr ""
+
msgid "CycleAnalyticsStage|Code"
msgstr ""
@@ -4144,6 +4198,9 @@ msgstr ""
msgid "Edit public deploy key"
msgstr ""
+msgid "Edit stage"
+msgstr ""
+
msgid "Edit wiki page"
msgstr ""
@@ -5244,6 +5301,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 ""
@@ -5658,6 +5718,9 @@ msgstr ""
msgid "Hide shared projects"
msgstr ""
+msgid "Hide stage"
+msgstr ""
+
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
@@ -6484,7 +6547,7 @@ msgstr ""
msgid "Latest changes"
msgstr ""
-msgid "Latest pipeline for this branch"
+msgid "Latest pipeline for the most recent commit on this branch"
msgstr ""
msgid "Lead"
@@ -6570,9 +6633,6 @@ msgstr ""
msgid "List your Bitbucket Server repositories"
msgstr ""
-msgid "List your GitHub repositories"
-msgstr ""
-
msgid "Live preview"
msgstr ""
@@ -7355,9 +7415,15 @@ msgstr ""
msgid "No Tag"
msgstr ""
+msgid "No active admin user found"
+msgstr ""
+
msgid "No activities found"
msgstr ""
+msgid "No application_settings found"
+msgstr ""
+
msgid "No available namespaces to fork the project."
msgstr ""
@@ -7427,9 +7493,6 @@ msgstr ""
msgid "No milestones to show"
msgstr ""
-msgid "No one can merge"
-msgstr ""
-
msgid "No other labels with such name or description"
msgstr ""
@@ -7984,7 +8047,7 @@ msgstr ""
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr ""
-msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgid "PipelineStatusTooltip|Pipeline: %{ciStatus}"
msgstr ""
msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
@@ -8008,6 +8071,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 ""
@@ -8701,6 +8767,9 @@ msgstr ""
msgid "Project access must be granted explicitly to each user."
msgstr ""
+msgid "Project already created"
+msgstr ""
+
msgid "Project and wiki repositories"
msgstr ""
@@ -9357,6 +9426,9 @@ msgstr ""
msgid "Remove spent time"
msgstr ""
+msgid "Remove stage"
+msgstr ""
+
msgid "Remove time estimate"
msgstr ""
@@ -11145,9 +11217,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 ""
@@ -11923,6 +11992,9 @@ msgstr ""
msgid "To add the entry manually, provide the following details to the application on your phone."
msgstr ""
+msgid "To connect GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories."
+msgstr ""
+
msgid "To define internal users, first enable new users set to external"
msgstr ""
@@ -11941,12 +12013,6 @@ msgstr ""
msgid "To help improve GitLab, we would like to periodically collect usage information. This can be changed at any time in %{settings_link_start}Settings%{link_end}. %{info_link_start}More Information%{link_end}"
msgstr ""
-msgid "To import GitHub repositories, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
-msgstr ""
-
-msgid "To import GitHub repositories, you first need to authorize GitLab to access the list of your GitHub repositories:"
-msgstr ""
-
msgid "To import an SVN repository, check out %{svn_link}."
msgstr ""
@@ -12873,15 +12939,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 ""
@@ -12897,19 +12957,16 @@ msgstr ""
msgid "WikiPageConflictMessage|the page"
msgstr ""
-msgid "WikiPageCreate|Create %{page_title}"
-msgstr ""
-
-msgid "WikiPageEdit|Update %{page_title}"
+msgid "WikiPageCreate|Create %{pageTitle}"
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"
@@ -12930,6 +12987,9 @@ msgstr ""
msgid "Wiki|Page history"
msgstr ""
+msgid "Wiki|Page title"
+msgstr ""
+
msgid "Wiki|Page version"
msgstr ""
@@ -13386,18 +13446,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 ""
@@ -13437,6 +13494,9 @@ msgstr ""
msgid "cannot include leading slash or directory traversal."
msgstr ""
+msgid "cannot merge"
+msgstr ""
+
msgid "comment"
msgstr ""
@@ -13484,6 +13544,9 @@ msgstr ""
msgid "done"
msgstr ""
+msgid "e.g. %{token}"
+msgstr ""
+
msgid "element is not a hierarchy"
msgstr ""
@@ -13864,6 +13927,9 @@ msgstr ""
msgid "no contributions"
msgstr ""
+msgid "no one can merge"
+msgstr ""
+
msgid "none"
msgstr ""
@@ -13901,6 +13967,9 @@ msgstr ""
msgid "pending comment"
msgstr ""
+msgid "pipeline"
+msgstr ""
+
msgid "private"
msgstr ""
@@ -13916,6 +13985,12 @@ msgstr ""
msgid "project avatar"
msgstr ""
+msgid "prometheus.enable is not present in gitlab.yml"
+msgstr ""
+
+msgid "prometheus.listen_address is not present in gitlab.yml"
+msgstr ""
+
msgid "quick actions"
msgstr ""
@@ -13934,6 +14009,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..384584a944a 100644
--- a/package.json
+++ b/package.json
@@ -26,20 +26,20 @@
"stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
"test": "node scripts/frontend/test",
"webpack": "NODE_OPTIONS=\"--max-old-space-size=3584\" webpack --config config/webpack.config.js",
- "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.js",
- "webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
+ "webpack-prod": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.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",
+ "@gitlab/ui": "5.18.0",
+ "@gitlab/visual-review-tools": "^1.0.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -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..16e5e730a6b 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -30,7 +30,7 @@ and corresponding views / partials / selectors in CE / EE.
Whenever `qa:selectors` job fails in your merge request, you are supposed to
fix [page objects](../doc/development/testing_guide/end_to_end/page_objects.md). You should also trigger end-to-end tests
-using `package-and-qa` manual action, to test if everything works fine.
+using `package-and-qa-manual` manual action, to test if everything works fine.
## How can I use it?
@@ -39,6 +39,11 @@ have an instance available you can follow the instructions below to use
the [GitLab Development Kit (GDK)][GDK].
This is the recommended option if you would like to contribute to the tests.
+Note: GitLab QA uses [Selenium WebDriver](https://www.seleniumhq.org/) via
+[Cabybara](http://teamcapybara.github.io/capybara/), and by default it targets Chrome as
+the browser to use. You will need to have Chrome (or Chromium) and
+[chromedriver](https://chromedriver.chromium.org/) installed / in your `$PATH`.
+
### Run the end-to-end tests in a local development environment
Follow the GDK instructions to [prepare](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/prepare.md)
@@ -123,10 +128,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/page/project/import/github.rb b/qa/qa/page/project/import/github.rb
index 5973a5a958e..cc0c4e1e835 100644
--- a/qa/qa/page/project/import/github.rb
+++ b/qa/qa/page/project/import/github.rb
@@ -9,7 +9,7 @@ module QA
view 'app/views/import/github/new.html.haml' do
element :personal_access_token_field, 'text_field_tag :personal_access_token' # rubocop:disable QA/ElementWithPattern
- element :list_repos_button, "submit_tag _('List your GitHub repositories')" # rubocop:disable QA/ElementWithPattern
+ element :authenticate_button, "submit_tag _('Authenticate')" # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/import_projects/components/provider_repo_table_row.vue' do
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index b5a36862389..37bca97fec7 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -39,6 +39,10 @@ module QA
element :commit_button
end
+ view 'app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue' do
+ element :start_new_mr_checkbox
+ end
+
def has_file?(file_name)
within_element(:file_list) do
page.has_content? file_name
@@ -100,6 +104,7 @@ module QA
# animation is still in process even when the buttons have the
# expected visibility.
commit_success_msg_shown = retry_until do
+ uncheck_element :start_new_mr_checkbox
click_element :commit_button
wait(reload: false) do
diff --git a/qa/qa/resource/issue.rb b/qa/qa/resource/issue.rb
index 51b2af8b4ef..16ab59352f3 100644
--- a/qa/qa/resource/issue.rb
+++ b/qa/qa/resource/issue.rb
@@ -3,7 +3,7 @@
module QA
module Resource
class Issue < Base
- attr_writer :description
+ attr_writer :description, :milestone
attribute :project do
Project.fabricate! do |resource|
@@ -44,7 +44,9 @@ module QA
{
labels: labels,
title: title
- }
+ }.tap do |hash|
+ hash[:milestone_id] = @milestone.id if @milestone
+ end
end
end
end
diff --git a/qa/qa/resource/label.rb b/qa/qa/resource/label.rb
index 3750725c440..b5e88d8aefc 100644
--- a/qa/qa/resource/label.rb
+++ b/qa/qa/resource/label.rb
@@ -7,6 +7,7 @@ module QA
class Label < Base
attr_accessor :description, :color
+ attribute :id
attribute :title
attribute :project do
diff --git a/qa/qa/runtime/api/request.rb b/qa/qa/runtime/api/request.rb
index 310c1dfeeb4..724b499d32f 100644
--- a/qa/qa/runtime/api/request.rb
+++ b/qa/qa/runtime/api/request.rb
@@ -12,6 +12,10 @@ module QA
@session_address = Runtime::Address.new(api_client.address, request_path)
end
+ def mask_url
+ @session_address.address.sub(/private_token=.*/, "private_token=[****]")
+ end
+
def url
@session_address.address
end
diff --git a/qa/qa/runtime/fixtures.rb b/qa/qa/runtime/fixtures.rb
index 72004d5b00a..02cecffd4df 100644
--- a/qa/qa/runtime/fixtures.rb
+++ b/qa/qa/runtime/fixtures.rb
@@ -3,10 +3,19 @@
module QA
module Runtime
module Fixtures
+ include Support::Api
+
+ TemplateNotFoundError = Class.new(RuntimeError)
+
def fetch_template_from_api(api_path, key)
request = Runtime::API::Request.new(api_client, "/templates/#{api_path}/#{key}")
- get request.url
- json_body[:content]
+ response = get(request.url)
+
+ unless response.code == HTTP_STATUS_OK
+ raise TemplateNotFoundError, "Template at #{request.mask_url} could not be found (#{response.code}): `#{response}`."
+ end
+
+ parse_body(response)[:content]
end
private
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index f51c16f472c..1c1f552e224 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -19,7 +19,7 @@ module QA
page.add_member(user.username)
end
- expect(page).to have_content(/#{user.name} (. )?@#{user.username} Given access/)
+ expect(page).to have_content(/@#{user.username}(\n| )?Given access/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
index 317e31feea8..cdb85902758 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Plan' do
+ # Failure issue https://gitlab.com/gitlab-org/quality/staging/issues/68
+ context 'Plan', :quarantine do
describe 'filter issue comments activities' do
let(:issue_title) { 'issue title' }
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/repository/user_views_commit_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
index f915d412bf3..21785ca3ed3 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb
@@ -49,7 +49,7 @@ module QA
Page::Project::Commit::Show.perform(&:select_email_patches)
- expect(page).to have_content("From: #{@user.name} <#{@user.public_email}>")
+ expect(page).to have_content(/From: "?#{Regexp.escape(@user.name)}"? <#{@user.public_email}>/)
expect(page).to have_content('Subject: [PATCH] Add second file')
expect(page).to have_content('diff --git a/second b/second')
end
diff --git a/rubocop/cop/migration/add_limit_to_string_columns.rb b/rubocop/cop/migration/add_limit_to_string_columns.rb
new file mode 100644
index 00000000000..30affcbb089
--- /dev/null
+++ b/rubocop/cop/migration/add_limit_to_string_columns.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+require_relative '../../migration_helpers'
+
+module RuboCop
+ module Cop
+ module Migration
+ # Cop that enforces length constraints to string columns
+ class AddLimitToStringColumns < RuboCop::Cop::Cop
+ include MigrationHelpers
+
+ ADD_COLUMNS_METHODS = %i(add_column add_column_with_default).freeze
+
+ MSG = 'String columns should have a limit constraint. 255 is suggested'.freeze
+
+ def on_def(node)
+ return unless in_migration?(node)
+
+ node.each_descendant(:send) do |send_node|
+ next unless string_operation?(send_node)
+
+ add_offense(send_node, location: :selector) unless limit_on_string_column?(send_node)
+ end
+ end
+
+ private
+
+ def string_operation?(node)
+ modifier = node.children[0]
+ migration_method = node.children[1]
+
+ if migration_method == :string
+ modifier.type == :lvar
+ elsif ADD_COLUMNS_METHODS.include?(migration_method)
+ modifier.nil? && string_column?(node.children[4])
+ end
+ end
+
+ def string_column?(column_type)
+ column_type.type == :sym && column_type.value == :string
+ end
+
+ def limit_on_string_column?(node)
+ migration_method = node.children[1]
+
+ if migration_method == :string
+ limit_present?(node.children)
+ elsif ADD_COLUMNS_METHODS.include?(migration_method)
+ limit_present?(node)
+ end
+ end
+
+ def limit_present?(statement)
+ !(statement.to_s =~ /:limit/).nil?
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 58a7ead6f13..d1328c4eb38 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -17,6 +17,7 @@ require_relative 'cop/migration/add_column'
require_relative 'cop/migration/add_concurrent_foreign_key'
require_relative 'cop/migration/add_concurrent_index'
require_relative 'cop/migration/add_index'
+require_relative 'cop/migration/add_limit_to_string_columns'
require_relative 'cop/migration/add_reference'
require_relative 'cop/migration/add_timestamps'
require_relative 'cop/migration/datetime'
diff --git a/spec/config/smime_signature_settings_spec.rb b/spec/config/smime_signature_settings_spec.rb
new file mode 100644
index 00000000000..4f0c227d866
--- /dev/null
+++ b/spec/config/smime_signature_settings_spec.rb
@@ -0,0 +1,56 @@
+require 'fast_spec_helper'
+
+describe SmimeSignatureSettings do
+ describe '.parse' do
+ let(:default_smime_key) { Rails.root.join('.gitlab_smime_key') }
+ let(:default_smime_cert) { Rails.root.join('.gitlab_smime_cert') }
+
+ it 'sets correct default values to disabled' do
+ parsed_settings = described_class.parse(nil)
+
+ expect(parsed_settings['enabled']).to be(false)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ context 'when providing custom values' do
+ it 'sets correct default values to disabled' do
+ custom_settings = Settingslogic.new({})
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(false)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ it 'enables smime with default key and cert' do
+ custom_settings = Settingslogic.new({
+ 'enabled' => true
+ })
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(true)
+ expect(parsed_settings['key_file']).to eq(default_smime_key)
+ expect(parsed_settings['cert_file']).to eq(default_smime_cert)
+ end
+
+ it 'enables smime with custom key and cert' do
+ custom_key = '/custom/key'
+ custom_cert = '/custom/cert'
+ custom_settings = Settingslogic.new({
+ 'enabled' => true,
+ 'key_file' => custom_key,
+ 'cert_file' => custom_cert
+ })
+
+ parsed_settings = described_class.parse(custom_settings)
+
+ expect(parsed_settings['enabled']).to be(true)
+ expect(parsed_settings['key_file']).to eq(custom_key)
+ expect(parsed_settings['cert_file']).to eq(custom_cert)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/issuable_collections_spec.rb b/spec/controllers/concerns/issuable_collections_spec.rb
index f210537aad5..7bdf5c49425 100644
--- a/spec/controllers/concerns/issuable_collections_spec.rb
+++ b/spec/controllers/concerns/issuable_collections_spec.rb
@@ -24,78 +24,6 @@ describe IssuableCollections do
controller
end
- describe '#set_sort_order_from_user_preference' do
- describe 'when sort param given' do
- let(:params) { { sort: 'updated_desc' } }
-
- context 'when issuable_sorting_field is defined' do
- before do
- controller.class.define_method(:issuable_sorting_field) { :issues_sort}
- end
-
- it 'sets user_preference with the right value' do
- controller.send(:set_sort_order_from_user_preference)
-
- expect(user.user_preference.reload.issues_sort).to eq('updated_desc')
- end
- end
-
- context 'when no issuable_sorting_field is defined on the controller' do
- it 'does not touch user_preference' do
- allow(user).to receive(:user_preference)
-
- controller.send(:set_sort_order_from_user_preference)
-
- expect(user).not_to have_received(:user_preference)
- end
- end
- end
-
- context 'when a user sorting preference exists' do
- let(:params) { {} }
-
- before do
- controller.class.define_method(:issuable_sorting_field) { :issues_sort }
- end
-
- it 'returns the set preference' do
- user.user_preference.update(issues_sort: 'updated_asc')
-
- sort_preference = controller.send(:set_sort_order_from_user_preference)
-
- expect(sort_preference).to eq('updated_asc')
- end
- end
- end
-
- describe '#set_set_order_from_cookie' do
- describe 'when sort param given' do
- let(:cookies) { {} }
- let(:params) { { sort: 'downvotes_asc' } }
-
- it 'sets the cookie with the right values and flags' do
- allow(controller).to receive(:cookies).and_return(cookies)
-
- controller.send(:set_sort_order_from_cookie)
-
- expect(cookies['issue_sort']).to eq({ value: 'popularity', secure: false, httponly: false })
- end
- end
-
- describe 'when cookie exists' do
- let(:cookies) { { 'issue_sort' => 'id_asc' } }
- let(:params) { {} }
-
- it 'sets the cookie with the right values and flags' do
- allow(controller).to receive(:cookies).and_return(cookies)
-
- controller.send(:set_sort_order_from_cookie)
-
- expect(cookies['issue_sort']).to eq({ value: 'created_asc', secure: false, httponly: false })
- end
- end
- end
-
describe '#page_count_for_relation' do
let(:params) { { state: 'opened' } }
diff --git a/spec/controllers/concerns/sorting_preference_spec.rb b/spec/controllers/concerns/sorting_preference_spec.rb
new file mode 100644
index 00000000000..a36124c6776
--- /dev/null
+++ b/spec/controllers/concerns/sorting_preference_spec.rb
@@ -0,0 +1,93 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SortingPreference do
+ let(:user) { create(:user) }
+
+ let(:controller_class) do
+ Class.new do
+ def self.helper_method(name); end
+
+ include SortingPreference
+ include SortingHelper
+ end
+ end
+
+ let(:controller) { controller_class.new }
+
+ before do
+ allow(controller).to receive(:params).and_return(ActionController::Parameters.new(params))
+ allow(controller).to receive(:current_user).and_return(user)
+ allow(controller).to receive(:legacy_sort_cookie_name).and_return('issuable_sort')
+ allow(controller).to receive(:sorting_field).and_return(:issues_sort)
+ end
+
+ describe '#set_sort_order_from_user_preference' do
+ subject { controller.send(:set_sort_order_from_user_preference) }
+
+ context 'when sort param given' do
+ let(:params) { { sort: 'updated_desc' } }
+
+ context 'when sorting_field is defined' do
+ it 'sets user_preference with the right value' do
+ is_expected.to eq('updated_desc')
+ end
+ end
+
+ context 'when no sorting_field is defined on the controller' do
+ before do
+ allow(controller).to receive(:sorting_field).and_return(nil)
+ end
+
+ it 'does not touch user_preference' do
+ expect(user).not_to receive(:user_preference)
+
+ subject
+ end
+ end
+ end
+
+ context 'when a user sorting preference exists' do
+ let(:params) { {} }
+
+ before do
+ user.user_preference.update!(issues_sort: 'updated_asc')
+ end
+
+ it 'returns the set preference' do
+ is_expected.to eq('updated_asc')
+ end
+ end
+ end
+
+ describe '#set_set_order_from_cookie' do
+ subject { controller.send(:set_sort_order_from_cookie) }
+
+ before do
+ allow(controller).to receive(:cookies).and_return(cookies)
+ end
+
+ context 'when sort param given' do
+ let(:cookies) { {} }
+ let(:params) { { sort: 'downvotes_asc' } }
+
+ it 'sets the cookie with the right values and flags' do
+ subject
+
+ expect(cookies['issue_sort']).to eq(value: 'popularity', secure: false, httponly: false)
+ end
+ end
+
+ context 'when cookie exists' do
+ let(:cookies) { { 'issue_sort' => 'id_asc' } }
+ let(:params) { {} }
+
+ it 'sets the cookie with the right values and flags' do
+ subject
+
+ expect(cookies['issue_sort']).to eq(value: 'created_asc', secure: false, httponly: false)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index 6591901a9dc..8b95c9f2496 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -40,6 +40,14 @@ describe Dashboard::ProjectsController do
expect(assigns(:projects)).to eq([project, project2])
end
+
+ context 'project sorting' do
+ let(:project) { create(:project) }
+
+ it_behaves_like 'set sort order from user preference' do
+ let(:sorting_param) { 'created_asc' }
+ end
+ end
end
end
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index 463586ee422..6752d2b8ebd 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -3,56 +3,91 @@
require 'spec_helper'
describe Explore::ProjectsController do
- describe 'GET #index.json' do
- render_views
+ shared_examples 'explore projects' do
+ describe 'GET #index.json' do
+ render_views
- before do
- get :index, format: :json
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
end
- it { is_expected.to respond_with(:success) }
- end
+ describe 'GET #trending.json' do
+ render_views
- describe 'GET #trending.json' do
- render_views
+ before do
+ get :trending, format: :json
+ end
- before do
- get :trending, format: :json
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #starred.json' do
+ render_views
+
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
end
- it { is_expected.to respond_with(:success) }
+ describe 'GET #trending' do
+ context 'sorting by update date' do
+ let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
+ let(:project2) { create(:project, :public, updated_at: 1.day.ago) }
+
+ before do
+ create(:trending_project, project: project1)
+ create(:trending_project, project: project2)
+ end
+
+ it 'sorts by last updated' do
+ get :trending, params: { sort: 'updated_desc' }
+
+ expect(assigns(:projects)).to eq [project2, project1]
+ end
+
+ it 'sorts by oldest updated' do
+ get :trending, params: { sort: 'updated_asc' }
+
+ expect(assigns(:projects)).to eq [project1, project2]
+ end
+ end
+ end
end
- describe 'GET #starred.json' do
- render_views
+ context 'when user is signed in' do
+ let(:user) { create(:user) }
before do
- get :starred, format: :json
+ sign_in(user)
end
- it { is_expected.to respond_with(:success) }
- end
+ include_examples 'explore projects'
- describe 'GET #trending' do
- context 'sorting by update date' do
- let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
- let(:project2) { create(:project, :public, updated_at: 1.day.ago) }
+ context 'user preference sorting' do
+ let(:project) { create(:project) }
- before do
- create(:trending_project, project: project1)
- create(:trending_project, project: project2)
+ it_behaves_like 'set sort order from user preference' do
+ let(:sorting_param) { 'created_asc' }
end
+ end
+ end
- it 'sorts by last updated' do
- get :trending, params: { sort: 'updated_desc' }
+ context 'when user is not signed in' do
+ include_examples 'explore projects'
- expect(assigns(:projects)).to eq [project2, project1]
- end
+ context 'user preference sorting' do
+ let(:project) { create(:project) }
+ let(:sorting_param) { 'created_asc' }
- it 'sorts by oldest updated' do
- get :trending, params: { sort: 'updated_asc' }
+ it 'does not set sort order from user preference' do
+ expect_any_instance_of(UserPreference).not_to receive(:update)
- expect(assigns(:projects)).to eq [project1, project2]
+ get :index, params: { sort: sorting_param }
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/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index fab47aa4701..187c7864ad7 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1104,18 +1104,39 @@ describe Projects::IssuesController do
project.add_developer(user)
end
+ subject do
+ post(:toggle_award_emoji, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue.iid,
+ name: emoji_name
+ })
+ end
+ let(:emoji_name) { 'thumbsup' }
+
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: issue.iid,
- name: "thumbsup"
- })
+ subject
end.to change { issue.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
+
+ it "removes the already awarded emoji" do
+ create(:award_emoji, awardable: issue, name: emoji_name, user: user)
+
+ expect { subject }.to change { AwardEmoji.count }.by(-1)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'marks Todos on the Issue as done' do
+ todo = create(:todo, target: issue, project: project, user: user)
+
+ subject
+
+ expect(todo.reload).to be_done
+ end
end
describe 'POST create_merge_request' do
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/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 9ab565dc2e8..4500c412521 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -543,23 +543,32 @@ describe Projects::NotesController do
project.add_developer(user)
end
+ subject { post(:toggle_award_emoji, params: request_params.merge(name: emoji_name)) }
+ let(:emoji_name) { 'thumbsup' }
+
it "toggles the award emoji" do
expect do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
+ subject
end.to change { note.award_emoji.count }.by(1)
expect(response).to have_gitlab_http_status(200)
end
it "removes the already awarded emoji" do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
+ create(:award_emoji, awardable: note, name: emoji_name, user: user)
- expect do
- post(:toggle_award_emoji, params: request_params.merge(name: "thumbsup"))
- end.to change { AwardEmoji.count }.by(-1)
+ expect { subject }.to change { AwardEmoji.count }.by(-1)
expect(response).to have_gitlab_http_status(200)
end
+
+ it 'marks Todos on the Noteable as done' do
+ todo = create(:todo, target: note.noteable, project: project, user: user)
+
+ subject
+
+ expect(todo.reload).to be_done
+ end
end
describe "resolving and unresolving" do
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 9a50ea79f5e..212d8b15252 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
@@ -393,4 +397,69 @@ describe Projects::PipelinesController do
end
end
end
+
+ describe 'GET latest' do
+ let(:branch_main) { project.repository.branches[0] }
+ let(:branch_secondary) { project.repository.branches[1] }
+
+ let!(:pipeline_master) do
+ create(:ci_pipeline,
+ ref: branch_main.name,
+ sha: branch_main.target,
+ project: project)
+ end
+
+ let!(:pipeline_secondary) do
+ create(:ci_pipeline,
+ ref: branch_secondary.name,
+ sha: branch_secondary.target,
+ project: project)
+ end
+
+ before do
+ project.change_head(branch_main.name)
+ project.reload_default_branch
+ end
+
+ context 'no ref provided' do
+ it 'shows latest pipeline for the default project branch' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: nil }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_master.id)
+ end
+ end
+
+ context 'ref provided' do
+ before do
+ create(:ci_pipeline, ref: 'master', project: project)
+ end
+
+ it 'shows the latest pipeline for the provided ref' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: branch_secondary.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_secondary.id)
+ end
+
+ context 'newer pipeline exists for older sha' do
+ before do
+ create(:ci_pipeline, ref: branch_secondary.name, sha: project.commit(branch_secondary.name).parent, project: project)
+ end
+
+ it 'shows the provided ref with the last sha/pipeline combo' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, latest: true, ref: branch_secondary.name }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(assigns(:pipeline)).to have_attributes(id: pipeline_secondary.id)
+ end
+ end
+ end
+
+ it 'renders a 404 if no pipeline is found for the ref' do
+ get :show, params: { namespace_id: project.namespace, project_id: project, ref: 'no-branch' }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ 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 7085cba08d5..5774ff7c576 100644
--- a/spec/controllers/projects/starrers_controller_spec.rb
+++ b/spec/controllers/projects/starrers_controller_spec.rb
@@ -32,6 +32,20 @@ describe Projects::StarrersController do
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
before do
project.update_attribute(:visibility_level, Project::PUBLIC)
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/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index 083a1c1383a..c732caa6160 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -318,6 +318,102 @@ describe ProjectsController do
end
end
+ describe 'POST #archive' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'for a user with the ability to archive a project' do
+ before do
+ group.add_owner(user)
+
+ post :archive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'archives the project' do
+ expect(project.reload.archived?).to be_truthy
+ end
+
+ it 'redirects to projects path' do
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'for a user that does not have the ability to archive a project' do
+ before do
+ project.add_maintainer(user)
+
+ post :archive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'does not archive the project' do
+ expect(project.reload.archived?).to be_falsey
+ end
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST #unarchive' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :archived, group: group) }
+
+ before do
+ sign_in(user)
+ end
+
+ context 'for a user with the ability to unarchive a project' do
+ before do
+ group.add_owner(user)
+
+ post :unarchive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'unarchives the project' do
+ expect(project.reload.archived?).to be_falsey
+ end
+
+ it 'redirects to projects path' do
+ expect(response).to have_gitlab_http_status(302)
+ expect(response).to redirect_to(project_path(project))
+ end
+ end
+
+ context 'for a user that does not have the ability to unarchive a project' do
+ before do
+ project.add_maintainer(user)
+
+ post :unarchive, params: {
+ namespace_id: project.namespace.path,
+ id: project.path
+ }
+ end
+
+ it 'does not unarchive the project' do
+ expect(project.reload.archived?).to be_truthy
+ end
+
+ it 'returns 404' do
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
describe '#housekeeping' do
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index fed4fc810f2..35487682462 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -129,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/snippets/notes_controller_spec.rb b/spec/controllers/snippets/notes_controller_spec.rb
index 652533ac49f..fd4b95ce226 100644
--- a/spec/controllers/snippets/notes_controller_spec.rb
+++ b/spec/controllers/snippets/notes_controller_spec.rb
@@ -288,11 +288,13 @@ describe Snippets::NotesController do
describe 'POST toggle_award_emoji' do
let(:note) { create(:note_on_personal_snippet, noteable: public_snippet) }
+ let(:emoji_name) { 'thumbsup'}
+
before do
sign_in(user)
end
- subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: "thumbsup" }) }
+ subject { post(:toggle_award_emoji, params: { snippet_id: public_snippet, id: note.id, name: emoji_name }) }
it "toggles the award emoji" do
expect { subject }.to change { note.award_emoji.count }.by(1)
@@ -301,7 +303,7 @@ describe Snippets::NotesController do
end
it "removes the already awarded emoji when it exists" do
- note.toggle_award_emoji('thumbsup', user) # create award emoji before
+ create(:award_emoji, awardable: note, name: emoji_name, user: user)
expect { subject }.to change { AwardEmoji.count }.by(-1)
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/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/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 4e7b25115d7..902ecdcd3e8 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -236,6 +236,15 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(2)')).to have_content(planning.title)
end
+ it 'dragging does not duplicate list' do
+ selector = '.board:not(.is-ghost) .board-header'
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+
+ drag(list_from_index: 2, list_to_index: 1, selector: '.board-header', perform_drop: false)
+
+ expect(page).to have_selector(selector, text: development.title, count: 1)
+ end
+
it 'issue moves between lists' do
drag(list_from_index: 1, from_index: 1, list_to_index: 2)
@@ -576,7 +585,7 @@ describe 'Issue Boards', :js do
end
end
- def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0)
+ def drag(selector: '.board-list', list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, perform_drop: true)
# ensure there is enough horizontal space for four boards
resize_window(2000, 800)
@@ -585,7 +594,8 @@ describe 'Issue Boards', :js do
list_from_index: list_from_index,
from_index: from_index,
to_index: to_index,
- list_to_index: list_to_index)
+ list_to_index: list_to_index,
+ perform_drop: perform_drop)
end
def wait_for_board_cards(board_number, expected_cards)
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index e2100c8562b..e4728f37217 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -169,7 +169,7 @@ describe 'Dashboard Projects' do
expect(page).to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).to have_css('.ci-status-link')
expect(page).to have_css('.ci-status-icon-success')
- expect(page).to have_link('Commit: passed')
+ expect(page).to have_link('Pipeline: passed')
end
end
@@ -189,7 +189,7 @@ describe 'Dashboard Projects' do
expect(page).not_to have_xpath("//a[@href='#{pipelines_project_commit_path(project, project.commit, ref: pipeline.ref)}']")
expect(page).not_to have_css('.ci-status-link')
expect(page).not_to have_css('.ci-status-icon-success')
- expect(page).not_to have_link('Commit: passed')
+ expect(page).not_to have_link('Pipeline: passed')
end
end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index 010a5de6930..22a0d268243 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -280,7 +280,7 @@ describe 'New project' do
end
it 'shows import instructions' do
- expect(page).to have_content('Import repositories from GitHub')
+ expect(page).to have_content('Authenticate with GitHub')
expect(current_path).to eq new_import_github_path
end
end
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/show/user_sees_last_commit_ci_status_spec.rb b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
index a1cad261875..fdc238d55cf 100644
--- a/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
+++ b/spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb
@@ -18,7 +18,7 @@ describe 'Projects > Show > User sees last commit CI status' do
page.within '.blob-commit-info' do
expect(page).to have_content(project.commit.sha[0..6])
- expect(page).to have_link('Commit: skipped')
+ expect(page).to have_link('Pipeline: skipped')
end
end
end
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 7ac5da86702..99285011405 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -32,10 +32,12 @@ describe 'Multi-file editor new directory', :js do
click_button('Create directory')
end
+ expect(page).to have_content('folder name')
+
first('.ide-tree-actions button').click
- page.within('.modal-dialog') do
- find('.form-control').set('file name')
+ page.within('.modal') do
+ find('.form-control').set('folder name/file name')
click_button('Create file')
end
@@ -44,13 +46,18 @@ describe 'Multi-file editor new directory', :js do
find('.js-ide-commit-mode').click
- find('.multi-file-commit-list-item').hover
click_button 'Stage'
fill_in('commit-message', with: 'commit message ide')
+ find(:css, ".js-ide-commit-new-mr input").set(false)
+
+ wait_for_requests
+
page.within '.multi-file-commit-form' do
click_button('Commit')
+
+ wait_for_requests
end
find('.js-ide-edit-mode').click
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
index 00eefe9db42..780575a5975 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -36,15 +36,20 @@ describe 'Multi-file editor new file', :js do
find('.js-ide-commit-mode').click
- find('.multi-file-commit-list-item').hover
click_button 'Stage'
fill_in('commit-message', with: 'commit message ide')
+ find(:css, ".js-ide-commit-new-mr input").set(false)
+
page.within '.multi-file-commit-form' do
click_button('Commit')
+
+ wait_for_requests
end
+ find('.js-ide-edit-mode').click
+
expect(page).to have_content('file name')
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/finders/award_emojis_finder_spec.rb b/spec/finders/award_emojis_finder_spec.rb
new file mode 100644
index 00000000000..ccac475daad
--- /dev/null
+++ b/spec/finders/award_emojis_finder_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojisFinder do
+ set(:issue_1) { create(:issue) }
+ set(:issue_1_thumbsup) { create(:award_emoji, name: 'thumbsup', awardable: issue_1) }
+ set(:issue_1_thumbsdown) { create(:award_emoji, name: 'thumbsdown', awardable: issue_1) }
+ # Create a matching set of emoji for a second issue.
+ # These should never appear in our finder results
+ set(:issue_2) { create(:issue) }
+ set(:issue_2_thumbsup) { create(:award_emoji, name: 'thumbsup', awardable: issue_2) }
+ set(:issue_2_thumbsdown) { create(:award_emoji, name: 'thumbsdown', awardable: issue_2) }
+
+ describe 'param validation' do
+ it 'raises an error if `name` is invalid' do
+ expect { described_class.new(issue_1, { name: 'invalid' }).execute }.to raise_error(
+ ArgumentError,
+ 'Invalid name param'
+ )
+ end
+
+ it 'raises an error if `awarded_by` is invalid' do
+ expectation = [ArgumentError, 'Invalid awarded_by param']
+
+ expect { described_class.new(issue_1, { awarded_by: issue_2 }).execute }.to raise_error(*expectation)
+ expect { described_class.new(issue_1, { awarded_by: 'not-an-id' }).execute }.to raise_error(*expectation)
+ expect { described_class.new(issue_1, { awarded_by: 1.123 }).execute }.to raise_error(*expectation)
+ end
+ end
+
+ describe '#execute' do
+ it 'scopes to the awardable' do
+ expect(described_class.new(issue_1).execute).to contain_exactly(
+ issue_1_thumbsup, issue_1_thumbsdown
+ )
+ end
+
+ it 'filters by emoji name' do
+ expect(described_class.new(issue_1, { name: 'thumbsup' }).execute).to contain_exactly(issue_1_thumbsup)
+ expect(described_class.new(issue_1, { name: '8ball' }).execute).to be_empty
+ end
+
+ it 'filters by user' do
+ expect(described_class.new(issue_1, { awarded_by: issue_1_thumbsup.user }).execute).to contain_exactly(issue_1_thumbsup)
+ expect(described_class.new(issue_1, { awarded_by: issue_2_thumbsup.user }).execute).to be_empty
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 9216ad0060b..fe725b97c21 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -3,7 +3,7 @@
"required": [
"sha",
"created_at",
- "finished_at",
+ "deployed_at",
"iid",
"tag",
"last?",
@@ -12,7 +12,7 @@
],
"properties": {
"created_at": { "type": "string" },
- "finished_at": { "type": ["string", "null"] },
+ "deployed_at": { "type": ["string", "null"] },
"id": { "type": "integer" },
"iid": { "type": "integer" },
"last?": { "type": "boolean" },
diff --git a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
index 214b67a9a0f..9945de8a856 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_sidebar.json
@@ -2,6 +2,7 @@
"type": "object",
"properties" : {
"id": { "type": "integer" },
+ "iid": { "type": "integer" },
"type": { "type": "string" },
"author_id": { "type": "integer" },
"project_id": { "type": "integer" },
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/api_spec.js b/spec/frontend/api_spec.js
index 7004373be0e..62ba0d36982 100644
--- a/spec/frontend/api_spec.js
+++ b/spec/frontend/api_spec.js
@@ -151,6 +151,28 @@ describe('Api', () => {
});
});
+ describe('projectUsers', () => {
+ it('fetches all users of a particular project', done => {
+ const query = 'dummy query';
+ const options = { unused: 'option' };
+ const projectPath = 'gitlab-org%2Fgitlab-ce';
+ const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/users`;
+ mock.onGet(expectedUrl).reply(200, [
+ {
+ name: 'test',
+ },
+ ]);
+
+ Api.projectUsers('gitlab-org/gitlab-ce', query, options)
+ .then(response => {
+ expect(response.length).toBe(1);
+ expect(response[0].name).toBe('test');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('projectMergeRequests', () => {
const projectPath = 'abc';
const expectedUrl = `${dummyUrlRoot}/api/${dummyApiVersion}/projects/${projectPath}/merge_requests`;
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/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 246500a2f34..45ac1a86ab3 100644
--- a/spec/frontend/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
@@ -62,12 +62,4 @@ describe('IDE commit module mutations', () => {
expect(state.shouldCreateMR).toBe(false);
});
});
-
- describe('INTERACT_WITH_NEW_MR', () => {
- it('sets interactedWithNewMR to true', () => {
- mutations.INTERACT_WITH_NEW_MR(state);
-
- expect(state.interactedWithNewMR).toBe(true);
- });
- });
});
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/monitoring/embed/embed_spec.js b/spec/frontend/monitoring/embed/embed_spec.js
index 3b18a0f77c7..1ce14e2418a 100644
--- a/spec/frontend/monitoring/embed/embed_spec.js
+++ b/spec/frontend/monitoring/embed/embed_spec.js
@@ -1,7 +1,7 @@
import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex';
import Embed from '~/monitoring/components/embed.vue';
-import MonitorAreaChart from '~/monitoring/components/charts/area.vue';
+import MonitorTimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import { TEST_HOST } from 'helpers/test_constants';
import { groups, initialState, metricsData, metricsWithData } from './mock_data';
@@ -55,7 +55,7 @@ describe('Embed', () => {
it('shows an empty state when no metrics are present', () => {
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(MonitorAreaChart).exists()).toBe(false);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(false);
});
});
@@ -71,8 +71,8 @@ describe('Embed', () => {
it('shows a chart when metrics are present', () => {
wrapper.setProps({});
expect(wrapper.find('.metrics-embed').exists()).toBe(true);
- expect(wrapper.find(MonitorAreaChart).exists()).toBe(true);
- expect(wrapper.findAll(MonitorAreaChart).length).toBe(2);
+ expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true);
+ expect(wrapper.findAll(MonitorTimeSeriesChart).length).toBe(2);
});
});
});
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
new file mode 100644
index 00000000000..452d4cd07cc
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_link_spec.js
@@ -0,0 +1,85 @@
+import { shallowMount } from '@vue/test-utils';
+import { joinPaths } from '~/lib/utils/url_utility';
+import { TEST_HOST } from 'helpers/test_constants';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import userDataMock from '../../user_data_mock';
+
+const TOOLTIP_PLACEMENT = 'bottom';
+const { name: USER_NAME, username: USER_USERNAME } = userDataMock();
+const TEST_ISSUABLE_TYPE = 'merge_request';
+
+describe('AssigneeAvatarLink component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ showLess: true,
+ rootPath: TEST_HOST,
+ tooltipPlacement: TOOLTIP_PLACEMENT,
+ singleUser: false,
+ issuableType: TEST_ISSUABLE_TYPE,
+ ...props,
+ };
+
+ wrapper = shallowMount(AssigneeAvatarLink, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTooltipText = () => wrapper.attributes('data-original-title');
+
+ it('has the root url present in the assigneeUrl method', () => {
+ createComponent();
+ const assigneeUrl = joinPaths(TEST_HOST, USER_USERNAME);
+
+ expect(wrapper.attributes().href).toEqual(assigneeUrl);
+ });
+
+ it('renders assignee avatar', () => {
+ createComponent();
+
+ expect(wrapper.find(AssigneeAvatar).props()).toEqual(
+ expect.objectContaining({
+ issuableType: TEST_ISSUABLE_TYPE,
+ user: userDataMock(),
+ }),
+ );
+ });
+
+ describe.each`
+ issuableType | tooltipHasName | canMerge | expected
+ ${'merge_request'} | ${true} | ${true} | ${USER_NAME}
+ ${'merge_request'} | ${true} | ${false} | ${`${USER_NAME} (cannot merge)`}
+ ${'merge_request'} | ${false} | ${true} | ${''}
+ ${'merge_request'} | ${false} | ${false} | ${'Cannot merge'}
+ ${'issue'} | ${true} | ${true} | ${USER_NAME}
+ ${'issue'} | ${true} | ${false} | ${USER_NAME}
+ ${'issue'} | ${false} | ${true} | ${''}
+ ${'issue'} | ${false} | ${false} | ${''}
+ `(
+ 'with $issuableType and tooltipHasName=$tooltipHasName and canMerge=$canMerge',
+ ({ issuableType, tooltipHasName, canMerge, expected }) => {
+ beforeEach(() => {
+ createComponent({
+ issuableType,
+ tooltipHasName,
+ user: {
+ ...userDataMock(),
+ can_merge: canMerge,
+ },
+ });
+ });
+
+ it('sets tooltip', () => {
+ expect(findTooltipText()).toBe(expected);
+ });
+ },
+ );
+});
diff --git a/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
new file mode 100644
index 00000000000..d60ae17733b
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/assignee_avatar_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import userDataMock from '../../user_data_mock';
+
+const TEST_AVATAR = `${TEST_HOST}/avatar.png`;
+const TEST_DEFAULT_AVATAR_URL = `${TEST_HOST}/default/avatar/url.png`;
+
+describe('AssigneeAvatar', () => {
+ let origGon;
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ imgSize: 24,
+ issuableType: 'merge_request',
+ ...props,
+ };
+
+ wrapper = shallowMount(AssigneeAvatar, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ beforeEach(() => {
+ origGon = window.gon;
+ window.gon = { default_avatar_url: TEST_DEFAULT_AVATAR_URL };
+ });
+
+ afterEach(() => {
+ window.gon = origGon;
+ wrapper.destroy();
+ });
+
+ const findImg = () => wrapper.find('img');
+
+ it('does not show warning icon if assignee can merge', () => {
+ createComponent();
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(false);
+ });
+
+ it('shows warning icon if assignee cannot merge', () => {
+ createComponent({
+ user: {
+ can_merge: false,
+ },
+ });
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(true);
+ });
+
+ it('does not show warning icon for issuableType = "issue"', () => {
+ createComponent({
+ issuableType: 'issue',
+ });
+
+ expect(wrapper.find('.merge-icon').exists()).toBe(false);
+ });
+
+ it.each`
+ avatar | avatar_url | expected | desc
+ ${TEST_AVATAR} | ${null} | ${TEST_AVATAR} | ${'with avatar'}
+ ${null} | ${TEST_AVATAR} | ${TEST_AVATAR} | ${'with avatar_url'}
+ ${null} | ${null} | ${TEST_DEFAULT_AVATAR_URL} | ${'with no avatar'}
+ `('$desc', ({ avatar, avatar_url, expected }) => {
+ createComponent({
+ user: {
+ avatar,
+ avatar_url,
+ },
+ });
+
+ expect(findImg().attributes('src')).toEqual(expected);
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
new file mode 100644
index 00000000000..ff0c8d181b5
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_list_spec.js
@@ -0,0 +1,189 @@
+import { shallowMount } from '@vue/test-utils';
+import CollapsedAssigneeList from '~/sidebar/components/assignees/collapsed_assignee_list.vue';
+import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import UsersMockHelper from 'helpers/user_mock_data_helper';
+
+const DEFAULT_MAX_COUNTER = 99;
+
+describe('CollapsedAssigneeList component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ users: [],
+ issuableType: 'merge_request',
+ ...props,
+ };
+
+ wrapper = shallowMount(CollapsedAssigneeList, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ const findNoUsersIcon = () => wrapper.find('i[aria-label=None]');
+ const findAvatarCounter = () => wrapper.find('.avatar-counter');
+ const findAssignees = () => wrapper.findAll(CollapsedAssignee);
+ const getTooltipTitle = () => wrapper.attributes('data-original-title');
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('No assignees/users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: [],
+ });
+ });
+
+ it('has no users', () => {
+ expect(findNoUsersIcon().exists()).toBe(true);
+ });
+ });
+
+ describe('One assignee/user', () => {
+ let users;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(1);
+ });
+
+ it('should not show no users icon', () => {
+ createComponent({ users });
+
+ expect(findNoUsersIcon().exists()).toBe(false);
+ });
+
+ it('has correct "cannot merge" tooltip when user cannot merge', () => {
+ users[0].can_merge = false;
+
+ createComponent({ users });
+
+ expect(getTooltipTitle()).toContain('cannot merge');
+ });
+
+ it('does not have "merge" word in tooltip if user can merge', () => {
+ users[0].can_merge = true;
+
+ createComponent({ users });
+
+ expect(getTooltipTitle()).not.toContain('merge');
+ });
+ });
+
+ describe('More than one assignees/users', () => {
+ let users;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(2);
+
+ createComponent({ users });
+ });
+
+ it('has multiple-users class', () => {
+ expect(wrapper.classes('multiple-users')).toBe(true);
+ });
+
+ it('does not display an avatar count', () => {
+ expect(findAvatarCounter().exists()).toBe(false);
+ });
+
+ it('returns just two collapsed users', () => {
+ expect(findAssignees().length).toBe(2);
+ });
+ });
+
+ describe('More than two assignees/users', () => {
+ let users;
+ let userNames;
+
+ beforeEach(() => {
+ users = UsersMockHelper.createNumberRandomUsers(3);
+ userNames = users.map(x => x.name).join(', ');
+ });
+
+ describe('default', () => {
+ beforeEach(() => {
+ createComponent({ users });
+ });
+
+ it('does display an avatar count', () => {
+ expect(findAvatarCounter().exists()).toBe(true);
+ expect(findAvatarCounter().text()).toEqual('+2');
+ });
+
+ it('returns one collapsed users', () => {
+ expect(findAssignees().length).toBe(1);
+ });
+ });
+
+ it('has corrent "no one can merge" tooltip when no one can merge', () => {
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = false;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (no one can merge)`);
+ });
+
+ it('has correct "cannot merge" tooltip when one user can merge', () => {
+ users[0].can_merge = true;
+ users[1].can_merge = false;
+ users[2].can_merge = false;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (1/3 can merge)`);
+ });
+
+ it('has correct "cannot merge" tooltip when more than one user can merge', () => {
+ users[0].can_merge = false;
+ users[1].can_merge = true;
+ users[2].can_merge = true;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(`${userNames} (2/3 can merge)`);
+ });
+
+ it('does not have "merge" in tooltip if everyone can merge', () => {
+ users[0].can_merge = true;
+ users[1].can_merge = true;
+ users[2].can_merge = true;
+
+ createComponent({
+ users,
+ });
+
+ expect(getTooltipTitle()).toEqual(userNames);
+ });
+
+ it('displays the correct avatar count', () => {
+ users = UsersMockHelper.createNumberRandomUsers(5);
+
+ createComponent({
+ users,
+ });
+
+ expect(findAvatarCounter().text()).toEqual(`+${users.length - 1}`);
+ });
+
+ it('displays the correct avatar count via a computed property if more than default max counter', () => {
+ users = UsersMockHelper.createNumberRandomUsers(100);
+
+ createComponent({
+ users,
+ });
+
+ expect(findAvatarCounter().text()).toEqual(`${DEFAULT_MAX_COUNTER}+`);
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
new file mode 100644
index 00000000000..f9ca7bc1ecb
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/collapsed_assignee_spec.js
@@ -0,0 +1,49 @@
+import { shallowMount } from '@vue/test-utils';
+import CollapsedAssignee from '~/sidebar/components/assignees/collapsed_assignee.vue';
+import AssigneeAvatar from '~/sidebar/components/assignees/assignee_avatar.vue';
+import userDataMock from '../../user_data_mock';
+
+const TEST_USER = userDataMock();
+const TEST_ISSUABLE_TYPE = 'merge_request';
+
+describe('CollapsedAssignee assignee component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ user: userDataMock(),
+ issuableType: TEST_ISSUABLE_TYPE,
+ ...props,
+ };
+
+ wrapper = shallowMount(CollapsedAssignee, {
+ propsData,
+ sync: false,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('has author name', () => {
+ createComponent();
+
+ expect(
+ wrapper
+ .find('.author')
+ .text()
+ .trim(),
+ ).toEqual(TEST_USER.name);
+ });
+
+ it('has assignee avatar', () => {
+ createComponent();
+
+ expect(wrapper.find(AssigneeAvatar).props()).toEqual({
+ imgSize: 24,
+ user: TEST_USER,
+ issuableType: TEST_ISSUABLE_TYPE,
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
new file mode 100644
index 00000000000..6398351834c
--- /dev/null
+++ b/spec/frontend/sidebar/components/assignees/uncollapsed_assignee_list_spec.js
@@ -0,0 +1,103 @@
+import { mount } from '@vue/test-utils';
+import UncollapsedAssigneeList from '~/sidebar/components/assignees/uncollapsed_assignee_list.vue';
+import AssigneeAvatarLink from '~/sidebar/components/assignees/assignee_avatar_link.vue';
+import { TEST_HOST } from 'helpers/test_constants';
+import userDataMock from '../../user_data_mock';
+import UsersMockHelper from '../../../helpers/user_mock_data_helper';
+
+const DEFAULT_RENDER_COUNT = 5;
+
+describe('UncollapsedAssigneeList component', () => {
+ let wrapper;
+
+ function createComponent(props = {}) {
+ const propsData = {
+ users: [],
+ rootPath: TEST_HOST,
+ ...props,
+ };
+
+ wrapper = mount(UncollapsedAssigneeList, {
+ sync: false,
+ propsData,
+ });
+ }
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findMoreButton = () => wrapper.find('.user-list-more button');
+
+ describe('One assignee/user', () => {
+ let user;
+
+ beforeEach(() => {
+ user = userDataMock();
+
+ createComponent({
+ users: [user],
+ });
+ });
+
+ it('only has one user', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(1);
+ });
+
+ it('calls the AssigneeAvatarLink with the proper props', () => {
+ expect(wrapper.find(AssigneeAvatarLink).exists()).toBe(true);
+ expect(wrapper.find(AssigneeAvatarLink).props().tooltipPlacement).toEqual('left');
+ });
+
+ it('Shows one user with avatar, username and author name', () => {
+ expect(wrapper.text()).toContain(user.name);
+ expect(wrapper.text()).toContain(`@${user.username}`);
+ });
+ });
+
+ describe('n+ more label', () => {
+ describe('when users count is rendered users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: UsersMockHelper.createNumberRandomUsers(DEFAULT_RENDER_COUNT),
+ });
+ });
+
+ it('does not show more label', () => {
+ expect(findMoreButton().exists()).toBe(false);
+ });
+ });
+
+ describe('when more than rendered users', () => {
+ beforeEach(() => {
+ createComponent({
+ users: UsersMockHelper.createNumberRandomUsers(DEFAULT_RENDER_COUNT + 1),
+ });
+ });
+
+ it('shows "+1 more" label', () => {
+ expect(findMoreButton().text()).toBe('+ 1 more');
+ });
+
+ it('shows truncated users', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT);
+ });
+
+ describe('when more button is clicked', () => {
+ beforeEach(() => {
+ findMoreButton().trigger('click');
+
+ return wrapper.vm.$nextTick();
+ });
+
+ it('shows "show less" label', () => {
+ expect(findMoreButton().text()).toBe('- show less');
+ });
+
+ it('shows all users', () => {
+ expect(wrapper.findAll(AssigneeAvatarLink).length).toBe(DEFAULT_RENDER_COUNT + 1);
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/sidebar/user_data_mock.js b/spec/frontend/sidebar/user_data_mock.js
new file mode 100644
index 00000000000..8ad70bb3499
--- /dev/null
+++ b/spec/frontend/sidebar/user_data_mock.js
@@ -0,0 +1,9 @@
+export default () => ({
+ avatar_url: 'mock_path',
+ id: 1,
+ name: 'Root',
+ state: 'active',
+ username: 'root',
+ web_url: '',
+ can_merge: true,
+});
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index df8a625319b..d52aeb1fe6b 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -93,3 +93,9 @@ Object.assign(global, {
clearTimeout(id);
},
});
+
+// make sure that each test actually tests something
+// see https://jestjs.io/docs/en/expect#expecthasassertions
+beforeEach(() => {
+ expect.hasAssertions();
+});
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
index cd0bf50f8e9..0e862c683d3 100644
--- a/spec/frontend/tracking_spec.js
+++ b/spec/frontend/tracking_spec.js
@@ -15,6 +15,12 @@ describe('Tracking', () => {
snowplowSpy = jest.spyOn(window, 'snowplow');
});
+ afterEach(() => {
+ window.doNotTrack = undefined;
+ navigator.doNotTrack = undefined;
+ navigator.msDoNotTrack = undefined;
+ });
+
it('tracks to snowplow (our current tracking system)', () => {
Tracking.event('_category_', '_eventName_', { label: '_label_' });
@@ -31,6 +37,27 @@ describe('Tracking', () => {
expect(snowplowSpy).not.toHaveBeenCalled();
});
+
+ it('skips tracking if the user does not want to be tracked (general spec)', () => {
+ window.doNotTrack = '1';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+
+ it('skips tracking if the user does not want to be tracked (firefox legacy)', () => {
+ navigator.doNotTrack = 'yes';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+
+ it('skips tracking if the user does not want to be tracked (IE legacy)', () => {
+ navigator.msDoNotTrack = '1';
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
});
describe('tracking interface events', () => {
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/graphql/types/namespace_type_spec.rb b/spec/graphql/types/namespace_type_spec.rb
index e1153832cc9..f476dd7286f 100644
--- a/spec/graphql/types/namespace_type_spec.rb
+++ b/spec/graphql/types/namespace_type_spec.rb
@@ -8,7 +8,7 @@ describe GitlabSchema.types['Namespace'] do
it 'has the expected fields' do
expected_fields = %w[
id name path full_name full_path description description_html visibility
- lfs_enabled request_access_enabled projects
+ lfs_enabled request_access_enabled projects root_storage_statistics
]
is_expected.to have_graphql_fields(*expected_fields)
diff --git a/spec/graphql/types/root_storage_statistics_type_spec.rb b/spec/graphql/types/root_storage_statistics_type_spec.rb
new file mode 100644
index 00000000000..8c69c13aa73
--- /dev/null
+++ b/spec/graphql/types/root_storage_statistics_type_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe GitlabSchema.types['RootStorageStatistics'] do
+ it { expect(described_class.graphql_name).to eq('RootStorageStatistics') }
+
+ it 'has all the required fields' do
+ is_expected.to have_graphql_fields(:storage_size, :repository_size, :lfs_objects_size,
+ :build_artifacts_size, :packages_size, :wiki_size)
+ end
+
+ it { is_expected.to require_graphql_authorizations(:read_statistics) }
+end
diff --git a/spec/helpers/ci_status_helper_spec.rb b/spec/helpers/ci_status_helper_spec.rb
index bc2422aba90..4f665dc0514 100644
--- a/spec/helpers/ci_status_helper_spec.rb
+++ b/spec/helpers/ci_status_helper_spec.rb
@@ -53,4 +53,80 @@ describe CiStatusHelper do
expect(helper.pipeline_status_cache_key(pipeline_status)).to eq("pipeline-status/123abc-success")
end
end
+
+ describe "#render_status_with_link" do
+ subject { helper.render_status_with_link("success") }
+
+ it "renders a passed status icon" do
+ is_expected.to include("<span class=\"ci-status-link ci-status-icon-success d-inline-flex")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the success status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+
+ context "when pipeline has commit path" do
+ subject { helper.render_status_with_link("success", "/commit-path") }
+
+ it "links to commit" do
+ is_expected.to include("href=\"/commit-path\"")
+ end
+
+ it "does not contain a span element" do
+ is_expected.not_to include("<span")
+ end
+
+ it "has 'Pipeline' as the status type in the title" do
+ is_expected.to include("title=\"Pipeline: passed\"")
+ end
+
+ it "has the correct status icon" do
+ is_expected.to include("ci-status-icon-success")
+ end
+ end
+
+ context "when different type than pipeline is provided" do
+ subject { helper.render_status_with_link("success", type: "commit") }
+
+ it "has the provided type in the title" do
+ is_expected.to include("title=\"Commit: passed\"")
+ end
+ end
+
+ context "when tooltip_placement is provided" do
+ subject { helper.render_status_with_link("success", tooltip_placement: "right") }
+
+ it "has the provided tooltip placement" do
+ is_expected.to include("data-placement=\"right\"")
+ end
+ end
+
+ context "when additional CSS classes are provided" do
+ subject { helper.render_status_with_link("success", cssclass: "extra-class") }
+
+ it "has appended extra class to icon classes" do
+ is_expected.to include("class=\"ci-status-link ci-status-icon-success d-inline-flex extra-class\"")
+ end
+ end
+
+ context "when container is provided" do
+ subject { helper.render_status_with_link("success", container: "my-container") }
+
+ it "has the provided container in data" do
+ is_expected.to include("data-container=\"my-container\"")
+ end
+ end
+
+ context "when icon_size is provided" do
+ subject { helper.render_status_with_link("success", icon_size: 24) }
+
+ it "has the svg class to change size" do
+ is_expected.to include("<svg class=\"s24\">")
+ end
+ end
+ end
end
diff --git a/spec/initializers/action_mailer_hooks_spec.rb b/spec/initializers/action_mailer_hooks_spec.rb
new file mode 100644
index 00000000000..3826ed9b00a
--- /dev/null
+++ b/spec/initializers/action_mailer_hooks_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe 'ActionMailer hooks' do
+ describe 'smime signature interceptor' do
+ before do
+ class_spy(ActionMailer::Base).as_stubbed_const
+ end
+
+ it 'is disabled by default' do
+ load Rails.root.join('config/initializers/action_mailer_hooks.rb')
+
+ expect(ActionMailer::Base).not_to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ end
+
+ describe 'interceptor testbed' do
+ where(:email_enabled, :email_smime_enabled, :smime_interceptor_enabled) do
+ [
+ [false, false, false],
+ [false, true, false],
+ [true, false, false],
+ [true, true, true]
+ ]
+ end
+
+ with_them do
+ before do
+ stub_config_setting(email_enabled: email_enabled)
+ stub_config_setting(email_smime: { enabled: email_smime_enabled })
+ end
+
+ it 'is enabled depending on settings' do
+ load Rails.root.join('config/initializers/action_mailer_hooks.rb')
+
+ if smime_interceptor_enabled
+ expect(ActionMailer::Base).to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ else
+ expect(ActionMailer::Base).not_to(
+ have_received(:register_interceptor).with(Gitlab::Email::Hook::SmimeSignatureInterceptor))
+ end
+ end
+ end
+ end
+ end
+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/components/commit_sidebar/actions_spec.js b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
index b903abe63fc..a3db3ee1b18 100644
--- a/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/actions_spec.js
@@ -1,30 +1,28 @@
import Vue from 'vue';
-import store from '~/ide/stores';
-import consts from '~/ide/stores/modules/commit/constants';
+import { createStore } from '~/ide/stores';
import commitActions from '~/ide/components/commit_sidebar/actions.vue';
+import consts from '~/ide/stores/modules/commit/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from 'spec/ide/helpers';
-import { projectData } from 'spec/ide/mock_data';
+import { projectData, branches } from 'spec/ide/mock_data';
+
+const ACTION_UPDATE_COMMIT_ACTION = 'commit/updateCommitAction';
describe('IDE commit sidebar actions', () => {
+ let store;
let vm;
- const createComponent = ({
- hasMR = false,
- commitAction = consts.COMMIT_TO_NEW_BRANCH,
- mergeRequestsEnabled = true,
- currentBranchId = 'master',
- shouldCreateMR = false,
- } = {}) => {
+
+ const createComponent = ({ hasMR = false, currentBranchId = 'master' } = {}) => {
const Component = Vue.extend(commitActions);
vm = createComponentWithStore(Component, store);
vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
- vm.$store.state.commit.commitAction = commitAction;
- Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
- vm.$store.state.projects.abcproject.merge_requests_enabled = mergeRequestsEnabled;
- vm.$store.state.commit.shouldCreateMR = shouldCreateMR;
+
+ const proj = { ...projectData };
+ proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
+
+ Vue.set(vm.$store.state.projects, 'abcproject', proj);
if (hasMR) {
vm.$store.state.currentMergeRequestId = '1';
@@ -33,13 +31,19 @@ describe('IDE commit sidebar actions', () => {
] = { foo: 'bar' };
}
- return vm.$mount();
+ vm.$mount();
+
+ return vm;
};
+ beforeEach(() => {
+ store = createStore();
+ spyOn(store, 'dispatch');
+ });
+
afterEach(() => {
vm.$destroy();
-
- resetStore(vm.$store);
+ vm = null;
});
it('renders 2 groups', () => {
@@ -73,4 +77,152 @@ describe('IDE commit sidebar actions', () => {
expect(vm.commitToCurrentBranchText).not.toContain(injectedSrc);
});
});
+
+ describe('updateSelectedCommitAction', () => {
+ it('does not return anything if currentBranch does not exist', () => {
+ createComponent({ currentBranchId: null });
+
+ expect(vm.$store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('calls again after staged changes', done => {
+ createComponent({ currentBranchId: null });
+
+ vm.$store.state.currentBranchId = 'master';
+ vm.$store.state.changedFiles.push({});
+ vm.$store.state.stagedFiles.push({});
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ jasmine.anything(),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ describe('default branch', () => {
+ it('dispatches correct action for default branch', () => {
+ createComponent({
+ currentBranchId: 'master',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledTimes(1);
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+
+ describe('protected branch', () => {
+ describe('with write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'protected/access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'protected/access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+ });
+
+ describe('without write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'protected/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'protected/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+ });
+
+ describe('regular branch', () => {
+ describe('with write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'regular',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'regular',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_CURRENT_BRANCH,
+ );
+ });
+ });
+
+ describe('without write access', () => {
+ it('dispatches correct action when MR exists', () => {
+ createComponent({
+ hasMR: true,
+ currentBranchId: 'regular/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+
+ it('dispatches correct action when MR does not exists', () => {
+ createComponent({
+ hasMR: false,
+ currentBranchId: 'regular/no-access',
+ });
+
+ expect(vm.$store.dispatch).toHaveBeenCalledWith(
+ ACTION_UPDATE_COMMIT_ACTION,
+ consts.COMMIT_TO_NEW_BRANCH,
+ );
+ });
+ });
+ });
+ });
});
diff --git a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
index 7017bfcd6a6..5f2db695241 100644
--- a/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/new_merge_request_option_spec.js
@@ -1,33 +1,36 @@
import Vue from 'vue';
import store from '~/ide/stores';
-import consts from '~/ide/stores/modules/commit/constants';
import NewMergeRequestOption from '~/ide/components/commit_sidebar/new_merge_request_option.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { projectData } from 'spec/ide/mock_data';
+import { projectData, branches } from 'spec/ide/mock_data';
import { resetStore } from 'spec/ide/helpers';
+import consts from '../../../../../app/assets/javascripts/ide/stores/modules/commit/constants';
describe('create new MR checkbox', () => {
let vm;
- const createComponent = ({
- hasMR = false,
- commitAction = consts.COMMIT_TO_NEW_BRANCH,
- currentBranchId = 'master',
- } = {}) => {
+ const setMR = () => {
+ vm.$store.state.currentMergeRequestId = '1';
+ vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
+ store.state.currentMergeRequestId
+ ] = { foo: 'bar' };
+ };
+
+ const createComponent = ({ currentBranchId = 'master', createNewBranch = false } = {}) => {
const Component = Vue.extend(NewMergeRequestOption);
vm = createComponentWithStore(Component, store);
+ vm.$store.state.commit.commitAction = createNewBranch
+ ? consts.COMMIT_TO_NEW_BRANCH
+ : consts.COMMIT_TO_CURRENT_BRANCH;
+
vm.$store.state.currentBranchId = currentBranchId;
vm.$store.state.currentProjectId = 'abcproject';
- vm.$store.state.commit.commitAction = commitAction;
- Vue.set(vm.$store.state.projects, 'abcproject', { ...projectData });
- if (hasMR) {
- vm.$store.state.currentMergeRequestId = '1';
- vm.$store.state.projects[store.state.currentProjectId].mergeRequests[
- store.state.currentMergeRequestId
- ] = { foo: 'bar' };
- }
+ const proj = JSON.parse(JSON.stringify(projectData));
+ proj.branches[currentBranchId] = branches.find(branch => branch.name === currentBranchId);
+
+ Vue.set(vm.$store.state.projects, 'abcproject', proj);
return vm.$mount();
};
@@ -38,30 +41,131 @@ describe('create new MR checkbox', () => {
resetStore(vm.$store);
});
- it('is hidden when an MR already exists and committing to current branch', () => {
- createComponent({
- hasMR: true,
- commitAction: consts.COMMIT_TO_CURRENT_BRANCH,
- currentBranchId: 'feature',
+ describe('for default branch', () => {
+ describe('is rendered when pushing to a new branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'master',
+ createNewBranch: true,
+ });
+ });
+
+ it('has NO new MR', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('has new MR', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).not.toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
- expect(vm.$el.textContent).toBe('');
+ describe('is NOT rendered when pushing to the same branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'master',
+ createNewBranch: false,
+ });
+ });
+
+ it('has NO new MR', () => {
+ expect(vm.$el.textContent).toBe('');
+ });
+
+ it('has new MR', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
- it('does not hide checkbox if MR does not exist', () => {
- createComponent({ hasMR: false });
+ describe('for protected branch', () => {
+ describe('when user does not have the write access', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'protected/no-access',
+ });
+ });
+
+ it('is rendered if MR does not exists', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is rendered if MR exists', done => {
+ setMR();
- expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).not.toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when user has the write access', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'protected/access',
+ });
+ });
+
+ it('is rendered if MR does not exist', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is hidden if MR exists', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
});
- it('does not hide checkbox when creating a new branch', () => {
- createComponent({ commitAction: consts.COMMIT_TO_NEW_BRANCH });
+ describe('for regular branch', () => {
+ beforeEach(() => {
+ createComponent({
+ currentBranchId: 'regular',
+ });
+ });
- expect(vm.$el.querySelector('input[type="checkbox"]').hidden).toBe(false);
+ it('is rendered if no MR exists', () => {
+ expect(vm.$el.textContent).not.toBe('');
+ });
+
+ it('is hidden if MR exists', done => {
+ setMR();
+
+ vm.$nextTick()
+ .then(() => {
+ expect(vm.$el.textContent).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
it('dispatches toggleShouldCreateMR when clicking checkbox', () => {
- createComponent();
+ createComponent({
+ currentBranchId: 'regular',
+ });
const el = vm.$el.querySelector('input[type="checkbox"]');
spyOn(vm.$store, 'dispatch');
el.dispatchEvent(new Event('change'));
diff --git a/spec/javascripts/ide/mock_data.js b/spec/javascripts/ide/mock_data.js
index 570a396c5e3..c02c7e5d45e 100644
--- a/spec/javascripts/ide/mock_data.js
+++ b/spec/javascripts/ide/mock_data.js
@@ -176,23 +176,51 @@ export const branches = [
committed_date: '2018-08-01T00:20:05Z',
},
can_push: true,
+ protected: true,
+ default: true,
},
{
id: 2,
- name: 'feature/lorem-ipsum',
+ name: 'protected/no-access',
commit: {
message: 'Update some stuff',
committed_date: '2018-08-02T00:00:05Z',
},
- can_push: true,
+ can_push: false,
+ protected: true,
+ default: false,
},
{
id: 3,
- name: 'feature/dolar-amit',
+ name: 'protected/access',
+ commit: {
+ message: 'Update some stuff',
+ committed_date: '2018-08-02T00:00:05Z',
+ },
+ can_push: true,
+ protected: true,
+ default: false,
+ },
+ {
+ id: 4,
+ name: 'regular',
commit: {
message: 'Update some more stuff',
committed_date: '2018-06-30T00:20:05Z',
},
can_push: true,
+ protected: false,
+ default: false,
+ },
+ {
+ id: 5,
+ name: 'regular/no-access',
+ commit: {
+ message: 'Update some more stuff',
+ committed_date: '2018-06-30T00:20:05Z',
+ },
+ can_push: false,
+ protected: false,
+ default: false,
},
];
diff --git a/spec/javascripts/ide/stores/getters_spec.js b/spec/javascripts/ide/stores/getters_spec.js
index 735bbd47f55..73a8d993a13 100644
--- a/spec/javascripts/ide/stores/getters_spec.js
+++ b/spec/javascripts/ide/stores/getters_spec.js
@@ -221,4 +221,36 @@ describe('IDE store getters', () => {
});
});
});
+
+ describe('canPushToBranch', () => {
+ it('returns false when no currentBranch exists', () => {
+ const localGetters = {
+ currentProject: undefined,
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeFalsy();
+ });
+
+ it('returns true when can_push to currentBranch', () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ currentBranch: { can_push: true },
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeTruthy();
+ });
+
+ it('returns false when !can_push to currentBranch', () => {
+ const localGetters = {
+ currentProject: {
+ default_branch: 'master',
+ },
+ currentBranch: { can_push: false },
+ };
+
+ expect(getters.canPushToBranch({}, localGetters)).toBeFalsy();
+ });
+ });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 14d861f21d2..091b454c0d2 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -57,6 +57,44 @@ describe('IDE commit module actions', () => {
.then(done)
.catch(done.fail);
});
+
+ it('sets shouldCreateMR to true if "Create new MR" option is visible', done => {
+ store.state.shouldHideNewMrOption = false;
+
+ testAction(
+ actions.updateCommitAction,
+ {},
+ store.state,
+ [
+ {
+ type: mutationTypes.UPDATE_COMMIT_ACTION,
+ payload: { commitAction: jasmine.anything() },
+ },
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: true },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('sets shouldCreateMR to false if "Create new MR" option is hidden', done => {
+ store.state.shouldHideNewMrOption = true;
+
+ testAction(
+ actions.updateCommitAction,
+ {},
+ store.state,
+ [
+ {
+ type: mutationTypes.UPDATE_COMMIT_ACTION,
+ payload: { commitAction: jasmine.anything() },
+ },
+ { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR, payload: false },
+ ],
+ [],
+ done,
+ );
+ });
});
describe('updateBranchName', () => {
@@ -541,147 +579,10 @@ describe('IDE commit module actions', () => {
actions.toggleShouldCreateMR,
{},
store.state,
- [
- { type: mutationTypes.TOGGLE_SHOULD_CREATE_MR },
- { type: mutationTypes.INTERACT_WITH_NEW_MR },
- ],
+ [{ type: mutationTypes.TOGGLE_SHOULD_CREATE_MR }],
[],
done,
);
});
});
-
- describe('setShouldCreateMR', () => {
- beforeEach(() => {
- store.state.projects = {
- project: {
- default_branch: 'master',
- branches: {
- master: {
- name: 'master',
- },
- feature: {
- name: 'feature',
- },
- },
- },
- };
-
- store.state.currentProjectId = 'project';
- });
-
- it('sets to false when the current branch already has an MR', done => {
- store.state.commit.currentMergeRequestId = 1;
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.currentBranchId = 'feature';
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('changes to false when current branch is the default branch and user has not interacted', done => {
- store.state.commit.interactedWithNewMR = false;
- store.state.currentBranchId = 'master';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('changes to true when "create new branch" is selected and user has not interacted', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- store.state.commit.interactedWithNewMR = false;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, true]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change anything if user has interacted and comitting to new branch', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_NEW_BRANCH;
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change anything if user has interacted and comitting to branch without MR', done => {
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.commit.currentMergeRequestId = null;
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
-
- it('still changes to false if hiding the checkbox', done => {
- store.state.currentBranchId = 'feature';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit.calls.allArgs()[0]).toEqual(
- jasmine.arrayContaining([`commit/${mutationTypes.TOGGLE_SHOULD_CREATE_MR}`, false]),
- );
- done();
- })
- .catch(done.fail);
- });
-
- it('does not change to false when on master and user has interacted even if MR exists', done => {
- store.state.currentBranchId = 'master';
- store.state.commit.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
- store.state.currentMergeRequestId = '1';
- store.state.commit.interactedWithNewMR = true;
- spyOn(store, 'commit').and.callThrough();
-
- store
- .dispatch('commit/setShouldCreateMR')
- .then(() => {
- expect(store.commit).not.toHaveBeenCalled();
- done();
- })
- .catch(done.fail);
- });
- });
});
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 6e71a790deb..07445c22917 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -1,6 +1,6 @@
import commitState from '~/ide/stores/modules/commit/state';
-import consts from '~/ide/stores/modules/commit/constants';
import * as getters from '~/ide/stores/modules/commit/getters';
+import consts from '~/ide/stores/modules/commit/constants';
describe('IDE commit module getters', () => {
let state;
@@ -55,15 +55,15 @@ describe('IDE commit module getters', () => {
});
});
- it('defualts to currentBranchId', () => {
- expect(getters.branchName(state, null, rootState)).toBe('master');
+ it('defaults to currentBranchId when not committing to a new branch', () => {
+ localGetters.isCreatingNewBranch = false;
+
+ expect(getters.branchName(state, localGetters, rootState)).toBe('master');
});
- describe('COMMIT_TO_NEW_BRANCH', () => {
+ describe('commit to a new branch', () => {
beforeEach(() => {
- Object.assign(state, {
- commitAction: consts.COMMIT_TO_NEW_BRANCH,
- });
+ localGetters.isCreatingNewBranch = true;
});
it('uses newBranchName when not empty', () => {
@@ -144,4 +144,152 @@ describe('IDE commit module getters', () => {
});
});
});
+
+ describe('isCreatingNewBranch', () => {
+ it('returns false if NOT creating a new branch', () => {
+ state.commitAction = consts.COMMIT_TO_CURRENT_BRANCH;
+
+ expect(getters.isCreatingNewBranch(state)).toBeFalsy();
+ });
+
+ it('returns true if creating a new branch', () => {
+ state.commitAction = consts.COMMIT_TO_NEW_BRANCH;
+
+ expect(getters.isCreatingNewBranch(state)).toBeTruthy();
+ });
+ });
+
+ describe('shouldHideNewMrOption', () => {
+ let localGetters = {};
+ let rootGetters = {};
+
+ beforeEach(() => {
+ localGetters = {
+ isCreatingNewBranch: null,
+ };
+ rootGetters = {
+ isOnDefaultBranch: null,
+ hasMergeRequest: null,
+ canPushToBranch: null,
+ };
+ });
+
+ describe('NO existing MR for the branch', () => {
+ beforeEach(() => {
+ rootGetters.hasMergeRequest = false;
+ });
+
+ it('should never hide "New MR" option', () => {
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+
+ describe('existing MR for the branch', () => {
+ beforeEach(() => {
+ rootGetters.hasMergeRequest = true;
+ });
+
+ it('should NOT hide "New MR" option if user can NOT push to the current branch', () => {
+ rootGetters.canPushToBranch = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+
+ it('should hide "New MR" option if user can push to the current branch', () => {
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy();
+ });
+ });
+
+ describe('user can NOT push the branch', () => {
+ beforeEach(() => {
+ rootGetters.canPushToBranch = false;
+ });
+
+ it('should never hide "New MR" option', () => {
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+
+ describe('user can push to the branch', () => {
+ beforeEach(() => {
+ rootGetters.canPushToBranch = true;
+ });
+
+ it('should NOT hide "New MR" option if there is NO existing MR for the current branch', () => {
+ rootGetters.hasMergeRequest = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+
+ it('should hide "New MR" option if there is existing MR for the current branch', () => {
+ rootGetters.hasMergeRequest = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy();
+ });
+ });
+
+ describe('default branch', () => {
+ beforeEach(() => {
+ rootGetters.isOnDefaultBranch = true;
+ });
+
+ describe('committing to the same branch', () => {
+ beforeEach(() => {
+ localGetters.isCreatingNewBranch = false;
+ rootGetters.canPushToBranch = true;
+ });
+
+ it('should hide "New MR" when there is an existing MR', () => {
+ rootGetters.hasMergeRequest = true;
+
+ expect(
+ getters.shouldHideNewMrOption(state, localGetters, null, rootGetters),
+ ).toBeTruthy();
+ });
+
+ it('should hide "New MR" when there is no existing MR', () => {
+ rootGetters.hasMergeRequest = false;
+
+ expect(
+ getters.shouldHideNewMrOption(state, localGetters, null, rootGetters),
+ ).toBeTruthy();
+ });
+ });
+
+ describe('creating a new branch', () => {
+ beforeEach(() => {
+ localGetters.isCreatingNewBranch = true;
+ });
+
+ it('should NOT hide "New MR" option no matter existence of an MR or write access', () => {
+ rootGetters.hasMergeRequest = false;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+
+ rootGetters.hasMergeRequest = true;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+
+ rootGetters.hasMergeRequest = false;
+ rootGetters.canPushToBranch = false;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
+ });
+
+ it('should never hide "New MR" option when creating a new branch', () => {
+ localGetters.isCreatingNewBranch = true;
+
+ rootGetters.isOnDefaultBranch = false;
+ rootGetters.hasMergeRequest = true;
+ rootGetters.canPushToBranch = true;
+
+ expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy();
+ });
+ });
});
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/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 296ee85089f..85949f2ae86 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -895,6 +895,45 @@ describe('common_utils', () => {
});
});
+ describe('searchBy', () => {
+ const searchSpace = {
+ iid: 1,
+ reference: '&1',
+ title: 'Error omnis quos consequatur ullam a vitae sed omnis libero cupiditate.',
+ url: '/groups/gitlab-org/-/epics/1',
+ };
+
+ it('returns null when `query` or `searchSpace` params are empty/undefined', () => {
+ expect(commonUtils.searchBy('omnis', null)).toBeNull();
+ expect(commonUtils.searchBy('', searchSpace)).toBeNull();
+ expect(commonUtils.searchBy()).toBeNull();
+ });
+
+ it('returns object with matching props based on `query` & `searchSpace` params', () => {
+ // String `omnis` is found only in `title` prop so return just that
+ expect(commonUtils.searchBy('omnis', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ title: searchSpace.title,
+ }),
+ );
+
+ // String `1` is found in both `iid` and `reference` props so return both
+ expect(commonUtils.searchBy('1', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ iid: searchSpace.iid,
+ reference: searchSpace.reference,
+ }),
+ );
+
+ // String `/epics/1` is found in `url` prop so return just that
+ expect(commonUtils.searchBy('/epics/1', searchSpace)).toEqual(
+ jasmine.objectContaining({
+ url: searchSpace.url,
+ }),
+ );
+ });
+ });
+
describe('isScopedLabel', () => {
it('returns true when `::` is present in title', () => {
expect(commonUtils.isScopedLabel({ title: 'foo::bar' })).toBe(true);
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index 57f99a09002..1e49a955815 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -225,6 +225,14 @@ describe('Area component', () => {
});
describe('chartOptions', () => {
+ describe('dataZoom', () => {
+ it('contains an svg object within an array to properly render icon', () => {
+ const dataZoomObject = [{}];
+
+ expect(areaChart.vm.chartOptions.dataZoom).toEqual(dataZoomObject);
+ });
+ });
+
describe('yAxis formatter', () => {
let format;
diff --git a/spec/javascripts/monitoring/charts/time_series_spec.js b/spec/javascripts/monitoring/charts/time_series_spec.js
new file mode 100644
index 00000000000..d145a64e8d0
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/time_series_spec.js
@@ -0,0 +1,335 @@
+import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
+import { GlLink } from '@gitlab/ui';
+import { GlAreaChart, GlLineChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
+import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
+import TimeSeries from '~/monitoring/components/charts/time_series.vue';
+import * as types from '~/monitoring/stores/mutation_types';
+import { TEST_HOST } from 'spec/test_constants';
+import MonitoringMock, { deploymentData, mockProjectPath } from '../mock_data';
+
+describe('Time series component', () => {
+ const mockSha = 'mockSha';
+ const mockWidgets = 'mockWidgets';
+ const mockSvgPathContent = 'mockSvgPathContent';
+ const projectPath = `${TEST_HOST}${mockProjectPath}`;
+ const commitUrl = `${projectPath}/commit/${mockSha}`;
+ let mockGraphData;
+ let makeTimeSeriesChart;
+ let spriteSpy;
+ let store;
+
+ beforeEach(() => {
+ store = createStore();
+ store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
+ store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
+ [mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
+
+ makeTimeSeriesChart = (graphData, type) =>
+ shallowMount(TimeSeries, {
+ propsData: {
+ graphData: { ...graphData, type },
+ containerWidth: 0,
+ deploymentData: store.state.monitoringDashboard.deploymentData,
+ projectPath,
+ },
+ slots: {
+ default: mockWidgets,
+ },
+ sync: false,
+ store,
+ });
+
+ spriteSpy = spyOnDependency(TimeSeries, 'getSvgIconPathContent').and.callFake(
+ () => new Promise(resolve => resolve(mockSvgPathContent)),
+ );
+ });
+
+ describe('general functions', () => {
+ let timeSeriesChart;
+
+ beforeEach(() => {
+ timeSeriesChart = makeTimeSeriesChart(mockGraphData, 'area-chart');
+ });
+
+ it('renders chart title', () => {
+ expect(timeSeriesChart.find('.js-graph-title').text()).toBe(mockGraphData.title);
+ });
+
+ it('contains graph widgets from slot', () => {
+ expect(timeSeriesChart.find('.js-graph-widgets').text()).toBe(mockWidgets);
+ });
+
+ describe('when exportMetricsToCsvEnabled is disabled', () => {
+ beforeEach(() => {
+ store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
+ });
+
+ it('does not render the Download CSV button', done => {
+ timeSeriesChart.vm.$nextTick(() => {
+ expect(timeSeriesChart.contains('glbutton-stub')).toBe(false);
+ done();
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('formatTooltipText', () => {
+ const mockDate = deploymentData[0].created_at;
+ const mockCommitUrl = deploymentData[0].commitUrl;
+ const generateSeriesData = type => ({
+ seriesData: [
+ {
+ seriesName: timeSeriesChart.vm.chartData[0].name,
+ componentSubType: type,
+ value: [mockDate, 5.55555],
+ seriesIndex: 0,
+ },
+ ],
+ value: mockDate,
+ });
+
+ describe('when series is of line type', () => {
+ beforeEach(done => {
+ timeSeriesChart.vm.formatTooltipText(generateSeriesData('line'));
+ timeSeriesChart.vm.$nextTick(done);
+ });
+
+ it('formats tooltip title', () => {
+ expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip content', () => {
+ const name = 'Core Usage';
+ const value = '5.556';
+ const seriesLabel = timeSeriesChart.find(GlChartSeriesLabel);
+
+ expect(seriesLabel.vm.color).toBe('');
+ expect(shallowWrapperContainsSlotText(seriesLabel, 'default', name)).toBe(true);
+ expect(timeSeriesChart.vm.tooltip.content).toEqual([{ name, value, color: undefined }]);
+ expect(
+ shallowWrapperContainsSlotText(
+ timeSeriesChart.find(GlAreaChart),
+ 'tooltipContent',
+ value,
+ ),
+ ).toBe(true);
+ });
+ });
+
+ describe('when series is of scatter type', () => {
+ beforeEach(() => {
+ timeSeriesChart.vm.formatTooltipText(generateSeriesData('scatter'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(timeSeriesChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip sha', () => {
+ expect(timeSeriesChart.vm.tooltip.sha).toBe('f5bcd1d9');
+ });
+
+ it('formats tooltip commit url', () => {
+ expect(timeSeriesChart.vm.tooltip.commitUrl).toBe(mockCommitUrl);
+ });
+ });
+ });
+
+ describe('setSvg', () => {
+ const mockSvgName = 'mockSvgName';
+
+ beforeEach(done => {
+ timeSeriesChart.vm.setSvg(mockSvgName);
+ timeSeriesChart.vm.$nextTick(done);
+ });
+
+ it('gets svg path content', () => {
+ expect(spriteSpy).toHaveBeenCalledWith(mockSvgName);
+ });
+
+ it('sets svg path content', () => {
+ timeSeriesChart.vm.$nextTick(() => {
+ expect(timeSeriesChart.vm.svgs[mockSvgName]).toBe(`path://${mockSvgPathContent}`);
+ });
+ });
+ });
+
+ describe('onResize', () => {
+ const mockWidth = 233;
+
+ beforeEach(() => {
+ spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
+ width: mockWidth,
+ }));
+ timeSeriesChart.vm.onResize();
+ });
+
+ it('sets area chart width', () => {
+ expect(timeSeriesChart.vm.width).toBe(mockWidth);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('chartData', () => {
+ let chartData;
+ const seriesData = () => chartData[0];
+
+ beforeEach(() => {
+ ({ chartData } = timeSeriesChart.vm);
+ });
+
+ it('utilizes all data points', () => {
+ const { values } = mockGraphData.queries[0].result[0];
+
+ expect(chartData.length).toBe(1);
+ expect(seriesData().data.length).toBe(values.length);
+ });
+
+ it('creates valid data', () => {
+ const { data } = seriesData();
+
+ expect(
+ data.filter(
+ ([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number',
+ ).length,
+ ).toBe(data.length);
+ });
+
+ it('formats line width correctly', () => {
+ expect(chartData[0].lineStyle.width).toBe(2);
+ });
+ });
+
+ describe('chartOptions', () => {
+ describe('yAxis formatter', () => {
+ let format;
+
+ beforeEach(() => {
+ format = timeSeriesChart.vm.chartOptions.yAxis.axisLabel.formatter;
+ });
+
+ it('rounds to 3 decimal places', () => {
+ expect(format(0.88888)).toBe('0.889');
+ });
+ });
+ });
+
+ describe('scatterSeries', () => {
+ it('utilizes deployment data', () => {
+ expect(timeSeriesChart.vm.scatterSeries.data).toEqual([
+ ['2017-05-31T21:23:37.881Z', 0],
+ ['2017-05-30T20:08:04.629Z', 0],
+ ['2017-05-30T17:42:38.409Z', 0],
+ ]);
+
+ expect(timeSeriesChart.vm.scatterSeries.symbolSize).toBe(14);
+ });
+ });
+
+ describe('yAxisLabel', () => {
+ it('constructs a label for the chart y-axis', () => {
+ expect(timeSeriesChart.vm.yAxisLabel).toBe('CPU');
+ });
+ });
+
+ describe('csvText', () => {
+ it('converts data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(timeSeriesChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadLink', () => {
+ it('produces a link to download metrics as csv', () => {
+ const link = timeSeriesChart.vm.downloadLink;
+
+ expect(link).toContain('blob:');
+ });
+ });
+ });
+
+ afterEach(() => {
+ timeSeriesChart.destroy();
+ });
+ });
+
+ describe('wrapped components', () => {
+ const glChartComponents = [
+ {
+ chartType: 'area-chart',
+ component: GlAreaChart,
+ },
+ {
+ chartType: 'line-chart',
+ component: GlLineChart,
+ },
+ ];
+
+ glChartComponents.forEach(dynamicComponent => {
+ describe(`GitLab UI: ${dynamicComponent.chartType}`, () => {
+ let timeSeriesAreaChart;
+ let glChart;
+
+ beforeEach(done => {
+ timeSeriesAreaChart = makeTimeSeriesChart(mockGraphData, dynamicComponent.chartType);
+ glChart = timeSeriesAreaChart.find(dynamicComponent.component);
+ timeSeriesAreaChart.vm.$nextTick(done);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glChart.exists()).toBe(true);
+ expect(glChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glChart.props();
+
+ expect(props.data).toBe(timeSeriesAreaChart.vm.chartData);
+ expect(props.option).toBe(timeSeriesAreaChart.vm.chartOptions);
+ expect(props.formatTooltipText).toBe(timeSeriesAreaChart.vm.formatTooltipText);
+ expect(props.thresholds).toBe(timeSeriesAreaChart.vm.thresholds);
+ });
+
+ it('recieves a tooltip title', done => {
+ const mockTitle = 'mockTitle';
+ timeSeriesAreaChart.vm.tooltip.title = mockTitle;
+
+ timeSeriesAreaChart.vm.$nextTick(() => {
+ expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', mockTitle)).toBe(true);
+ done();
+ });
+ });
+
+ describe('when tooltip is showing deployment data', () => {
+ beforeEach(done => {
+ timeSeriesAreaChart.vm.tooltip.isDeployment = true;
+ timeSeriesAreaChart.vm.$nextTick(done);
+ });
+
+ it('uses deployment title', () => {
+ expect(shallowWrapperContainsSlotText(glChart, 'tooltipTitle', 'Deployed')).toBe(true);
+ });
+
+ it('renders clickable commit sha in tooltip content', done => {
+ timeSeriesAreaChart.vm.tooltip.sha = mockSha;
+ timeSeriesAreaChart.vm.tooltip.commitUrl = commitUrl;
+
+ timeSeriesAreaChart.vm.$nextTick(() => {
+ const commitLink = timeSeriesAreaChart.find(GlLink);
+
+ expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true);
+ expect(commitLink.attributes('href')).toEqual(commitUrl);
+ done();
+ });
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index 624d8b14c8f..f3ec7520c6f 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -13,7 +13,7 @@ import MonitoringMock, {
environmentData,
singleGroupResponse,
dashboardGitResponse,
-} from './mock_data';
+} from '../mock_data';
const localVue = createLocalVue();
const propsData = {
@@ -414,6 +414,26 @@ describe('Dashboard', () => {
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();
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index 85e660d3925..17e7314e214 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -1,5 +1,7 @@
export const mockApiEndpoint = `${gl.TEST_HOST}/monitoring/mock`;
+export const mockProjectPath = '/frontend-fixtures/environments-project';
+
export const metricsGroupsAPIResponse = {
success: true,
data: [
@@ -902,7 +904,7 @@ export const metricsDashboardResponse = {
},
{
title: 'Memory Usage (Pod average)',
- type: 'area-chart',
+ type: 'line-chart',
y_label: 'Memory Used per Pod',
weight: 2,
metrics: [
diff --git a/spec/javascripts/monitoring/panel_type_spec.js b/spec/javascripts/monitoring/panel_type_spec.js
index 086be628093..a2366e74d43 100644
--- a/spec/javascripts/monitoring/panel_type_spec.js
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -1,7 +1,7 @@
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 TimeSeriesChart from '~/monitoring/components/charts/time_series.vue';
import { graphDataPrometheusQueryRange } from './mock_data';
import { createStore } from '~/monitoring/stores';
@@ -62,9 +62,10 @@ describe('Panel Type component', () => {
});
});
- describe('Area Chart panel type', () => {
+ describe('Time Series Chart panel type', () => {
it('is rendered', () => {
- expect(panelType.find(AreaChart).exists()).toBe(true);
+ expect(panelType.find(TimeSeriesChart).isVueInstance()).toBe(true);
+ expect(panelType.find(TimeSeriesChart).exists()).toBe(true);
});
it('sets clipboard text on the dropdown', () => {
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index 71dcba114a9..d69f469c7c7 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -14,6 +14,13 @@ import {
const discussionWithTwoUnresolvedNotes = 'merge_requests/resolved_diff_discussion.json';
+// Helper function to ensure that we're using the same schema across tests.
+const createDiscussionNeighborParams = (discussionId, diffOrder, step) => ({
+ discussionId,
+ diffOrder,
+ step,
+});
+
describe('Getters Notes Store', () => {
let state;
@@ -25,7 +32,6 @@ describe('Getters Notes Store', () => {
targetNoteHash: 'hash',
lastFetchedAt: 'timestamp',
isNotesFetched: false,
-
notesData: notesDataMock,
userData: userDataMock,
noteableData: noteableDataMock,
@@ -244,62 +250,104 @@ describe('Getters Notes Store', () => {
});
});
- describe('nextUnresolvedDiscussionId', () => {
- const localGetters = {
- unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
- };
+ describe('findUnresolvedDiscussionIdNeighbor', () => {
+ let localGetters;
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
+ };
+ });
- it('should return the ID of the discussion after the ID provided', () => {
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('123')).toBe('456');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('456')).toBe('789');
- expect(getters.nextUnresolvedDiscussionId(state, localGetters)('789')).toBe('123');
+ [
+ { step: 1, id: '123', expected: '456' },
+ { step: 1, id: '456', expected: '789' },
+ { step: 1, id: '789', expected: '123' },
+ { step: -1, id: '123', expected: '789' },
+ { step: -1, id: '456', expected: '123' },
+ { step: -1, id: '789', expected: '456' },
+ ].forEach(({ step, id, expected }) => {
+ it(`with step ${step} and id ${id}, returns next value`, () => {
+ const params = createDiscussionNeighborParams(id, true, step);
+
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe(
+ expected,
+ );
+ });
});
- });
- describe('previousUnresolvedDiscussionId', () => {
- describe('with unresolved discussions', () => {
- const localGetters = {
- unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
- };
+ describe('with 1 unresolved discussion', () => {
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123'],
+ };
+ });
+
+ [{ step: 1, id: '123', expected: '123' }, { step: -1, id: '123', expected: '123' }].forEach(
+ ({ step, id, expected }) => {
+ it(`with step ${step} and match, returns only value`, () => {
+ const params = createDiscussionNeighborParams(id, true, step);
- it('with bogus returns falsey', () => {
- expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('456');
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe(
+ expected,
+ );
+ });
+ },
+ );
+
+ it('with no match, returns only value', () => {
+ const params = createDiscussionNeighborParams('bogus', true, 1);
+
+ expect(getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params)).toBe('123');
});
+ });
- [
- { id: '123', expected: '789' },
- { id: '456', expected: '123' },
- { id: '789', expected: '456' },
- ].forEach(({ id, expected }) => {
- it(`with ${id}, returns previous value`, () => {
- expect(getters.previousUnresolvedDiscussionId(state, localGetters)(id)).toBe(expected);
+ describe('with 0 unresolved discussions', () => {
+ beforeEach(() => {
+ localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => [],
+ };
+ });
+
+ [{ step: 1 }, { step: -1 }].forEach(({ step }) => {
+ it(`with step ${step}, returns undefined`, () => {
+ const params = createDiscussionNeighborParams('bogus', true, step);
+
+ expect(
+ getters.findUnresolvedDiscussionIdNeighbor(state, localGetters)(params),
+ ).toBeUndefined();
});
});
});
+ });
- describe('with 1 unresolved discussion', () => {
- const localGetters = {
- unresolvedDiscussionsIdsOrdered: () => ['123'],
- };
+ describe('findUnresolvedDiscussionIdNeighbor aliases', () => {
+ let neighbor;
+ let findUnresolvedDiscussionIdNeighbor;
+ let localGetters;
- it('with bogus returns id', () => {
- expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('123');
- });
+ beforeEach(() => {
+ neighbor = {};
+ findUnresolvedDiscussionIdNeighbor = jasmine.createSpy().and.returnValue(neighbor);
+ localGetters = { findUnresolvedDiscussionIdNeighbor };
+ });
- it('with match, returns value', () => {
- expect(getters.previousUnresolvedDiscussionId(state, localGetters)('123')).toEqual('123');
+ describe('nextUnresolvedDiscussionId', () => {
+ it('should return result of find neighbor', () => {
+ const expectedParams = createDiscussionNeighborParams('123', true, 1);
+ const result = getters.nextUnresolvedDiscussionId(state, localGetters)('123', true);
+
+ expect(findUnresolvedDiscussionIdNeighbor).toHaveBeenCalledWith(expectedParams);
+ expect(result).toBe(neighbor);
});
});
- describe('with 0 unresolved discussions', () => {
- const localGetters = {
- unresolvedDiscussionsIdsOrdered: () => [],
- };
+ describe('previosuUnresolvedDiscussionId', () => {
+ it('should return result of find neighbor', () => {
+ const expectedParams = createDiscussionNeighborParams('123', true, -1);
+ const result = getters.previousUnresolvedDiscussionId(state, localGetters)('123', true);
- it('returns undefined', () => {
- expect(
- getters.previousUnresolvedDiscussionId(state, localGetters)('bogus'),
- ).toBeUndefined();
+ expect(findUnresolvedDiscussionIdNeighbor).toHaveBeenCalledWith(expectedParams);
+ expect(result).toBe(neighbor);
});
});
});
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index e7675669f7a..5ea3f85a247 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -84,12 +84,7 @@ describe('Registry List', () => {
it('should render empty message', done => {
setTimeout(() => {
- expect(
- vm.$el
- .querySelector('p')
- .textContent.trim()
- .replace(/[\r\n]+/g, ' '),
- ).toEqual(
+ expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual(
'With the Container Registry, every project can have its own space to store its Docker images. More Information',
);
done();
@@ -124,7 +119,9 @@ describe('Registry List', () => {
it('should render invalid characters error message', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.container-message')).not.toBe(null);
+ expect(vm.$el.querySelector('p')).not.toContain(
+ 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information',
+ );
done();
});
});
diff --git a/spec/javascripts/sidebar/assignee_title_spec.js b/spec/javascripts/sidebar/assignee_title_spec.js
index 509edba2036..7fff7c075d9 100644
--- a/spec/javascripts/sidebar/assignee_title_spec.js
+++ b/spec/javascripts/sidebar/assignee_title_spec.js
@@ -4,8 +4,10 @@ import AssigneeTitle from '~/sidebar/components/assignees/assignee_title.vue';
describe('AssigneeTitle component', () => {
let component;
let AssigneeTitleComponent;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(AssigneeTitle, 'trackEvent');
AssigneeTitleComponent = Vue.extend(AssigneeTitle);
});
@@ -102,4 +104,16 @@ describe('AssigneeTitle component', () => {
expect(component.$el.querySelector('.edit-link')).not.toBeNull();
});
+
+ it('calls trackEvent when edit is clicked', () => {
+ component = new AssigneeTitleComponent({
+ propsData: {
+ numberOfAssignees: 0,
+ editable: true,
+ },
+ }).$mount();
+ component.$el.querySelector('.js-sidebar-dropdown-toggle').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
});
diff --git a/spec/javascripts/sidebar/assignees_spec.js b/spec/javascripts/sidebar/assignees_spec.js
index 4ae2141d5f0..a1df5389a38 100644
--- a/spec/javascripts/sidebar/assignees_spec.js
+++ b/spec/javascripts/sidebar/assignees_spec.js
@@ -94,115 +94,9 @@ describe('Assignee component', () => {
expect(assignee.querySelector('.author').innerText.trim()).toEqual(UsersMock.user.name);
});
-
- it('Shows one user with avatar, username and author name', () => {
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [UsersMock.user],
- editable: true,
- },
- }).$mount();
-
- expect(component.$el.querySelector('.author-link')).not.toBeNull();
- // The image
- expect(component.$el.querySelector('.author-link img').getAttribute('src')).toEqual(
- UsersMock.user.avatar,
- );
- // Author name
- expect(component.$el.querySelector('.author-link .author').innerText.trim()).toEqual(
- UsersMock.user.name,
- );
- // Username
- expect(component.$el.querySelector('.author-link .username').innerText.trim()).toEqual(
- `@${UsersMock.user.username}`,
- );
- });
-
- it('has the root url present in the assigneeUrl method', () => {
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [UsersMock.user],
- editable: true,
- },
- }).$mount();
-
- expect(component.assigneeUrl(UsersMock.user).indexOf('http://localhost:3000/')).not.toEqual(
- -1,
- );
- });
-
- it('has correct "cannot merge" tooltip when user cannot merge', () => {
- const user = Object.assign({}, UsersMock.user, { can_merge: false });
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users: [user],
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('Cannot merge');
- });
});
describe('Two or more assignees/users', () => {
- it('has correct "cannot merge" tooltip when one user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = true;
- users[1].can_merge = false;
- users[2].can_merge = false;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('1/3 can merge');
- });
-
- it('has correct "cannot merge" tooltip when no user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
- users[0].can_merge = false;
- users[1].can_merge = false;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('No one can merge');
- });
-
- it('has correct "cannot merge" tooltip when more than one user can merge', () => {
- const users = UsersMockHelper.createNumberRandomUsers(3);
- users[0].can_merge = false;
- users[1].can_merge = true;
- users[2].can_merge = true;
-
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000/',
- users,
- editable: true,
- issuableType: 'merge_request',
- },
- }).$mount();
-
- expect(component.mergeNotAllowedTooltipMessage).toEqual('2/3 can merge');
- });
-
it('has no "cannot merge" tooltip when every user can merge', () => {
const users = UsersMockHelper.createNumberRandomUsers(2);
users[0].can_merge = true;
@@ -217,7 +111,7 @@ describe('Assignee component', () => {
},
}).$mount();
- expect(component.mergeNotAllowedTooltipMessage).toEqual(null);
+ expect(component.collapsedTooltipTitle).not.toContain('cannot merge');
});
it('displays two assignee icons when collapsed', () => {
@@ -295,8 +189,12 @@ describe('Assignee component', () => {
expect(component.$el.querySelector('.user-list-more')).toBe(null);
});
- it('sets tooltip container to body', () => {
- const users = UsersMockHelper.createNumberRandomUsers(2);
+ it('shows sorted assignee where "can merge" users are sorted first', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
+
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
@@ -305,98 +203,46 @@ describe('Assignee component', () => {
},
}).$mount();
- expect(component.$el.querySelector('.user-link').getAttribute('data-container')).toBe('body');
+ expect(component.sortedAssigness[0].can_merge).toBe(true);
});
- it('Shows the "show-less" assignees label', done => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
+ it('passes the sorted assignees to the uncollapsed-assignee-list', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
+
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
users,
- editable: true,
+ editable: false,
},
}).$mount();
- expect(component.$el.querySelectorAll('.user-item').length).toEqual(
- component.defaultRenderCount,
- );
-
- expect(component.$el.querySelector('.user-list-more')).not.toBe(null);
- const usersLabelExpectation = users.length - component.defaultRenderCount;
+ const userItems = component.$el.querySelectorAll('.user-list .user-item a');
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).not.toBe(
- `+${usersLabelExpectation} more`,
- );
- component.toggleShowLess();
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
+ expect(userItems.length).toBe(3);
+ expect(userItems[0].dataset.originalTitle).toBe(users[2].name);
});
- it('Shows the "show-less" when "n+ more " label is clicked', done => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000',
- users,
- editable: true,
- },
- }).$mount();
-
- component.$el.querySelector('.user-list-more .btn-link').click();
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
- });
+ it('passes the sorted assignees to the collapsed-assignee-list', () => {
+ const users = UsersMockHelper.createNumberRandomUsers(3);
+ users[0].can_merge = false;
+ users[1].can_merge = false;
+ users[2].can_merge = true;
- it('gets the count of avatar via a computed property ', () => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
component = new AssigneeComponent({
propsData: {
rootPath: 'http://localhost:3000',
users,
- editable: true,
+ editable: false,
},
}).$mount();
- expect(component.sidebarAvatarCounter).toEqual(`+${users.length - 1}`);
- });
+ const collapsedButton = component.$el.querySelector('.sidebar-collapsed-user button');
- describe('n+ more label', () => {
- beforeEach(() => {
- const users = UsersMockHelper.createNumberRandomUsers(6);
- component = new AssigneeComponent({
- propsData: {
- rootPath: 'http://localhost:3000',
- users,
- editable: true,
- },
- }).$mount();
- });
-
- it('shows "+1 more" label', () => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '+ 1 more',
- );
- });
-
- it('shows "show less" label', done => {
- component.toggleShowLess();
-
- Vue.nextTick(() => {
- expect(component.$el.querySelector('.user-list-more .btn-link').innerText.trim()).toBe(
- '- show less',
- );
- done();
- });
- });
+ expect(collapsedButton.innerText.trim()).toBe(users[2].name);
});
});
});
diff --git a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
index 486a7241e33..ea9e5677bc5 100644
--- a/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/confidential_issue_sidebar_spec.js
@@ -4,8 +4,10 @@ import confidentialIssueSidebar from '~/sidebar/components/confidential/confiden
describe('Confidential Issue Sidebar Block', () => {
let vm1;
let vm2;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(confidentialIssueSidebar, 'trackEvent');
const Component = Vue.extend(confidentialIssueSidebar);
const service = {
update: () => Promise.resolve(true),
@@ -67,4 +69,10 @@ describe('Confidential Issue Sidebar Block', () => {
done();
});
});
+
+ it('calls trackEvent when "Edit" is clicked', () => {
+ vm1.$el.querySelector('.confidential-edit').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
});
diff --git a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
index ca882032bdf..2d930428230 100644
--- a/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
+++ b/spec/javascripts/sidebar/lock/lock_issue_sidebar_spec.js
@@ -4,8 +4,10 @@ import lockIssueSidebar from '~/sidebar/components/lock/lock_issue_sidebar.vue';
describe('LockIssueSidebar', () => {
let vm1;
let vm2;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(lockIssueSidebar, 'trackEvent');
const Component = Vue.extend(lockIssueSidebar);
const mediator = {
@@ -59,6 +61,12 @@ describe('LockIssueSidebar', () => {
});
});
+ it('calls trackEvent when "Edit" is clicked', () => {
+ vm1.$el.querySelector('.lock-edit').click();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
+
it('displays the edit form when opened from collapsed state', done => {
expect(vm1.isLockDialogOpen).toBe(false);
diff --git a/spec/javascripts/sidebar/subscriptions_spec.js b/spec/javascripts/sidebar/subscriptions_spec.js
index 32728e58b06..2efa13f3fe8 100644
--- a/spec/javascripts/sidebar/subscriptions_spec.js
+++ b/spec/javascripts/sidebar/subscriptions_spec.js
@@ -6,8 +6,10 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Subscriptions', function() {
let vm;
let Subscriptions;
+ let statsSpy;
beforeEach(() => {
+ statsSpy = spyOnDependency(subscriptions, 'trackEvent');
Subscriptions = Vue.extend(subscriptions);
});
@@ -58,6 +60,13 @@ describe('Subscriptions', function() {
expect(vm.$emit).toHaveBeenCalledWith('toggleSubscription', jasmine.any(Object));
});
+ it('calls trackEvent when toggled', () => {
+ vm = mountComponent(Subscriptions, { subscribed: true });
+ vm.toggleSubscription();
+
+ expect(statsSpy).toHaveBeenCalled();
+ });
+
it('onClickCollapsedIcon method emits `toggleSidebar` event on component', () => {
vm = mountComponent(Subscriptions, { subscribed: true });
spyOn(vm, '$emit');
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
index 212519743aa..7216ad00cc1 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_rebase_spec.js
@@ -83,6 +83,24 @@ describe('Merge request widget rebase component', () => {
expect(text).toContain('foo');
expect(text.replace(/\s\s+/g, ' ')).toContain('to allow this merge request to be merged.');
});
+
+ it('should render the correct target branch name', () => {
+ const targetBranch = 'fake-branch-to-test-with';
+ vm = mountComponent(Component, {
+ mr: {
+ rebaseInProgress: false,
+ canPushToSourceBranch: false,
+ targetBranch,
+ },
+ service: {},
+ });
+
+ const elem = vm.$el.querySelector('.rebase-state-find-class-convention span');
+
+ expect(elem.innerHTML).toContain(
+ `Fast-forward merge is not possible. Rebase the source branch onto <span class="label-branch">${targetBranch}</span> to allow this merge request to be merged.`,
+ );
+ });
});
describe('methods', () => {
diff --git a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
index e27a506f426..e2cd0f084fd 100644
--- a/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
+++ b/spec/javascripts/vue_mr_widget/stores/mr_widget_store_spec.js
@@ -82,47 +82,5 @@ describe('MergeRequestStore', () => {
expect(store.isNothingToMergeState).toEqual(false);
});
});
-
- describe('mergePipelinesEnabled', () => {
- it('should set mergePipelinesEnabled = true when merge_pipelines_enabled is true', () => {
- store.setData({ ...mockData, merge_pipelines_enabled: true });
-
- expect(store.mergePipelinesEnabled).toBe(true);
- });
-
- it('should set mergePipelinesEnabled = false when merge_pipelines_enabled is not provided', () => {
- store.setData({ ...mockData, merge_pipelines_enabled: undefined });
-
- expect(store.mergePipelinesEnabled).toBe(false);
- });
- });
-
- describe('mergeTrainsCount', () => {
- it('should set mergeTrainsCount when merge_trains_count is provided', () => {
- store.setData({ ...mockData, merge_trains_count: 3 });
-
- expect(store.mergeTrainsCount).toBe(3);
- });
-
- it('should set mergeTrainsCount = 0 when merge_trains_count is not provided', () => {
- store.setData({ ...mockData, merge_trains_count: undefined });
-
- expect(store.mergeTrainsCount).toBe(0);
- });
- });
-
- describe('mergeTrainIndex', () => {
- it('should set mergeTrainIndex when merge_train_index is provided', () => {
- store.setData({ ...mockData, merge_train_index: 3 });
-
- expect(store.mergeTrainIndex).toBe(3);
- });
-
- it('should not set mergeTrainIndex when merge_train_index is not provided', () => {
- store.setData({ ...mockData, merge_train_index: undefined });
-
- expect(store.mergeTrainIndex).toBeUndefined();
- });
- });
});
});
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/api/helpers/label_helpers_spec.rb b/spec/lib/api/helpers/label_helpers_spec.rb
new file mode 100644
index 00000000000..138e9a22d70
--- /dev/null
+++ b/spec/lib/api/helpers/label_helpers_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::Helpers::LabelHelpers do
+ describe 'create_service_params' do
+ let(:label_helper) do
+ Class.new do
+ include API::Helpers::LabelHelpers
+ end.new
+ end
+
+ context 'when a project is given' do
+ it 'returns the expected params' do
+ project = create(:project)
+ expect(label_helper.create_service_params(project)).to eq({ project: project })
+ end
+ end
+
+ context 'when a group is given' do
+ it 'returns the expected params' do
+ group = create(:group)
+ expect(label_helper.create_service_params(group)).to eq({ group: group })
+ end
+ end
+
+ context 'when something else is given' do
+ it 'raises a type error' do
+ expect { label_helper.create_service_params(Class.new) }.to raise_error(TypeError)
+ end
+ end
+ end
+end
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/analytics/cycle_analytics/stage_events/stage_event_spec.rb b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
new file mode 100644
index 00000000000..29f4be76a65
--- /dev/null
+++ b/spec/lib/gitlab/analytics/cycle_analytics/stage_events/stage_event_spec.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Analytics::CycleAnalytics::StageEvents::StageEvent do
+ it { expect(described_class).to respond_to(:name) }
+ it { expect(described_class).to respond_to(:identifier) }
+
+ it { expect(described_class.new({})).to respond_to(:object_type) }
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index edff38f05ec..098c33f9cb1 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -86,7 +86,7 @@ describe Gitlab::Auth do
let(:project) { build.project }
before do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
+ expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: true, login: 'gitlab-ci-token')
end
it 'recognises user-less build' do
@@ -106,7 +106,7 @@ describe Gitlab::Auth do
let(:project) { build.project }
before do
- expect(gl_auth).to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
+ expect(gl_auth).not_to receive(:rate_limit!).with('ip', success: false, login: 'gitlab-ci-token')
end
it 'denies authentication' do
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/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
index 4e4f1bf6ad3..a527783ffac 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -69,6 +69,34 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do
it { is_expected.to eq(false) }
end
+ context 'when right is nil' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left and right are nil' do
+ let(:left_value) { nil }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left is an empty string' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
+
+ it { is_expected.to eq(false) }
+ end
+
+ context 'when left and right are empty strings' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('') }
+
+ it { is_expected.to eq(true) }
+ end
+
context 'when left is a multiline string and matches right' do
let(:left_value) do
<<~TEXT
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
index 6b81008ffb1..fb4238ecaf3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb
@@ -69,6 +69,34 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotMatches do
it { is_expected.to eq(true) }
end
+ context 'when right is nil' do
+ let(:left_value) { 'my-awesome-string' }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left and right are nil' do
+ let(:left_value) { nil }
+ let(:right_value) { nil }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left is an empty string' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when left and right are empty strings' do
+ let(:left_value) { '' }
+ let(:right_value) { Gitlab::UntrustedRegexp.new('') }
+
+ it { is_expected.to eq(false) }
+ end
+
context 'when left is a multiline string and matches right' do
let(:left_value) do
<<~TEXT
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 1a9350d68bd..89431b80be3 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -15,6 +15,60 @@ 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
@@ -366,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] } }
@@ -397,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
@@ -476,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 d5567b4f166..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
@@ -1169,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 }
@@ -1195,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") }
@@ -1513,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/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index d3e73314b87..0372b770844 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -34,12 +34,12 @@ describe Gitlab::Daemon do
end
end
- describe 'when Daemon is enabled' do
+ context 'when Daemon is enabled' do
before do
allow(subject).to receive(:enabled?).and_return(true)
end
- describe 'when Daemon is stopped' do
+ context 'when Daemon is stopped' do
describe '#start' do
it 'starts the Daemon' do
expect { subject.start.join }.to change { subject.thread? }.from(false).to(true)
@@ -57,14 +57,14 @@ describe Gitlab::Daemon do
end
end
- describe 'when Daemon is running' do
+ context 'when Daemon is running' do
before do
- subject.start.join
+ subject.start
end
describe '#start' do
it "doesn't start running Daemon" do
- expect { subject.start.join }.not_to change { subject.thread? }
+ expect { subject.start.join }.not_to change { subject.thread }
expect(subject).to have_received(:start_working).once
end
@@ -76,11 +76,29 @@ describe Gitlab::Daemon do
expect(subject).to have_received(:stop_working)
end
+
+ context 'when stop_working raises exception' do
+ before do
+ allow(subject).to receive(:start_working) do
+ sleep(1000)
+ end
+ end
+
+ it 'shutdowns Daemon' do
+ expect(subject).to receive(:stop_working) do
+ subject.thread.raise(Interrupt)
+ end
+
+ expect(subject.thread).to be_alive
+ expect { subject.stop }.not_to raise_error
+ expect(subject.thread).to be_nil
+ end
+ end
end
end
end
- describe 'when Daemon is disabled' do
+ context 'when Daemon is disabled' do
before do
allow(subject).to receive(:enabled?).and_return(false)
end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 2731fc8573f..fe44b09aa24 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -619,10 +619,18 @@ describe Gitlab::Database::MigrationHelpers do
.with(/CREATE OR REPLACE FUNCTION foo()/m)
expect(model).to receive(:execute)
+ .with(/DROP TRIGGER IF EXISTS foo/m)
+
+ expect(model).to receive(:execute)
.with(/CREATE TRIGGER foo/m)
model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
end
+
+ it 'does not fail if trigger already exists' do
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ model.install_rename_triggers_for_postgresql('foo', :users, :old, :new)
+ end
end
describe '#remove_rename_triggers_for_postgresql' do
diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
index def20448bd9..9bd0d800086 100644
--- a/spec/services/self_monitoring/project/create_service_spec.rb
+++ b/spec/lib/gitlab/database_importers/self_monitoring/project/create_service_spec.rb
@@ -2,29 +2,48 @@
require 'spec_helper'
-describe SelfMonitoring::Project::CreateService do
+describe Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService do
describe '#execute' do
- let(:result) { subject.execute }
+ let(:result) { subject.execute! }
let(:prometheus_settings) do
- OpenStruct.new(
+ {
enable: true,
listen_address: 'localhost:9090'
- )
+ }
end
before do
- allow(Gitlab.config).to receive(:prometheus).and_return(prometheus_settings)
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ context 'without application_settings' do
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result).to eq(
+ status: :success
+ )
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
end
context 'without admin users' do
- it 'returns error' do
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
+
+ before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
+ end
+
+ it 'does not fail' do
expect(subject).to receive(:log_error).and_call_original
expect(result).to eq(
- status: :error,
- message: 'No active admin user found',
- failed_step: :validate_admins
+ status: :success
)
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
end
end
@@ -36,6 +55,7 @@ describe SelfMonitoring::Project::CreateService do
let!(:user) { create(:user, :admin) }
before do
+ allow(ApplicationSetting).to receive(:current_without_cache) { application_setting }
application_setting.allow_local_requests_from_web_hooks_and_services = true
end
@@ -56,8 +76,8 @@ describe SelfMonitoring::Project::CreateService do
it 'creates group' do
expect(result[:status]).to eq(:success)
expect(group).to be_persisted
- expect(group.name).to eq(described_class::GROUP_NAME)
- expect(group.path).to start_with(described_class::GROUP_PATH)
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
expect(group.path.split('-').last.length).to eq(8)
expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL)
end
@@ -77,9 +97,16 @@ describe SelfMonitoring::Project::CreateService do
end
it 'creates project with correct name and description' do
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
expect(result[:status]).to eq(:success)
expect(project.name).to eq(described_class::PROJECT_NAME)
- expect(project.description).to eq(described_class::PROJECT_DESCRIPTION)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
end
it 'adds all admins as maintainers' do
@@ -105,19 +132,30 @@ describe SelfMonitoring::Project::CreateService do
it 'returns error when saving project ID fails' do
allow(application_setting).to receive(:update) { false }
- expect(result[:status]).to eq(:error)
- expect(result[:failed_step]).to eq(:save_project_id)
- expect(result[:message]).to eq('Could not save project ID')
+ expect { result }.to raise_error(StandardError, 'Could not save project ID')
end
- it 'does not fail when a project already exists' do
- expect(result[:status]).to eq(:success)
+ context 'when project already exists' do
+ let(:existing_group) { create(:group) }
+ let(:existing_project) { create(:project, namespace: existing_group) }
- second_result = subject.execute
+ before do
+ admin1 = create(:user, :admin)
+ admin2 = create(:user, :admin)
+
+ existing_group.add_owner(user)
+ existing_group.add_users([admin1, admin2], Gitlab::Access::MAINTAINER)
+
+ application_setting.instance_administration_project_id = existing_project.id
+ end
+
+ it 'does not fail' do
+ expect(subject).to receive(:log_error).and_call_original
+ expect(result[:status]).to eq(:success)
- expect(second_result[:status]).to eq(:success)
- expect(second_result[:project]).to eq(project)
- expect(second_result[:group]).to eq(group)
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+ end
end
context 'when local requests from hooks and services are not allowed' do
@@ -138,8 +176,11 @@ describe SelfMonitoring::Project::CreateService do
end
context 'with non default prometheus address' do
- before do
- prometheus_settings.listen_address = 'https://localhost:9090'
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'https://localhost:9090'
+ }
end
it_behaves_like 'has prometheus service', 'https://localhost:9090'
@@ -157,8 +198,11 @@ describe SelfMonitoring::Project::CreateService do
end
context 'when prometheus setting is disabled in gitlab.yml' do
- before do
- prometheus_settings.enable = false
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'http://localhost:9090'
+ }
end
it 'does not configure prometheus' do
@@ -168,9 +212,7 @@ describe SelfMonitoring::Project::CreateService do
end
context 'when prometheus listen address is blank in gitlab.yml' do
- before do
- prometheus_settings.listen_address = ''
- end
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
it 'does not configure prometheus' do
expect(result).to include(status: :success)
@@ -192,11 +234,7 @@ describe SelfMonitoring::Project::CreateService do
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq({
- status: :error,
- message: 'Could not create project',
- failed_step: :create_project
- })
+ expect { result }.to raise_error(StandardError, 'Could not create project')
end
end
@@ -207,26 +245,21 @@ describe SelfMonitoring::Project::CreateService do
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq({
- status: :error,
- message: 'Could not add admins as members',
- failed_step: :add_group_members
- })
+ expect { result }.to raise_error(StandardError, 'Could not add admins as members')
end
end
context 'when prometheus manual configuration cannot be saved' do
- before do
- prometheus_settings.listen_address = 'httpinvalid://localhost:9090'
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'httpinvalid://localhost:9090'
+ }
end
it 'returns error' do
expect(subject).to receive(:log_error).and_call_original
- expect(result).to eq(
- status: :error,
- message: 'Could not save prometheus manual configuration',
- failed_step: :add_prometheus_manual_configuration
- )
+ expect { result }.to raise_error(StandardError, 'Could not save prometheus manual configuration')
end
end
end
diff --git a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
index 0c58cf088cc..c8ed12523d0 100644
--- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
@@ -13,9 +13,6 @@ describe Gitlab::Email::Hook::DisableEmailInterceptor do
end
after do
- # Removing interceptor from the list because unregister_interceptor is
- # implemented in later version of mail gem
- # See: https://github.com/mikel/mail/pull/705
Mail.unregister_interceptor(described_class)
end
diff --git a/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
new file mode 100644
index 00000000000..35aa663b0a5
--- /dev/null
+++ b/spec/lib/gitlab/email/hook/smime_signature_interceptor_spec.rb
@@ -0,0 +1,52 @@
+require 'spec_helper'
+
+describe Gitlab::Email::Hook::SmimeSignatureInterceptor do
+ include SmimeHelper
+
+ # cert generation is an expensive operation and they are used read-only,
+ # so we share them as instance variables in all tests
+ before :context do
+ @root_ca = generate_root
+ @cert = generate_cert(root_ca: @root_ca)
+ end
+
+ let(:root_certificate) do
+ Gitlab::Email::Smime::Certificate.new(@root_ca[:key], @root_ca[:cert])
+ end
+
+ let(:certificate) do
+ Gitlab::Email::Smime::Certificate.new(@cert[:key], @cert[:cert])
+ end
+
+ let(:mail) do
+ ActionMailer::Base.mail(to: 'test@example.com', from: 'info@example.com', body: 'signed hello')
+ end
+
+ before do
+ allow(Gitlab::Email::Smime::Certificate).to receive_messages(from_files: certificate)
+
+ Mail.register_interceptor(described_class)
+ mail.deliver_now
+ end
+
+ after do
+ Mail.unregister_interceptor(described_class)
+ end
+
+ it 'signs the email appropriately with SMIME' do
+ expect(mail.header['To'].value).to eq('test@example.com')
+ expect(mail.header['From'].value).to eq('info@example.com')
+ expect(mail.header['Content-Type'].value).to match('multipart/signed').and match('protocol="application/x-pkcs7-signature"')
+
+ # verify signature and obtain pkcs7 encoded content
+ p7enc = Gitlab::Email::Smime::Signer.verify_signature(
+ cert: certificate.cert,
+ ca_cert: root_certificate.cert,
+ signed_data: mail.encoded)
+
+ # envelope in a Mail object and obtain the body
+ decoded_mail = Mail.new(p7enc.data)
+
+ expect(decoded_mail.body.encoded).to eq('signed hello')
+ end
+end
diff --git a/spec/lib/gitlab/email/smime/certificate_spec.rb b/spec/lib/gitlab/email/smime/certificate_spec.rb
new file mode 100644
index 00000000000..90b27602413
--- /dev/null
+++ b/spec/lib/gitlab/email/smime/certificate_spec.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Email::Smime::Certificate do
+ include SmimeHelper
+
+ # cert generation is an expensive operation and they are used read-only,
+ # so we share them as instance variables in all tests
+ before :context do
+ @root_ca = generate_root
+ @cert = generate_cert(root_ca: @root_ca)
+ end
+
+ describe 'testing environment setup' do
+ describe 'generate_root' do
+ subject { @root_ca }
+
+ it 'generates a root CA that expires a long way in the future' do
+ expect(subject[:cert].not_after).to be > 999.years.from_now
+ end
+ end
+
+ describe 'generate_cert' do
+ subject { @cert }
+
+ it 'generates a cert properly signed by the root CA' do
+ expect(subject[:cert].issuer).to eq(@root_ca[:cert].subject)
+ end
+
+ it 'generates a cert that expires soon' do
+ expect(subject[:cert].not_after).to be < 60.minutes.from_now
+ end
+
+ it 'generates a cert intended for email signing' do
+ expect(subject[:cert].extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
+ end
+
+ context 'passing in INFINITE_EXPIRY' do
+ subject { generate_cert(root_ca: @root_ca, expires_in: SmimeHelper::INFINITE_EXPIRY) }
+
+ it 'generates a cert that expires a long way in the future' do
+ expect(subject[:cert].not_after).to be > 999.years.from_now
+ end
+ end
+ end
+ end
+
+ describe '.from_strings' do
+ it 'parses correctly a certificate and key' do
+ parsed_cert = described_class.from_strings(@cert[:key].to_s, @cert[:cert].to_pem)
+
+ common_cert_tests(parsed_cert, @cert, @root_ca)
+ end
+ end
+
+ describe '.from_files' do
+ it 'parses correctly a certificate and key' do
+ allow(File).to receive(:read).with('a_key').and_return(@cert[:key].to_s)
+ allow(File).to receive(:read).with('a_cert').and_return(@cert[:cert].to_pem)
+
+ parsed_cert = described_class.from_files('a_key', 'a_cert')
+
+ common_cert_tests(parsed_cert, @cert, @root_ca)
+ end
+ end
+
+ def common_cert_tests(parsed_cert, cert, root_ca)
+ expect(parsed_cert.cert).to be_a(OpenSSL::X509::Certificate)
+ expect(parsed_cert.cert.subject).to eq(cert[:cert].subject)
+ expect(parsed_cert.cert.issuer).to eq(root_ca[:cert].subject)
+ expect(parsed_cert.cert.not_before).to eq(cert[:cert].not_before)
+ expect(parsed_cert.cert.not_after).to eq(cert[:cert].not_after)
+ expect(parsed_cert.cert.extensions).to include(an_object_having_attributes(oid: 'extendedKeyUsage', value: match('E-mail Protection')))
+ expect(parsed_cert.key).to be_a(OpenSSL::PKey::RSA)
+ end
+end
diff --git a/spec/lib/gitlab/email/smime/signer_spec.rb b/spec/lib/gitlab/email/smime/signer_spec.rb
new file mode 100644
index 00000000000..56048b7148c
--- /dev/null
+++ b/spec/lib/gitlab/email/smime/signer_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Email::Smime::Signer do
+ include SmimeHelper
+
+ it 'signs data appropriately with SMIME' do
+ root_certificate = generate_root
+ certificate = generate_cert(root_ca: root_certificate)
+
+ signed_content = described_class.sign(
+ cert: certificate[:cert],
+ key: certificate[:key],
+ data: 'signed content')
+ expect(signed_content).not_to be_nil
+
+ p7enc = described_class.verify_signature(
+ cert: certificate[:cert],
+ ca_cert: root_certificate[:cert],
+ signed_data: signed_content)
+
+ expect(p7enc).not_to be_nil
+ expect(p7enc.data).to eq('signed content')
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index e9fb6c0125c..99d563e03ec 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -27,6 +27,16 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.filesystem_id' do
+ it 'returns an empty string when the storage is not found in the response' do
+ response = double("response")
+ allow(response).to receive(:storage_statuses).and_return([])
+ allow_any_instance_of(Gitlab::GitalyClient::ServerService).to receive(:info).and_return(response)
+
+ expect(described_class.filesystem_id('default')).to eq(nil)
+ end
+ end
+
describe '.stub_class' do
it 'returns the gRPC health check stub' do
expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
diff --git a/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
new file mode 100644
index 00000000000..38931f7ab5e
--- /dev/null
+++ b/spec/lib/gitlab/graphql/loaders/batch_root_storage_statistics_loader_spec.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Loaders::BatchRootStorageStatisticsLoader do
+ describe '#find' do
+ it 'only queries once for project statistics' do
+ stats = create_list(:namespace_root_storage_statistics, 2)
+ namespace1 = stats.first.namespace
+ namespace2 = stats.last.namespace
+
+ expect do
+ described_class.new(namespace1.id).find
+ described_class.new(namespace2.id).find
+ end.not_to exceed_query_limit(1)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
index 534cf219520..2cf4b367c0b 100644
--- a/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/release_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::ReleaseFormatter do
diff --git a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
index 3cd096eb0ad..919847fe061 100644
--- a/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/user_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::UserFormatter do
diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
index 7519707293c..639fb9d80eb 100644
--- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LegacyGithubImport::WikiFormatter do
diff --git a/spec/lib/gitlab/loop_helpers_spec.rb b/spec/lib/gitlab/loop_helpers_spec.rb
index e17a0342d64..7e59b41d5b9 100644
--- a/spec/lib/gitlab/loop_helpers_spec.rb
+++ b/spec/lib/gitlab/loop_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::LoopHelpers do
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index ded93e23c08..c1135f710ea 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ManifestImport::Manifest do
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index a7487972f51..a8cfcfb41d3 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ManifestImport::ProjectCreator do
diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb
index 09e518ff989..b93538cae5a 100644
--- a/spec/lib/gitlab/markup_helper_spec.rb
+++ b/spec/lib/gitlab/markup_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::MarkupHelper do
diff --git a/spec/lib/gitlab/metrics/background_transaction_spec.rb b/spec/lib/gitlab/metrics/background_transaction_spec.rb
index 17445fe6de5..d87d2c839ad 100644
--- a/spec/lib/gitlab/metrics/background_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/background_transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::BackgroundTransaction do
diff --git a/spec/lib/gitlab/metrics/delta_spec.rb b/spec/lib/gitlab/metrics/delta_spec.rb
index 718387cdee1..9bb011dc8fc 100644
--- a/spec/lib/gitlab/metrics/delta_spec.rb
+++ b/spec/lib/gitlab/metrics/delta_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Delta do
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index 977bc250049..0e2f274f157 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Instrumentation do
diff --git a/spec/lib/gitlab/metrics/method_call_spec.rb b/spec/lib/gitlab/metrics/method_call_spec.rb
index d9379cfe674..3b5e04e2df5 100644
--- a/spec/lib/gitlab/metrics/method_call_spec.rb
+++ b/spec/lib/gitlab/metrics/method_call_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::MethodCall do
diff --git a/spec/lib/gitlab/metrics/methods_spec.rb b/spec/lib/gitlab/metrics/methods_spec.rb
index 9d41ed2442b..bca94deb1d8 100644
--- a/spec/lib/gitlab/metrics/methods_spec.rb
+++ b/spec/lib/gitlab/metrics/methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Methods do
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index d240b8a01fd..611b59231ba 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Metric do
diff --git a/spec/lib/gitlab/metrics/prometheus_spec.rb b/spec/lib/gitlab/metrics/prometheus_spec.rb
index 3d4dd5fdf01..b37624982e2 100644
--- a/spec/lib/gitlab/metrics/prometheus_spec.rb
+++ b/spec/lib/gitlab/metrics/prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Prometheus, :prometheus do
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index b84387204ee..1c1681cc5ab 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::RackMiddleware do
diff --git a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
index ebe66948a91..c29db3a93ec 100644
--- a/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/requests_rack_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::RequestsRackMiddleware do
diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
index 2923048f742..2d4b27a6ac1 100644
--- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::InfluxSampler do
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 5005a5d9ebc..8c4071a7ed1 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::RubySampler do
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 4b697b2ba0f..cdfd95e3885 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Samplers::UnicornSampler do
diff --git a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
index 61eb059a731..9eea3eb79dc 100644
--- a/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_metrics_exporter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::SidekiqMetricsExporter do
diff --git a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
index ae1d8b47fe9..bb95d5ab2ad 100644
--- a/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/sidekiq_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::SidekiqMiddleware do
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index 9f3af1acef7..25c0e7b695a 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActionView do
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index ee6d6fc961f..1624cea8bda 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActiveRecord do
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index e04056b3450..ab0d89b2683 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::Subscribers::RailsCache do
diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb
index 3b434a02f63..6d2764a06f2 100644
--- a/spec/lib/gitlab/metrics/system_spec.rb
+++ b/spec/lib/gitlab/metrics/system_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::System do
diff --git a/spec/lib/gitlab/metrics/web_transaction_spec.rb b/spec/lib/gitlab/metrics/web_transaction_spec.rb
index 0b3b23e930f..2b35f07cc0d 100644
--- a/spec/lib/gitlab/metrics/web_transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/web_transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics::WebTransaction do
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 03c185ddc07..f0ba12c1cd0 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Metrics do
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 86bdc479b66..07fda691ac8 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::BasicHealthCheck do
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb
index 3f6ada6832a..33797817578 100644
--- a/spec/lib/gitlab/middleware/multipart_spec.rb
+++ b/spec/lib/gitlab/middleware/multipart_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'tempfile'
diff --git a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
index 14f2c3cb86f..31359abdce3 100644
--- a/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
+++ b/spec/lib/gitlab/middleware/rails_queue_duration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::RailsQueueDuration do
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 24d49a049b6..d2c8f4ab0bd 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::ReadOnly do
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
index 5e3aa877409..3ca40f4ebd0 100644
--- a/spec/lib/gitlab/middleware/release_env_spec.rb
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Middleware::ReleaseEnv do
diff --git a/spec/lib/gitlab/multi_collection_paginator_spec.rb b/spec/lib/gitlab/multi_collection_paginator_spec.rb
index 28cd704b05a..f2049884b83 100644
--- a/spec/lib/gitlab/multi_collection_paginator_spec.rb
+++ b/spec/lib/gitlab/multi_collection_paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::MultiCollectionPaginator do
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index bfd456cdd7e..b16eccbcb2c 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ObjectHierarchy do
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
index 43f6d13f7ba..8aa6d17ac9e 100644
--- a/spec/lib/gitlab/octokit/middleware_spec.rb
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Octokit::Middleware do
diff --git a/spec/lib/gitlab/omniauth_initializer_spec.rb b/spec/lib/gitlab/omniauth_initializer_spec.rb
index ef5c93e5c6b..99684bb2ab2 100644
--- a/spec/lib/gitlab/omniauth_initializer_spec.rb
+++ b/spec/lib/gitlab/omniauth_initializer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OmniauthInitializer do
diff --git a/spec/lib/gitlab/optimistic_locking_spec.rb b/spec/lib/gitlab/optimistic_locking_spec.rb
index 6fdf61ee0a7..9dfcb775dfa 100644
--- a/spec/lib/gitlab/optimistic_locking_spec.rb
+++ b/spec/lib/gitlab/optimistic_locking_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OptimisticLocking do
diff --git a/spec/lib/gitlab/other_markup_spec.rb b/spec/lib/gitlab/other_markup_spec.rb
index e26f39e193e..b5cf5b0999d 100644
--- a/spec/lib/gitlab/other_markup_spec.rb
+++ b/spec/lib/gitlab/other_markup_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OtherMarkup do
diff --git a/spec/lib/gitlab/otp_key_rotator_spec.rb b/spec/lib/gitlab/otp_key_rotator_spec.rb
index 6e6e9ce29ac..f5a567d5ea0 100644
--- a/spec/lib/gitlab/otp_key_rotator_spec.rb
+++ b/spec/lib/gitlab/otp_key_rotator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::OtpKeyRotator do
diff --git a/spec/lib/gitlab/pages_client_spec.rb b/spec/lib/gitlab/pages_client_spec.rb
index da6d26f4aee..84381843221 100644
--- a/spec/lib/gitlab/pages_client_spec.rb
+++ b/spec/lib/gitlab/pages_client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PagesClient do
diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb
index 84b2e2dc823..7dcdad7ff92 100644
--- a/spec/lib/gitlab/path_regex_spec.rb
+++ b/spec/lib/gitlab/path_regex_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PathRegex do
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index 71c109db1f1..8d8ac2aebbe 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PerformanceBar do
diff --git a/spec/lib/gitlab/phabricator_import/importer_spec.rb b/spec/lib/gitlab/phabricator_import/importer_spec.rb
index bf14010a187..99a6e4dad6b 100644
--- a/spec/lib/gitlab/phabricator_import/importer_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::Importer do
diff --git a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
index 096321cda5f..918ff28c8f5 100644
--- a/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/user_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::UserFinder, :clean_gitlab_redis_cache do
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index a44947445c9..b6f2524a9d0 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/plugin_spec.rb b/spec/lib/gitlab/plugin_spec.rb
index 33dd4f79130..a8ddd774f3f 100644
--- a/spec/lib/gitlab/plugin_spec.rb
+++ b/spec/lib/gitlab/plugin_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Plugin do
diff --git a/spec/lib/gitlab/polling_interval_spec.rb b/spec/lib/gitlab/polling_interval_spec.rb
index eb8e618156b..979164269bd 100644
--- a/spec/lib/gitlab/polling_interval_spec.rb
+++ b/spec/lib/gitlab/polling_interval_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PollingInterval do
diff --git a/spec/lib/gitlab/popen/runner_spec.rb b/spec/lib/gitlab/popen/runner_spec.rb
index 2e2cb4ca28f..de19106eaee 100644
--- a/spec/lib/gitlab/popen/runner_spec.rb
+++ b/spec/lib/gitlab/popen/runner_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Popen::Runner do
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index c1b84e9f077..29afd9df74e 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Popen do
diff --git a/spec/lib/gitlab/profiler_spec.rb b/spec/lib/gitlab/profiler_spec.rb
index 5af52db7a1f..a19392f4bcb 100644
--- a/spec/lib/gitlab/profiler_spec.rb
+++ b/spec/lib/gitlab/profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Profiler do
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index 75e2d5e1319..82ccb42f8a6 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectAuthorizations do
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index c7462500c82..0dbfcf96124 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectSearchResults do
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index c7c82d07508..83acd979a80 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectTemplate do
diff --git a/spec/lib/gitlab/project_transfer_spec.rb b/spec/lib/gitlab/project_transfer_spec.rb
index 0b9b1f537b5..d54817ea02b 100644
--- a/spec/lib/gitlab/project_transfer_spec.rb
+++ b/spec/lib/gitlab/project_transfer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ProjectTransfer do
diff --git a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
index 1a108003bc2..3f97a69b5eb 100644
--- a/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
+++ b/spec/lib/gitlab/prometheus/additional_metrics_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::AdditionalMetricsParser do
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
index c7169717fc1..4bdc57c8c04 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_deployment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsDeploymentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
index a6589f0c0a3..35dbdd55cfa 100644
--- a/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/additional_metrics_environment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::AdditionalMetricsEnvironmentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
index ffe3ad85baa..0ad2de218fe 100644
--- a/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/deployment_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::DeploymentQuery do
diff --git a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
index 936447b8474..35034d814bf 100644
--- a/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
+++ b/spec/lib/gitlab/prometheus/queries/matched_metric_query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Prometheus::Queries::MatchedMetricQuery do
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index 0a4e8dbced5..86a1c14ed3f 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::PrometheusClient do
diff --git a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
index f8faeffb935..2db6d2fb60f 100644
--- a/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
+++ b/spec/lib/gitlab/query_limiting/active_support_subscriber_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::ActiveSupportSubscriber do
diff --git a/spec/lib/gitlab/query_limiting/middleware_spec.rb b/spec/lib/gitlab/query_limiting/middleware_spec.rb
index a04bcdecb4b..fb1c30118c2 100644
--- a/spec/lib/gitlab/query_limiting/middleware_spec.rb
+++ b/spec/lib/gitlab/query_limiting/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::Middleware do
diff --git a/spec/lib/gitlab/query_limiting/transaction_spec.rb b/spec/lib/gitlab/query_limiting/transaction_spec.rb
index b72b8574174..39d5a575efc 100644
--- a/spec/lib/gitlab/query_limiting/transaction_spec.rb
+++ b/spec/lib/gitlab/query_limiting/transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting::Transaction do
diff --git a/spec/lib/gitlab/query_limiting_spec.rb b/spec/lib/gitlab/query_limiting_spec.rb
index 42877b1e2dd..f0d0340cd6e 100644
--- a/spec/lib/gitlab/query_limiting_spec.rb
+++ b/spec/lib/gitlab/query_limiting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QueryLimiting do
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index 21f2c87a755..45b710adf07 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::CommandDefinition do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 78b9b3804c3..c98c36622f5 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::Dsl do
diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb
index 873bb359d6e..f1acb5b7049 100644
--- a/spec/lib/gitlab/quick_actions/extractor_spec.rb
+++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::Extractor do
diff --git a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
index 8b58f0b3725..fd149cd1114 100644
--- a/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
+++ b/spec/lib/gitlab/quick_actions/spend_time_and_date_separator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::SpendTimeAndDateSeparator do
diff --git a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
index 1bb8bc51c96..e4f25bc35a9 100644
--- a/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/substitution_definition_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::QuickActions::SubstitutionDefinition do
diff --git a/spec/lib/gitlab/redis/cache_spec.rb b/spec/lib/gitlab/redis/cache_spec.rb
index 5a4f17cfcf6..0718998f981 100644
--- a/spec/lib/gitlab/redis/cache_spec.rb
+++ b/spec/lib/gitlab/redis/cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Cache do
diff --git a/spec/lib/gitlab/redis/queues_spec.rb b/spec/lib/gitlab/redis/queues_spec.rb
index 01ca25635a9..93207b6f469 100644
--- a/spec/lib/gitlab/redis/queues_spec.rb
+++ b/spec/lib/gitlab/redis/queues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Queues do
diff --git a/spec/lib/gitlab/redis/shared_state_spec.rb b/spec/lib/gitlab/redis/shared_state_spec.rb
index 24b73745dc5..aa61fd99eb5 100644
--- a/spec/lib/gitlab/redis/shared_state_spec.rb
+++ b/spec/lib/gitlab/redis/shared_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::SharedState do
diff --git a/spec/lib/gitlab/redis/wrapper_spec.rb b/spec/lib/gitlab/redis/wrapper_spec.rb
index 0c22a0d62cc..e4cc42130db 100644
--- a/spec/lib/gitlab/redis/wrapper_spec.rb
+++ b/spec/lib/gitlab/redis/wrapper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Redis::Wrapper do
diff --git a/spec/lib/gitlab/reference_counter_spec.rb b/spec/lib/gitlab/reference_counter_spec.rb
index b2344d1870a..f9361d08faf 100644
--- a/spec/lib/gitlab/reference_counter_spec.rb
+++ b/spec/lib/gitlab/reference_counter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ReferenceCounter do
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index ba295386a55..e19210d8fbf 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Regex do
diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb
index 8fbda929064..cffd7cc89e7 100644
--- a/spec/lib/gitlab/repo_path_spec.rb
+++ b/spec/lib/gitlab/repo_path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ::Gitlab::RepoPath do
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 0295138fc3a..fd1338b55a6 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -1,9 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
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 +209,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_cache_spec.rb b/spec/lib/gitlab/repository_cache_spec.rb
index 741ee12633f..6a684595eb8 100644
--- a/spec/lib/gitlab/repository_cache_spec.rb
+++ b/spec/lib/gitlab/repository_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RepositoryCache 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..87e51f801e5
--- /dev/null
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+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/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index 23e45aff1c5..a744f48da1f 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestContext do
diff --git a/spec/lib/gitlab/request_forgery_protection_spec.rb b/spec/lib/gitlab/request_forgery_protection_spec.rb
index 305de613866..b7a3dc16eff 100644
--- a/spec/lib/gitlab/request_forgery_protection_spec.rb
+++ b/spec/lib/gitlab/request_forgery_protection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestForgeryProtection, :allow_forgery_protection do
diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb
index b37ee558e1a..a75f3c66156 100644
--- a/spec/lib/gitlab/request_profiler/profile_spec.rb
+++ b/spec/lib/gitlab/request_profiler/profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::RequestProfiler::Profile do
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index 498c045b6cd..f157189a72d 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RequestProfiler do
diff --git a/spec/lib/gitlab/route_map_spec.rb b/spec/lib/gitlab/route_map_spec.rb
index a39c774429e..d5e70b91fb4 100644
--- a/spec/lib/gitlab/route_map_spec.rb
+++ b/spec/lib/gitlab/route_map_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::RouteMap do
diff --git a/spec/lib/gitlab/routing_spec.rb b/spec/lib/gitlab/routing_spec.rb
index 01d5acfc15b..965564cb83b 100644
--- a/spec/lib/gitlab/routing_spec.rb
+++ b/spec/lib/gitlab/routing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Routing do
diff --git a/spec/lib/gitlab/sanitizers/exif_spec.rb b/spec/lib/gitlab/sanitizers/exif_spec.rb
index bd5f330c7a1..22c5f27dc6d 100644
--- a/spec/lib/gitlab/sanitizers/exif_spec.rb
+++ b/spec/lib/gitlab/sanitizers/exif_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sanitizers::Exif do
diff --git a/spec/lib/gitlab/sanitizers/svg_spec.rb b/spec/lib/gitlab/sanitizers/svg_spec.rb
index df46a874528..a8c7495376d 100644
--- a/spec/lib/gitlab/sanitizers/svg_spec.rb
+++ b/spec/lib/gitlab/sanitizers/svg_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sanitizers::SVG do
diff --git a/spec/lib/gitlab/search/found_blob_spec.rb b/spec/lib/gitlab/search/found_blob_spec.rb
index da263bc7523..3496fb29836 100644
--- a/spec/lib/gitlab/search/found_blob_spec.rb
+++ b/spec/lib/gitlab/search/found_blob_spec.rb
@@ -1,4 +1,5 @@
# coding: utf-8
+# frozen_string_literal: true
require 'spec_helper'
@@ -108,7 +109,7 @@ describe Gitlab::Search::FoundBlob do
end
context 'with ISO-8859-1' do
- let(:search_result) { "master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n".force_encoding(Encoding::ASCII_8BIT) }
+ let(:search_result) { (+"master:encoding/iso8859.txt\x001\x00\xC4\xFC\nmaster:encoding/iso8859.txt\x002\x00\nmaster:encoding/iso8859.txt\x003\x00foo\n").force_encoding(Encoding::ASCII_8BIT) }
it 'returns results as UTF-8' do
expect(subject.filename).to eq('encoding/iso8859.txt')
diff --git a/spec/lib/gitlab/search/query_spec.rb b/spec/lib/gitlab/search/query_spec.rb
index 2d00428fffa..112e9a59f04 100644
--- a/spec/lib/gitlab/search/query_spec.rb
+++ b/spec/lib/gitlab/search/query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Search::Query do
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index c287da19343..5621c686b8a 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SearchResults do
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index af8b059b984..9c4f3b8f42e 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sentry do
@@ -65,6 +67,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 +77,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 +91,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/serializer/ci/variables_spec.rb b/spec/lib/gitlab/serializer/ci/variables_spec.rb
index 1d1fd5b0763..900508420c9 100644
--- a/spec/lib/gitlab/serializer/ci/variables_spec.rb
+++ b/spec/lib/gitlab/serializer/ci/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Serializer::Ci::Variables do
diff --git a/spec/lib/gitlab/serializer/pagination_spec.rb b/spec/lib/gitlab/serializer/pagination_spec.rb
index c54be78f050..1e7f441f258 100644
--- a/spec/lib/gitlab/serializer/pagination_spec.rb
+++ b/spec/lib/gitlab/serializer/pagination_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Serializer::Pagination do
diff --git a/spec/lib/gitlab/shard_health_cache_spec.rb b/spec/lib/gitlab/shard_health_cache_spec.rb
index e1a69261939..f747849b5e9 100644
--- a/spec/lib/gitlab/shard_health_cache_spec.rb
+++ b/spec/lib/gitlab/shard_health_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ShardHealthCache, :clean_gitlab_redis_cache do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index bce2e754176..0ba16b93ee7 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'stringio'
diff --git a/spec/lib/gitlab/sherlock/collection_spec.rb b/spec/lib/gitlab/sherlock/collection_spec.rb
index 873ed14f804..bdc89c3d3cf 100644
--- a/spec/lib/gitlab/sherlock/collection_spec.rb
+++ b/spec/lib/gitlab/sherlock/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Collection do
diff --git a/spec/lib/gitlab/sherlock/file_sample_spec.rb b/spec/lib/gitlab/sherlock/file_sample_spec.rb
index 394421504e0..b09ba5c62dc 100644
--- a/spec/lib/gitlab/sherlock/file_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/file_sample_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::FileSample do
diff --git a/spec/lib/gitlab/sherlock/line_profiler_spec.rb b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
index f2f8040fa0b..c1997606839 100644
--- a/spec/lib/gitlab/sherlock/line_profiler_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_profiler_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::LineProfiler do
diff --git a/spec/lib/gitlab/sherlock/line_sample_spec.rb b/spec/lib/gitlab/sherlock/line_sample_spec.rb
index 5f02f6a3213..b68e8cc0266 100644
--- a/spec/lib/gitlab/sherlock/line_sample_spec.rb
+++ b/spec/lib/gitlab/sherlock/line_sample_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::LineSample do
diff --git a/spec/lib/gitlab/sherlock/location_spec.rb b/spec/lib/gitlab/sherlock/location_spec.rb
index b295a624b35..7b40c84c2d1 100644
--- a/spec/lib/gitlab/sherlock/location_spec.rb
+++ b/spec/lib/gitlab/sherlock/location_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Location do
diff --git a/spec/lib/gitlab/sherlock/middleware_spec.rb b/spec/lib/gitlab/sherlock/middleware_spec.rb
index 2016023df06..8d6e362f622 100644
--- a/spec/lib/gitlab/sherlock/middleware_spec.rb
+++ b/spec/lib/gitlab/sherlock/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Middleware do
diff --git a/spec/lib/gitlab/sherlock/query_spec.rb b/spec/lib/gitlab/sherlock/query_spec.rb
index 426071c7f92..13c7e6f8f8b 100644
--- a/spec/lib/gitlab/sherlock/query_spec.rb
+++ b/spec/lib/gitlab/sherlock/query_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Query do
diff --git a/spec/lib/gitlab/sherlock/transaction_spec.rb b/spec/lib/gitlab/sherlock/transaction_spec.rb
index 4a14dfbec56..2245c3ee8e2 100644
--- a/spec/lib/gitlab/sherlock/transaction_spec.rb
+++ b/spec/lib/gitlab/sherlock/transaction_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Sherlock::Transaction do
diff --git a/spec/lib/gitlab/sidekiq_config_spec.rb b/spec/lib/gitlab/sidekiq_config_spec.rb
index 0c66d764851..1e8ccb447b1 100644
--- a/spec/lib/gitlab/sidekiq_config_spec.rb
+++ b/spec/lib/gitlab/sidekiq_config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::SidekiqConfig do
diff --git a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
index fed9aeba30c..a2cb38ec5b1 100644
--- a/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/json_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqLogging::JSONFormatter do
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 5621d3d17d1..1b89c094a6b 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqLogging::StructuredLogger do
@@ -36,7 +38,9 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: done: 0.0 sec',
'job_status' => 'done',
'duration' => 0.0,
- "completed_at" => timestamp.iso8601(3)
+ "completed_at" => timestamp.iso8601(3),
+ "system_s" => 0.0,
+ "user_s" => 0.0
)
end
let(:exception_payload) do
@@ -52,6 +56,13 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
allow(Sidekiq).to receive(:logger).and_return(logger)
allow(subject).to receive(:current_time).and_return(timestamp.to_f)
+
+ allow(Process).to receive(:times).and_return(
+ stime: 0.0,
+ utime: 0.0,
+ cutime: 0.0,
+ cstime: 0.0
+ )
end
subject { described_class.new }
@@ -177,5 +188,31 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
end
end
end
+
+ def ctime(times)
+ times[:cstime] + times[:cutime]
+ end
+
+ context 'with ctime value greater than 0' do
+ let(:times_start) { { stime: 0.04999, utime: 0.0483, cstime: 0.0188, cutime: 0.0188 } }
+ let(:times_end) { { stime: 0.0699, utime: 0.0699, cstime: 0.0399, cutime: 0.0399 } }
+
+ before do
+ end_payload['system_s'] = 0.02
+ end_payload['user_s'] = 0.022
+ end_payload['child_s'] = 0.042
+
+ allow(Process).to receive(:times).and_return(times_start, times_end)
+ end
+
+ it 'logs with ctime data and other cpu data' do
+ Timecop.freeze(timestamp) do
+ expect(logger).to receive(:info).with(start_payload.except('args')).ordered
+ expect(logger).to receive(:info).with(end_payload.except('args')).ordered
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index 1de9a644610..bf3bc8e1add 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqMiddleware::MemoryKiller do
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/sidekiq_middleware/monitor_spec.rb b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
new file mode 100644
index 00000000000..7319cdc2399
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/monitor_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::Monitor do
+ let(:monitor) { described_class.new }
+
+ describe '#call' do
+ let(:worker) { double }
+ let(:job) { { 'jid' => 'job-id' } }
+ let(:queue) { 'my-queue' }
+
+ it 'calls SidekiqMonitor' do
+ expect(Gitlab::SidekiqMonitor.instance).to receive(:within_job)
+ .with('job-id', 'my-queue')
+ .and_call_original
+
+ expect { |blk| monitor.call(worker, job, queue, &blk) }.to yield_control
+ end
+
+ it 'passthroughs the return value' do
+ result = monitor.call(worker, job, queue) do
+ 'value'
+ end
+
+ expect(result).to eq('value')
+ end
+
+ context 'when cancel happens' do
+ subject do
+ monitor.call(worker, job, queue) do
+ raise Gitlab::SidekiqMonitor::CancelledError
+ end
+ end
+
+ it 'skips the job' do
+ expect { subject }.to raise_error(Sidekiq::JobRetry::Skip)
+ end
+
+ it 'puts job in DeadSet' do
+ ::Sidekiq::DeadSet.new.clear
+
+ expect do
+ subject rescue Sidekiq::JobRetry::Skip
+ end.to change { ::Sidekiq::DeadSet.new.size }.by(1)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_monitor_spec.rb b/spec/lib/gitlab/sidekiq_monitor_spec.rb
new file mode 100644
index 00000000000..bbd7bf90217
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_monitor_spec.rb
@@ -0,0 +1,261 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMonitor do
+ let(:monitor) { described_class.new }
+
+ describe '#within_job' do
+ it 'tracks thread' do
+ blk = proc do
+ expect(monitor.jobs_thread['jid']).not_to be_nil
+
+ "OK"
+ end
+
+ expect(monitor.within_job('jid', 'queue', &blk)).to eq("OK")
+ end
+
+ context 'when job is canceled' do
+ let(:jid) { SecureRandom.hex }
+
+ before do
+ described_class.cancel_job(jid)
+ end
+
+ it 'does not execute a block' do
+ expect do |blk|
+ monitor.within_job(jid, 'queue', &blk)
+ rescue described_class::CancelledError
+ end.not_to yield_control
+ end
+
+ it 'raises exception' do
+ expect { monitor.within_job(jid, 'queue') }.to raise_error(
+ described_class::CancelledError)
+ end
+ end
+ end
+
+ describe '#start_working' do
+ subject { monitor.send(:start_working) }
+
+ before do
+ # we want to run at most once cycle
+ # we toggle `enabled?` flag after the first call
+ stub_const('Gitlab::SidekiqMonitor::RECONNECT_TIME', 0)
+ allow(monitor).to receive(:enabled?).and_return(true, false)
+
+ allow(Sidekiq.logger).to receive(:info)
+ allow(Sidekiq.logger).to receive(:warn)
+ end
+
+ context 'when structured logging is used' do
+ it 'logs start message' do
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ action: 'start',
+ message: 'Starting Monitor Daemon')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ subject
+ end
+
+ it 'logs stop message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'stop',
+ message: 'Stopping Monitor Daemon')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ subject
+ end
+
+ it 'logs StandardError message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'exception',
+ message: 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(StandardError, 'My Exception')
+
+ expect { subject }.not_to raise_error
+ end
+
+ it 'logs and raises Exception message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'exception',
+ message: 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(Exception, 'My Exception')
+
+ expect { subject }.to raise_error(Exception, 'My Exception')
+ end
+ end
+
+ context 'when StandardError is raised' do
+ it 'does retry connection' do
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+ .and_raise(StandardError, 'My Exception')
+
+ expect(::Gitlab::Redis::SharedState).to receive(:with)
+
+ # we expect to run `process_messages` twice
+ expect(monitor).to receive(:enabled?).and_return(true, true, false)
+
+ subject
+ end
+ end
+
+ context 'when message is published' do
+ let(:subscribed) { double }
+
+ before do
+ expect_any_instance_of(::Redis).to receive(:subscribe)
+ .and_yield(subscribed)
+
+ expect(subscribed).to receive(:message)
+ .and_yield(
+ described_class::NOTIFICATION_CHANNEL,
+ payload
+ )
+
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ action: 'start',
+ message: 'Starting Monitor Daemon')
+
+ expect(Sidekiq.logger).to receive(:info)
+ .with(
+ class: described_class.to_s,
+ channel: described_class::NOTIFICATION_CHANNEL,
+ message: 'Received payload on channel',
+ payload: payload
+ )
+ end
+
+ context 'and message is valid' do
+ let(:payload) { '{"action":"cancel","jid":"my-jid"}' }
+
+ it 'processes cancel' do
+ expect(monitor).to receive(:process_job_cancel).with('my-jid')
+
+ subject
+ end
+ end
+
+ context 'and message is not valid json' do
+ let(:payload) { '{"action"}' }
+
+ it 'skips processing' do
+ expect(monitor).not_to receive(:process_job_cancel)
+
+ subject
+ end
+ end
+ end
+ end
+
+ describe '#stop' do
+ let!(:monitor_thread) { monitor.start }
+
+ it 'does stop the thread' do
+ expect(monitor_thread).to be_alive
+
+ expect { monitor.stop }.not_to raise_error
+
+ expect(monitor_thread).not_to be_alive
+ expect { monitor_thread.value }.to raise_error(Interrupt)
+ end
+ end
+
+ describe '#process_job_cancel' do
+ subject { monitor.send(:process_job_cancel, jid) }
+
+ context 'when jid is missing' do
+ let(:jid) { nil }
+
+ it 'does not run thread' do
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when jid is provided' do
+ let(:jid) { 'my-jid' }
+
+ context 'when jid is not found' do
+ it 'does not log cancellation message' do
+ expect(Sidekiq.logger).not_to receive(:warn)
+ expect(subject).to be_nil
+ end
+ end
+
+ context 'when jid is found' do
+ let(:thread) { Thread.new { sleep 1000 } }
+
+ before do
+ monitor.jobs_thread[jid] = thread
+ end
+
+ after do
+ thread.kill
+ rescue
+ end
+
+ it 'does log cancellation message' do
+ expect(Sidekiq.logger).to receive(:warn)
+ .with(
+ class: described_class.to_s,
+ action: 'cancel',
+ message: 'Canceling thread with CancelledError',
+ jid: 'my-jid',
+ thread_id: thread.object_id)
+
+ expect(subject).to be_a(Thread)
+
+ subject.join
+ end
+
+ it 'does cancel the thread' do
+ expect(subject).to be_a(Thread)
+
+ subject.join
+
+ # we wait for the thread to be cancelled
+ # by `process_job_cancel`
+ expect { thread.join(5) }.to raise_error(described_class::CancelledError)
+ end
+ end
+ end
+ end
+
+ describe '.cancel_job' do
+ subject { described_class.cancel_job('my-jid') }
+
+ it 'sets a redis key' do
+ expect_any_instance_of(::Redis).to receive(:setex)
+ .with('sidekiq:cancel:my-jid', anything, 1)
+
+ subject
+ end
+
+ it 'notifies all workers' do
+ payload = '{"action":"cancel","jid":"my-jid"}'
+
+ expect_any_instance_of(::Redis).to receive(:publish)
+ .with('sidekiq:cancel:notifications', payload)
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sidekiq_signals_spec.rb b/spec/lib/gitlab/sidekiq_signals_spec.rb
index 77ecd1840d2..10f1bad32cd 100644
--- a/spec/lib/gitlab/sidekiq_signals_spec.rb
+++ b/spec/lib/gitlab/sidekiq_signals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqSignals do
diff --git a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
index 37d9e1d3e6b..1ca8cea66fc 100644
--- a/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/client_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus::ClientMiddleware do
diff --git a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
index 04e09d3dec8..40bcb49d1d3 100644
--- a/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status/server_middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus::ServerMiddleware do
diff --git a/spec/lib/gitlab/sidekiq_status_spec.rb b/spec/lib/gitlab/sidekiq_status_spec.rb
index 884f27b212c..7b5c75b2f3b 100644
--- a/spec/lib/gitlab/sidekiq_status_spec.rb
+++ b/spec/lib/gitlab/sidekiq_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqStatus do
diff --git a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
index 7debf70a16f..2aa7d1fd6d8 100644
--- a/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning/manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqVersioning::Manager do
diff --git a/spec/lib/gitlab/sidekiq_versioning_spec.rb b/spec/lib/gitlab/sidekiq_versioning_spec.rb
index fa6d42e730d..dade5961775 100644
--- a/spec/lib/gitlab/sidekiq_versioning_spec.rb
+++ b/spec/lib/gitlab/sidekiq_versioning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SidekiqVersioning, :sidekiq, :redis do
diff --git a/spec/lib/gitlab/slash_commands/command_spec.rb b/spec/lib/gitlab/slash_commands/command_spec.rb
index eceacac58af..c4ea8cbf2b1 100644
--- a/spec/lib/gitlab/slash_commands/command_spec.rb
+++ b/spec/lib/gitlab/slash_commands/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Command do
diff --git a/spec/lib/gitlab/slash_commands/deploy_spec.rb b/spec/lib/gitlab/slash_commands/deploy_spec.rb
index 25f3e8a0409..93a724d8e12 100644
--- a/spec/lib/gitlab/slash_commands/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/deploy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Deploy do
diff --git a/spec/lib/gitlab/slash_commands/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
index 9a990e1fad7..962ac3668bc 100644
--- a/spec/lib/gitlab/slash_commands/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueMove, service: true do
diff --git a/spec/lib/gitlab/slash_commands/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
index 59de11766d8..90f0518a63e 100644
--- a/spec/lib/gitlab/slash_commands/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueNew do
diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
index 47787307990..b766a9a1361 100644
--- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueSearch do
diff --git a/spec/lib/gitlab/slash_commands/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
index 5c4ba2736ba..e53f79dcd86 100644
--- a/spec/lib/gitlab/slash_commands/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/issue_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::IssueShow do
diff --git a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
index ef3d217f7be..286fec892e6 100644
--- a/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Access do
diff --git a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
index d16d122c64e..9c2e9ab982f 100644
--- a/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/deploy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::Deploy do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
index 58c341a284e..56b64d32192 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueMove do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
index 76e4bad88fd..f926783fbea 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueNew do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
index 5a7ec0685fe..e1c011133c4 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueSearch do
diff --git a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
index 8f607d7a9c9..56d6bf1c788 100644
--- a/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
+++ b/spec/lib/gitlab/slash_commands/presenters/issue_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SlashCommands::Presenters::IssueShow do
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index 35df38f052b..89d290aaa81 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SnippetSearchResults do
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index 5d2164491b5..e6194924f5a 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::CTE do
diff --git a/spec/lib/gitlab/sql/glob_spec.rb b/spec/lib/gitlab/sql/glob_spec.rb
index 3147b52dcc5..83eed309ecc 100644
--- a/spec/lib/gitlab/sql/glob_spec.rb
+++ b/spec/lib/gitlab/sql/glob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Glob do
diff --git a/spec/lib/gitlab/sql/pattern_spec.rb b/spec/lib/gitlab/sql/pattern_spec.rb
index 98838712eae..31944d51b3c 100644
--- a/spec/lib/gitlab/sql/pattern_spec.rb
+++ b/spec/lib/gitlab/sql/pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Pattern do
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 407a4d8a247..20e36c224b0 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::RecursiveCTE do
diff --git a/spec/lib/gitlab/sql/union_spec.rb b/spec/lib/gitlab/sql/union_spec.rb
index fe6422c32b6..f8f6da19fa5 100644
--- a/spec/lib/gitlab/sql/union_spec.rb
+++ b/spec/lib/gitlab/sql/union_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SQL::Union do
diff --git a/spec/lib/gitlab/ssh_public_key_spec.rb b/spec/lib/gitlab/ssh_public_key_spec.rb
index a6ea07e8b6d..f8becb0c796 100644
--- a/spec/lib/gitlab/ssh_public_key_spec.rb
+++ b/spec/lib/gitlab/ssh_public_key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::SSHPublicKey, lib: true do
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
index 7a03ea4154c..0295bf1265f 100644
--- a/spec/lib/gitlab/string_placeholder_replacer_spec.rb
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringPlaceholderReplacer do
diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb
index 6bc02459dbd..7ed43db3d10 100644
--- a/spec/lib/gitlab/string_range_marker_spec.rb
+++ b/spec/lib/gitlab/string_range_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringRangeMarker do
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index 37b1298b962..2b19edbe7f9 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::StringRegexMarker do
diff --git a/spec/lib/gitlab/tcp_checker_spec.rb b/spec/lib/gitlab/tcp_checker_spec.rb
index 4acf0334496..49f04f269ae 100644
--- a/spec/lib/gitlab/tcp_checker_spec.rb
+++ b/spec/lib/gitlab/tcp_checker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::TcpChecker do
diff --git a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
index c7f58fbd2a5..082ffa855b7 100644
--- a/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
+++ b/spec/lib/gitlab/template/finders/global_template_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::Finders::GlobalTemplateFinder do
diff --git a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
index e329d55d837..c8f2a37c5d6 100644
--- a/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
+++ b/spec/lib/gitlab/template/finders/repo_template_finders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::Finders::RepoTemplateFinder do
diff --git a/spec/lib/gitlab/template/gitignore_template_spec.rb b/spec/lib/gitlab/template/gitignore_template_spec.rb
index 97797f42aaa..e8f632889ad 100644
--- a/spec/lib/gitlab/template/gitignore_template_spec.rb
+++ b/spec/lib/gitlab/template/gitignore_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::GitignoreTemplate do
diff --git a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
index 5f0a7e925ca..52e100768a7 100644
--- a/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
+++ b/spec/lib/gitlab/template/gitlab_ci_yml_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::GitlabCiYmlTemplate do
diff --git a/spec/lib/gitlab/template/issue_template_spec.rb b/spec/lib/gitlab/template/issue_template_spec.rb
index 7098499f996..54e46d3a9ec 100644
--- a/spec/lib/gitlab/template/issue_template_spec.rb
+++ b/spec/lib/gitlab/template/issue_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::IssueTemplate do
diff --git a/spec/lib/gitlab/template/merge_request_template_spec.rb b/spec/lib/gitlab/template/merge_request_template_spec.rb
index bd7ff64aa8a..bbc184d4dfc 100644
--- a/spec/lib/gitlab/template/merge_request_template_spec.rb
+++ b/spec/lib/gitlab/template/merge_request_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Template::MergeRequestTemplate do
diff --git a/spec/lib/gitlab/themes_spec.rb b/spec/lib/gitlab/themes_spec.rb
index a8213988f70..e0278eb9c7f 100644
--- a/spec/lib/gitlab/themes_spec.rb
+++ b/spec/lib/gitlab/themes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Themes, lib: true do
diff --git a/spec/lib/gitlab/tree_summary_spec.rb b/spec/lib/gitlab/tree_summary_spec.rb
index e22f898dc4c..e15463ed0eb 100644
--- a/spec/lib/gitlab/tree_summary_spec.rb
+++ b/spec/lib/gitlab/tree_summary_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::TreeSummary do
diff --git a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
index f1882e03581..68402e64012 100644
--- a/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp/ruby_syntax_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples'
require 'support/helpers/stub_feature_flags'
diff --git a/spec/lib/gitlab/untrusted_regexp_spec.rb b/spec/lib/gitlab/untrusted_regexp_spec.rb
index 9d483f13a5e..4cc21e94a83 100644
--- a/spec/lib/gitlab/untrusted_regexp_spec.rb
+++ b/spec/lib/gitlab/untrusted_regexp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/shared_examples/malicious_regexp_shared_examples'
diff --git a/spec/lib/gitlab/uploads_transfer_spec.rb b/spec/lib/gitlab/uploads_transfer_spec.rb
index 4275e7b015b..16560fc8f12 100644
--- a/spec/lib/gitlab/uploads_transfer_spec.rb
+++ b/spec/lib/gitlab/uploads_transfer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UploadsTransfer do
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index 45d9022abeb..df8a1f82f81 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlBlocker do
diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb
index bbcb92608d8..08d3c638f9e 100644
--- a/spec/lib/gitlab/url_builder_spec.rb
+++ b/spec/lib/gitlab/url_builder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlBuilder do
diff --git a/spec/lib/gitlab/url_sanitizer_spec.rb b/spec/lib/gitlab/url_sanitizer_spec.rb
index 7242255d535..b39609c594b 100644
--- a/spec/lib/gitlab/url_sanitizer_spec.rb
+++ b/spec/lib/gitlab/url_sanitizer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UrlSanitizer do
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 9bbd9394d57..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
@@ -248,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')
@@ -266,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/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb
index 9da06bb40f4..c25bd14fcba 100644
--- a/spec/lib/gitlab/user_access_spec.rb
+++ b/spec/lib/gitlab/user_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::UserAccess do
diff --git a/spec/lib/gitlab/utils/deep_size_spec.rb b/spec/lib/gitlab/utils/deep_size_spec.rb
index 1e619a15980..47dfc04f46f 100644
--- a/spec/lib/gitlab/utils/deep_size_spec.rb
+++ b/spec/lib/gitlab/utils/deep_size_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::DeepSize do
diff --git a/spec/lib/gitlab/utils/merge_hash_spec.rb b/spec/lib/gitlab/utils/merge_hash_spec.rb
index 4fa7bb31301..72620e549a9 100644
--- a/spec/lib/gitlab/utils/merge_hash_spec.rb
+++ b/spec/lib/gitlab/utils/merge_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::MergeHash do
describe '.crush' do
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 9e7c97f8095..5855c4374a9 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Utils::Override do
diff --git a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
index 064c2707d06..80b0935a7ed 100644
--- a/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::SanitizeNodeLink do
diff --git a/spec/lib/gitlab/utils/strong_memoize_spec.rb b/spec/lib/gitlab/utils/strong_memoize_spec.rb
index 473f8100771..26baaf873a8 100644
--- a/spec/lib/gitlab/utils/strong_memoize_spec.rb
+++ b/spec/lib/gitlab/utils/strong_memoize_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils::StrongMemoize do
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 0c20b3aa4c8..890918d4a7c 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Utils do
diff --git a/spec/lib/gitlab/verify/job_artifacts_spec.rb b/spec/lib/gitlab/verify/job_artifacts_spec.rb
index 6e916a56564..b50ec1528d4 100644
--- a/spec/lib/gitlab/verify/job_artifacts_spec.rb
+++ b/spec/lib/gitlab/verify/job_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::JobArtifacts do
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
index 2feaedd6f14..c27c9b6efa1 100644
--- a/spec/lib/gitlab/verify/lfs_objects_spec.rb
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::LfsObjects do
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
index 38c30fab1ba..a3d3f5d46f3 100644
--- a/spec/lib/gitlab/verify/uploads_spec.rb
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Verify::Uploads do
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 30035c79e58..8c14b187410 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Gitlab::VersionInfo' do
diff --git a/spec/lib/gitlab/view/presenter/base_spec.rb b/spec/lib/gitlab/view/presenter/base_spec.rb
index 02c2fd47197..e196ab23482 100644
--- a/spec/lib/gitlab/view/presenter/base_spec.rb
+++ b/spec/lib/gitlab/view/presenter/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Base do
diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb
index 940a2ce6ebd..0a21cd1358e 100644
--- a/spec/lib/gitlab/view/presenter/delegated_spec.rb
+++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Delegated do
diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb
index 6120bafb2e3..515a1b0a8e4 100644
--- a/spec/lib/gitlab/view/presenter/factory_spec.rb
+++ b/spec/lib/gitlab/view/presenter/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Factory do
diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb
index 1795ed2405b..70e2b170a36 100644
--- a/spec/lib/gitlab/view/presenter/simple_spec.rb
+++ b/spec/lib/gitlab/view/presenter/simple_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::View::Presenter::Simple do
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 0a170a157fe..75dc7d8e6d1 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::VisibilityLevel do
diff --git a/spec/lib/gitlab/wiki_file_finder_spec.rb b/spec/lib/gitlab/wiki_file_finder_spec.rb
index 025d1203dc5..fdd95d5e6e6 100644
--- a/spec/lib/gitlab/wiki_file_finder_spec.rb
+++ b/spec/lib/gitlab/wiki_file_finder_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::WikiFileFinder do
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index 451e18ed91b..5c5ff46112f 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Workhorse do
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index c293f58c9cb..cbc5649fc09 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 'gitlab'
diff --git a/spec/lib/google_api/auth_spec.rb b/spec/lib/google_api/auth_spec.rb
index 87a3f43274f..a25004ac385 100644
--- a/spec/lib/google_api/auth_spec.rb
+++ b/spec/lib/google_api/auth_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GoogleApi::Auth do
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index 1fefc947636..c24998d32f8 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe GoogleApi::CloudPlatform::Client do
diff --git a/spec/lib/json_web_token/rsa_token_spec.rb b/spec/lib/json_web_token/rsa_token_spec.rb
index a3c54651e80..a127c787e28 100644
--- a/spec/lib/json_web_token/rsa_token_spec.rb
+++ b/spec/lib/json_web_token/rsa_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe JSONWebToken::RSAToken do
let(:rsa_key) do
OpenSSL::PKey::RSA.new <<-eos.strip_heredoc
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index d7e7560d962..916d11ce0ed 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe JSONWebToken::Token do
let(:token) { described_class.new }
diff --git a/spec/lib/mattermost/client_spec.rb b/spec/lib/mattermost/client_spec.rb
index dc11a414717..5fe35eb5f93 100644
--- a/spec/lib/mattermost/client_spec.rb
+++ b/spec/lib/mattermost/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Client do
diff --git a/spec/lib/mattermost/command_spec.rb b/spec/lib/mattermost/command_spec.rb
index 7c194749dfb..f8c451a1522 100644
--- a/spec/lib/mattermost/command_spec.rb
+++ b/spec/lib/mattermost/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Command do
diff --git a/spec/lib/mattermost/session_spec.rb b/spec/lib/mattermost/session_spec.rb
index 346455067a7..ea12bd76c8d 100644
--- a/spec/lib/mattermost/session_spec.rb
+++ b/spec/lib/mattermost/session_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Session, type: :request do
diff --git a/spec/lib/mattermost/team_spec.rb b/spec/lib/mattermost/team_spec.rb
index 030aa5d06a8..2823dab67c9 100644
--- a/spec/lib/mattermost/team_spec.rb
+++ b/spec/lib/mattermost/team_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Mattermost::Team do
diff --git a/spec/lib/microsoft_teams/activity_spec.rb b/spec/lib/microsoft_teams/activity_spec.rb
index 7890ae2e7b0..3fad2437f3e 100644
--- a/spec/lib/microsoft_teams/activity_spec.rb
+++ b/spec/lib/microsoft_teams/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeams::Activity do
diff --git a/spec/lib/microsoft_teams/notifier_spec.rb b/spec/lib/microsoft_teams/notifier_spec.rb
index 2aaa7c24ad8..64ab8d85807 100644
--- a/spec/lib/microsoft_teams/notifier_spec.rb
+++ b/spec/lib/microsoft_teams/notifier_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MicrosoftTeams::Notifier do
diff --git a/spec/lib/milestone_array_spec.rb b/spec/lib/milestone_array_spec.rb
index df91677b925..375cb87dde6 100644
--- a/spec/lib/milestone_array_spec.rb
+++ b/spec/lib/milestone_array_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe MilestoneArray do
diff --git a/spec/lib/object_storage/direct_upload_spec.rb b/spec/lib/object_storage/direct_upload_spec.rb
index 8ccbd90ddb8..fae0c636bdc 100644
--- a/spec/lib/object_storage/direct_upload_spec.rb
+++ b/spec/lib/object_storage/direct_upload_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ObjectStorage::DirectUpload do
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index c1eaf0bb0bf..bdf3ea6be98 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe OmniAuth::Strategies::Jwt do
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/lib/rspec_flaky/config_spec.rb b/spec/lib/rspec_flaky/config_spec.rb
index 4a71b1feebd..13b2219267b 100644
--- a/spec/lib/rspec_flaky/config_spec.rb
+++ b/spec/lib/rspec_flaky/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Config, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/example_spec.rb b/spec/lib/rspec_flaky/example_spec.rb
index 5b4fd5ddf3e..4679dd818db 100644
--- a/spec/lib/rspec_flaky/example_spec.rb
+++ b/spec/lib/rspec_flaky/example_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Example do
diff --git a/spec/lib/rspec_flaky/flaky_example_spec.rb b/spec/lib/rspec_flaky/flaky_example_spec.rb
index d19c34bebb3..092bbc781a5 100644
--- a/spec/lib/rspec_flaky/flaky_example_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_example_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::FlakyExample, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
index 6731a27ed17..2e224cda61b 100644
--- a/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
+++ b/spec/lib/rspec_flaky/flaky_examples_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::FlakyExamplesCollection, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/listener_spec.rb b/spec/lib/rspec_flaky/listener_spec.rb
index ef085445081..44b8d99b74f 100644
--- a/spec/lib/rspec_flaky/listener_spec.rb
+++ b/spec/lib/rspec_flaky/listener_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Listener, :aggregate_failures do
diff --git a/spec/lib/rspec_flaky/report_spec.rb b/spec/lib/rspec_flaky/report_spec.rb
index 7d57d99f7e5..6a98a7a4e6b 100644
--- a/spec/lib/rspec_flaky/report_spec.rb
+++ b/spec/lib/rspec_flaky/report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe RspecFlaky::Report, :aggregate_failures do
diff --git a/spec/lib/safe_zip/entry_spec.rb b/spec/lib/safe_zip/entry_spec.rb
index 115e28c5994..0974f732188 100644
--- a/spec/lib/safe_zip/entry_spec.rb
+++ b/spec/lib/safe_zip/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::Entry do
diff --git a/spec/lib/safe_zip/extract_params_spec.rb b/spec/lib/safe_zip/extract_params_spec.rb
index 85e22cfa495..f66d3de89ee 100644
--- a/spec/lib/safe_zip/extract_params_spec.rb
+++ b/spec/lib/safe_zip/extract_params_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::ExtractParams do
diff --git a/spec/lib/safe_zip/extract_spec.rb b/spec/lib/safe_zip/extract_spec.rb
index b75a8fede00..3b8c64c1c9f 100644
--- a/spec/lib/safe_zip/extract_spec.rb
+++ b/spec/lib/safe_zip/extract_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SafeZip::Extract do
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
index 847a01d186c..a8d82d70e89 100644
--- a/spec/lib/serializers/json_spec.rb
+++ b/spec/lib/serializers/json_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Serializers::JSON do
diff --git a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
index a0fb86345f3..f132f608ab6 100644
--- a/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
+++ b/spec/lib/system_check/app/git_user_default_ssh_config_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemCheck::App::GitUserDefaultSSHConfigCheck do
diff --git a/spec/lib/system_check/base_check_spec.rb b/spec/lib/system_check/base_check_spec.rb
index faf8c99e772..ccb7b483bdc 100644
--- a/spec/lib/system_check/base_check_spec.rb
+++ b/spec/lib/system_check/base_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe SystemCheck::BaseCheck do
diff --git a/spec/lib/system_check/orphans/namespace_check_spec.rb b/spec/lib/system_check/orphans/namespace_check_spec.rb
index 2a61ff3ad65..f7491e40438 100644
--- a/spec/lib/system_check/orphans/namespace_check_spec.rb
+++ b/spec/lib/system_check/orphans/namespace_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check/orphans/repository_check_spec.rb b/spec/lib/system_check/orphans/repository_check_spec.rb
index b0c2267d177..a5e06f30e75 100644
--- a/spec/lib/system_check/orphans/repository_check_spec.rb
+++ b/spec/lib/system_check/orphans/repository_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check/simple_executor_spec.rb b/spec/lib/system_check/simple_executor_spec.rb
index e71e9da369d..94094343ec6 100644
--- a/spec/lib/system_check/simple_executor_spec.rb
+++ b/spec/lib/system_check/simple_executor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/system_check_spec.rb b/spec/lib/system_check_spec.rb
index 4d9e17fa6ec..f3ed6ca31c9 100644
--- a/spec/lib/system_check_spec.rb
+++ b/spec/lib/system_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rake_helper'
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index a2f5c2e7121..2cb4727bd4b 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe UploadedFile do
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index dcc4b70a382..6cba7df114c 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -543,6 +543,73 @@ describe Notify do
end
end
+ describe '#mail_thread' do
+ set(:mail_thread_note) { create(:note) }
+
+ let(:headers) do
+ {
+ from: 'someone@test.com',
+ to: 'someone-else@test.com',
+ subject: 'something',
+ template_name: '_note_email' # re-use this for testing
+ }
+ end
+
+ let(:mailer) do
+ mailer = described_class.new
+ mailer.instance_variable_set(:@note, mail_thread_note)
+ mailer
+ end
+
+ context 'the model has no namespace' do
+ class TopLevelThing
+ include Referable
+ include Noteable
+
+ def to_reference(*_args)
+ 'tlt-ref'
+ end
+
+ def id
+ 'tlt-id'
+ end
+ end
+
+ subject do
+ mailer.send(:mail_thread, TopLevelThing.new, headers)
+ end
+
+ it 'has X-GitLab-Namespaced-Thing-ID header' do
+ expect(subject.header['X-GitLab-TopLevelThing-ID'].value).to eq('tlt-id')
+ end
+ end
+
+ context 'the model has a namespace' do
+ module Namespaced
+ class Thing
+ include Referable
+ include Noteable
+
+ def to_reference(*_args)
+ 'some-reference'
+ end
+
+ def id
+ 'some-id'
+ end
+ end
+ end
+
+ subject do
+ mailer.send(:mail_thread, Namespaced::Thing.new, headers)
+ end
+
+ it 'has X-GitLab-Namespaced-Thing-ID header' do
+ expect(subject.header['X-GitLab-Namespaced-Thing-ID'].value).to eq('some-id')
+ end
+ end
+ end
+
context 'for issue notes' do
let(:host) { Gitlab.config.gitlab.host }
diff --git a/spec/migrations/add_gitlab_instance_administration_project_spec.rb b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
new file mode 100644
index 00000000000..08e20a4e8ff
--- /dev/null
+++ b/spec/migrations/add_gitlab_instance_administration_project_spec.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190801072937_add_gitlab_instance_administration_project.rb')
+
+describe AddGitlabInstanceAdministrationProject, :migration do
+ let(:application_settings) { table(:application_settings) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
+ let(:members) { table(:members) }
+
+ let(:service_class) do
+ Gitlab::DatabaseImporters::SelfMonitoring::Project::CreateService
+ end
+
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+
+ stub_config(prometheus: prometheus_settings)
+ end
+
+ describe 'down' do
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ it 'deletes group and project' do
+ migrate!
+
+ expect(Project.count).to eq(1)
+ expect(Group.count).to eq(1)
+
+ schema_migrate_down!
+
+ expect(Project.count).to eq(0)
+ expect(Group.count).to eq(0)
+ end
+ end
+
+ describe 'up' do
+ context 'without application_settings' do
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'without admin users' do
+ let!(:application_setting) { application_settings.create! }
+
+ it 'does not fail' do
+ migrate!
+
+ expect(Project.count).to eq(0)
+ end
+ end
+
+ context 'with admin users' do
+ let(:project) { Project.last }
+ let(:group) { Group.last }
+ let!(:application_setting) { application_settings.create! }
+ let!(:user) { users.create!(admin: true, email: 'admin1@example.com', projects_limit: 10, state: :active) }
+
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ shared_examples 'has prometheus service' do |listen_address|
+ it do
+ migrate!
+
+ prometheus = project.prometheus_service
+ expect(prometheus).to be_persisted
+ expect(prometheus).not_to eq(nil)
+ expect(prometheus.api_url).to eq(listen_address)
+ expect(prometheus.active).to eq(true)
+ expect(prometheus.manual_configuration).to eq(true)
+ end
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'creates GitLab Instance Administrator group' do
+ migrate!
+
+ expect(group).to be_persisted
+ expect(group.name).to eq('GitLab Instance Administrators')
+ expect(group.path).to start_with('gitlab-instance-administrators')
+ expect(group.path.split('-').last.length).to eq(8)
+ expect(group.visibility_level).to eq(service_class::VISIBILITY_LEVEL)
+ end
+
+ it 'creates project with internal visibility' do
+ migrate!
+
+ expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
+ expect(project).to be_persisted
+ end
+
+ it 'creates project with correct name and description' do
+ migrate!
+
+ path = 'administration/monitoring/gitlab_instance_administration_project/index'
+ docs_path = Rails.application.routes.url_helpers.help_page_path(path)
+
+ expect(project.name).to eq(service_class::PROJECT_NAME)
+ expect(project.description).to eq(
+ 'This project is automatically generated and will be used to help monitor this GitLab instance. ' \
+ "[More information](#{docs_path})"
+ )
+ expect(File).to exist("doc/#{path}.md")
+ end
+
+ it 'adds all admins as maintainers' do
+ admin1 = users.create!(admin: true, email: 'admin2@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin3@example.com', projects_limit: 10, state: :active)
+ users.create!(email: 'nonadmin1@example.com', projects_limit: 10, state: :active)
+
+ migrate!
+
+ expect(project.owner).to eq(group)
+ expect(group.members.collect(&:user).collect(&:id)).to contain_exactly(user.id, admin1.id, admin2.id)
+ expect(group.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::OWNER,
+ Gitlab::Access::MAINTAINER,
+ Gitlab::Access::MAINTAINER
+ )
+ end
+
+ it 'saves the project id' do
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.instance_administration_project_id).to eq(project.id)
+ end
+
+ it 'does not fail when a project already exists' do
+ group = namespaces.create!(
+ path: 'gitlab-instance-administrators',
+ name: 'GitLab Instance Administrators',
+ type: 'Group'
+ )
+ project = projects.create!(
+ namespace_id: group.id,
+ name: 'GitLab Instance Administration'
+ )
+
+ admin1 = users.create!(admin: true, email: 'admin4@example.com', projects_limit: 10, state: :active)
+ admin2 = users.create!(admin: true, email: 'admin5@example.com', projects_limit: 10, state: :active)
+
+ members.create!(
+ user_id: admin1.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+ members.create!(
+ user_id: admin2.id,
+ source_id: group.id,
+ source_type: 'Namespace',
+ type: 'GroupMember',
+ access_level: GroupMember::MAINTAINER,
+ notification_level: NotificationSetting.levels[:global]
+ )
+
+ stub_application_setting(instance_administration_project: project)
+
+ migrate!
+
+ expect(Project.last.id).to eq(project.id)
+ expect(Group.last.id).to eq(group.id)
+ end
+
+ context 'when local requests from hooks and services are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'has prometheus service', 'http://localhost:9090'
+
+ it 'does not overwrite the existing whitelist' do
+ application_setting.update!(outbound_local_requests_whitelist: ['example.com'])
+
+ migrate!
+
+ application_setting.reload
+ expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'localhost'
+ )
+ end
+ end
+
+ context 'with non default prometheus address' do
+ let(:prometheus_settings) do
+ {
+ enable: true,
+ listen_address: 'https://localhost:9090'
+ }
+ end
+
+ it_behaves_like 'has prometheus service', 'https://localhost:9090'
+ end
+
+ context 'when prometheus setting is not present in gitlab.yml' do
+ before do
+ allow(Gitlab.config).to receive(:prometheus).and_raise(Settingslogic::MissingSetting)
+ end
+
+ it 'does not fail' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus setting is disabled in gitlab.yml' do
+ let(:prometheus_settings) do
+ {
+ enable: false,
+ listen_address: 'localhost:9090'
+ }
+ end
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+
+ context 'when prometheus listen address is blank in gitlab.yml' do
+ let(:prometheus_settings) { { enable: true, listen_address: '' } }
+
+ it 'does not configure prometheus' do
+ migrate!
+
+ expect(project.prometheus_service).to be_nil
+ end
+ end
+ end
+ end
+end
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
index 4e3923e82b1..83d6ff754c5 100644
--- a/spec/models/analytics/cycle_analytics/project_stage_spec.rb
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -6,4 +6,18 @@ describe Analytics::CycleAnalytics::ProjectStage do
describe 'associations' do
it { is_expected.to belong_to(:project) }
end
+
+ it 'default stages must be valid' do
+ project = create(:project)
+
+ Gitlab::Analytics::CycleAnalytics::DefaultStages.all.each do |params|
+ stage = described_class.new(params.merge(project: project))
+ expect(stage).to be_valid
+ end
+ end
+
+ it_behaves_like "cycle analytics stage" do
+ let(:parent) { create(:project) }
+ let(:parent_name) { :project }
+ end
end
diff --git a/spec/models/award_emoji_spec.rb b/spec/models/award_emoji_spec.rb
index 8452ac69734..b15b26b1630 100644
--- a/spec/models/award_emoji_spec.rb
+++ b/spec/models/award_emoji_spec.rb
@@ -44,6 +44,29 @@ describe AwardEmoji do
end
end
+ describe 'scopes' do
+ set(:thumbsup) { create(:award_emoji, name: 'thumbsup') }
+ set(:thumbsdown) { create(:award_emoji, name: 'thumbsdown') }
+
+ describe '.upvotes' do
+ it { expect(described_class.upvotes).to contain_exactly(thumbsup) }
+ end
+
+ describe '.downvotes' do
+ it { expect(described_class.downvotes).to contain_exactly(thumbsdown) }
+ end
+
+ describe '.named' do
+ it { expect(described_class.named('thumbsup')).to contain_exactly(thumbsup) }
+ it { expect(described_class.named(%w[thumbsup thumbsdown])).to contain_exactly(thumbsup, thumbsdown) }
+ end
+
+ describe '.awarded_by' do
+ it { expect(described_class.awarded_by(thumbsup.user)).to contain_exactly(thumbsup) }
+ it { expect(described_class.awarded_by([thumbsup.user, thumbsdown.user])).to contain_exactly(thumbsup, thumbsdown) }
+ end
+ end
+
describe 'expiring ETag cache' do
context 'on a note' do
let(:note) { create(:note_on_issue) }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 4aac4b640f4..bc853d45085 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -149,6 +149,56 @@ describe Ci::Build do
end
end
+ describe '.with_stale_live_trace' do
+ subject { described_class.with_stale_live_trace }
+
+ context 'when build has a stale live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) }
+
+ it 'selects the build' do
+ is_expected.to eq([build])
+ end
+ end
+
+ context 'when build does not have a stale live trace' do
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago) }
+
+ it 'does not select the build' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.finished_before' do
+ subject { described_class.finished_before(date) }
+
+ let(:date) { 1.hour.ago }
+
+ context 'when build has finished one day ago' do
+ let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago) }
+
+ it 'selects the build' do
+ is_expected.to eq([build])
+ end
+ end
+
+ context 'when build has finished 30 minutes ago' do
+ let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago) }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when build is still running' do
+ let!(:build) { create(:ci_build, :running) }
+
+ it 'returns an empty array' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.with_reports' do
subject { described_class.with_reports(Ci::JobArtifact.test_reports) }
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 78b151631c1..70ff3cf5dc4 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -80,6 +80,13 @@ describe Ci::Runner do
end
end
+ describe 'constraints' do
+ it '.UPDATE_CONTACT_COLUMN_EVERY' do
+ expect(described_class::UPDATE_CONTACT_COLUMN_EVERY.max)
+ .to be <= described_class::ONLINE_CONTACT_TIMEOUT
+ end
+ end
+
describe '#access_level' do
context 'when creating new runner and access_level is nil' do
let(:runner) do
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 9e7106281ee..76da42cf243 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -82,16 +82,6 @@ describe Awardable do
end
end
- describe "#toggle_award_emoji" do
- it "adds an emoji if it isn't awarded yet" do
- expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)
- end
-
- it "toggles already awarded emoji" do
- expect { issue.toggle_award_emoji("thumbsdown", award_emoji.user) }.to change { AwardEmoji.count }.by(-1)
- end
- end
-
describe 'querying award_emoji on an Awardable' do
let(:issue) { create(:issue) }
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index d4e631f109b..51ed8e9421b 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -322,4 +322,30 @@ describe Deployment do
end
end
end
+
+ describe '#deployed_by' do
+ it 'returns the deployment user if there is no deployable' do
+ deployment_user = create(:user)
+ deployment = create(:deployment, deployable: nil, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(deployment_user)
+ end
+
+ it 'returns the deployment user if the deployable have no user' do
+ deployment_user = create(:user)
+ build = create(:ci_build, user: nil)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(deployment_user)
+ end
+
+ it 'returns the deployable user if there is one' do
+ build_user = create(:user)
+ deployment_user = create(:user)
+ build = create(:ci_build, user: build_user)
+ deployment = create(:deployment, deployable: build, user: deployment_user)
+
+ expect(deployment.deployed_by).to eq(build_user)
+ end
+ end
end
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/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 5341278db7c..9e12831a704 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -8,6 +8,19 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
it { is_expected.to delegate_method(:all_projects).to(:namespace) }
+ context 'scopes' do
+ describe '.for_namespace_ids' do
+ it 'returns only requested namespaces' do
+ stats = create_list(:namespace_root_storage_statistics, 3)
+ namespace_ids = stats[0..1].map { |s| s.namespace_id }
+
+ requested_stats = described_class.for_namespace_ids(namespace_ids).pluck(:namespace_id)
+
+ expect(requested_stats).to eq(namespace_ids)
+ end
+ end
+ end
+
describe '#recalculate!' do
let(:namespace) { create(:group) }
let(:root_storage_statistics) { create(:namespace_root_storage_statistics, namespace: namespace) }
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 419e1dc2459..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 }
+
+ 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
- expect(repository.tag_exists?('foobar')).to eq(true)
- expect(repository.tag_exists?('master')).to eq(false)
+ 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
diff --git a/spec/policies/namespace/root_storage_statistics_policy_spec.rb b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
new file mode 100644
index 00000000000..8d53050fffb
--- /dev/null
+++ b/spec/policies/namespace/root_storage_statistics_policy_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Namespace::RootStorageStatisticsPolicy do
+ using RSpec::Parameterized::TableSyntax
+
+ describe '#rules' do
+ let(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace) }
+ let(:user) { create(:user) }
+
+ subject { Ability.allowed?(user, :read_statistics, statistics) }
+
+ shared_examples 'deny anonymous users' do
+ context 'when the users is anonymous' do
+ let(:user) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when the namespace is a personal namespace' do
+ let(:owner) { create(:user) }
+ let(:namespace) { owner.namespace }
+
+ include_examples 'deny anonymous users'
+
+ context 'when the user is not the owner' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when the user is the owner' do
+ let(:user) { owner }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when the namespace is a group' do
+ let(:user) { create(:user) }
+ let(:external) { create(:user, :external) }
+
+ shared_examples 'allows only owners' do |group_type|
+ let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel.level_value(group_type.to_s)) }
+ let(:namespace) { group }
+
+ include_examples 'deny anonymous users'
+
+ where(:user_type, :outcome) do
+ [
+ [:non_member, false],
+ [:guest, false],
+ [:reporter, false],
+ [:developer, false],
+ [:maintainer, false],
+ [:owner, true]
+ ]
+ end
+
+ with_them do
+ before do
+ group.add_user(user, user_type) unless user_type == :non_member
+ end
+
+ it { is_expected.to eq(outcome) }
+
+ context 'when the user is external' do
+ let(:user) { external }
+
+ it { is_expected.to eq(outcome) }
+ end
+ end
+ end
+
+ include_examples 'allows only owners', :public
+ include_examples 'allows only owners', :private
+ include_examples 'allows only owners', :internal
+ end
+ end
+end
diff --git a/spec/policies/namespace_policy_spec.rb b/spec/policies/namespace_policy_spec.rb
index 99fa8b1fe44..216aaae70ee 100644
--- a/spec/policies/namespace_policy_spec.rb
+++ b/spec/policies/namespace_policy_spec.rb
@@ -6,7 +6,7 @@ describe NamespacePolicy do
let(:admin) { create(:admin) }
let(:namespace) { create(:namespace, owner: owner) }
- let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace] }
+ let(:owner_permissions) { [:create_projects, :admin_namespace, :read_namespace, :read_statistics] }
subject { described_class.new(current_user, namespace) }
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/award_emoji_spec.rb b/spec/requests/api/award_emoji_spec.rb
index 6c67d84b59b..342fcfa1041 100644
--- a/spec/requests/api/award_emoji_spec.rb
+++ b/spec/requests/api/award_emoji_spec.rb
@@ -155,6 +155,14 @@ describe API::AwardEmoji do
expect(json_response['user']['username']).to eq(user.username)
end
+ it 'marks Todos on the Issue as done' do
+ todo = create(:todo, target: issue, project: project, user: user)
+
+ post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user), params: { name: '8ball' }
+
+ expect(todo.reload).to be_done
+ end
+
it "returns a 400 bad request error if the name is not given" do
post api("/projects/#{project.id}/issues/#{issue.iid}/award_emoji", user)
@@ -209,6 +217,14 @@ describe API::AwardEmoji do
expect(json_response['user']['username']).to eq(user.username)
end
+ it 'marks Todos on the Noteable as done' do
+ todo = create(:todo, target: note2.noteable, project: project, user: user)
+
+ post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: 'rocket' }
+
+ expect(todo.reload).to be_done
+ end
+
it "normalizes +1 as thumbsup award" do
post api("/projects/#{project.id}/issues/#{issue.iid}/notes/#{note.id}/award_emoji", user), params: { name: '+1' }
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index ef09c6effbb..0420201efe3 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -9,59 +9,11 @@ describe API::Discussions do
project.add_developer(user)
end
- context 'with cross-reference system notes', :request_store do
- let(:merge_request) { create(:merge_request) }
- let(:project) { merge_request.project }
- let(:new_merge_request) { create(:merge_request) }
- let(:commit) { new_merge_request.project.commit }
- let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
- let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
- let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
- let(:pat) { create(:personal_access_token, user: user) }
-
+ context 'when discussions have cross-reference system notes' do
let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/discussions" }
+ let(:notes_in_response) { json_response.first['notes'] }
- before do
- project.add_developer(user)
- new_merge_request.project.add_developer(user)
- end
-
- it 'returns only the note that the user should see' do
- hidden_merge_request = create(:merge_request)
- new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
- new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
- create(:system_note_metadata, note: new_note, action: 'cross_reference')
-
- get api(url, user, personal_access_token: pat)
- expect(response).to have_gitlab_http_status(200)
- expect(json_response.count).to eq(1)
- expect(json_response.first['notes'].count).to eq(1)
-
- parsed_note = json_response.first['notes'].first
- expect(parsed_note['id']).to eq(note.id)
- expect(parsed_note['body']).to eq(cross_reference)
- expect(parsed_note['system']).to be true
- end
-
- it 'avoids Git calls and N+1 SQL queries' do
- expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id)
-
- control = ActiveRecord::QueryRecorder.new do
- get api(url, user, personal_access_token: pat)
- end
-
- expect(response).to have_gitlab_http_status(200)
-
- RequestStore.clear!
-
- new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
- create(:system_note_metadata, note: new_note, action: 'cross_reference')
-
- RequestStore.clear!
-
- expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control)
- expect(response).to have_gitlab_http_status(200)
- end
+ it_behaves_like 'with cross-reference system notes'
end
context 'when noteable is an Issue' do
diff --git a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
index 3982125a38a..5b910d5bfe0 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/add_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'Adding an AwardEmoji' do
include GraphqlHelpers
- let(:current_user) { create(:user) }
- let(:awardable) { create(:note) }
- let(:project) { awardable.project }
+ set(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
let(:emoji_name) { 'thumbsup' }
let(:mutation) do
variables = {
@@ -43,7 +43,7 @@ describe 'Adding an AwardEmoji' do
end
context 'when the given awardable is not an Awardable' do
- let(:awardable) { create(:label) }
+ let(:awardable) { create(:label, project: project) }
it_behaves_like 'a mutation that does not create an AwardEmoji'
@@ -52,7 +52,7 @@ describe 'Adding an AwardEmoji' do
end
context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
- let(:awardable) { create(:system_note) }
+ let(:awardable) { create(:system_note, project: project) }
it_behaves_like 'a mutation that does not create an AwardEmoji'
@@ -73,6 +73,13 @@ describe 'Adding an AwardEmoji' do
expect(mutation_response['awardEmoji']['name']).to eq(emoji_name)
end
+ describe 'marking Todos as done' do
+ let(:user) { current_user}
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
context 'when there were active record validation errors' do
before do
expect_next_instance_of(AwardEmoji) do |award|
diff --git a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
index 31145730f10..ae628d3e56c 100644
--- a/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
+++ b/spec/requests/api/graphql/mutations/award_emojis/toggle_spec.rb
@@ -5,9 +5,9 @@ require 'spec_helper'
describe 'Toggling an AwardEmoji' do
include GraphqlHelpers
- let(:current_user) { create(:user) }
- let(:awardable) { create(:note) }
- let(:project) { awardable.project }
+ set(:current_user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
let(:emoji_name) { 'thumbsup' }
let(:mutation) do
variables = {
@@ -40,7 +40,7 @@ describe 'Toggling an AwardEmoji' do
end
context 'when the given awardable is not an Awardable' do
- let(:awardable) { create(:label) }
+ let(:awardable) { create(:label, project: project) }
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
@@ -49,7 +49,7 @@ describe 'Toggling an AwardEmoji' do
end
context 'when the given awardable is an Awardable but still cannot be awarded an emoji' do
- let(:awardable) { create(:system_note) }
+ let(:awardable) { create(:system_note, project: project) }
it_behaves_like 'a mutation that does not create or destroy an AwardEmoji'
@@ -81,6 +81,13 @@ describe 'Toggling an AwardEmoji' do
expect(mutation_response['toggledOn']).to eq(true)
end
+ describe 'marking Todos as done' do
+ let(:user) { current_user}
+ subject { post_graphql_mutation(mutation, current_user: user) }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
context 'when there were active record validation errors' do
before do
expect_next_instance_of(AwardEmoji) do |award|
diff --git a/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
new file mode 100644
index 00000000000..ac76d991bd4
--- /dev/null
+++ b/spec/requests/api/graphql/namespace/root_storage_statistics_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'rendering namespace statistics' do
+ include GraphqlHelpers
+
+ let(:namespace) { user.namespace }
+ let!(:statistics) { create(:namespace_root_storage_statistics, namespace: namespace, packages_size: 5.megabytes) }
+ let(:user) { create(:user) }
+
+ let(:query) do
+ graphql_query_for('namespace',
+ { 'fullPath' => namespace.full_path },
+ "rootStorageStatistics { #{all_graphql_fields_for('RootStorageStatistics')} }")
+ end
+
+ shared_examples 'a working namespace with storage statistics query' do
+ it_behaves_like 'a working graphql query' do
+ before do
+ post_graphql(query, current_user: user)
+ end
+ end
+
+ it 'includes the packages size if the user can read the statistics' do
+ post_graphql(query, current_user: user)
+
+ expect(graphql_data['namespace']['rootStorageStatistics']).not_to be_blank
+ expect(graphql_data['namespace']['rootStorageStatistics']['packagesSize']).to eq(5.megabytes)
+ end
+ end
+
+ it_behaves_like 'a working namespace with storage statistics query'
+
+ context 'when the namespace is a group' do
+ let(:group) { create(:group) }
+ let(:namespace) { group }
+
+ before do
+ group.add_owner(user)
+ end
+
+ it_behaves_like 'a working namespace with storage statistics query'
+
+ context 'when the namespace is public' do
+ let(:group) { create(:group, :public)}
+
+ it 'hides statistics for unauthenticated requests' do
+ post_graphql(query, current_user: nil)
+
+ expect(graphql_data['namespace']).to be_blank
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/graphql/project/project_statistics_spec.rb b/spec/requests/api/graphql/project/project_statistics_spec.rb
index 14a3f37b779..ddee8537454 100644
--- a/spec/requests/api/graphql/project/project_statistics_spec.rb
+++ b/spec/requests/api/graphql/project/project_statistics_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'rendering namespace statistics' do
+describe 'rendering project statistics' do
include GraphqlHelpers
let(:project) { create(:project) }
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..f19c2dcc6fe 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
@@ -603,6 +607,22 @@ describe API::Issues do
expect_paginated_array_response([closed_issue.id, issue.id])
end
+ context 'with issues list sort options' do
+ it 'accepts only predefined order by params' do
+ API::Helpers::IssuesHelpers.sort_options.each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it 'fails to sort with non predefined options' do
+ %w(milestone title abracadabra).each do |sort_opt|
+ get api('/issues', user), params: { order_by: sort_opt, sort: 'asc' }
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
it 'matches V4 response schema' do
get api('/issues', user)
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index ad0974f55a3..f6640fe41d0 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -6,6 +6,180 @@ describe API::Labels do
let!(:label1) { create(:label, title: 'label1', project: project) }
let!(:priority_label) { create(:label, title: 'bug', project: project, priority: 3) }
+ shared_examples 'label update API' do
+ it 'returns 200 if name is changed' do
+ request_params = {
+ new_name: 'New Label'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq(label1.color)
+ end
+
+ it 'returns 200 if colors is changed' do
+ request_params = {
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['color']).to eq('#FFFFFF')
+ end
+
+ it 'returns 200 if a priority is added' do
+ request_params = {
+ priority: 3
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(label1.name)
+ expect(json_response['priority']).to eq(3)
+ end
+
+ it 'returns 400 if no new parameters given' do
+ put api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
+ 'at least one parameter must be provided')
+ end
+
+ it 'returns 400 when color code is too short' do
+ request_params = {
+ color: '#FF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for too long color code' do
+ request_params = {
+ color: '#FFAAFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['color']).to eq(['must be a valid color code'])
+ end
+
+ it 'returns 400 for invalid priority' do
+ request_params = {
+ priority: 'foo'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 200 if name and colors and description are changed' do
+ request_params = {
+ new_name: 'New Label',
+ color: '#FFFFFF',
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 400 for invalid name' do
+ request_params = {
+ new_name: ',',
+ color: '#FFFFFF'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']['title']).to eq(['is invalid'])
+ end
+
+ it 'returns 200 if description is changed' do
+ request_params = {
+ description: 'test'
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns 200 if priority is changed' do
+ request_params = {
+ priority: 10
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to eq(10)
+ end
+
+ it 'returns 200 if a priority is removed' do
+ label = find_by_spec_params(spec_params)
+ expect(label).not_to be_nil
+
+ label.priorities.create(project: label.project, priority: 1)
+ label.save!
+
+ request_params = {
+ priority: nil
+ }.merge(spec_params)
+
+ put api("/projects/#{project.id}/labels", user),
+ params: request_params
+
+ expect(response.status).to eq(200)
+ expect(json_response['id']).to eq(expected_response_label_id)
+ expect(json_response['priority']).to be_nil
+ end
+
+ def find_by_spec_params(params)
+ if params.key?(:label_id)
+ Label.find(params[:label_id])
+ else
+ Label.find_by(name: params[:name])
+ end
+ end
+ end
+
+ shared_examples 'label delete API' do
+ it 'returns 204 for existing label' do
+ delete api("/projects/#{project.id}/labels", user), params: spec_params
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+ end
+
before do
project.add_maintainer(user)
end
@@ -208,174 +382,92 @@ describe API::Labels do
end
describe 'DELETE /projects/:id/labels' do
- it 'returns 204 for existing label' do
- delete api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { name: 'label1' } }
+ end
- expect(response).to have_gitlab_http_status(204)
+ it_behaves_like 'label delete API' do
+ let(:spec_params) { { label_id: label1.id } }
end
it 'returns 404 for non existing label' do
delete api("/projects/#{project.id}/labels", user), params: { name: 'label2' }
+
expect(response).to have_gitlab_http_status(404)
expect(json_response['message']).to eq('404 Label Not Found')
end
it 'returns 400 for wrong parameters' do
delete api("/projects/#{project.id}/labels", user)
- expect(response).to have_gitlab_http_status(400)
- end
-
- it_behaves_like '412 response' do
- let(:request) { api("/projects/#{project.id}/labels", user) }
- let(:params) { { name: 'label1' } }
- end
- end
- describe 'PUT /projects/:id/labels' do
- it 'returns 200 if name and colors and description are changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: 'New Label',
- color: '#FFFFFF',
- description: 'test'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq('#FFFFFF')
- expect(json_response['description']).to eq('test')
+ expect(response).to have_gitlab_http_status(400)
end
- it 'returns 200 if name is changed' do
- put api("/projects/#{project.id}/labels", user),
+ it 'fails if label_id and name are given in params' do
+ delete api("/projects/#{project.id}/labels", user),
params: {
- name: 'label1',
- new_name: 'New Label'
+ label_id: label1.id,
+ name: priority_label.name
}
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq('New Label')
- expect(json_response['color']).to eq(label1.color)
- end
- it 'returns 200 if colors is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FFFFFF'
- }
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['color']).to eq('#FFFFFF')
+ expect(response).to have_gitlab_http_status(400)
end
- it 'returns 200 if description is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'bug',
- description: 'test'
- }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['description']).to eq('test')
- expect(json_response['priority']).to eq(3)
+ it_behaves_like '412 response' do
+ let(:request) { api("/projects/#{project.id}/labels", user) }
+ let(:params) { { name: 'label1' } }
end
+ end
- it 'returns 200 if priority is changed' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'bug',
- priority: 10
- }
-
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to eq(10)
+ describe 'PUT /projects/:id/labels' do
+ context 'when using name' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { name: 'label1' } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if a priority is added' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 3
- }
-
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(label1.name)
- expect(json_response['priority']).to eq(3)
+ context 'when using label_id' do
+ it_behaves_like 'label update API' do
+ let(:spec_params) { { label_id: label1.id } }
+ let(:expected_response_label_id) { label1.id }
+ end
end
- it 'returns 200 if the priority is removed' do
+ it 'returns 404 if label does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: priority_label.name,
- priority: nil
+ name: 'label2',
+ new_name: 'label3'
}
- expect(response.status).to eq(200)
- expect(json_response['name']).to eq(priority_label.name)
- expect(json_response['priority']).to be_nil
+ expect(response).to have_gitlab_http_status(404)
end
- it 'returns 404 if label does not exist' do
+ it 'returns 404 if label by id does not exist' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'label2',
+ label_id: 0,
new_name: 'label3'
}
+
expect(response).to have_gitlab_http_status(404)
end
- it 'returns 400 if no label name given' do
+ it 'returns 400 if no label name and id is given' do
put api("/projects/#{project.id}/labels", user), params: { new_name: 'label2' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('name is missing')
- end
-
- it 'returns 400 if no new parameters given' do
- put api("/projects/#{project.id}/labels", user), params: { name: 'label1' }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['error']).to eq('new_name, color, description, priority are missing, '\
- 'at least one parameter must be provided')
- end
- it 'returns 400 for invalid name' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- new_name: ',',
- color: '#FFFFFF'
- }
expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['title']).to eq(['is invalid'])
+ expect(json_response['error']).to eq('label_id, name are missing, exactly one parameter must be provided')
end
- it 'returns 400 when color code is too short' do
+ it 'fails if label_id and name are given in params' do
put api("/projects/#{project.id}/labels", user),
params: {
- name: 'label1',
- color: '#FF'
+ label_id: label1.id,
+ name: priority_label.name,
+ new_name: 'New Label'
}
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
- end
-
- it 'returns 400 for too long color code' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- color: '#FFAAFFFF'
- }
- expect(response).to have_gitlab_http_status(400)
- expect(json_response['message']['color']).to eq(['must be a valid color code'])
- end
-
- it 'returns 400 for invalid priority' do
- put api("/projects/#{project.id}/labels", user),
- params: {
- name: 'label1',
- priority: 'foo'
- }
expect(response).to have_gitlab_http_status(400)
end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index 424f0a82e43..6c1e30791d2 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -9,6 +9,13 @@ describe API::Notes do
project.add_reporter(user)
end
+ context 'when there are cross-reference system notes' do
+ let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/notes" }
+ let(:notes_in_response) { json_response }
+
+ it_behaves_like 'with cross-reference system notes'
+ end
+
context "when noteable is an Issue" do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:note, noteable: issue, project: project, author: user) }
diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb
index 35b3dd219f7..174b3214d13 100644
--- a/spec/requests/api/pipelines_spec.rb
+++ b/spec/requests/api/pipelines_spec.rb
@@ -17,6 +17,8 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines ' do
+ it_behaves_like 'pipelines visibility table'
+
context 'authorized user' do
it 'returns project pipelines' do
get api("/projects/#{project.id}/pipelines", user)
@@ -401,6 +403,15 @@ describe API::Pipelines do
end
describe 'GET /projects/:id/pipelines/:pipeline_id' do
+ it_behaves_like 'pipelines visibility table' do
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines/#{pipeline.id}"
+ end
+
+ let(:api_response) { response_status == 200 ? response : json_response }
+ let(:response_200) { match_response_schema('public_api/v4/pipeline/detail') }
+ end
+
context 'authorized user' do
it 'exposes known attributes' do
get api("/projects/#{project.id}/pipelines/#{pipeline.id}", user)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 1d7ca85cdd2..5465fe0c366 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1494,6 +1494,17 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(404)
end
+
+ it 'filters out users listed in skip_users' do
+ other_user = create(:user)
+ project.team.add_developer(other_user)
+
+ get api("/projects/#{project.id}/users?skip_users=#{user.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(1)
+ expect(json_response[0]['id']).to eq(other_user.id)
+ end
end
end
diff --git a/spec/requests/jwt_controller_spec.rb b/spec/requests/jwt_controller_spec.rb
index bba473f1c20..8b2c698fee1 100644
--- a/spec/requests/jwt_controller_spec.rb
+++ b/spec/requests/jwt_controller_spec.rb
@@ -108,6 +108,14 @@ describe JwtController do
end
end
end
+
+ it 'does not cause session based checks to be activated' do
+ expect(Gitlab::Session).not_to receive(:with_session)
+
+ get '/jwt/auth', params: parameters, headers: headers
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
context 'using invalid login' 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/rubocop/cop/migration/add_limit_to_string_columns_spec.rb b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
new file mode 100644
index 00000000000..97a3ae8f2bc
--- /dev/null
+++ b/spec/rubocop/cop/migration/add_limit_to_string_columns_spec.rb
@@ -0,0 +1,268 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/migration/add_limit_to_string_columns'
+
+describe RuboCop::Cop::Migration::AddLimitToStringColumns do
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'in migration' do
+ before do
+ allow(cop).to receive(:in_migration?).and_return(true)
+
+ inspect_source(migration)
+ end
+
+ context 'when creating a table' do
+ context 'with string columns and limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false, limit: 255
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, limit: 255, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns and no limit' do
+ let(:migration) do
+ %q(
+ class CreateUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :users do |t|
+ t.string :username, null: false
+ t.timestamps_with_timezone null: true
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class CreateMilestoneReleases < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :milestone_releases do |t|
+ t.integer :milestone_id
+ t.integer :release_id
+ end
+ end
+ end
+ )
+ end
+
+ it 'register no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'when adding columns' do
+ context 'with string columns with limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+
+ context 'with limit in a different position' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string, limit: 255, default: 'example@email.com'
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with string columns with no limit' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :email, :string
+ end
+ end
+ )
+ end
+
+ it 'registers offense' do
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.first.message)
+ .to eq('String columns should have a limit constraint. 255 is suggested')
+ end
+ end
+
+ context 'with no string columns' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :users, :active, :boolean, default: false
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'with add_column_with_default' do
+ context 'with a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, limit: 2, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+
+ context 'without a limit' do
+ let(:migration) do
+ %q(
+ class AddRuleTypeToApprovalMergeRequestRules < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_with_default(:approval_merge_request_rules, :rule_type, :string, default: 1)
+ end
+ end
+ )
+ end
+
+ it 'registers an offense' do
+ expect(cop.offenses.size).to eq(1)
+ end
+ end
+ end
+
+ context 'with methods' do
+ let(:migration) do
+ %q(
+ class AddEmailToUsers < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column_if_table_not_exists :users, :first_name, :string, limit: 255
+ search_namespace(user_name)
+ end
+
+ def add_column_if_not_exists(table, name, *args)
+ add_column(table, name, *args) unless column_exists?(table, name)
+ end
+
+ def search_namespace(username)
+ Uniquify.new.string(username) do |str|
+ query = "SELECT id FROM namespaces WHERE parent_id IS NULL AND path='#{str}' LIMIT 1"
+ connection.exec_query(query)
+ end
+ end
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+ end
+
+ context 'outside of migrations' do
+ let(:active_record_model) do
+ %q(
+ class User < ApplicationRecord
+ end
+ )
+ end
+
+ it 'registers no offense' do
+ inspect_source(active_record_model)
+
+ expect(cop.offenses.size).to eq(0)
+ end
+ end
+end
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index c0ea2b3c389..1b19eac9a97 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -32,8 +32,8 @@ describe DeploymentEntity do
expect(subject).to include(:created_at)
end
- it 'exposes finished_at' do
- expect(subject).to include(:finished_at)
+ it 'exposes deployed_at' do
+ expect(subject).to include(:deployed_at)
end
context 'when the pipeline has another manual action' do
diff --git a/spec/serializers/merge_request_sidebar_basic_entity_spec.rb b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
new file mode 100644
index 00000000000..b364b1a3306
--- /dev/null
+++ b/spec/serializers/merge_request_sidebar_basic_entity_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe MergeRequestSidebarBasicEntity do
+ let(:project) { create :project, :repository }
+ let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
+ let(:user) { create(:user) }
+
+ let(:request) { double('request', current_user: user, project: project) }
+
+ let(:entity) { described_class.new(merge_request, request: request).as_json }
+
+ describe '#current_user' do
+ it 'contains attributes related to the current user' do
+ expect(entity[:current_user].keys).to contain_exactly(
+ :id, :name, :username, :state, :avatar_url, :web_url, :todo,
+ :can_edit, :can_move, :can_admin_label, :can_merge
+ )
+ end
+ end
+end
diff --git a/spec/services/award_emojis/add_service_spec.rb b/spec/services/award_emojis/add_service_spec.rb
new file mode 100644
index 00000000000..037db39ba80
--- /dev/null
+++ b/spec/services/award_emojis/add_service_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::AddService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:awardable) { create(:note, project: project) }
+ let(:name) { 'thumbsup' }
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ context 'when user is not authorized' do
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error state' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:forbidden)
+ end
+ end
+
+ context 'when user is authorized' do
+ before do
+ project.add_developer(user)
+ end
+
+ it 'creates an award emoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the award emoji' do
+ result = service.execute
+
+ expect(result[:award]).to be_kind_of(AwardEmoji)
+ end
+
+ it 'return a success status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'sets the correct properties on the award emoji' do
+ award = service.execute[:award]
+
+ expect(award.name).to eq(name)
+ expect(award.user).to eq(user)
+ end
+
+ describe 'marking Todos as done' do
+ subject { service.execute }
+
+ include_examples 'creating award emojis marks Todos as done'
+ end
+
+ context 'when the awardable cannot have emoji awarded to it' do
+ before do
+ expect(awardable).to receive(:emoji_awardable?).and_return(false)
+ end
+
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:unprocessable_entity)
+ end
+ end
+
+ context 'when the awardable is invalid' do
+ before do
+ expect_next_instance_of(AwardEmoji) do |award|
+ expect(award).to receive(:valid?).and_return(false)
+ expect(award).to receive_message_chain(:errors, :full_messages).and_return(['Error 1', 'Error 2'])
+ end
+ end
+
+ it 'does not add an emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ end
+
+ it 'returns an error message' do
+ result = service.execute
+
+ expect(result[:message]).to eq('Error 1 and Error 2')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/awarded_emoji_finder_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
index d4479df7418..a0dea31b403 100644
--- a/spec/finders/awarded_emoji_finder_spec.rb
+++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe AwardedEmojiFinder do
+describe AwardEmojis::CollectUserEmojiService do
describe '#execute' do
it 'returns an Array containing the awarded emoji names' do
user = create(:user)
diff --git a/spec/services/award_emojis/destroy_service_spec.rb b/spec/services/award_emojis/destroy_service_spec.rb
new file mode 100644
index 00000000000..c4a7d5ec20e
--- /dev/null
+++ b/spec/services/award_emojis/destroy_service_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::DestroyService do
+ set(:user) { create(:user) }
+ set(:awardable) { create(:note) }
+ set(:project) { awardable.project }
+ let(:name) { 'thumbsup' }
+ let!(:award_from_other_user) do
+ create(:award_emoji, name: name, awardable: awardable, user: create(:user))
+ end
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ shared_examples_for 'a service that does not authorize the user' do |error:|
+ it 'does not remove the emoji' do
+ expect { service.execute }.not_to change { AwardEmoji.count }
+ end
+
+ it 'returns an error state' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:error)
+ expect(result[:http_status]).to eq(:forbidden)
+ end
+
+ it 'returns a nil award' do
+ result = service.execute
+
+ expect(result).to have_key(:award)
+ expect(result[:award]).to be_nil
+ end
+
+ it 'returns the error' do
+ result = service.execute
+
+ expect(result[:message]).to eq(error)
+ expect(result[:errors]).to eq([error])
+ end
+ end
+
+ context 'when user is not authorized' do
+ it_behaves_like 'a service that does not authorize the user',
+ error: 'User cannot destroy emoji on the awardable'
+ end
+
+ context 'when the user is authorized' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when user has not awarded an emoji to the awardable' do
+ let!(:award_from_user) { create(:award_emoji, name: name, user: user) }
+
+ it_behaves_like 'a service that does not authorize the user',
+ error: 'User has not awarded emoji of type thumbsup on the awardable'
+ end
+
+ context 'when user has awarded an emoji to the awardable' do
+ let!(:award_from_user) { create(:award_emoji, name: name, awardable: awardable, user: user) }
+
+ it 'removes the emoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns a success status' do
+ result = service.execute
+
+ expect(result[:status]).to eq(:success)
+ end
+
+ it 'returns no errors' do
+ result = service.execute
+
+ expect(result).not_to have_key(:error)
+ expect(result).not_to have_key(:errors)
+ end
+
+ it 'returns the destroyed award' do
+ result = service.execute
+
+ expect(result[:award]).to eq(award_from_user)
+ expect(result[:award]).to be_destroyed
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/award_emojis/toggle_service_spec.rb b/spec/services/award_emojis/toggle_service_spec.rb
new file mode 100644
index 00000000000..972a1d5fc06
--- /dev/null
+++ b/spec/services/award_emojis/toggle_service_spec.rb
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::ToggleService do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ set(:awardable) { create(:note, project: project) }
+ let(:name) { 'thumbsup' }
+ subject(:service) { described_class.new(awardable, name, user) }
+
+ describe '#execute' do
+ context 'when user has awarded an emoji' do
+ let!(:award_from_other_user) { create(:award_emoji, name: name, awardable: awardable, user: create(:user)) }
+ let!(:award) { create(:award_emoji, name: name, awardable: awardable, user: user) }
+
+ it 'calls AwardEmojis::DestroyService' do
+ expect(AwardEmojis::AddService).not_to receive(:new)
+
+ expect_next_instance_of(AwardEmojis::DestroyService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ service.execute
+ end
+
+ it 'destroys an AwardEmoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(-1)
+ end
+
+ it 'returns the result of DestroyService#execute' do
+ mock_result = double(foo: true)
+
+ expect_next_instance_of(AwardEmojis::DestroyService) do |service|
+ expect(service).to receive(:execute).and_return(mock_result)
+ end
+
+ result = service.execute
+
+ expect(result).to eq(mock_result)
+ end
+ end
+
+ context 'when user has not awarded an emoji' do
+ it 'calls AwardEmojis::AddService' do
+ expect_next_instance_of(AwardEmojis::AddService) do |service|
+ expect(service).to receive(:execute)
+ end
+
+ expect(AwardEmojis::DestroyService).not_to receive(:new)
+
+ service.execute
+ end
+
+ it 'creates an AwardEmoji' do
+ expect { service.execute }.to change { AwardEmoji.count }.by(1)
+ end
+
+ it 'returns the result of AddService#execute' do
+ mock_result = double(foo: true)
+
+ expect_next_instance_of(AwardEmojis::AddService) do |service|
+ expect(service).to receive(:execute).and_return(mock_result)
+ end
+
+ result = service.execute
+
+ expect(result).to eq(mock_result)
+ end
+ end
+ end
+end
diff --git a/spec/services/ci/update_build_queue_service_spec.rb b/spec/services/ci/update_build_queue_service_spec.rb
index 4b869385128..522dd1ba1c2 100644
--- a/spec/services/ci/update_build_queue_service_spec.rb
+++ b/spec/services/ci/update_build_queue_service_spec.rb
@@ -7,84 +7,108 @@ describe Ci::UpdateBuildQueueService do
let(:build) { create(:ci_build, pipeline: pipeline) }
let(:pipeline) { create(:ci_pipeline, project: project) }
- context 'when updating specific runners' do
- let(:runner) { create(:ci_runner, :project, projects: [project]) }
-
- context 'when there is a runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
- end
+ shared_examples 'refreshes runner' do
+ it 'ticks runner queue value' do
+ expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
end
+ end
- context 'when there is no runner that can pick build' do
- let(:another_project) { create(:project) }
- let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
-
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ shared_examples 'does not refresh runner' do
+ it 'ticks runner queue value' do
+ expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
end
end
- context 'when updating shared runners' do
- let(:runner) { create(:ci_runner, :instance) }
-
- context 'when there is no runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
+ shared_examples 'matching build' do
+ context 'when there is a online runner that can pick build' do
+ before do
+ runner.update!(contacted_at: 30.minutes.ago)
end
+
+ it_behaves_like 'refreshes runner'
end
+ end
+ shared_examples 'mismatching tags' do
context 'when there is no runner that can pick build due to tag mismatch' do
before do
build.tag_list = [:docker]
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
+ end
- context 'when there is no runner that can pick build due to being disabled on project' do
+ shared_examples 'recent runner queue' do
+ context 'when there is runner with expired cache' do
before do
- build.project.shared_runners_enabled = false
+ runner.update!(contacted_at: Ci::Runner.recent_queue_deadline)
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
+ context 'when ci_update_queues_for_online_runners is enabled' do
+ before do
+ stub_feature_flags(ci_update_queues_for_online_runners: true)
+ end
+
+ it_behaves_like 'does not refresh runner'
+ end
+
+ context 'when ci_update_queues_for_online_runners is disabled' do
+ before do
+ stub_feature_flags(ci_update_queues_for_online_runners: false)
+ end
+
+ it_behaves_like 'refreshes runner'
end
end
end
- context 'when updating group runners' do
- let(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
- let(:runner) { create(:ci_runner, :group, groups: [group]) }
+ context 'when updating specific runners' do
+ let(:runner) { create(:ci_runner, :project, projects: [project]) }
- context 'when there is a runner that can pick build' do
- it 'ticks runner queue value' do
- expect { subject.execute(build) }.to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
+
+ context 'when the runner is assigned to another project' do
+ let(:another_project) { create(:project) }
+ let(:runner) { create(:ci_runner, :project, projects: [another_project]) }
+
+ it_behaves_like 'does not refresh runner'
end
+ end
- context 'when there is no runner that can pick build due to tag mismatch' do
+ context 'when updating shared runners' do
+ let(:runner) { create(:ci_runner, :instance) }
+
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
+
+ context 'when there is no runner that can pick build due to being disabled on project' do
before do
- build.tag_list = [:docker]
+ build.project.shared_runners_enabled = false
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
+ end
+
+ context 'when updating group runners' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
+
+ it_behaves_like 'matching build'
+ it_behaves_like 'mismatching tags'
+ it_behaves_like 'recent runner queue'
context 'when there is no runner that can pick build due to being disabled on project' do
before do
build.project.group_runners_enabled = false
end
- it 'does not tick runner queue value' do
- expect { subject.execute(build) }.not_to change { runner.ensure_runner_queue_value }
- end
+ it_behaves_like 'does not refresh runner'
end
end
end
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/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index b0b74407812..d4fa62fa85d 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -78,6 +78,7 @@ describe Projects::CreateService, '#execute' do
expect(project).to be_valid
expect(project.owner).to eq(group)
expect(project.namespace).to eq(group)
+ expect(project.team.owners).to include(user)
expect(user.authorized_projects).to include(project)
end
end
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/capybara.rb b/spec/support/capybara.rb
index 8accc5c1df5..4c688094352 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -47,6 +47,9 @@ Capybara.register_driver :chrome do |app|
# Explicitly set user-data-dir to prevent crashes. See https://gitlab.com/gitlab-org/gitlab-ce/issues/58882#note_179811508
options.add_argument("user-data-dir=/tmp/chrome") if ENV['CI'] || ENV['CI_SERVER']
+ # Chrome 75 defaults to W3C mode which doesn't allow console log access
+ options.add_option(:w3c, false)
+
Capybara::Selenium::Driver.new(
app,
browser: :chrome,
diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb
index 6099f87323f..2e9932f2e8a 100644
--- a/spec/support/helpers/drag_to_helper.rb
+++ b/spec/support/helpers/drag_to_helper.rb
@@ -1,8 +1,23 @@
# frozen_string_literal: true
module DragTo
- def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000)
- evaluate_script("simulateDrag({scrollable: $('#{scrollable}').get(0), duration: #{duration}, from: {el: $('#{selector}').eq(#{list_from_index}).get(0), index: #{from_index}}, to: {el: $('#{selector}').eq(#{list_to_index}).get(0), index: #{to_index}}});")
+ def drag_to(list_from_index: 0, from_index: 0, to_index: 0, list_to_index: 0, selector: '', scrollable: 'body', duration: 1000, perform_drop: true)
+ js = <<~JS
+ simulateDrag({
+ scrollable: document.querySelector('#{scrollable}'),
+ duration: #{duration},
+ from: {
+ el: document.querySelectorAll('#{selector}')[#{list_from_index}],
+ index: #{from_index}
+ },
+ to: {
+ el: document.querySelectorAll('#{selector}')[#{list_to_index}],
+ index: #{to_index}
+ },
+ performDrop: #{perform_drop}
+ });
+ JS
+ evaluate_script(js)
Timeout.timeout(Capybara.default_max_wait_time) do
loop while drag_active?
diff --git a/spec/support/helpers/smime_helper.rb b/spec/support/helpers/smime_helper.rb
new file mode 100644
index 00000000000..656b3e196ba
--- /dev/null
+++ b/spec/support/helpers/smime_helper.rb
@@ -0,0 +1,55 @@
+module SmimeHelper
+ include OpenSSL
+
+ INFINITE_EXPIRY = 1000.years
+ SHORT_EXPIRY = 30.minutes
+
+ def generate_root
+ issue(signed_by: nil, expires_in: INFINITE_EXPIRY, certificate_authority: true)
+ end
+
+ def generate_cert(root_ca:, expires_in: SHORT_EXPIRY)
+ issue(signed_by: root_ca, expires_in: expires_in, certificate_authority: false)
+ end
+
+ # returns a hash { key:, cert: } containing a generated key, cert pair
+ def issue(email_address: 'test@example.com', signed_by:, expires_in:, certificate_authority:)
+ key = OpenSSL::PKey::RSA.new(4096)
+ public_key = key.public_key
+
+ subject = if certificate_authority
+ X509::Name.parse("/CN=EU")
+ else
+ X509::Name.parse("/CN=#{email_address}")
+ end
+
+ cert = X509::Certificate.new
+ cert.subject = subject
+
+ cert.issuer = signed_by&.fetch(:cert, nil)&.subject || subject
+
+ cert.not_before = Time.now
+ cert.not_after = expires_in.from_now
+ cert.public_key = public_key
+ cert.serial = 0x0
+ cert.version = 2
+
+ extension_factory = X509::ExtensionFactory.new
+ if certificate_authority
+ extension_factory.subject_certificate = cert
+ extension_factory.issuer_certificate = cert
+ cert.add_extension(extension_factory.create_extension('subjectKeyIdentifier', 'hash'))
+ cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:TRUE', true))
+ cert.add_extension(extension_factory.create_extension('keyUsage', 'cRLSign,keyCertSign', true))
+ else
+ cert.add_extension(extension_factory.create_extension('subjectAltName', "email:#{email_address}", false))
+ cert.add_extension(extension_factory.create_extension('basicConstraints', 'CA:FALSE', true))
+ cert.add_extension(extension_factory.create_extension('keyUsage', 'digitalSignature,keyEncipherment', true))
+ cert.add_extension(extension_factory.create_extension('extendedKeyUsage', 'clientAuth,emailProtection', false))
+ end
+
+ cert.sign(signed_by&.fetch(:key, nil) || key, Digest::SHA256.new)
+
+ { key: key, cert: cert }
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c8b2bf040e6..dec7898d8d2 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -30,6 +30,10 @@ module StubConfiguration
allow(Gitlab.config.gitlab).to receive_messages(to_settings(messages))
end
+ def stub_config(messages)
+ allow(Gitlab.config).to receive_messages(to_settings(messages))
+ end
+
def stub_default_url_options(host: "localhost", protocol: "http")
url_options = { host: host, protocol: protocol }
allow(Rails.application.routes).to receive(:default_url_options).and_return(url_options)
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_contexts/policies/group_policy_shared_context.rb b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
index fd24c443288..b89723b1e1a 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -31,7 +31,8 @@ RSpec.shared_context 'GroupPolicy context' do
:admin_group_member,
:change_visibility_level,
:set_note_created_at,
- :create_subgroup
+ :create_subgroup,
+ :read_statistics
].compact
end
diff --git a/spec/support/shared_examples/award_emoji_todo_shared_examples.rb b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb
new file mode 100644
index 00000000000..88ad37d232f
--- /dev/null
+++ b/spec/support/shared_examples/award_emoji_todo_shared_examples.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+# Shared examples to that test code that creates AwardEmoji also mark Todos
+# as done.
+#
+# The examples expect these to be defined in the calling spec:
+# - `subject` the callable code that executes the creation of an AwardEmoji
+# - `user`
+# - `project`
+RSpec.shared_examples 'creating award emojis marks Todos as done' do
+ using RSpec::Parameterized::TableSyntax
+
+ before do
+ project.add_developer(user)
+ end
+
+ where(:type, :expectation) do
+ :issue | true
+ :merge_request | true
+ :project_snippet | false
+ end
+
+ with_them do
+ let(:project) { awardable.project }
+ let(:awardable) { create(type) }
+ let!(:todo) { create(:todo, target: awardable, project: project, user: user) }
+
+ it do
+ subject
+
+ expect(todo.reload.done?).to eq(expectation)
+ end
+ end
+
+ # Notes have more complicated rules than other Todoables
+ describe 'for notes' do
+ let!(:todo) { create(:todo, target: awardable.noteable, project: project, user: user) }
+
+ context 'regular Notes' do
+ let(:awardable) { create(:note, project: project) }
+
+ it 'marks the Todo as done' do
+ subject
+
+ expect(todo.reload.done?).to eq(true)
+ end
+ end
+
+ context 'PersonalSnippet Notes' do
+ let(:awardable) { create(:note, noteable: create(:personal_snippet, author: user)) }
+
+ it 'does not mark the Todo as done' do
+ subject
+
+ expect(todo.reload.done?).to eq(false)
+ end
+ end
+ 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/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
index 1cd14ea2251..d89eded6e69 100644
--- a/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb
@@ -2,14 +2,14 @@
shared_examples 'set sort order from user preference' do
describe '#set_sort_order_from_user_preference' do
- # There is no issuable_sorting_field defined in any CE controllers yet,
+ # There is no sorting_field defined in any CE controllers yet,
# however any other field present in user_preferences table can be used for testing.
context 'when database is in read-only mode' do
it 'does not update user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(true)
- expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
+ expect_any_instance_of(UserPreference).not_to receive(:update).with({ controller.send(:sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end
@@ -19,7 +19,7 @@ shared_examples 'set sort order from user preference' do
it 'updates user preference' do
allow(Gitlab::Database).to receive(:read_only?).and_return(false)
- expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:issuable_sorting_field) => sorting_param })
+ expect_any_instance_of(UserPreference).to receive(:update).with({ controller.send(:sorting_field) => sorting_param })
get :index, params: { namespace_id: project.namespace, project_id: project, sort: sorting_param }
end
diff --git a/spec/support/shared_examples/cycle_analytics_stage_examples.rb b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
new file mode 100644
index 00000000000..151f5325e84
--- /dev/null
+++ b/spec/support/shared_examples/cycle_analytics_stage_examples.rb
@@ -0,0 +1,74 @@
+# frozen_string_literal: true
+
+shared_examples_for 'cycle analytics stage' do
+ let(:valid_params) do
+ {
+ name: 'My Stage',
+ parent: parent,
+ start_event_identifier: :merge_request_created,
+ end_event_identifier: :merge_request_merged
+ }
+ end
+
+ describe 'validation' do
+ it 'is valid' do
+ expect(described_class.new(valid_params)).to be_valid
+ end
+
+ it 'validates presence of parent' do
+ stage = described_class.new(valid_params.except(:parent))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[parent_name]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of start_event_identifier' do
+ stage = described_class.new(valid_params.except(:start_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:start_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'validates presence of end_event_identifier' do
+ stage = described_class.new(valid_params.except(:end_event_identifier))
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event_identifier]).to eq([{ error: :blank }])
+ end
+
+ it 'is invalid when end_event is not allowed for the given start_event' do
+ invalid_params = valid_params.merge(
+ start_event_identifier: :merge_request_merged,
+ end_event_identifier: :merge_request_created
+ )
+ stage = described_class.new(invalid_params)
+
+ expect(stage).not_to be_valid
+ expect(stage.errors.details[:end_event]).to eq([{ error: :not_allowed_for_the_given_start_event }])
+ end
+ end
+
+ describe '#subject_model' do
+ it 'infers the model from the start event' do
+ stage = described_class.new(valid_params)
+
+ expect(stage.subject_model).to eq(MergeRequest)
+ end
+ end
+
+ describe '#start_event' do
+ it 'builds start_event object based on start_event_identifier' do
+ stage = described_class.new(start_event_identifier: 'merge_request_created')
+
+ expect(stage.start_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestCreated)
+ end
+ end
+
+ describe '#end_event' do
+ it 'builds end_event object based on end_event_identifier' do
+ stage = described_class.new(end_event_identifier: 'merge_request_merged')
+
+ expect(stage.end_event).to be_a_kind_of(Gitlab::Analytics::CycleAnalytics::StageEvents::MergeRequestMerged)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index fc72287f265..a36bc2dc9b5 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,5 +1,59 @@
# frozen_string_literal: true
+shared_examples 'with cross-reference system notes' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:new_merge_request) { create(:merge_request) }
+ let(:commit) { new_merge_request.project.commit }
+ let!(:note) { create(:system_note, noteable: merge_request, project: project, note: cross_reference) }
+ let!(:note_metadata) { create(:system_note_metadata, note: note, action: 'cross_reference') }
+ let(:cross_reference) { "test commit #{commit.to_reference(project)}" }
+ let(:pat) { create(:personal_access_token, user: user) }
+
+ before do
+ project.add_developer(user)
+ new_merge_request.project.add_developer(user)
+
+ hidden_merge_request = create(:merge_request)
+ new_cross_reference = "test commit #{hidden_merge_request.project.commit}"
+ new_note = create(:system_note, noteable: merge_request, project: project, note: new_cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+ end
+
+ it 'returns only the note that the user should see' do
+ get api(url, user, personal_access_token: pat)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.count).to eq(1)
+ expect(notes_in_response.count).to eq(1)
+
+ parsed_note = notes_in_response.first
+ expect(parsed_note['id']).to eq(note.id)
+ expect(parsed_note['body']).to eq(cross_reference)
+ expect(parsed_note['system']).to be true
+ end
+
+ it 'avoids Git calls and N+1 SQL queries', :request_store do
+ expect_any_instance_of(Repository).not_to receive(:find_commit).with(commit.id)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api(url, user, personal_access_token: pat)
+ end
+
+ expect(response).to have_gitlab_http_status(200)
+
+ RequestStore.clear!
+
+ new_note = create(:system_note, noteable: merge_request, project: project, note: cross_reference)
+ create(:system_note_metadata, note: new_note, action: 'cross_reference')
+
+ RequestStore.clear!
+
+ expect { get api(url, user, personal_access_token: pat) }.not_to exceed_query_limit(control)
+ expect(response).to have_gitlab_http_status(200)
+ end
+end
+
shared_examples 'discussions API' do |parent_type, noteable_type, id_name, can_reply_to_individual_notes: false|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "returns an array of discussions" do
diff --git a/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
new file mode 100644
index 00000000000..dfd07176b1c
--- /dev/null
+++ b/spec/support/shared_examples/requests/api/pipelines/visibility_table_examples.rb
@@ -0,0 +1,235 @@
+# frozen_string_literal: true
+
+shared_examples 'pipelines visibility table' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:ci_user) { create(:user) }
+ let(:api_user) { user_role && ci_user }
+
+ let(:pipelines_api_path) do
+ "/projects/#{project.id}/pipelines"
+ end
+
+ let(:response_200) do
+ a_collection_containing_exactly(
+ a_hash_including('sha', 'ref', 'status', 'web_url', 'id' => pipeline.id)
+ )
+ end
+
+ let(:response_40x) do
+ a_hash_including('message')
+ end
+
+ let(:expected_response) do
+ if response_status == 200
+ response_200
+ else
+ response_40x
+ end
+ end
+
+ let(:api_response) { json_response }
+
+ let(:visibility_levels) do
+ {
+ private: Gitlab::VisibilityLevel::PRIVATE,
+ internal: Gitlab::VisibilityLevel::INTERNAL,
+ public: Gitlab::VisibilityLevel::PUBLIC
+ }
+ end
+
+ let(:builds_access_levels) do
+ {
+ enabled: ProjectFeature::ENABLED,
+ private: ProjectFeature::PRIVATE
+ }
+ end
+
+ let(:project_attributes) do
+ {
+ visibility_level: visibility_levels[visibility_level],
+ public_builds: public_builds
+ }
+ end
+
+ let(:project_feature_attributes) do
+ {
+ builds_access_level: builds_access_levels[builds_access_level]
+ }
+ end
+
+ where(:visibility_level, :builds_access_level, :public_builds, :is_admin, :user_role, :response_status) do
+ :private | :enabled | true | true | :non_member | 200
+ :private | :enabled | true | true | :guest | 200
+ :private | :enabled | true | true | :reporter | 200
+ :private | :enabled | true | true | :developer | 200
+ :private | :enabled | true | true | :maintainer | 200
+
+ :private | :enabled | true | false | nil | 404
+ :private | :enabled | true | false | :non_member | 404
+ :private | :enabled | true | false | :guest | 200
+ :private | :enabled | true | false | :reporter | 200
+ :private | :enabled | true | false | :developer | 200
+ :private | :enabled | true | false | :maintainer | 200
+
+ :private | :enabled | false | true | :non_member | 200
+ :private | :enabled | false | true | :guest | 200
+ :private | :enabled | false | true | :reporter | 200
+ :private | :enabled | false | true | :developer | 200
+ :private | :enabled | false | true | :maintainer | 200
+
+ :private | :enabled | false | false | nil | 404
+ :private | :enabled | false | false | :non_member | 404
+ :private | :enabled | false | false | :guest | 403
+ :private | :enabled | false | false | :reporter | 200
+ :private | :enabled | false | false | :developer | 200
+ :private | :enabled | false | false | :maintainer | 200
+
+ :private | :private | true | true | :non_member | 200
+ :private | :private | true | true | :guest | 200
+ :private | :private | true | true | :reporter | 200
+ :private | :private | true | true | :developer | 200
+ :private | :private | true | true | :maintainer | 200
+
+ :private | :private | true | false | nil | 404
+ :private | :private | true | false | :non_member | 404
+ :private | :private | true | false | :guest | 200
+ :private | :private | true | false | :reporter | 200
+ :private | :private | true | false | :developer | 200
+ :private | :private | true | false | :maintainer | 200
+
+ :private | :private | false | true | :non_member | 200
+ :private | :private | false | true | :guest | 200
+ :private | :private | false | true | :reporter | 200
+ :private | :private | false | true | :developer | 200
+ :private | :private | false | true | :maintainer | 200
+
+ :private | :private | false | false | nil | 404
+ :private | :private | false | false | :non_member | 404
+ :private | :private | false | false | :guest | 403
+ :private | :private | false | false | :reporter | 200
+ :private | :private | false | false | :developer | 200
+ :private | :private | false | false | :maintainer | 200
+
+ :internal | :enabled | true | true | :non_member | 200
+ :internal | :enabled | true | true | :guest | 200
+ :internal | :enabled | true | true | :reporter | 200
+ :internal | :enabled | true | true | :developer | 200
+ :internal | :enabled | true | true | :maintainer | 200
+
+ :internal | :enabled | true | false | nil | 404
+ :internal | :enabled | true | false | :non_member | 200
+ :internal | :enabled | true | false | :guest | 200
+ :internal | :enabled | true | false | :reporter | 200
+ :internal | :enabled | true | false | :developer | 200
+ :internal | :enabled | true | false | :maintainer | 200
+
+ :internal | :enabled | false | true | :non_member | 200
+ :internal | :enabled | false | true | :guest | 200
+ :internal | :enabled | false | true | :reporter | 200
+ :internal | :enabled | false | true | :developer | 200
+ :internal | :enabled | false | true | :maintainer | 200
+
+ :internal | :enabled | false | false | nil | 404
+ :internal | :enabled | false | false | :non_member | 403
+ :internal | :enabled | false | false | :guest | 403
+ :internal | :enabled | false | false | :reporter | 200
+ :internal | :enabled | false | false | :developer | 200
+ :internal | :enabled | false | false | :maintainer | 200
+
+ :internal | :private | true | true | :non_member | 200
+ :internal | :private | true | true | :guest | 200
+ :internal | :private | true | true | :reporter | 200
+ :internal | :private | true | true | :developer | 200
+ :internal | :private | true | true | :maintainer | 200
+
+ :internal | :private | true | false | nil | 404
+ :internal | :private | true | false | :non_member | 403
+ :internal | :private | true | false | :guest | 200
+ :internal | :private | true | false | :reporter | 200
+ :internal | :private | true | false | :developer | 200
+ :internal | :private | true | false | :maintainer | 200
+
+ :internal | :private | false | true | :non_member | 200
+ :internal | :private | false | true | :guest | 200
+ :internal | :private | false | true | :reporter | 200
+ :internal | :private | false | true | :developer | 200
+ :internal | :private | false | true | :maintainer | 200
+
+ :internal | :private | false | false | nil | 404
+ :internal | :private | false | false | :non_member | 403
+ :internal | :private | false | false | :guest | 403
+ :internal | :private | false | false | :reporter | 200
+ :internal | :private | false | false | :developer | 200
+ :internal | :private | false | false | :maintainer | 200
+
+ :public | :enabled | true | true | :non_member | 200
+ :public | :enabled | true | true | :guest | 200
+ :public | :enabled | true | true | :reporter | 200
+ :public | :enabled | true | true | :developer | 200
+ :public | :enabled | true | true | :maintainer | 200
+
+ :public | :enabled | true | false | nil | 200
+ :public | :enabled | true | false | :non_member | 200
+ :public | :enabled | true | false | :guest | 200
+ :public | :enabled | true | false | :reporter | 200
+ :public | :enabled | true | false | :developer | 200
+ :public | :enabled | true | false | :maintainer | 200
+
+ :public | :enabled | false | true | :non_member | 200
+ :public | :enabled | false | true | :guest | 200
+ :public | :enabled | false | true | :reporter | 200
+ :public | :enabled | false | true | :developer | 200
+ :public | :enabled | false | true | :maintainer | 200
+
+ :public | :enabled | false | false | nil | 403
+ :public | :enabled | false | false | :non_member | 403
+ :public | :enabled | false | false | :guest | 403
+ :public | :enabled | false | false | :reporter | 200
+ :public | :enabled | false | false | :developer | 200
+ :public | :enabled | false | false | :maintainer | 200
+
+ :public | :private | true | true | :non_member | 200
+ :public | :private | true | true | :guest | 200
+ :public | :private | true | true | :reporter | 200
+ :public | :private | true | true | :developer | 200
+ :public | :private | true | true | :maintainer | 200
+
+ :public | :private | true | false | nil | 403
+ :public | :private | true | false | :non_member | 403
+ :public | :private | true | false | :guest | 200
+ :public | :private | true | false | :reporter | 200
+ :public | :private | true | false | :developer | 200
+ :public | :private | true | false | :maintainer | 200
+
+ :public | :private | false | true | :non_member | 200
+ :public | :private | false | true | :guest | 200
+ :public | :private | false | true | :reporter | 200
+ :public | :private | false | true | :developer | 200
+ :public | :private | false | true | :maintainer | 200
+
+ :public | :private | false | false | nil | 403
+ :public | :private | false | false | :non_member | 403
+ :public | :private | false | false | :guest | 403
+ :public | :private | false | false | :reporter | 200
+ :public | :private | false | false | :developer | 200
+ :public | :private | false | false | :maintainer | 200
+ end
+
+ with_them do
+ before do
+ ci_user.update!(admin: is_admin) if user_role
+
+ project.update!(project_attributes)
+ project.project_feature.update!(project_feature_attributes)
+ project.add_role(ci_user, user_role) if user_role && user_role != :non_member
+
+ get api(pipelines_api_path, api_user)
+ end
+
+ it do
+ expect(response).to have_gitlab_http_status(response_status)
+ expect(api_response).to match(expected_response)
+ end
+ end
+end
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/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index 28381fdc3be..01232e2a58b 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -5,6 +5,8 @@ require 'spec_helper'
describe Ci::ArchiveTracesCronWorker do
subject { described_class.new.perform }
+ let(:finished_at) { 1.day.ago }
+
before do
stub_feature_flags(ci_enable_live_trace: true)
end
@@ -28,7 +30,7 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when a job succeeded' do
- let!(:build) { create(:ci_build, :success, :trace_live) }
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
it_behaves_like 'archives trace'
@@ -39,9 +41,15 @@ describe Ci::ArchiveTracesCronWorker do
subject
end
+ context 'when the job finished recently' do
+ let(:finished_at) { 1.hour.ago }
+
+ it_behaves_like 'does not archive trace'
+ end
+
context 'when a trace had already been archived' do
let!(:build) { create(:ci_build, :success, :trace_live, :trace_artifact) }
- let!(:build2) { create(:ci_build, :success, :trace_live) }
+ let!(:build2) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
it 'continues to archive live traces' do
subject
@@ -52,7 +60,7 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when an unexpected exception happened during archiving' do
- let!(:build) { create(:ci_build, :success, :trace_live) }
+ let!(:build) { create(:ci_build, :success, :trace_live, finished_at: finished_at) }
before do
allow(Gitlab::Sentry).to receive(:track_exception)
@@ -71,7 +79,7 @@ describe Ci::ArchiveTracesCronWorker do
end
context 'when a job was cancelled' do
- let!(:build) { create(:ci_build, :canceled, :trace_live) }
+ let!(:build) { create(:ci_build, :canceled, :trace_live, finished_at: finished_at) }
it_behaves_like 'archives trace'
end
diff --git a/yarn.lock b/yarn.lock
index a295039ec54..dd85729333a 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"
@@ -1003,10 +1003,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.68.0.tgz#d631bd84ea7907592240d8417e82ba66d6a54c0c"
integrity sha512-3JmIq0bHg4InjLooM+kQFPfg3d7B1Pye67pN9+12kZXIa0nRGuwKEq3WSbcS+ACdg5jcVPNPYqStItEO4teHdw==
-"@gitlab/ui@5.15.0":
- version "5.15.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.15.0.tgz#1ce92cfed77dcd63a90d751043b42b19e64431c9"
- integrity sha512-YrYgERcmTxC+oP4GaY7onqvYgvTsyGCiiegQbZbXdNRLGGAmvfxWPzQRz8Ci9yIYkLvi0X2AV7BT8RTVOPQgXg==
+"@gitlab/ui@5.18.0":
+ version "5.18.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.18.0.tgz#f7f93ad41673f5ae081cd2246dda5456d3c956a2"
+ integrity sha512-umB7JCKVDZOajed0N563JBYSpiyK+VDL2eCdkinW+twWhMct8onoW4CLTpkgBi6Z9Y1Bq47aqnFtGf+1IKNIGw==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
@@ -1021,6 +1021,11 @@
vue "^2.6.10"
vue-loader "^15.4.2"
+"@gitlab/visual-review-tools@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/visual-review-tools/-/visual-review-tools-1.0.0.tgz#6012e0a19797c1f5dad34ccf4dacdaf38e400a73"
+ integrity sha512-xMvz9IwrXisQ1MH+Tj6lfbQcQSiQy88nTPuQV6OTLBGuV+vIQeVwXeIkQeTKuSpd0GqZvigPdRqxyQCa3blpIg==
+
"@gitlab/vue-toasted@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@gitlab/vue-toasted/-/vue-toasted-1.2.1.tgz#f407b5aa710863e5b7f021f4a1f66160331ab263"