summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Watson <jwatson@gitlab.com>2019-08-21 10:43:30 -0400
committerJeremy Watson <jwatson@gitlab.com>2019-08-21 10:43:30 -0400
commitaec4ce4ac538992ae09c8a9c77be62a22ea239f1 (patch)
tree0ec040a1d020a0096f60f1363db7fd1751967e9c
parentad799726ae697d12664b8c3903e8297e7bfb4088 (diff)
parentef0f1509dd2a2a3ba5798362e2be21108b705a85 (diff)
downloadgitlab-ce-docs-group-managed-accounts.tar.gz
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into docs-group-managed-accountsdocs-group-managed-accounts
-rw-r--r--.dockerignore72
-rw-r--r--.eslintrc.yml6
-rw-r--r--.gitlab-ci.yml3
-rw-r--r--.gitlab/CODEOWNERS16
-rw-r--r--.gitlab/ci/docs.gitlab-ci.yml11
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml59
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml4
-rw-r--r--.gitlab/ci/memory.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml20
-rw-r--r--.gitlab/ci/rails.gitlab-ci.yml13
-rw-r--r--.gitlab/ci/reports.gitlab-ci.yml10
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml57
-rw-r--r--.gitlab/ci/test-metadata.gitlab-ci.yml3
-rw-r--r--.gitlab/ci/yaml.gitlab-ci.yml2
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md1
-rw-r--r--.gitlab/issue_templates/Feature proposal.md2
-rw-r--r--.gitlab/issue_templates/Security Release.md20
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md2
-rw-r--r--.gitlab/issue_templates/Test plan.md2
-rw-r--r--.gitlab/merge_request_templates/Database changes.md13
-rw-r--r--.haml-lint.yml4
-rw-r--r--.haml-lint_todo.yml1020
-rw-r--r--.mdlrc.style11
-rw-r--r--.prettierignore1
-rw-r--r--.rubocop.yml6
-rw-r--r--.rubocop_todo.yml19
-rw-r--r--CHANGELOG.md363
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--GITLAB_WORKHORSE_VERSION2
-rw-r--r--Gemfile39
-rw-r--r--Gemfile.lock87
-rw-r--r--LICENSE10
-rw-r--r--README.md23
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js1
-rw-r--r--app/assets/javascripts/badges/components/badge.vue2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_gfm.js2
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js1
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_metrics.js24
-rw-r--r--app/assets/javascripts/behaviors/preview_markdown.js4
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts.js4
-rw-r--r--app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js2
-rw-r--r--app/assets/javascripts/boards/components/board_blank_state.vue11
-rw-r--r--app/assets/javascripts/boards/components/board_form.vue219
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue1
-rw-r--r--app/assets/javascripts/boards/components/boards_selector.vue334
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue1
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue1
-rw-r--r--app/assets/javascripts/boards/components/project_select.vue8
-rw-r--r--app/assets/javascripts/boards/ee_functions.js7
-rw-r--r--app/assets/javascripts/boards/index.js19
-rw-r--r--app/assets/javascripts/boards/mount_multiple_boards_switcher.js37
-rw-r--r--app/assets/javascripts/boards/services/board_service.js5
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js62
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue1
-rw-r--r--app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue11
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js1
-rw-r--r--app/assets/javascripts/commits.js5
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-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.js26
-rw-r--r--app/assets/javascripts/cycle_analytics/cycle_analytics_service.js8
-rw-r--r--app/assets/javascripts/diffs/components/app.vue2
-rw-r--r--app/assets/javascripts/diffs/components/commit_item.vue2
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussion_reply.vue1
-rw-r--r--app/assets/javascripts/diffs/components/diff_expansion_cell.vue244
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue3
-rw-r--r--app/assets/javascripts/diffs/components/diff_gutter_avatars.vue5
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue106
-rw-r--r--app/assets/javascripts/diffs/components/hidden_files_warning.vue1
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_expansion_row.vue53
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_table_row.vue5
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_view.vue18
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_expansion_row.vue56
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue11
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_view.vue17
-rw-r--r--app/assets/javascripts/diffs/store/actions.js6
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js27
-rw-r--r--app/assets/javascripts/diffs/store/utils.js26
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue3
-rw-r--r--app/assets/javascripts/environments/components/stop_environment_modal.vue1
-rw-r--r--app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue2
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/pageInfo.fragment.graphql4
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue2
-rw-r--r--app/assets/javascripts/ide/components/branches/item.vue1
-rw-r--r--app/assets/javascripts/ide/components/branches/search_list.vue26
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/list_item.vue2
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/success_message.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_row_extra.vue1
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue1
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue28
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue21
-rw-r--r--app/assets/javascripts/ide/constants.js4
-rw-r--r--app/assets/javascripts/ide/services/index.js8
-rw-r--r--app/assets/javascripts/ide/stores/mutations/file.js12
-rw-r--r--app/assets/javascripts/ide/stores/utils.js10
-rw-r--r--app/assets/javascripts/issuable_suggestions/components/item.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/edit_actions.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/edited.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/fields/description_template.vue1
-rw-r--r--app/assets/javascripts/issue_show/components/form.vue5
-rw-r--r--app/assets/javascripts/jobs/components/commit_block.vue1
-rw-r--r--app/assets/javascripts/jobs/components/empty_state.vue41
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue13
-rw-r--r--app/assets/javascripts/jobs/components/job_log.vue45
-rw-r--r--app/assets/javascripts/jobs/components/manual_variables_form.vue179
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue9
-rw-r--r--app/assets/javascripts/jobs/index.js28
-rw-r--r--app/assets/javascripts/jobs/store/actions.js14
-rw-r--r--app/assets/javascripts/lib/utils/color_utils.js25
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js7
-rw-r--r--app/assets/javascripts/lib/utils/datetime_utility.js15
-rw-r--r--app/assets/javascripts/lib/utils/forms.js12
-rw-r--r--app/assets/javascripts/lib/utils/icons_path.js3
-rw-r--r--app/assets/javascripts/lib/utils/text_utility.js8
-rw-r--r--app/assets/javascripts/lib/utils/url_utility.js12
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/manual_ordering.js2
-rw-r--r--app/assets/javascripts/members.js2
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue107
-rw-r--r--app/assets/javascripts/monitoring/components/charts/empty_chart.vue41
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue334
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue374
-rw-r--r--app/assets/javascripts/monitoring/components/embed.vue107
-rw-r--r--app/assets/javascripts/monitoring/components/graph_group.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue97
-rw-r--r--app/assets/javascripts/monitoring/constants.js33
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js14
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
-rw-r--r--app/assets/javascripts/monitoring/utils.js45
-rw-r--r--app/assets/javascripts/mr_notes/init_notes.js27
-rw-r--r--app/assets/javascripts/mr_notes/stores/index.js2
-rw-r--r--app/assets/javascripts/mr_notes/stores/modules/index.js4
-rw-r--r--app/assets/javascripts/mr_popover/components/mr_popover.vue6
-rw-r--r--app/assets/javascripts/notebook/cells/markdown.vue2
-rw-r--r--app/assets/javascripts/notebook/cells/output/image.vue2
-rw-r--r--app/assets/javascripts/notebook/index.vue2
-rw-r--r--app/assets/javascripts/notes/components/diff_with_note.vue1
-rw-r--r--app/assets/javascripts/notes/components/discussion_actions.vue27
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue10
-rw-r--r--app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue51
-rw-r--r--app/assets/javascripts/notes/components/note_edited_text.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue8
-rw-r--r--app/assets/javascripts/notes/services/notes_service.js7
-rw-r--r--app/assets/javascripts/notes/stores/actions.js8
-rw-r--r--app/assets/javascripts/notes/stores/getters.js9
-rw-r--r--app/assets/javascripts/notes/stores/utils.js2
-rw-r--r--app/assets/javascripts/operation_settings/components/external_dashboard.vue5
-rw-r--r--app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js49
-rw-r--r--app/assets/javascripts/pages/groups/merge_requests/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/clusters/show/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/project.js23
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue16
-rw-r--r--app/assets/javascripts/pages/projects/wikis/wikis.js42
-rw-r--r--app/assets/javascripts/pages/search/show/refresh_counts.js24
-rw-r--r--app/assets/javascripts/pages/search/show/search.js11
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/pdf/index.vue24
-rw-r--r--app/assets/javascripts/pdf/page/index.vue8
-rw-r--r--app/assets/javascripts/performance_bar/components/detailed_metric.vue17
-rw-r--r--app/assets/javascripts/performance_bar/components/performance_bar_app.vue48
-rw-r--r--app/assets/javascripts/persistent_user_callout.js25
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue6
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue25
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_artifacts.vue1
-rw-r--r--app/assets/javascripts/pipelines/mixins/pipelines.js2
-rw-r--r--app/assets/javascripts/privacy_policy_update_callout.js8
-rw-r--r--app/assets/javascripts/projects/gke_cluster_namespace/index.js37
-rw-r--r--app/assets/javascripts/registry/components/app.vue2
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue2
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue178
-rw-r--r--app/assets/javascripts/registry/stores/actions.js2
-rw-r--r--app/assets/javascripts/releases/components/release_block.vue1
-rw-r--r--app/assets/javascripts/reports/components/modal.vue7
-rw-r--r--app/assets/javascripts/reports/components/report_item.vue2
-rw-r--r--app/assets/javascripts/reports/components/report_link.vue1
-rw-r--r--app/assets/javascripts/reports/components/report_section.vue2
-rw-r--r--app/assets/javascripts/reports/components/summary_row.vue4
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue1
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue1
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql7
-rw-r--r--app/assets/javascripts/right_sidebar.js10
-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/assignees.vue214
-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/subscriptions/subscriptions.vue32
-rw-r--r--app/assets/javascripts/star.js2
-rw-r--r--app/assets/javascripts/tracking.js67
-rw-r--r--app/assets/javascripts/users_select.js66
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/comment.js179
-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/constants.js41
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/form_elements.js17
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/index.js28
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/login.js46
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/mr_id.js63
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/note.js2
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/utils.js9
-rw-r--r--app/assets/javascripts/visual_review_toolbar/components/wrapper.js47
-rw-r--r--app/assets/javascripts/visual_review_toolbar/index.js25
-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.js45
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/index.js14
-rw-r--r--app/assets/javascripts/visual_review_toolbar/store/state.js67
-rw-r--r--app/assets/javascripts/visual_review_toolbar/styles/toolbar.css18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/deployment.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue12
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue6
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js11
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js91
-rw-r--r--app/assets/javascripts/vue_shared/components/changed_file_icon.vue11
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue22
-rw-r--r--app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue3
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue8
-rw-r--r--app/assets/javascripts/vue_shared/components/icon.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/toolbar.vue1
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue7
-rw-r--r--app/assets/javascripts/vue_shared/components/select2_select.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue2
-rw-r--r--app/assets/javascripts/vue_shared/directives/autofocusonshow.js39
-rw-r--r--app/assets/stylesheets/_ee/application_ee.scss5
-rw-r--r--app/assets/stylesheets/application.scss5
-rw-r--r--app/assets/stylesheets/errors.scss2
-rw-r--r--app/assets/stylesheets/framework/badges.scss2
-rw-r--r--app/assets/stylesheets/framework/callout.scss8
-rw-r--r--app/assets/stylesheets/framework/common.scss4
-rw-r--r--app/assets/stylesheets/framework/flash.scss1
-rw-r--r--app/assets/stylesheets/framework/header.scss2
-rw-r--r--app/assets/stylesheets/framework/lists.scss16
-rw-r--r--app/assets/stylesheets/framework/responsive_tables.scss2
-rw-r--r--app/assets/stylesheets/framework/tooltips.scss5
-rw-r--r--app/assets/stylesheets/framework/variables.scss1
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss4
-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.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss3
-rw-r--r--app/assets/stylesheets/pages/builds.scss20
-rw-r--r--app/assets/stylesheets/pages/container_registry.scss17
-rw-r--r--app/assets/stylesheets/pages/cycle_analytics.scss50
-rw-r--r--app/assets/stylesheets/pages/diff.scss11
-rw-r--r--app/assets/stylesheets/pages/groups.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss49
-rw-r--r--app/assets/stylesheets/pages/members.scss40
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss23
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss5
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss27
-rw-r--r--app/assets/stylesheets/pages/reports.scss5
-rw-r--r--app/assets/stylesheets/pages/search.scss45
-rw-r--r--app/assets/stylesheets/pages/settings.scss13
-rw-r--r--app/assets/stylesheets/pages/todos.scss2
-rw-r--r--app/assets/stylesheets/pages/users.scss105
-rw-r--r--app/assets/stylesheets/pages/wiki.scss17
-rw-r--r--app/assets/stylesheets/performance_bar.scss11
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/admin/groups_controller.rb3
-rw-r--r--app/controllers/admin/requests_profiles_controller.rb12
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/application_controller.rb21
-rw-r--r--app/controllers/autocomplete_controller.rb4
-rw-r--r--app/controllers/boards/issues_controller.rb2
-rw-r--r--app/controllers/chaos_controller.rb54
-rw-r--r--app/controllers/concerns/authenticates_with_two_factor.rb4
-rw-r--r--app/controllers/concerns/confirm_email_warning.rb25
-rw-r--r--app/controllers/concerns/group_tree.rb20
-rw-r--r--app/controllers/concerns/invisible_captcha.rb51
-rw-r--r--app/controllers/concerns/issuable_actions.rb25
-rw-r--r--app/controllers/concerns/issuable_collections.rb61
-rw-r--r--app/controllers/concerns/issuable_collections_action.rb2
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/concerns/sessionless_authentication.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/concerns/uploads_actions.rb2
-rw-r--r--app/controllers/concerns/with_performance_bar.rb4
-rw-r--r--app/controllers/confirmations_controller.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb22
-rw-r--r--app/controllers/explore/projects_controller.rb19
-rw-r--r--app/controllers/graphql_controller.rb4
-rw-r--r--app/controllers/groups/group_members_controller.rb13
-rw-r--r--app/controllers/groups_controller.rb8
-rw-r--r--app/controllers/ide_controller.rb1
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb8
-rw-r--r--app/controllers/import/github_controller.rb8
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb2
-rw-r--r--app/controllers/projects/artifacts_controller.rb5
-rw-r--r--app/controllers/projects/badges_controller.rb3
-rw-r--r--app/controllers/projects/blob_controller.rb6
-rw-r--r--app/controllers/projects/branches_controller.rb2
-rw-r--r--app/controllers/projects/build_artifacts_controller.rb2
-rw-r--r--app/controllers/projects/cycle_analytics/events_controller.rb10
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb10
-rw-r--r--app/controllers/projects/environments_controller.rb11
-rw-r--r--app/controllers/projects/git_http_client_controller.rb7
-rw-r--r--app/controllers/projects/issues_controller.rb6
-rw-r--r--app/controllers/projects/jobs_controller.rb6
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/content_controller.rb23
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb8
-rw-r--r--app/controllers/projects/merge_requests_controller.rb5
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb20
-rw-r--r--app/controllers/projects/registry/tags_controller.rb34
-rw-r--r--app/controllers/projects/repositories_controller.rb60
-rw-r--r--app/controllers/projects/snippets_controller.rb3
-rw-r--r--app/controllers/projects/starrers_controller.rb20
-rw-r--r--app/controllers/projects/triggers_controller.rb12
-rw-r--r--app/controllers/projects/variables_controller.rb2
-rw-r--r--app/controllers/projects/wikis_controller.rb36
-rw-r--r--app/controllers/projects_controller.rb31
-rw-r--r--app/controllers/registrations_controller.rb16
-rw-r--r--app/controllers/search_controller.rb17
-rw-r--r--app/controllers/sessions_controller.rb18
-rw-r--r--app/controllers/snippets/notes_controller.rb4
-rw-r--r--app/controllers/snippets_controller.rb3
-rw-r--r--app/controllers/users_controller.rb41
-rw-r--r--app/finders/autocomplete/move_to_project_finder.rb9
-rw-r--r--app/finders/award_emojis_finder.rb55
-rw-r--r--app/finders/awarded_emoji_finder.rb21
-rw-r--r--app/finders/branches_finder.rb2
-rw-r--r--app/finders/clusters/knative_services_finder.rb16
-rw-r--r--app/finders/clusters/kubernetes_namespace_finder.rb36
-rw-r--r--app/finders/container_repositories_finder.rb34
-rw-r--r--app/finders/group_descendants_finder.rb2
-rw-r--r--app/finders/group_members_finder.rb12
-rw-r--r--app/finders/issuable_finder.rb1
-rw-r--r--app/finders/members_finder.rb41
-rw-r--r--app/finders/notes_finder.rb42
-rw-r--r--app/finders/projects/serverless/functions_finder.rb70
-rw-r--r--app/finders/remote_mirror_finder.rb15
-rw-r--r--app/finders/starred_projects_finder.rb11
-rw-r--r--app/finders/users_star_projects_finder.rb36
-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/group_type.rb8
-rw-r--r--app/helpers/application_settings_helper.rb9
-rw-r--r--app/helpers/avatars_helper.rb2
-rw-r--r--app/helpers/dashboard_helper.rb15
-rw-r--r--app/helpers/groups_helper.rb9
-rw-r--r--app/helpers/labels_helper.rb5
-rw-r--r--app/helpers/notifications_helper.rb14
-rw-r--r--app/helpers/projects_helper.rb15
-rw-r--r--app/helpers/search_helper.rb34
-rw-r--r--app/helpers/services_helper.rb2
-rw-r--r--app/helpers/sessions_helper.rb7
-rw-r--r--app/helpers/sorting_helper.rb19
-rw-r--r--app/helpers/submodule_helper.rb14
-rw-r--r--app/helpers/system_note_helper.rb1
-rw-r--r--app/helpers/todos_helper.rb21
-rw-r--r--app/helpers/tracking_helper.rb17
-rw-r--r--app/helpers/users_helper.rb2
-rw-r--r--app/helpers/wiki_helper.rb2
-rw-r--r--app/mailers/emails/members.rb17
-rw-r--r--app/mailers/emails/remote_mirrors.rb2
-rw-r--r--app/mailers/notify.rb20
-rw-r--r--app/mailers/previews/notify_preview.rb8
-rw-r--r--app/models/active_session.rb14
-rw-r--r--app/models/analytics/cycle_analytics.rb9
-rw-r--r--app/models/analytics/cycle_analytics/project_stage.rb9
-rw-r--r--app/models/application_setting.rb12
-rw-r--r--app/models/application_setting_implementation.rb79
-rw-r--r--app/models/award_emoji.rb6
-rw-r--r--app/models/ci/build.rb31
-rw-r--r--app/models/ci/build_need.rb14
-rw-r--r--app/models/ci/job_artifact.rb4
-rw-r--r--app/models/ci/job_variable.rb14
-rw-r--r--app/models/ci/pipeline.rb25
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/ci/variable.rb1
-rw-r--r--app/models/clusters/applications/cert_manager.rb42
-rw-r--r--app/models/clusters/applications/helm.rb28
-rw-r--r--app/models/clusters/applications/knative.rb60
-rw-r--r--app/models/clusters/applications/prometheus.rb21
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/cluster.rb68
-rw-r--r--app/models/clusters/clusters_hierarchy.rb4
-rw-r--r--app/models/clusters/kubernetes_namespace.rb31
-rw-r--r--app/models/clusters/platforms/kubernetes.rb32
-rw-r--r--app/models/commit.rb2
-rw-r--r--app/models/commit_status.rb29
-rw-r--r--app/models/concerns/awardable.rb26
-rw-r--r--app/models/concerns/cacheable_attributes.rb9
-rw-r--r--app/models/concerns/case_sensitivity.rb4
-rw-r--r--app/models/concerns/ci/metadatable.rb1
-rw-r--r--app/models/concerns/descendant.rb11
-rw-r--r--app/models/concerns/diff_positionable_note.rb8
-rw-r--r--app/models/concerns/group_descendant.rb4
-rw-r--r--app/models/concerns/has_environment_scope.rb78
-rw-r--r--app/models/concerns/has_status.rb5
-rw-r--r--app/models/concerns/issuable.rb7
-rw-r--r--app/models/concerns/maskable.rb5
-rw-r--r--app/models/concerns/new_has_variable.rb14
-rw-r--r--app/models/concerns/project_api_compatibility.rb6
-rw-r--r--app/models/concerns/prometheus_adapter.rb6
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/concerns/relative_positioning.rb141
-rw-r--r--app/models/concerns/routable.rb43
-rw-r--r--app/models/concerns/taskable.rb3
-rw-r--r--app/models/concerns/token_authenticatable.rb4
-rw-r--r--app/models/concerns/update_project_statistics.rb7
-rw-r--r--app/models/container_repository.rb11
-rw-r--r--app/models/cycle_analytics/base.rb27
-rw-r--r--app/models/cycle_analytics/group_level.rb29
-rw-r--r--app/models/cycle_analytics/level_base.rb27
-rw-r--r--app/models/cycle_analytics/project_level.rb5
-rw-r--r--app/models/dashboard_group_milestone.rb3
-rw-r--r--app/models/deploy_key.rb6
-rw-r--r--app/models/deploy_keys_project.rb3
-rw-r--r--app/models/deployment.rb11
-rw-r--r--app/models/deployment_metrics.rb13
-rw-r--r--app/models/diff_note.rb7
-rw-r--r--app/models/environment.rb58
-rw-r--r--app/models/group.rb15
-rw-r--r--app/models/hooks/system_hook.rb4
-rw-r--r--app/models/hooks/web_hook.rb6
-rw-r--r--app/models/issue.rb11
-rw-r--r--app/models/label.rb8
-rw-r--r--app/models/list.rb3
-rw-r--r--app/models/member.rb4
-rw-r--r--app/models/members/group_member.rb4
-rw-r--r--app/models/merge_request.rb41
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/models/milestone.rb27
-rw-r--r--app/models/namespace.rb9
-rw-r--r--app/models/namespace/aggregation_schedule.rb14
-rw-r--r--app/models/note.rb6
-rw-r--r--app/models/notification_recipient.rb8
-rw-r--r--app/models/pages_domain.rb2
-rw-r--r--app/models/personal_access_token.rb1
-rw-r--r--app/models/project.rb137
-rw-r--r--app/models/project_auto_devops.rb6
-rw-r--r--app/models/project_services/chat_message/pipeline_message.rb225
-rw-r--r--app/models/project_services/emails_on_push_service.rb1
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/project_services/kubernetes_service.rb133
-rw-r--r--app/models/project_services/mock_deployment_service.rb2
-rw-r--r--app/models/project_services/prometheus_service.rb9
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/models/project_statistics.rb6
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/prometheus_metric.rb4
-rw-r--r--app/models/prometheus_metric_enums.rb20
-rw-r--r--app/models/redirect_route.rb6
-rw-r--r--app/models/remote_mirror.rb59
-rw-r--r--app/models/repository.rb45
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/system_note_metadata.rb2
-rw-r--r--app/models/user.rb44
-rw-r--r--app/models/user_preference.rb2
-rw-r--r--app/models/users_star_project.rb28
-rw-r--r--app/policies/global_policy.rb3
-rw-r--r--app/policies/group_policy.rb11
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/blob_presenter.rb8
-rw-r--r--app/presenters/blobs/unfold_presenter.rb85
-rw-r--r--app/presenters/merge_request_presenter.rb8
-rw-r--r--app/presenters/projects/settings/deploy_keys_presenter.rb40
-rw-r--r--app/serializers/analytics_issue_entity.rb6
-rw-r--r--app/serializers/analytics_merge_request_entity.rb2
-rw-r--r--app/serializers/analytics_stage_entity.rb4
-rw-r--r--app/serializers/deploy_key_entity.rb7
-rw-r--r--app/serializers/deployment_entity.rb1
-rw-r--r--app/serializers/deployment_serializer.rb2
-rw-r--r--app/serializers/group_analytics_stage_entity.rb16
-rw-r--r--app/serializers/group_analytics_stage_serializer.rb5
-rw-r--r--app/serializers/issuable_sidebar_basic_entity.rb5
-rw-r--r--app/serializers/issue_entity.rb7
-rw-r--r--app/serializers/merge_request_poll_cached_widget_entity.rb103
-rw-r--r--app/serializers/merge_request_poll_widget_entity.rb142
-rw-r--r--app/serializers/merge_request_serializer.rb6
-rw-r--r--app/serializers/merge_request_sidebar_basic_entity.rb9
-rw-r--r--app/serializers/merge_request_widget_entity.rb282
-rw-r--r--app/serializers/stage_entity.rb8
-rw-r--r--app/serializers/user_serializer.rb17
-rw-r--r--app/serializers/variable_entity.rb1
-rw-r--r--app/services/application_settings/update_service.rb9
-rw-r--r--app/services/auth/container_registry_authentication_service.rb10
-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/archive_trace_service.rb28
-rw-r--r--app/services/ci/play_build_service.rb4
-rw-r--r--app/services/ci/process_pipeline_service.rb97
-rw-r--r--app/services/ci/retry_build_service.rb2
-rw-r--r--app/services/clusters/build_kubernetes_namespace_service.rb35
-rw-r--r--app/services/clusters/create_service.rb7
-rw-r--r--app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb5
-rw-r--r--app/services/clusters/refresh_service.rb40
-rw-r--r--app/services/cohorts_service.rb6
-rw-r--r--app/services/commits/create_service.rb18
-rw-r--r--app/services/create_snippet_service.rb1
-rw-r--r--app/services/files/multi_service.rb1
-rw-r--r--app/services/git/base_hooks_service.rb64
-rw-r--r--app/services/git/branch_hooks_service.rb29
-rw-r--r--app/services/groups/nested_create_service.rb4
-rw-r--r--app/services/groups/transfer_service.rb1
-rw-r--r--app/services/groups/update_service.rb5
-rw-r--r--app/services/issuable/bulk_update_service.rb2
-rw-r--r--app/services/issuable/clone/attributes_rewriter.rb2
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/issues/update_service.rb2
-rw-r--r--app/services/members/base_service.rb4
-rw-r--r--app/services/members/destroy_service.rb2
-rw-r--r--app/services/merge_requests/build_service.rb28
-rw-r--r--app/services/merge_requests/mergeability_check_service.rb45
-rw-r--r--app/services/merge_requests/push_options_handler_service.rb37
-rw-r--r--app/services/merge_requests/rebase_service.rb3
-rw-r--r--app/services/metrics/dashboard/base_embed_service.rb36
-rw-r--r--app/services/metrics/dashboard/base_service.rb67
-rw-r--r--app/services/metrics/dashboard/custom_metric_embed_service.rb123
-rw-r--r--app/services/metrics/dashboard/default_embed_service.rb63
-rw-r--r--app/services/metrics/dashboard/dynamic_embed_service.rb78
-rw-r--r--app/services/metrics/dashboard/project_dashboard_service.rb48
-rw-r--r--app/services/metrics/dashboard/system_dashboard_service.rb47
-rw-r--r--app/services/notes/base_service.rb4
-rw-r--r--app/services/notes/create_service.rb5
-rw-r--r--app/services/notification_service.rb23
-rw-r--r--app/services/projects/base_move_relations_service.rb12
-rw-r--r--app/services/projects/destroy_service.rb14
-rw-r--r--app/services/projects/fetch_statistics_increment_service.rb7
-rw-r--r--app/services/projects/move_deploy_keys_projects_service.rb3
-rw-r--r--app/services/projects/move_lfs_objects_projects_service.rb3
-rw-r--r--app/services/projects/move_notification_settings_service.rb3
-rw-r--r--app/services/projects/move_project_authorizations_service.rb3
-rw-r--r--app/services/projects/move_project_group_links_service.rb3
-rw-r--r--app/services/projects/move_project_members_service.rb2
-rw-r--r--app/services/projects/update_remote_mirror_service.rb53
-rw-r--r--app/services/projects/update_service.rb7
-rw-r--r--app/services/prometheus/proxy_service.rb2
-rw-r--r--app/services/quick_actions/interpret_service.rb31
-rw-r--r--app/services/self_monitoring/project/create_service.rb131
-rw-r--r--app/services/system_note_service.rb8
-rw-r--r--app/services/update_deployment_service.rb2
-rw-r--r--app/services/update_snippet_service.rb4
-rw-r--r--app/services/users/refresh_authorized_projects_service.rb8
-rw-r--r--app/services/web_hook_service.rb6
-rw-r--r--app/services/wiki_pages/base_service.rb6
-rw-r--r--app/services/zoom_notes_service.rb42
-rw-r--r--app/uploaders/records_uploads.rb12
-rw-r--r--app/validators/addressable_url_validator.rb2
-rw-r--r--app/validators/qualified_domain_array_validator.rb49
-rw-r--r--app/validators/system_hook_url_validator.rb18
-rw-r--r--app/views/admin/application_settings/_help_page.html.haml2
-rw-r--r--app/views/admin/application_settings/_outbound.html.haml17
-rw-r--r--app/views/admin/application_settings/_performance.html.haml6
-rw-r--r--app/views/admin/application_settings/_snowplow.html.haml30
-rw-r--r--app/views/admin/applications/_delete_form.html.haml2
-rw-r--r--app/views/admin/broadcast_messages/_form.html.haml22
-rw-r--r--app/views/admin/dashboard/index.html.haml53
-rw-r--r--app/views/admin/groups/show.html.haml5
-rw-r--r--app/views/admin/projects/show.html.haml5
-rw-r--r--app/views/admin/requests_profiles/index.html.haml3
-rw-r--r--app/views/admin/services/_form.html.haml5
-rw-r--r--app/views/admin/users/_access_levels.html.haml4
-rw-r--r--app/views/admin/users/index.html.haml2
-rw-r--r--app/views/ci/variables/_environment_scope.html.haml21
-rw-r--r--app/views/ci/variables/_environment_scope_header.html.haml2
-rw-r--r--app/views/clusters/clusters/_banner.html.haml5
-rw-r--r--app/views/clusters/clusters/_namespace.html.haml14
-rw-r--r--app/views/clusters/clusters/show.html.haml49
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml9
-rw-r--r--app/views/clusters/platforms/kubernetes/_form.html.haml8
-rw-r--r--app/views/devise/passwords/edit.html.haml6
-rw-r--r--app/views/devise/sessions/_new_base.html.haml17
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml6
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/devise/shared/_tabs_ldap.html.haml6
-rw-r--r--app/views/devise/shared/_tabs_normal.html.haml4
-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/explore/projects/_projects.html.haml3
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/_group_admin_settings.html.haml6
-rw-r--r--app/views/groups/_home_panel.html.haml3
-rw-r--r--app/views/groups/group_members/index.html.haml78
-rw-r--r--app/views/groups/issues.html.haml4
-rw-r--r--app/views/groups/merge_requests.html.haml8
-rw-r--r--app/views/groups/settings/_advanced.html.haml25
-rw-r--r--app/views/groups/settings/_permissions.html.haml12
-rw-r--r--app/views/groups/settings/_subgroup_creation_level.html.haml3
-rw-r--r--app/views/help/_shortcuts.html.haml168
-rw-r--r--app/views/import/bitbucket_server/status.html.haml11
-rw-r--r--app/views/layouts/_google_analytics.html.haml20
-rw-r--r--app/views/layouts/_head.html.haml4
-rw-r--r--app/views/layouts/_init_auto_complete.html.haml10
-rw-r--r--app/views/layouts/_init_client_detection_flags.html.haml8
-rw-r--r--app/views/layouts/_piwik.html.haml28
-rw-r--r--app/views/layouts/_search.html.haml5
-rw-r--r--app/views/layouts/_snowplow.html.haml29
-rw-r--r--app/views/layouts/errors.html.haml20
-rw-r--r--app/views/layouts/group.html.haml6
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml7
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml5
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml4
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml29
-rw-r--r--app/views/layouts/nav/sidebar/_project_packages_link.html.haml16
-rw-r--r--app/views/layouts/project.html.haml6
-rw-r--r--app/views/layouts/snippets.html.haml6
-rw-r--r--app/views/notify/new_merge_request_email.html.haml2
-rw-r--r--app/views/peek/_bar.html.haml3
-rw-r--r--app/views/peek/views/_gc.html.haml7
-rw-r--r--app/views/peek/views/_redis.html.haml7
-rw-r--r--app/views/peek/views/_sidekiq.html.haml7
-rw-r--r--app/views/profiles/notifications/_group_settings.html.haml9
-rw-r--r--app/views/profiles/notifications/_project_settings.html.haml9
-rw-r--r--app/views/profiles/personal_access_tokens/index.html.haml4
-rw-r--r--app/views/profiles/preferences/show.html.haml8
-rw-r--r--app/views/profiles/show.html.haml4
-rw-r--r--app/views/projects/_export.html.haml1
-rw-r--r--app/views/projects/_flash_messages.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml4
-rw-r--r--app/views/projects/buttons/_star.html.haml6
-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/edit.html.haml4
-rw-r--r--app/views/projects/forks/_fork_button.html.haml8
-rw-r--r--app/views/projects/issues/_issue.html.haml5
-rw-r--r--app/views/projects/jobs/show.html.haml4
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml7
-rw-r--r--app/views/projects/merge_requests/creations/_new_compare.html.haml2
-rw-r--r--app/views/projects/merge_requests/creations/_new_submit.html.haml3
-rw-r--r--app/views/projects/merge_requests/show.html.haml14
-rw-r--r--app/views/projects/mirrors/_disabled_mirror_badge.html.haml2
-rw-r--r--app/views/projects/mirrors/_instructions.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml9
-rw-r--r--app/views/projects/pages_domains/_certificate.html.haml18
-rw-r--r--app/views/projects/pages_domains/show.html.haml7
-rw-r--r--app/views/projects/pipelines/_info.html.haml2
-rw-r--r--app/views/projects/pipelines/charts.html.haml7
-rw-r--r--app/views/projects/pipelines/charts/_overall.haml21
-rw-r--r--app/views/projects/pipelines/charts/_pipeline_statistics.haml14
-rw-r--r--app/views/projects/pipelines/charts/_pipelines.haml2
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/projects/project_members/_team.html.haml4
-rw-r--r--app/views/projects/protected_branches/_create_protected_branch.html.haml4
-rw-r--r--app/views/projects/services/prometheus/_metrics.html.haml57
-rw-r--r--app/views/projects/services/prometheus/_show.html.haml9
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_form.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml6
-rw-r--r--app/views/projects/starrers/_starrer.html.haml19
-rw-r--r--app/views/projects/starrers/index.html.haml32
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/triggers/_trigger.html.haml3
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml20
-rw-r--r--app/views/projects/wikis/_main_links.html.haml6
-rw-r--r--app/views/projects/wikis/_new.html.haml18
-rw-r--r--app/views/projects/wikis/_sidebar.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml13
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--app/views/search/_category.html.haml84
-rw-r--r--app/views/search/_filter.html.haml14
-rw-r--r--app/views/search/_form.html.haml27
-rw-r--r--app/views/search/_results.html.haml16
-rw-r--r--app/views/search/results/_blob_data.html.haml6
-rw-r--r--app/views/search/show.html.haml6
-rw-r--r--app/views/shared/_issuable_meta_data.html.haml4
-rw-r--r--app/views/shared/_label_row.html.haml4
-rw-r--r--app/views/shared/_ref_switcher.html.haml15
-rw-r--r--app/views/shared/_remote_mirror_update_button.html.haml2
-rw-r--r--app/views/shared/_sidebar_toggle_button.html.haml2
-rw-r--r--app/views/shared/boards/_switcher.html.haml16
-rw-r--r--app/views/shared/form_elements/_description.html.haml2
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml3
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml7
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml6
-rw-r--r--app/views/shared/issuable/_sidebar_assignees.html.haml2
-rw-r--r--app/views/shared/issuable/_sort_dropdown.html.haml2
-rw-r--r--app/views/shared/issuable/form/_metadata.html.haml1
-rw-r--r--app/views/shared/members/_filter_2fa_dropdown.html.haml4
-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/members/_sort_dropdown.html.haml3
-rw-r--r--app/views/shared/milestones/_top.html.haml2
-rw-r--r--app/views/shared/notifications/_button.html.haml15
-rw-r--r--app/views/shared/notifications/_new_button.html.haml13
-rw-r--r--app/views/shared/projects/_list.html.haml34
-rw-r--r--app/views/shared/projects/_project.html.haml10
-rw-r--r--app/views/u2f/_register.html.haml2
-rw-r--r--app/views/users/show.html.haml8
-rw-r--r--app/workers/all_queues.yml6
-rw-r--r--app/workers/archive_trace_worker.rb2
-rw-r--r--app/workers/background_migration_worker.rb2
-rw-r--r--app/workers/chaos/cpu_spin_worker.rb12
-rw-r--r--app/workers/chaos/db_spin_worker.rb12
-rw-r--r--app/workers/chaos/kill_worker.rb14
-rw-r--r--app/workers/chaos/leak_mem_worker.rb12
-rw-r--r--app/workers/chaos/sleep_worker.rb12
-rw-r--r--app/workers/ci/archive_traces_cron_worker.rb2
-rw-r--r--app/workers/cluster_configure_worker.rb6
-rw-r--r--app/workers/cluster_project_configure_worker.rb4
-rw-r--r--app/workers/concerns/chaos_queue.rb9
-rw-r--r--app/workers/gitlab_usage_ping_worker.rb6
-rw-r--r--app/workers/namespaces/root_statistics_worker.rb6
-rw-r--r--app/workers/namespaces/schedule_aggregation_worker.rb6
-rw-r--r--app/workers/pipeline_process_worker.rb7
-rw-r--r--app/workers/post_receive.rb28
-rw-r--r--app/workers/process_commit_worker.rb17
-rw-r--r--app/workers/project_cache_worker.rb6
-rw-r--r--app/workers/remote_mirror_notification_worker.rb2
-rw-r--r--app/workers/repository_update_remote_mirror_worker.rb61
-rwxr-xr-xbin/changelog2
-rwxr-xr-xbin/secpick2
-rwxr-xr-xbin/web_puma2
-rw-r--r--changelogs/README.md2
-rw-r--r--changelogs/unreleased/-30974-issue-search-by-number.yml5
-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/11039-moved-code-difference-from-EE-to-CE.yml5
-rw-r--r--changelogs/unreleased/11090-export-design-management-lfs-data.yml5
-rw-r--r--changelogs/unreleased/11448-fix-cs-with-k8s-runners.yml5
-rw-r--r--changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml5
-rw-r--r--changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/12533-shared-runners-warning.yml5
-rw-r--r--changelogs/unreleased/12550-fullscrean.yml5
-rw-r--r--changelogs/unreleased/12553-preferences.yml5
-rw-r--r--changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml5
-rw-r--r--changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.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/29775-fix-nested-lists-unnecessary-margin.yml5
-rw-r--r--changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml5
-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/32452-multiple-discussions.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/35757-move-issues-in-boards-pderichs.yml5
-rw-r--r--changelogs/unreleased/38105-pre-release-tag.yml5
-rw-r--r--changelogs/unreleased/39217-remove-kubernetes-service-integration.yml5
-rw-r--r--changelogs/unreleased/40379-CJK-search-min-chars.yml5
-rw-r--r--changelogs/unreleased/42399-registry-confirm-deletion.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/44106-include-subgroups-in-group-activity.yml5
-rw-r--r--changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml5
-rw-r--r--changelogs/unreleased/45104-special-characters-in-project-name-path-prevent-users-from-using-the-container-registry.yml5
-rw-r--r--changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.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/48771-label-picker-line-break-on-long-label-titles.yml5
-rw-r--r--changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.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/50228-deploy-tokens-custom-username.yml5
-rw-r--r--changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml5
-rw-r--r--changelogs/unreleased/51794-add-ordering-to-runner-jobs-api.yml5
-rw-r--r--changelogs/unreleased/51952-forking-via-webide.yml5
-rw-r--r--changelogs/unreleased/51952-redirect-to-webide-in-fork.yml5
-rw-r--r--changelogs/unreleased/52366-improved-group-lists-ui.yml5
-rw-r--r--changelogs/unreleased/52442-minimal-remove-mysql-support.yml5
-rw-r--r--changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml5
-rw-r--r--changelogs/unreleased/52954-allow-developers-to-delete-tags.yml5
-rw-r--r--changelogs/unreleased/53357-fix-plus-in-upload-file-names.yml5
-rw-r--r--changelogs/unreleased/53811-issue-boards-to-core-projects-backend-ce.yml5
-rw-r--r--changelogs/unreleased/54117-transactional-rebase.yml5
-rw-r--r--changelogs/unreleased/54595-incorrect-reaction-emoji-placement-in-discussion.yml5
-rw-r--r--changelogs/unreleased/55487-enable-group-terminals-button.yml5
-rw-r--r--changelogs/unreleased/55564-remove-if-in-before-after-action.yml5
-rw-r--r--changelogs/unreleased/55623-group-cluster-apis.yml5
-rw-r--r--changelogs/unreleased/55902-disable-creation-of-non-rbac-kubernetes-clusters.yml5
-rw-r--r--changelogs/unreleased/55953-renamed-discussion-to-thread.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/57538-normalize-users-private-profile-field.yml5
-rw-r--r--changelogs/unreleased/57793-fix-line-age.yml5
-rw-r--r--changelogs/unreleased/57815.yml5
-rw-r--r--changelogs/unreleased/57918-encrypt-feature-flags-tokens-changelog.yml5
-rw-r--r--changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml5
-rw-r--r--changelogs/unreleased/57973-errors-in-application-settings-panel-shows-wrong-panel.yml5
-rw-r--r--changelogs/unreleased/58035-expand-mr-diff.yml5
-rw-r--r--changelogs/unreleased/58065-uniform-html-txt-email.yml5
-rw-r--r--changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml5
-rw-r--r--changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml6
-rw-r--r--changelogs/unreleased/58802-rename-webide.yml5
-rw-r--r--changelogs/unreleased/58808-fix-image-diff-on-text.yml5
-rw-r--r--changelogs/unreleased/59028-fix-extra-plus-in-diffs.yml5
-rw-r--r--changelogs/unreleased/59257-find-new-branches-harder.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/60617-enable-project-cluster-jit.yml5
-rw-r--r--changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml5
-rw-r--r--changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml5
-rw-r--r--changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml5
-rw-r--r--changelogs/unreleased/60856-deleting-binary-file.yml5
-rw-r--r--changelogs/unreleased/60859-upload-after-delete.yml5
-rw-r--r--changelogs/unreleased/60860-keep-empty-folders-in-tree.yml5
-rw-r--r--changelogs/unreleased/60879-fix-reports-timing-out.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/61005-grafanaInAdminSettingsMonitoringMenu.yml5
-rw-r--r--changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml5
-rw-r--r--changelogs/unreleased/61201-pass-identities-to-external-authorization.yml5
-rw-r--r--changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml5
-rw-r--r--changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.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/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml5
-rw-r--r--changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml6
-rw-r--r--changelogs/unreleased/61613-spacing-mr-widgets.yml5
-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/62088-search-back.yml5
-rw-r--r--changelogs/unreleased/62124-new-threaded-discussion-design.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/62183-update-response-code-for-bulk-delete-api-for-container-registry.yml5
-rw-r--r--changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.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/62772-disable-kubernetes-credential-passthrough.yml5
-rw-r--r--changelogs/unreleased/62772-migrate-managed-clusters-to-unmanaged.yml6
-rw-r--r--changelogs/unreleased/62826-graphql-emoji-mutations.yml5
-rw-r--r--changelogs/unreleased/62826-graphql-note-mutations.yml5
-rw-r--r--changelogs/unreleased/62938-wcag-aa-edited-text-color.yml5
-rw-r--r--changelogs/unreleased/62968-environment-details-header-border-misaligned.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/62980-username-availability-checker-breaks-inline-validation.yml5
-rw-r--r--changelogs/unreleased/63079-migrate-clusters-with-no-token-to-unmanaged.yml6
-rw-r--r--changelogs/unreleased/63181-collapsible-line.yml5
-rw-r--r--changelogs/unreleased/63200-reply-button-broken.yml5
-rw-r--r--changelogs/unreleased/63227-fix-double-border.yml5
-rw-r--r--changelogs/unreleased/63247-add-conf-toast-and-link.yml5
-rw-r--r--changelogs/unreleased/63261-the-graphql-query-for-the-mr-popover-failes-on-the-frontend.yml6
-rw-r--r--changelogs/unreleased/63298-prevent-excessive-sanitization-asciidoc.yml5
-rw-r--r--changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml5
-rw-r--r--changelogs/unreleased/63475-fix-n-1.yml5
-rw-r--r--changelogs/unreleased/63479-jira-capitalization.yml5
-rw-r--r--changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml5
-rw-r--r--changelogs/unreleased/63507-fix-race-condition-fetching-token.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/63559-remove-avatar-from-sign-in.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/63590-pipeline-actions-cause-full-refresh.yml5
-rw-r--r--changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml5
-rw-r--r--changelogs/unreleased/63667-hashed-storage-migration-count-correctly.yml5
-rw-r--r--changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml5
-rw-r--r--changelogs/unreleased/63691-fix-doc-link.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/63873-process-start-time.yml6
-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/63945-update-mixin-deep-to-1-3-2.yml5
-rw-r--r--changelogs/unreleased/63971-remove-istanbul.yml5
-rw-r--r--changelogs/unreleased/64066-fix-uneven-click-areas.yml5
-rw-r--r--changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml5
-rw-r--r--changelogs/unreleased/64081-override-helm-release-name.yml5
-rw-r--r--changelogs/unreleased/64091-fix-broken-terminal.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/64161-gitlab-fqdn.yml5
-rw-r--r--changelogs/unreleased/64176-fix-error-handling.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/64249-align-container-registry-empty-state-with-design-guidelines.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/64295-predictable-environment-slugs.yml5
-rw-r--r--changelogs/unreleased/64314-ci-icon.yml5
-rw-r--r--changelogs/unreleased/64315-mget_sessions_in_chunks.yml5
-rw-r--r--changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml5
-rw-r--r--changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml5
-rw-r--r--changelogs/unreleased/64341-user-callout-deferred-link-support.yml5
-rw-r--r--changelogs/unreleased/64407-vfazio-quirk-omniauth-strategies-openidconnect.yml5
-rw-r--r--changelogs/unreleased/64416-lodash-4-6-2-for-prototype-pollution.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/64645-asciidoctor-preserve-footnote-link-ids.yml5
-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/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/66023-starrers-count-do-not-match-after-searching.yml5
-rw-r--r--changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml5
-rw-r--r--changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml5
-rw-r--r--changelogs/unreleased/66161-replace-expand-icons.yml5
-rw-r--r--changelogs/unreleased/9928ee-add-rule_type-to-approval-project-rules.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/Remove-unresolved-class-in-discussion-header.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/add-caching-to-archive-endpoint.yml5
-rw-r--r--changelogs/unreleased/add-clusters-to-deployment.yml5
-rw-r--r--changelogs/unreleased/add-git-blame-api.yml5
-rw-r--r--changelogs/unreleased/add-metrics-dashboard-permission-check.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-salesforce-logo.yml5
-rw-r--r--changelogs/unreleased/add-strategies-column-to-scopes-table.yml5
-rw-r--r--changelogs/unreleased/add-support-for-start-sha-to-commits-api.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/allow-reactive-caching-of-nil.yml5
-rw-r--r--changelogs/unreleased/always-allow-prometheus-access-in-dev.yml5
-rw-r--r--changelogs/unreleased/always-display-environment-selector.yml5
-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/api-doc-negative-commit-message-push-rule.yml5
-rw-r--r--changelogs/unreleased/asciidoc-enable-syntax-highlighting.yml5
-rw-r--r--changelogs/unreleased/asciidoctor-upgrade.yml5
-rw-r--r--changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml5
-rw-r--r--changelogs/unreleased/bjk-64064_cache_metrics.yml5
-rw-r--r--changelogs/unreleased/bjk-fix_prom_example.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-markdown-graphql.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/bvl-rename-routes-after-user-rename.yml5
-rw-r--r--changelogs/unreleased/bw-add-index-for-relative-position.yml5
-rw-r--r--changelogs/unreleased/caneldem-master-patch-77839.yml5
-rw-r--r--changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.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/centralize-markdownlint-config.yml5
-rw-r--r--changelogs/unreleased/check-min-schema-migrate.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/clusters-group-cte.yml5
-rw-r--r--changelogs/unreleased/create-merge-train-ref-ce.yml5
-rw-r--r--changelogs/unreleased/db-update-geo-nodes-primary.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/dohtaset.yml5
-rw-r--r--changelogs/unreleased/double-slash-64592.yml5
-rw-r--r--changelogs/unreleased/dz-remove-deprecated-group-routes.yml5
-rw-r--r--changelogs/unreleased/dz-remove-deprecated-user-routes.yml5
-rw-r--r--changelogs/unreleased/embedded-metrics-be-2.yml5
-rw-r--r--changelogs/unreleased/enable-specific-embeds.yml5
-rw-r--r--changelogs/unreleased/expose-saml-provider-id-to-users-api.yml5
-rw-r--r--changelogs/unreleased/extract_auto_deploy_into_base_image.yml5
-rw-r--r--changelogs/unreleased/fe-delete-old-boardservice-on-component-modal-footer.yml6
-rw-r--r--changelogs/unreleased/fe-delete-old-boardservice.yml6
-rw-r--r--changelogs/unreleased/fe-fix-merge-url-params-with-plus.yml5
-rw-r--r--changelogs/unreleased/fe-issue-reorder.yml5
-rw-r--r--changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml5
-rw-r--r--changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml5
-rw-r--r--changelogs/unreleased/feature-uninstall_cluster_ingress.yml5
-rw-r--r--changelogs/unreleased/feature-uninstall_jupyter_hub_app.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-broken-vue-i18n-strings.yml5
-rw-r--r--changelogs/unreleased/fix-comment-race-condition.yml5
-rw-r--r--changelogs/unreleased/fix-commits-api-empty-refname.yml5
-rw-r--r--changelogs/unreleased/fix-facivon-url-if-uploads-object-store-enabled.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-jupyter-git-v3.yml5
-rw-r--r--changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-schedule-edge-case.yml6
-rw-r--r--changelogs/unreleased/fix-sidekiq-transaction-check-race.yml5
-rw-r--r--changelogs/unreleased/fix-unicorn-sampler-workers-count.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-fix-subgroup-search-url.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-63955-fix-import-with-source-branch-deleted.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-53-0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.49.0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.51.0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.52.0.yml5
-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/graphql-tree-last-commit.yml5
-rw-r--r--changelogs/unreleased/group-milestones-dashboard-blunceford.yml5
-rw-r--r--changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml5
-rw-r--r--changelogs/unreleased/hfy-apply-knative-cluster-role-on-service-account-creation.yml5
-rw-r--r--changelogs/unreleased/hide-restricted-visibility-radio.yml5
-rw-r--r--changelogs/unreleased/id-clean-up-mr-assignees.yml5
-rw-r--r--changelogs/unreleased/id-extract-widget-into-different-request.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/id-stale-branches.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-63222.yml5
-rw-r--r--changelogs/unreleased/issue-zoom-url.yml5
-rw-r--r--changelogs/unreleased/issue_58494.yml5
-rw-r--r--changelogs/unreleased/issue_64021.yml5
-rw-r--r--changelogs/unreleased/jc-detect-nfs-for-rugged.yml5
-rw-r--r--changelogs/unreleased/jc-remove-catfile-flag.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-enable-object-dedupe-by-default.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/knative-0-6.yml5
-rw-r--r--changelogs/unreleased/label-descr-push-opts.yml5
-rw-r--r--changelogs/unreleased/limit-amount-of-tests-returned.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/mh-board-tooltips.yml5
-rw-r--r--changelogs/unreleased/mh-boards-filter-bar.yml5
-rw-r--r--changelogs/unreleased/mh-collapsible-boards.yml5
-rw-r--r--changelogs/unreleased/mh-colon-autocomplete.yml5
-rw-r--r--changelogs/unreleased/mh-mermaid-linebreaks.yml5
-rw-r--r--changelogs/unreleased/move-all-configs-to-global.yml5
-rw-r--r--changelogs/unreleased/mw-project-list-color-fix.yml5
-rw-r--r--changelogs/unreleased/new-cycle-analytics-backend-migrations.yml5
-rw-r--r--changelogs/unreleased/optimize-note-indexes.yml5
-rw-r--r--changelogs/unreleased/osw-persist-tmp-snippet-uploads.yml5
-rw-r--r--changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml5
-rw-r--r--changelogs/unreleased/paginate-license-management.yml5
-rw-r--r--changelogs/unreleased/patch-29.yml5
-rw-r--r--changelogs/unreleased/pb-update-gitlab-shell-9-4-0.yml5
-rw-r--r--changelogs/unreleased/po-raw-changes-encoding.yml5
-rw-r--r--changelogs/unreleased/post-migrate-private-profile.yml5
-rw-r--r--changelogs/unreleased/pre-releases-38105a.yml5
-rw-r--r--changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml5
-rw-r--r--changelogs/unreleased/project_api.yml5
-rw-r--r--changelogs/unreleased/rails-template-update.yml5
-rw-r--r--changelogs/unreleased/refactor-sentry.yml5
-rw-r--r--changelogs/unreleased/registry-fix-multi-delete-modal.yml5
-rw-r--r--changelogs/unreleased/remove-auto-ssl-ff.yml6
-rw-r--r--changelogs/unreleased/remove-kubernetes-service-deployment-platform.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-support-for-legacy-pipeline-triggers.yml5
-rw-r--r--changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml6
-rw-r--r--changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml5
-rw-r--r--changelogs/unreleased/report-missing-job-dependency.yml6
-rw-r--r--changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml5
-rw-r--r--changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml5
-rw-r--r--changelogs/unreleased/rj-fix-manual-order.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/sanitize_rake_ldap_check_output.yml5
-rw-r--r--changelogs/unreleased/search-blob-basenames.yml5
-rw-r--r--changelogs/unreleased/security-2858-fix-color-validation.yml5
-rw-r--r--changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml5
-rw-r--r--changelogs/unreleased/security-59581-related-merge-requests-count.yml5
-rw-r--r--changelogs/unreleased/security-60551-fix-upload-scope.yml5
-rw-r--r--changelogs/unreleased/security-DOS_issue_comments_banzai.yml5
-rw-r--r--changelogs/unreleased/security-bvl-enforce-graphql-type-authorization.yml5
-rw-r--r--changelogs/unreleased/security-fp-prevent-billion-laughs-attack.yml5
-rw-r--r--changelogs/unreleased/security-mr-head-pipeline-leak.yml5
-rw-r--r--changelogs/unreleased/security-notes-in-private-snippets.yml5
-rw-r--r--changelogs/unreleased/security-prevent-detection-of-merge-request-template-name.yml5
-rw-r--r--changelogs/unreleased/set-higher-ttl-for-trace-write.yml5
-rw-r--r--changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml5
-rw-r--r--changelogs/unreleased/sh-add-force-random-password-user-api.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.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-add-thread-memory-cache.yml5
-rw-r--r--changelogs/unreleased/sh-audit-event-json-log-format-from-and-to.yml5
-rw-r--r--changelogs/unreleased/sh-avoid-loading-pipeline-status.yml5
-rw-r--r--changelogs/unreleased/sh-break-out-invited-group-members.yml5
-rw-r--r--changelogs/unreleased/sh-bump-fog-aws.yml5
-rw-r--r--changelogs/unreleased/sh-cache-feature-flag-names.yml5
-rw-r--r--changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml5
-rw-r--r--changelogs/unreleased/sh-cache-flipper-names-memory-cache.yml5
-rw-r--r--changelogs/unreleased/sh-cache-negative-entries-find-commit.yml5
-rw-r--r--changelogs/unreleased/sh-clean-up-bitbucket-import-errors.yml5
-rw-r--r--changelogs/unreleased/sh-disable-reactive-caching-automatic-retries.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-enable-bootsnap.yml5
-rw-r--r--changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml5
-rw-r--r--changelogs/unreleased/sh-fix-discussions-api-perf.yml5
-rw-r--r--changelogs/unreleased/sh-fix-httpclient-ssl.yml5
-rw-r--r--changelogs/unreleased/sh-fix-import-export-suggestions.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-63349.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-63910.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml5
-rw-r--r--changelogs/unreleased/sh-fix-pipelines-not-being-created.yml5
-rw-r--r--changelogs/unreleased/sh-fix-special-role-error-500.yml5
-rw-r--r--changelogs/unreleased/sh-handle-nil-replication-lag.yml5
-rw-r--r--changelogs/unreleased/sh-ignore-git-errors-delete-project.yml5
-rw-r--r--changelogs/unreleased/sh-improve-redis-peek.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-optimize-todos-controller.yml5
-rw-r--r--changelogs/unreleased/sh-post-receive-cache-clear-once.yml5
-rw-r--r--changelogs/unreleased/sh-remove-import-columns-from-projects.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-service-template-bug.yml5
-rw-r--r--changelogs/unreleased/sh-strong-memoize-appearances.yml5
-rw-r--r--changelogs/unreleased/sh-support-csp-nonce.yml5
-rw-r--r--changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml5
-rw-r--r--changelogs/unreleased/sh-update-mermaid.yml4
-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-upgrade-rouge-3-5-1.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/slugify.yml5
-rw-r--r--changelogs/unreleased/small-s-in-elasticsearch-in-code.yml5
-rw-r--r--changelogs/unreleased/small-s-in-elasticsearch.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/support-jsonb-default-value.yml5
-rw-r--r--changelogs/unreleased/tc-rake-orphan-artifacts.yml5
-rw-r--r--changelogs/unreleased/tr-embed-metric-links.yml5
-rw-r--r--changelogs/unreleased/tr-param-undefined-fix.yml5
-rw-r--r--changelogs/unreleased/tr-remove-embed-metrics-flag.yml5
-rw-r--r--changelogs/unreleased/transaction-metrics.yml5
-rw-r--r--changelogs/unreleased/tz-update-mr-count-over-tabs.yml6
-rw-r--r--changelogs/unreleased/unicorn-sampler-fix.yml5
-rw-r--r--changelogs/unreleased/update-babel-to-7-5-5.yml5
-rw-r--r--changelogs/unreleased/update-clair-version.yml6
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.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-pages-to-1-7-0.yml5
-rw-r--r--changelogs/unreleased/update-pagination-texts.yml5
-rw-r--r--changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml5
-rw-r--r--changelogs/unreleased/update-tar-to-2-2-2.yml5
-rw-r--r--changelogs/unreleased/update-todo-in-ui.yml5
-rw-r--r--changelogs/unreleased/use-pg-9-6-11-on-ci.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-jest-markdown-header.yml5
-rw-r--r--changelogs/unreleased/winh-notes-service-applySuggestion.yml5
-rw-r--r--changelogs/unreleased/winh-notes-service-deleteNote.yml5
-rw-r--r--changelogs/unreleased/winh-notes-service-toggleAward.yml5
-rw-r--r--changelogs/unreleased/winh-updateResolvableDiscussionsCounts-typo.yml5
-rw-r--r--changelogs/unreleased/z-index-fix-for-diff-file-dropdown.yml5
-rw-r--r--changelogs/unreleased/z-index-tools.yml5
-rw-r--r--changelogs/unreleased/zj-gitaly-usage-data.yml5
-rw-r--r--config.ru10
-rw-r--r--config/application.rb50
-rw-r--r--config/boot.rb6
-rw-r--r--config/database.yml.mysql60
-rw-r--r--config/gitlab.yml.example37
-rw-r--r--config/initializers/0_inflections.rb1
-rw-r--r--config/initializers/0_inject_enterprise_edition_module.rb19
-rw-r--r--config/initializers/0_license.rb2
-rw-r--r--config/initializers/1_settings.rb12
-rw-r--r--config/initializers/7_prometheus_metrics.rb25
-rw-r--r--config/initializers/8_devise.rb2
-rw-r--r--config/initializers/active_record_data_types.rb100
-rw-r--r--config/initializers/active_record_migration.rb10
-rw-r--r--config/initializers/active_record_mysql_timestamp.rb30
-rw-r--r--config/initializers/ar_mysql_jsonb_support.rb31
-rw-r--r--config/initializers/ar_native_database_types.rb12
-rw-r--r--config/initializers/connection_fix.rb32
-rw-r--r--config/initializers/content_security_policy.rb15
-rw-r--r--config/initializers/elastic_client_setup.rb74
-rw-r--r--config/initializers/invisible_captcha.rb7
-rw-r--r--config/initializers/load_balancing.rb2
-rw-r--r--config/initializers/lograge.rb7
-rw-r--r--config/initializers/mysql_ignore_postgresql_options.rb42
-rw-r--r--config/initializers/mysql_set_length_for_binary_indexes.rb30
-rw-r--r--config/initializers/octokit.rb1
-rw-r--r--config/initializers/peek.rb50
-rw-r--r--config/initializers/postgresql_opclasses_support.rb211
-rw-r--r--config/initializers/rack_attack_logging.rb4
-rw-r--r--config/initializers/sidekiq.rb2
-rw-r--r--config/karma.config.js10
-rw-r--r--config/knative/api_resources.yml64
-rw-r--r--config/locales/doorkeeper.en.yml2
-rw-r--r--config/locales/en.yml6
-rw-r--r--config/locales/invisible_captcha.en.yml4
-rw-r--r--config/prometheus/cluster_metrics.yml16
-rw-r--r--config/prometheus/common_metrics.yml50
-rw-r--r--config/routes.rb4
-rw-r--r--config/routes/admin.rb2
-rw-r--r--config/routes/git_http.rb12
-rw-r--r--config/routes/project.rb19
-rw-r--r--config/routes/user.rb8
-rw-r--r--config/routes/wiki.rb1
-rw-r--r--config/settings.rb14
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--config/webpack.config.js22
-rw-r--r--danger/database/Dangerfile42
-rw-r--r--danger/gemfile/Dangerfile3
-rw-r--r--danger/roulette/Dangerfile15
-rw-r--r--db/fixtures/development/14_pipelines.rb93
-rw-r--r--db/fixtures/development/15_award_emoji.rb35
-rw-r--r--db/migrate/20171230123729_init_schema.rb38
-rw-r--r--db/migrate/20180206200543_reset_events_primary_key_sequence.rb16
-rw-r--r--db/migrate/20180403035759_create_project_ci_cd_settings.rb6
-rw-r--r--db/migrate/20180406204716_add_limits_ci_build_trace_chunks_raw_data_for_mysql.rb13
-rw-r--r--db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb1
-rw-r--r--db/migrate/20180824202952_add_outbound_requests_whitelist_to_application_settings.rb9
-rw-r--r--db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb19
-rw-r--r--db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb4
-rw-r--r--db/migrate/20190206193120_add_index_to_tags.rb2
-rw-r--r--db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb2
-rw-r--r--db/migrate/20190611100201_add_geo_container_repository_updated_events_table.rb17
-rw-r--r--db/migrate/20190611100202_add_index_to_geo_event_log.rb17
-rw-r--r--db/migrate/20190612111201_add_geo_container_repository_counters.rb16
-rw-r--r--db/migrate/20190612111404_add_geo_container_sync_capacity.rb13
-rw-r--r--db/migrate/20190626175626_add_group_creation_level_to_namespaces.rb18
-rw-r--r--db/migrate/20190627100221_add_mr_productivity_metrics.rb14
-rw-r--r--db/migrate/20190627122264_add_foreign_keys_for_container_repository.rb25
-rw-r--r--db/migrate/20190703043358_add_commit_id_to_draft_notes.rb11
-rw-r--r--db/migrate/20190709220014_import_common_metrics_y_axis.rb13
-rw-r--r--db/migrate/20190709220143_add_index_to_issues_relative_position.rb20
-rw-r--r--db/migrate/20190711124721_create_job_variables.rb23
-rw-r--r--db/migrate/20190712040400_add_environment_id_to_clusters_kubernetes_namespaces.rb10
-rw-r--r--db/migrate/20190712040412_index_clusters_kubernetes_namespaces_on_environment_id.rb18
-rw-r--r--db/migrate/20190712064021_add_namespace_per_environment_flag_to_clusters.rb20
-rw-r--r--db/migrate/20190715042813_add_issue_id_to_versions.rb13
-rw-r--r--db/migrate/20190715043954_set_issue_id_for_all_versions.rb19
-rw-r--r--db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb27
-rw-r--r--db/migrate/20190715142138_add_raw_blob_request_limit_to_application_settings.rb9
-rw-r--r--db/migrate/20190715173819_add_object_storage_flag_to_geo_node.rb21
-rw-r--r--db/migrate/20190715215532_add_project_emails_disabled.rb9
-rw-r--r--db/migrate/20190715215549_add_group_emails_disabled.rb9
-rw-r--r--db/migrate/20190716144222_create_analytics_cycle_analytics_project_stages.rb35
-rw-r--r--db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb22
-rw-r--r--db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb14
-rw-r--r--db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb30
-rw-r--r--db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb17
-rw-r--r--db/migrate/20190726101133_add_allow_local_requests_from_system_hooks_to_application_settings.rb18
-rw-r--r--db/migrate/20190729062536_create_analytics_cycle_analytics_group_stages.rb35
-rw-r--r--db/migrate/20190729090456_add_index_on_environments_with_state.rb17
-rw-r--r--db/migrate/20190731084415_add_build_need.rb20
-rw-r--r--db/migrate/20190801060809_delete_kubernetes_services.rb13
-rw-r--r--db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb17
-rw-r--r--db/migrate/20190802012622_reorder_issues_project_id_relative_position_index.rb24
-rw-r--r--db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb10
-rw-r--r--db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb10
-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/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb30
-rw-r--r--db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb29
-rw-r--r--db/migrate/gpg_keys_limits_to_mysql.rb14
-rw-r--r--db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql.rb9
-rw-r--r--db/migrate/limits_to_mysql.rb8
-rw-r--r--db/migrate/markdown_cache_limits_to_mysql.rb13
-rw-r--r--db/migrate/merge_request_diff_file_limits_to_mysql.rb12
-rw-r--r--db/migrate/prometheus_metrics_limits_to_mysql.rb12
-rw-r--r--db/post_migrate/20180409170809_populate_missing_project_ci_cd_settings.rb4
-rw-r--r--db/post_migrate/20181219130552_update_project_import_visibility_level.rb2
-rw-r--r--db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb20
-rw-r--r--db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb40
-rw-r--r--db/post_migrate/20190715043944_remove_sha_index_from_versions.rb17
-rw-r--r--db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb17
-rw-r--r--db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb19
-rw-r--r--db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb17
-rw-r--r--db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb17
-rw-r--r--db/post_migrate/20190802235445_add_index_on_id_and_type_and_public_to_keys.rb23
-rw-r--r--db/post_migrate/20190812070645_migrate_private_profile_nulls.rb31
-rw-r--r--db/schema.rb1449
-rw-r--r--doc/README.md95
-rw-r--r--doc/administration/audit_events.md3
-rw-r--r--doc/administration/auditor_users.md2
-rw-r--r--doc/administration/auth/img/google_secure_ldap_add_step_1.pngbin28849 -> 9083 bytes
-rw-r--r--doc/administration/auth/img/google_secure_ldap_add_step_2.pngbin82115 -> 27207 bytes
-rw-r--r--doc/administration/auth/img/google_secure_ldap_client_settings.pngbin63959 -> 21302 bytes
-rw-r--r--doc/administration/auth/ldap-ee.md126
-rw-r--r--doc/administration/auth/ldap.md67
-rw-r--r--doc/administration/auth/oidc.md65
-rw-r--r--doc/administration/container_registry.md79
-rw-r--r--doc/administration/custom_hooks.md2
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.pngbin202130 -> 74553 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.pngbin85997 -> 28817 bytes
-rw-r--r--doc/administration/geo/disaster_recovery/index.md2
-rw-r--r--doc/administration/geo/replication/configuration.md4
-rw-r--r--doc/administration/geo/replication/img/geo_architecture.pngbin116506 -> 53225 bytes
-rw-r--r--doc/administration/geo/replication/index.md94
-rw-r--r--doc/administration/geo/replication/troubleshooting.md16
-rw-r--r--doc/administration/geo/replication/updating_the_geo_nodes.md36
-rw-r--r--doc/administration/gitaly/index.md831
-rw-r--r--doc/administration/high_availability/README.md35
-rw-r--r--doc/administration/high_availability/database.md1
-rw-r--r--doc/administration/high_availability/img/fully-distributed.pngbin46918 -> 46691 bytes
-rw-r--r--doc/administration/high_availability/img/horizontal.pngbin18660 -> 18179 bytes
-rw-r--r--doc/administration/high_availability/img/hybrid.pngbin20698 -> 20693 bytes
-rw-r--r--doc/administration/high_availability/load_balancer.md57
-rw-r--r--doc/administration/high_availability/monitoring_node.md2
-rw-r--r--doc/administration/high_availability/nfs.md5
-rw-r--r--doc/administration/high_availability/pgbouncer.md127
-rw-r--r--doc/administration/img/custom_hooks_error_msg.pngbin80442 -> 31281 bytes
-rw-r--r--doc/administration/index.md12
-rw-r--r--doc/administration/integration/plantuml.md4
-rw-r--r--doc/administration/job_artifacts.md1
-rw-r--r--doc/administration/logs.md42
-rw-r--r--doc/administration/merge_request_diffs.md1
-rw-r--r--doc/administration/monitoring/gitlab_instance_administration_project/index.md36
-rw-r--r--doc/administration/monitoring/index.md3
-rw-r--r--doc/administration/monitoring/performance/grafana_configuration.md32
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar.pngbin99331 -> 33642 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.pngbin91275 -> 83212 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_line_profiling.pngbin93063 -> 0 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_redis_calls.pngbin0 -> 70859 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_rugged_calls.pngbin0 -> 105305 bytes
-rw-r--r--doc/administration/monitoring/performance/img/performance_bar_sql_queries.pngbin128337 -> 143074 bytes
-rw-r--r--doc/administration/monitoring/performance/img/request_profile_result.pngbin3216 -> 11451 bytes
-rw-r--r--doc/administration/monitoring/performance/performance_bar.md11
-rw-r--r--doc/administration/monitoring/performance/request_profiling.md4
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md4
-rw-r--r--doc/administration/operations/fast_ssh_key_lookup.md4
-rw-r--r--doc/administration/operations/img/sidekiq-cluster.pngbin61923 -> 22576 bytes
-rw-r--r--doc/administration/operations/unicorn.md2
-rw-r--r--doc/administration/pages/img/lets_encrypt_integration_v12_1.pngbin98409 -> 33137 bytes
-rw-r--r--doc/administration/pages/source.md45
-rw-r--r--doc/administration/plugins.md32
-rw-r--r--doc/administration/raketasks/ldap.md25
-rw-r--r--doc/administration/raketasks/storage.md8
-rw-r--r--doc/administration/raketasks/uploads/migrate.md10
-rw-r--r--doc/administration/repository_checks.md4
-rw-r--r--doc/administration/repository_storage_paths.md16
-rw-r--r--doc/administration/repository_storage_types.md4
-rw-r--r--doc/administration/troubleshooting/debug.md2
-rw-r--r--doc/administration/troubleshooting/diagnostics_tools.md27
-rw-r--r--doc/administration/troubleshooting/elasticsearch.md345
-rw-r--r--doc/administration/troubleshooting/kubernetes_cheat_sheet.md251
-rw-r--r--doc/administration/uploads.md17
-rw-r--r--doc/api/README.md180
-rw-r--r--doc/api/api_resources.md144
-rw-r--r--doc/api/boards.md2
-rw-r--r--doc/api/commits.md10
-rw-r--r--doc/api/container_registry.md81
-rw-r--r--doc/api/dependencies.md6
-rw-r--r--doc/api/deploy_key_multiple_projects.md32
-rw-r--r--doc/api/deploy_keys.md29
-rw-r--r--doc/api/epics.md2
-rw-r--r--doc/api/geo_nodes.md93
-rw-r--r--doc/api/group_boards.md2
-rw-r--r--doc/api/group_clusters.md2
-rw-r--r--doc/api/group_labels.md9
-rw-r--r--doc/api/issues.md4
-rw-r--r--doc/api/jobs.md6
-rw-r--r--doc/api/labels.md9
-rw-r--r--doc/api/lint.md2
-rw-r--r--doc/api/merge_request_approvals.md306
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/oauth2.md18
-rw-r--r--doc/api/pipeline_triggers.md29
-rw-r--r--doc/api/project_clusters.md2
-rw-r--r--doc/api/project_level_variables.md4
-rw-r--r--doc/api/projects.md247
-rw-r--r--doc/api/releases/img/upcoming_release_v12_1.pngbin22635 -> 7407 bytes
-rw-r--r--doc/api/releases/links.md10
-rw-r--r--doc/api/repository_files.md69
-rw-r--r--doc/api/services.md62
-rw-r--r--doc/api/settings.md23
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/ci/README.md13
-rw-r--r--doc/ci/caching/index.md8
-rw-r--r--doc/ci/chatops/README.md10
-rw-r--r--doc/ci/ci_cd_for_external_repos/bitbucket_integration.md200
-rw-r--r--doc/ci/ci_cd_for_external_repos/github_integration.md36
-rw-r--r--doc/ci/directed_acyclic_graph/index.md76
-rw-r--r--doc/ci/docker/using_docker_build.md464
-rw-r--r--doc/ci/docker/using_docker_images.md150
-rw-r--r--doc/ci/docker/using_kaniko.md3
-rw-r--r--doc/ci/environments.md20
-rw-r--r--doc/ci/examples/README.md2
-rw-r--r--doc/ci/examples/artifactory_and_gitlab/index.md72
-rw-r--r--doc/ci/examples/browser_performance.md4
-rw-r--r--doc/ci/examples/code_quality.md10
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md8
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.pngbin67788 -> 26301 bytes
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/index.md2
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md2
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md111
-rw-r--r--doc/ci/img/collapsible_log.pngbin174697 -> 60771 bytes
-rw-r--r--doc/ci/img/deployments_view.pngbin58498 -> 23501 bytes
-rw-r--r--doc/ci/img/environments_available.pngbin20410 -> 8582 bytes
-rw-r--r--doc/ci/img/environments_mr_review_app.pngbin30140 -> 13474 bytes
-rw-r--r--doc/ci/img/manual_job_variables.pngbin0 -> 111239 bytes
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example_11_9.pngbin71292 -> 70061 bytes
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.pngbin139184 -> 136497 bytes
-rw-r--r--doc/ci/introduction/index.md41
-rw-r--r--doc/ci/jenkins/index.md232
-rw-r--r--doc/ci/merge_request_pipelines/img/merge_request.pngbin14044 -> 5292 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.pngbin10152 -> 4394 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.pngbin10889 -> 5296 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/index.md50
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.pngbin19577 -> 7654 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.pngbin24267 -> 9012 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.pngbin0 -> 23899 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.pngbin0 -> 13038 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.pngbin0 -> 6152 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.pngbin8365 -> 3548 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.pngbin10323 -> 4337 bytes
-rw-r--r--doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md108
-rw-r--r--doc/ci/multi_project_pipelines.md20
-rw-r--r--doc/ci/pipelines.md21
-rw-r--r--doc/ci/quick_start/README.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/review_apps/img/review_button.pngbin58187 -> 16524 bytes
-rw-r--r--doc/ci/review_apps/index.md4
-rw-r--r--doc/ci/runners/README.md26
-rw-r--r--doc/ci/services/mysql.md6
-rw-r--r--doc/ci/services/postgres.md7
-rw-r--r--doc/ci/ssh_keys/README.md82
-rw-r--r--doc/ci/triggers/README.md14
-rw-r--r--doc/ci/variables/README.md105
-rw-r--r--doc/ci/variables/img/custom_variables_output.pngbin89185 -> 32344 bytes
-rw-r--r--doc/ci/variables/img/new_custom_variables_example.pngbin216497 -> 68252 bytes
-rw-r--r--doc/ci/variables/img/override_variable_manual_pipeline.pngbin41714 -> 13885 bytes
-rw-r--r--doc/ci/variables/img/variable_types_usage_example.pngbin67003 -> 21983 bytes
-rw-r--r--doc/ci/variables/predefined_variables.md2
-rw-r--r--doc/ci/yaml/README.md147
-rw-r--r--doc/customization/branded_login_page.md35
-rw-r--r--doc/customization/branded_page_and_email_header.md34
-rw-r--r--doc/customization/favicon.md31
-rw-r--r--doc/customization/help_message.md37
-rw-r--r--doc/customization/help_message/help_text.pngbin86118 -> 38521 bytes
-rw-r--r--doc/customization/index.md20
-rw-r--r--doc/customization/issue_and_merge_request_template.md4
-rw-r--r--doc/customization/libravatar.md65
-rw-r--r--doc/customization/new_project_page.md36
-rw-r--r--doc/customization/system_header_and_footer_messages/appearance.pngbin124214 -> 32295 bytes
-rw-r--r--doc/development/README.md12
-rw-r--r--doc/development/architecture.md24
-rw-r--r--doc/development/automatic_ce_ee_merge.md15
-rw-r--r--doc/development/chaos_endpoints.md37
-rw-r--r--doc/development/code_review.md61
-rw-r--r--doc/development/contributing/community_roles.md2
-rw-r--r--doc/development/contributing/issue_workflow.md117
-rw-r--r--doc/development/contributing/merge_request_workflow.md34
-rw-r--r--doc/development/contributing/style_guides.md1
-rw-r--r--doc/development/database_debugging.md34
-rw-r--r--doc/development/database_review.md134
-rw-r--r--doc/development/diffs.md2
-rw-r--r--doc/development/documentation/feature-change-workflow.md30
-rw-r--r--doc/development/documentation/improvement-workflow.md6
-rw-r--r--doc/development/documentation/index.md146
-rw-r--r--doc/development/documentation/styleguide.md258
-rw-r--r--doc/development/ee_features.md46
-rw-r--r--doc/development/elasticsearch.md30
-rw-r--r--doc/development/fe_guide/architecture.md2
-rw-r--r--doc/development/fe_guide/axios.md5
-rw-r--r--doc/development/fe_guide/components.md45
-rw-r--r--doc/development/fe_guide/event_tracking.md97
-rw-r--r--doc/development/fe_guide/frontend_faq.md6
-rw-r--r--doc/development/fe_guide/graphql.md3
-rw-r--r--doc/development/fe_guide/icons.md16
-rw-r--r--doc/development/fe_guide/index.md2
-rw-r--r--doc/development/fe_guide/performance.md40
-rw-r--r--doc/development/fe_guide/style_guide_js.md863
-rw-r--r--doc/development/fe_guide/style_guide_scss.md3
-rw-r--r--doc/development/fe_guide/vuex.md44
-rw-r--r--doc/development/file_storage.md4
-rw-r--r--doc/development/filtering_by_label.md166
-rw-r--r--doc/development/geo.md26
-rw-r--r--doc/development/git_object_deduplication.md110
-rw-r--r--doc/development/go_guide/index.md102
-rw-r--r--doc/development/gotchas.md2
-rw-r--r--doc/development/hash_indexes.md2
-rw-r--r--doc/development/i18n/externalization.md221
-rw-r--r--doc/development/i18n/proofreader.md1
-rw-r--r--doc/development/img/architecture_simplified.pngbin61590 -> 34330 bytes
-rw-r--r--doc/development/img/distributed_tracing_jaeger_ui.pngbin1032713 -> 546331 bytes
-rw-r--r--doc/development/img/distributed_tracing_performance_bar.pngbin108809 -> 34370 bytes
-rw-r--r--doc/development/import_export.md5
-rw-r--r--doc/development/instrumentation.md2
-rw-r--r--doc/development/interacting_components.md29
-rw-r--r--doc/development/licensed_feature_availability.md6
-rw-r--r--doc/development/migration_style_guide.md11
-rw-r--r--doc/development/module_with_instance_variables.md20
-rw-r--r--doc/development/new_fe_guide/dependencies.md12
-rw-r--r--doc/development/new_fe_guide/development/accessibility.md5
-rw-r--r--doc/development/new_fe_guide/development/performance.md2
-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/index.md2
-rw-r--r--doc/development/new_fe_guide/style/javascript.md7
-rw-r--r--doc/development/performance.md8
-rw-r--r--doc/development/policies.md4
-rw-r--r--doc/development/query_recorder.md7
-rw-r--r--doc/development/rake_tasks.md3
-rw-r--r--doc/development/reusing_abstractions.md11
-rw-r--r--doc/development/sha1_as_binary.md2
-rw-r--r--doc/development/shell_scripting_guide/index.md118
-rw-r--r--doc/development/sql.md30
-rw-r--r--doc/development/testing_guide/best_practices.md24
-rw-r--r--doc/development/testing_guide/ci.md1
-rw-r--r--doc/development/testing_guide/end_to_end/index.md23
-rw-r--r--doc/development/testing_guide/end_to_end/page_objects.md7
-rw-r--r--doc/development/testing_guide/end_to_end/quick_start_guide.md42
-rw-r--r--doc/development/testing_guide/end_to_end/style_guide.md48
-rw-r--r--doc/development/testing_guide/flaky_tests.md4
-rw-r--r--doc/development/testing_guide/frontend_testing.md540
-rw-r--r--doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.pngbin64862 -> 0 bytes
-rw-r--r--doc/development/testing_guide/img/review_apps_cicd_architecture.pngbin136431 -> 0 bytes
-rw-r--r--doc/development/testing_guide/review_apps.md66
-rw-r--r--doc/development/testing_guide/testing_levels.md3
-rw-r--r--doc/development/understanding_explain_plans.md4
-rw-r--r--doc/development/utilities.md128
-rw-r--r--doc/development/ux_guide/animation.md4
-rw-r--r--doc/development/ux_guide/illustrations.md4
-rw-r--r--doc/development/verifying_database_capabilities.md10
-rw-r--r--doc/development/what_requires_downtime.md27
-rw-r--r--doc/gitlab-basics/README.md27
-rw-r--r--doc/gitlab-basics/add-file.md92
-rw-r--r--doc/gitlab-basics/add-image.md59
-rw-r--r--doc/gitlab-basics/add-merge-request.md39
-rw-r--r--doc/gitlab-basics/basic-git-commands.md6
-rw-r--r--doc/gitlab-basics/command-line-commands.md148
-rw-r--r--doc/gitlab-basics/create-branch.md20
-rw-r--r--doc/gitlab-basics/create-project.md119
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md27
-rw-r--r--doc/gitlab-basics/fork-project.md19
-rw-r--r--doc/gitlab-basics/start-using-git.md187
-rw-r--r--doc/install/aws/index.md4
-rw-r--r--doc/install/azure/index.md7
-rw-r--r--doc/install/docker.md2
-rw-r--r--doc/install/installation.md16
-rw-r--r--doc/install/requirements.md36
-rw-r--r--doc/integration/README.md4
-rw-r--r--doc/integration/elasticsearch.md31
-rw-r--r--doc/integration/facebook.md6
-rw-r--r--doc/integration/img/limit_namespace_filter.pngbin12777 -> 4531 bytes
-rw-r--r--doc/integration/img/limit_namespaces_projects_options.pngbin22650 -> 7850 bytes
-rw-r--r--doc/integration/img/salesforce_app_details.pngbin116022 -> 33688 bytes
-rw-r--r--doc/integration/img/salesforce_app_secret_details.pngbin199643 -> 56921 bytes
-rw-r--r--doc/integration/img/salesforce_oauth_app_details.pngbin171542 -> 50259 bytes
-rw-r--r--doc/integration/jenkins.md3
-rw-r--r--doc/integration/jenkins_deprecated.md2
-rw-r--r--doc/integration/jira_development_panel.md79
-rw-r--r--doc/integration/kerberos.md10
-rw-r--r--doc/integration/slack.md4
-rw-r--r--doc/legal/corporate_contributor_license_agreement.md26
-rw-r--r--doc/legal/individual_contributor_license_agreement.md26
-rw-r--r--doc/migrate_ci_to_ce/README.md2
-rw-r--r--doc/public_access/public_access.md4
-rw-r--r--doc/raketasks/backup_restore.md41
-rw-r--r--doc/raketasks/cleanup.md10
-rw-r--r--doc/raketasks/web_hooks.md5
-rw-r--r--doc/security/README.md2
-rw-r--r--doc/security/img/outbound_requests_section.pngbin7314 -> 0 bytes
-rw-r--r--doc/security/img/outbound_requests_section_v12_2.pngbin0 -> 6303 bytes
-rw-r--r--doc/security/img/whitelist.pngbin0 -> 10650 bytes
-rw-r--r--doc/security/rack_attack.md79
-rw-r--r--doc/security/rate_limits.md33
-rw-r--r--doc/security/reset_root_password.md2
-rw-r--r--doc/security/webhooks.md41
-rw-r--r--doc/subscriptions/billing_table.pngbin28321 -> 0 bytes
-rw-r--r--doc/subscriptions/img/additional_minutes.pngbin0 -> 12668 bytes
-rw-r--r--doc/subscriptions/img/buy_btn.pngbin0 -> 10917 bytes
-rw-r--r--doc/subscriptions/img/buy_minutes_card.pngbin0 -> 11875 bytes
-rw-r--r--doc/subscriptions/index.md312
-rw-r--r--doc/system_hooks/system_hooks.md5
-rw-r--r--doc/tools/email.md4
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md297
-rw-r--r--doc/topics/autodevops/quick_start_guide.md18
-rw-r--r--doc/topics/git/index.md6
-rw-r--r--doc/topics/git/migrate_to_git_lfs/index.md174
-rw-r--r--doc/topics/git/numerous_undo_possibilities_in_git/index.md92
-rw-r--r--doc/topics/git/partial_clone.md147
-rw-r--r--doc/topics/git/troubleshooting_git.md10
-rw-r--r--doc/topics/git/useful_git_commands.md210
-rw-r--r--doc/university/README.md4
-rw-r--r--doc/university/training/end-user/README.md231
-rw-r--r--doc/university/training/topics/bisect.md4
-rw-r--r--doc/university/training/topics/cherry_picking.md6
-rw-r--r--doc/university/training/topics/feature_branching.md4
-rw-r--r--doc/university/training/topics/getting_started.md23
-rw-r--r--doc/university/training/topics/git_add.md34
-rw-r--r--doc/university/training/topics/git_log.md30
-rw-r--r--doc/university/training/topics/merge_conflicts.md4
-rw-r--r--doc/university/training/topics/merge_requests.md2
-rw-r--r--doc/university/training/topics/rollback_commits.md26
-rw-r--r--doc/university/training/topics/stash.md62
-rw-r--r--doc/university/training/topics/tags.md12
-rw-r--r--doc/university/training/topics/unstage.md30
-rw-r--r--doc/update/README.md24
-rw-r--r--doc/update/mysql_to_postgresql.md108
-rw-r--r--doc/update/upgrading_from_source.md18
-rw-r--r--doc/user/abuse_reports.md22
-rw-r--r--doc/user/admin_area/abuse_reports.md68
-rw-r--r--doc/user/admin_area/broadcast_messages.md16
-rw-r--r--doc/user/admin_area/custom_project_templates.md45
-rw-r--r--doc/user/admin_area/img/abuse_report_blocked_user.pngbin13821 -> 7393 bytes
-rw-r--r--doc/user/admin_area/img/abuse_reports_page.pngbin215813 -> 86180 bytes
-rw-r--r--doc/user/admin_area/img/broadcast_messages.pngbin68535 -> 21458 bytes
-rw-r--r--doc/user/admin_area/img/license_details.pngbin119290 -> 65130 bytes
-rw-r--r--doc/user/admin_area/index.md2
-rw-r--r--doc/user/admin_area/license.md12
-rw-r--r--doc/user/admin_area/settings/account_and_limit_settings.md33
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md43
-rw-r--r--doc/user/admin_area/settings/email.md21
-rw-r--r--doc/user/admin_area/settings/img/additional_minutes.pngbin32045 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/admin_required_pipeline.pngbin64548 -> 22587 bytes
-rw-r--r--doc/user/admin_area/settings/img/buy_btn.pngbin26960 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/buy_minutes_card.pngbin29329 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/email_confirmation.pngbin0 -> 5239 bytes
-rw-r--r--doc/user/admin_area/settings/img/email_settings.pngbin53267 -> 0 bytes
-rw-r--r--doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.pngbin0 -> 58254 bytes
-rw-r--r--doc/user/admin_area/settings/img/user_and_ip_rate_limits.pngbin0 -> 64725 bytes
-rw-r--r--doc/user/admin_area/settings/index.md1
-rw-r--r--doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md22
-rw-r--r--doc/user/admin_area/settings/sign_up_restrictions.md23
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md4
-rw-r--r--doc/user/admin_area/settings/user_and_ip_rate_limits.md32
-rw-r--r--doc/user/analytics/cycle_analytics.md182
-rw-r--r--doc/user/analytics/index.md22
-rw-r--r--doc/user/application_security/container_scanning/index.md8
-rw-r--r--doc/user/application_security/dast/index.md43
-rw-r--r--doc/user/application_security/dependency_list/img/dependency_list_v12_2.pngbin0 -> 207114 bytes
-rw-r--r--doc/user/application_security/dependency_list/index.md49
-rw-r--r--doc/user/application_security/dependency_scanning/index.md43
-rw-r--r--doc/user/application_security/img/create_issue_with_list_hover.pngbin106954 -> 36833 bytes
-rw-r--r--doc/user/application_security/img/dismissed_info.pngbin20244 -> 7847 bytes
-rw-r--r--doc/user/application_security/img/interactive_reports.pngbin93910 -> 29814 bytes
-rw-r--r--doc/user/application_security/img/vulnerability_solution.pngbin3421 -> 3419 bytes
-rw-r--r--doc/user/application_security/index.md77
-rw-r--r--doc/user/application_security/license_management/img/license_management_add_license.pngbin87049 -> 24247 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_decision.pngbin5981 -> 5975 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_search.pngbin104278 -> 28237 bytes
-rw-r--r--doc/user/application_security/license_management/img/license_management_settings.pngbin171592 -> 44790 bytes
-rw-r--r--doc/user/application_security/license_management/index.md16
-rw-r--r--doc/user/application_security/sast/analyzers.md144
-rw-r--r--doc/user/application_security/sast/index.md69
-rw-r--r--doc/user/application_security/security_dashboard/img/dashboard.pngbin58585 -> 0 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/group_security_dashboard.pngbin0 -> 68332 bytes
-rw-r--r--doc/user/application_security/security_dashboard/img/project_security_dashboard.pngbin126356 -> 73425 bytes
-rw-r--r--doc/user/application_security/security_dashboard/index.md30
-rw-r--r--doc/user/asciidoc.md4
-rw-r--r--doc/user/clusters/applications.md40
-rw-r--r--doc/user/clusters/img/jupyter-gitclone.pngbin64120 -> 22862 bytes
-rw-r--r--doc/user/discussions/img/automatically_resolve_outdated_discussions.pngbin57642 -> 21735 bytes
-rw-r--r--doc/user/discussions/img/btn_new_issue_for_all_threads.pngbin19431 -> 6716 bytes
-rw-r--r--doc/user/discussions/img/commit_comment_mr_context.pngbin365956 -> 103558 bytes
-rw-r--r--doc/user/discussions/img/commit_comment_mr_discussions_tab.pngbin276385 -> 76927 bytes
-rw-r--r--doc/user/discussions/img/discussion_comment.pngbin60561 -> 18323 bytes
-rw-r--r--doc/user/discussions/img/image_resolved_discussion.pngbin199539 -> 55713 bytes
-rw-r--r--doc/user/discussions/img/make_suggestion.pngbin28447 -> 115084 bytes
-rw-r--r--doc/user/discussions/img/merge_request_commits_tab.pngbin169010 -> 54318 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve.pngbin247868 -> 63623 bytes
-rw-r--r--doc/user/discussions/img/mr_review_resolve2.pngbin232589 -> 62012 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment.pngbin183733 -> 50468 bytes
-rw-r--r--doc/user/discussions/img/mr_review_second_comment_added.pngbin161924 -> 46448 bytes
-rw-r--r--doc/user/discussions/img/mr_review_start.pngbin274186 -> 80651 bytes
-rw-r--r--doc/user/discussions/img/mr_review_unresolve.pngbin298513 -> 78767 bytes
-rw-r--r--doc/user/discussions/img/mr_review_unresolve2.pngbin8976 -> 8971 bytes
-rw-r--r--doc/user/discussions/img/multi-line-suggestion-preview.pngbin61692 -> 16919 bytes
-rw-r--r--doc/user/discussions/img/multi-line-suggestion-syntax.pngbin29753 -> 8831 bytes
-rw-r--r--doc/user/discussions/img/new_issue_for_thread.pngbin21119 -> 6725 bytes
-rw-r--r--doc/user/discussions/img/onion_skin_view.pngbin95809 -> 29955 bytes
-rw-r--r--doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.pngbin53804 -> 18257 bytes
-rw-r--r--doc/user/discussions/img/pending_review_comment.pngbin247865 -> 75625 bytes
-rw-r--r--doc/user/discussions/img/preview_issue_for_thread.pngbin119608 -> 33240 bytes
-rw-r--r--doc/user/discussions/img/preview_issue_for_threads.pngbin131506 -> 36192 bytes
-rw-r--r--doc/user/discussions/img/reply_to_comment_button.pngbin17224 -> 7602 bytes
-rw-r--r--doc/user/discussions/img/resolve_comment_button.pngbin11680 -> 3784 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_button.pngbin34562 -> 9842 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_issue_notice.pngbin44429 -> 13845 bytes
-rw-r--r--doc/user/discussions/img/resolve_thread_open_issue.pngbin57089 -> 21576 bytes
-rw-r--r--doc/user/discussions/img/review_comment_quickactions.pngbin270228 -> 78891 bytes
-rw-r--r--doc/user/discussions/img/review_preview.pngbin84789 -> 27664 bytes
-rw-r--r--doc/user/discussions/img/suggestion.pngbin39775 -> 149758 bytes
-rw-r--r--doc/user/discussions/img/swipe_view.pngbin71076 -> 20840 bytes
-rw-r--r--doc/user/discussions/img/thread_view.pngbin556169 -> 162019 bytes
-rw-r--r--doc/user/discussions/img/threads_resolved.pngbin12667 -> 3976 bytes
-rw-r--r--doc/user/discussions/img/two_up_view.pngbin118688 -> 34612 bytes
-rw-r--r--doc/user/discussions/index.md27
-rw-r--r--doc/user/gitlab_com/index.md174
-rw-r--r--doc/user/group/bulk_editing/img/bulk-editing.pngbin0 -> 99844 bytes
-rw-r--r--doc/user/group/bulk_editing/index.md31
-rw-r--r--doc/user/group/clusters/index.md10
-rw-r--r--doc/user/group/contribution_analytics/index.md3
-rw-r--r--doc/user/group/epics/img/bulk_editing.pngbin0 -> 72912 bytes
-rw-r--r--doc/user/group/epics/img/button_reopen_epic.pngbin14153 -> 14148 bytes
-rw-r--r--doc/user/group/epics/img/containing_epic.pngbin159939 -> 159919 bytes
-rw-r--r--doc/user/group/epics/img/epic_view.pngbin176759 -> 176740 bytes
-rw-r--r--doc/user/group/epics/index.md16
-rw-r--r--doc/user/group/img/group_file_template_dropdown.pngbin9519 -> 9516 bytes
-rw-r--r--doc/user/group/img/group_file_template_settings.pngbin6217 -> 6210 bytes
-rw-r--r--doc/user/group/index.md71
-rw-r--r--doc/user/group/insights/img/insights_example_stacked_bar_chart.pngbin86062 -> 40798 bytes
-rw-r--r--doc/user/group/insights/img/insights_group_configuration.pngbin24107 -> 14125 bytes
-rw-r--r--doc/user/group/insights/img/insights_sidebar_link.pngbin18826 -> 12327 bytes
-rw-r--r--doc/user/group/roadmap/img/roadmap_view.pngbin49774 -> 49757 bytes
-rw-r--r--doc/user/group/saml_sso/img/group_saml_configuration_information.pngbin50435 -> 16489 bytes
-rw-r--r--doc/user/group/saml_sso/img/group_saml_settings.pngbin89399 -> 50300 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_advanced.pngbin21568 -> 6920 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_attribute_mapping.pngbin95420 -> 34642 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_name_identifier_mapping.pngbin0 -> 59409 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_provisioning_status.pngbin0 -> 7756 bytes
-rw-r--r--doc/user/group/saml_sso/img/scim_token.pngbin154318 -> 57095 bytes
-rw-r--r--doc/user/group/saml_sso/img/unlink_group_saml.pngbin27077 -> 9399 bytes
-rw-r--r--doc/user/group/saml_sso/index.md2
-rw-r--r--doc/user/group/saml_sso/scim_setup.md76
-rw-r--r--doc/user/group/subgroups/index.md64
-rw-r--r--doc/user/index.md2
-rw-r--r--doc/user/instance/clusters/index.md1
-rw-r--r--doc/user/markdown.md127
-rw-r--r--doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.pngbin3948 -> 3922 bytes
-rw-r--r--doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.pngbin30837 -> 30826 bytes
-rw-r--r--doc/user/permissions.md29
-rw-r--r--doc/user/profile/account/delete_account.md90
-rw-r--r--doc/user/profile/account/two_factor_authentication.md18
-rw-r--r--doc/user/profile/active_sessions.md23
-rw-r--r--doc/user/profile/img/active_sessions_list.pngbin19360 -> 18633 bytes
-rw-r--r--doc/user/profile/index.md125
-rw-r--r--doc/user/profile/personal_access_tokens.md18
-rw-r--r--doc/user/profile/preferences.md40
-rw-r--r--doc/user/project/badges.md16
-rw-r--r--doc/user/project/bulk_editing.md23
-rw-r--r--doc/user/project/clusters/eks_and_gitlab/index.md2
-rw-r--r--doc/user/project/clusters/img/k8s_cluster_monitoring.pngbin43150 -> 43141 bytes
-rw-r--r--doc/user/project/clusters/index.md501
-rw-r--r--doc/user/project/clusters/kubernetes_pod_logs.md4
-rw-r--r--doc/user/project/clusters/runbooks/index.md4
-rw-r--r--doc/user/project/clusters/serverless/img/dns-entry.pngbin66116 -> 21299 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/function-details-loaded.pngbin93515 -> 34302 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/function-endpoint.pngbin42436 -> 14641 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/function-execution.pngbin223762 -> 73788 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/install-knative.pngbin86225 -> 27104 bytes
-rw-r--r--doc/user/project/clusters/serverless/img/serverless-page.pngbin191568 -> 50711 bytes
-rw-r--r--doc/user/project/clusters/serverless/index.md44
-rw-r--r--doc/user/project/container_registry.md71
-rw-r--r--doc/user/project/cycle_analytics.md162
-rw-r--r--doc/user/project/deploy_tokens/img/deploy_tokens.pngbin218635 -> 62979 bytes
-rw-r--r--doc/user/project/description_templates.md16
-rw-r--r--doc/user/project/file_lock.md2
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/autocomplete_characters_example1_v12_0.pngbin17510 -> 17510 bytes
-rw-r--r--[-rwxr-xr-x]doc/user/project/img/autocomplete_characters_example2_v12_0.pngbin14623 -> 14623 bytes
-rw-r--r--doc/user/project/img/container_registry.pngbin35202 -> 0 bytes
-rw-r--r--doc/user/project/img/cycle_analytics_landing_page.pngbin184131 -> 0 bytes
-rw-r--r--doc/user/project/img/deploy_boards_kubernetes_label.pngbin43978 -> 13750 bytes
-rw-r--r--doc/user/project/img/description_templates_default_settings.pngbin26395 -> 0 bytes
-rw-r--r--doc/user/project/img/description_templates_issue_settings.pngbin0 -> 34698 bytes
-rw-r--r--doc/user/project/img/description_templates_merge_request_settings.pngbin0 -> 144128 bytes
-rw-r--r--doc/user/project/img/file_lock.pngbin46281 -> 20461 bytes
-rw-r--r--doc/user/project/img/file_lock_merge_request_error_message.pngbin16254 -> 8219 bytes
-rw-r--r--doc/user/project/img/file_lock_repository_view.pngbin17644 -> 9805 bytes
-rw-r--r--doc/user/project/img/issue_boards_multiple.pngbin68373 -> 21635 bytes
-rw-r--r--doc/user/project/img/key_value_labels.pngbin6208 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_default.pngbin35620 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_default_v12_1.pngbin0 -> 58694 bytes
-rw-r--r--doc/user/project/img/labels_delete_v12_1.pngbin0 -> 9531 bytes
-rw-r--r--doc/user/project/img/labels_drag_priority_v12_1.gifbin0 -> 958437 bytes
-rw-r--r--doc/user/project/img/labels_epic_sidebar.pngbin47869 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_epic_sidebar_v12_1.pngbin0 -> 102375 bytes
-rw-r--r--doc/user/project/img/labels_generate_default.pngbin25797 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_generate_default_v12_1.pngbin0 -> 69038 bytes
-rw-r--r--doc/user/project/img/labels_group_issues.pngbin92070 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_group_issues_v12_1.pngbin0 -> 118770 bytes
-rw-r--r--doc/user/project/img/labels_key_value_v12_1.pngbin0 -> 102912 bytes
-rw-r--r--doc/user/project/img/labels_list.pngbin75215 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_list_v12_1.pngbin0 -> 104907 bytes
-rw-r--r--doc/user/project/img/labels_new_label_from_sidebar.gif (renamed from doc/user/project/img/new_label_from_sidebar.gif)bin759243 -> 759243 bytes
-rw-r--r--doc/user/project/img/labels_prioritized.pngbin56353 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_prioritized_v12_1.pngbin0 -> 51751 bytes
-rw-r--r--doc/user/project/img/labels_promotion.pngbin44021 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_promotion_v12_1.pngbin0 -> 9600 bytes
-rw-r--r--doc/user/project/img/labels_subscriptions.pngbin31716 -> 0 bytes
-rw-r--r--doc/user/project/img/labels_subscriptions_v12_1.pngbin0 -> 48037 bytes
-rw-r--r--doc/user/project/img/service_desk_disabled.pngbin18654 -> 11708 bytes
-rw-r--r--doc/user/project/img/service_desk_enabled.pngbin33243 -> 21514 bytes
-rw-r--r--doc/user/project/img/service_desk_issue_tracker.pngbin95087 -> 60824 bytes
-rw-r--r--doc/user/project/import/bitbucket.md18
-rw-r--r--doc/user/project/import/bitbucket_server.md12
-rw-r--r--doc/user/project/import/gemnasium.md42
-rw-r--r--doc/user/project/import/gitlab_com.md14
-rw-r--r--doc/user/project/import/img/bitbucket_server_import_select_project.pngbin19427 -> 0 bytes
-rw-r--r--doc/user/project/import/img/bitbucket_server_import_select_project_v12_3.pngbin0 -> 47059 bytes
-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/import/index.md2
-rw-r--r--doc/user/project/import/manifest.md5
-rw-r--r--doc/user/project/import/svn.md10
-rw-r--r--doc/user/project/import/tfs.md45
-rw-r--r--doc/user/project/import/tfvc.md46
-rw-r--r--doc/user/project/index.md19
-rw-r--r--doc/user/project/insights/img/insights_example_bar_chart.pngbin21767 -> 12118 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_bar_time_series_chart.pngbin34068 -> 21060 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_line_chart.pngbin120678 -> 50808 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_pie_chart.pngbin10889 -> 6985 bytes
-rw-r--r--doc/user/project/insights/img/insights_example_stacked_bar_chart.pngbin81587 -> 37751 bytes
-rw-r--r--doc/user/project/insights/img/insights_sidebar_link.pngbin21463 -> 13776 bytes
-rw-r--r--doc/user/project/insights/img/project_insights.pngbin41210 -> 26534 bytes
-rw-r--r--doc/user/project/integrations/img/download_as_csv.pngbin0 -> 33801 bytes
-rw-r--r--doc/user/project/integrations/img/embed_metrics.pngbin0 -> 102552 bytes
-rw-r--r--doc/user/project/integrations/img/generate_link_to_chart.pngbin0 -> 35573 bytes
-rw-r--r--doc/user/project/integrations/img/github_configuration.pngbin12515 -> 12258 bytes
-rw-r--r--doc/user/project/integrations/img/jira_add_user_to_group.pngbin266180 -> 83799 bytes
-rw-r--r--doc/user/project/integrations/img/jira_added_user_to_group.pngbin82473 -> 21646 bytes
-rw-r--r--doc/user/project/integrations/img/jira_api_token.pngbin21318 -> 7407 bytes
-rw-r--r--doc/user/project/integrations/img/jira_api_token_menu.pngbin41876 -> 14353 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_group.pngbin262453 -> 70535 bytes
-rw-r--r--doc/user/project/integrations/img/jira_create_new_user.pngbin173516 -> 40275 bytes
-rw-r--r--doc/user/project/integrations/img/jira_group_access.pngbin112706 -> 32162 bytes
-rw-r--r--doc/user/project/integrations/img/jira_issue_reference.pngbin64064 -> 19583 bytes
-rw-r--r--doc/user/project/integrations/img/jira_merge_request_close.pngbin64305 -> 21130 bytes
-rw-r--r--doc/user/project/integrations/img/jira_service_page.pngbin70807 -> 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/img/jira_user_management_link.pngbin206155 -> 57628 bytes
-rw-r--r--doc/user/project/integrations/img/mattermost_configuration.pngbin67672 -> 20459 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.pngbin0 -> 19349 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.pngbin0 -> 6871 bytes
-rw-r--r--doc/user/project/integrations/img/prometheus_service_alerts.pngbin40727 -> 16084 bytes
-rw-r--r--doc/user/project/integrations/img/slack_configuration.pngbin64873 -> 19812 bytes
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/integrations/jira_cloud_configuration.md2
-rw-r--r--doc/user/project/integrations/jira_server_configuration.md4
-rw-r--r--doc/user/project/integrations/mattermost.md2
-rw-r--r--doc/user/project/integrations/mattermost_slash_commands.md46
-rw-r--r--doc/user/project/integrations/mock_ci.md4
-rw-r--r--doc/user/project/integrations/prometheus.md103
-rw-r--r--doc/user/project/integrations/prometheus_library/haproxy.md1
-rw-r--r--doc/user/project/integrations/redmine.md18
-rw-r--r--doc/user/project/integrations/webhooks.md14
-rw-r--r--doc/user/project/issue_board.md12
-rw-r--r--doc/user/project/issues/confidential_issues.md46
-rw-r--r--doc/user/project/issues/csv_export.md2
-rw-r--r--doc/user/project/issues/design_management.md79
-rw-r--r--doc/user/project/issues/img/adding_note_to_design_1.pngbin0 -> 486937 bytes
-rw-r--r--doc/user/project/issues/img/adding_note_to_design_2.pngbin0 -> 495888 bytes
-rw-r--r--doc/user/project/issues/img/comment-or-discussion.pngbin8378 -> 4530 bytes
-rw-r--r--doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.pngbin0 -> 38985 bytes
-rw-r--r--doc/user/project/issues/img/confidential_mr_dropdown_v12_1.pngbin0 -> 40672 bytes
-rw-r--r--doc/user/project/issues/img/create_mr_from_issue.pngbin21286 -> 8632 bytes
-rw-r--r--doc/user/project/issues/img/design_management_v12_2.pngbin0 -> 115179 bytes
-rw-r--r--doc/user/project/issues/img/issues_main_view_numbered.pngbin278285 -> 107528 bytes
-rw-r--r--doc/user/project/issues/img/reopen-issue.pngbin2522 -> 1230 bytes
-rw-r--r--doc/user/project/issues/img/report-abuse.pngbin1620 -> 1255 bytes
-rw-r--r--doc/user/project/issues/img/show-all-activity.pngbin8119 -> 4501 bytes
-rw-r--r--doc/user/project/issues/index.md11
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md7
-rw-r--r--doc/user/project/issues/managing_issues.md4
-rw-r--r--doc/user/project/issues/related_issues.md2
-rw-r--r--doc/user/project/issues/sorting_issue_lists.md30
-rw-r--r--doc/user/project/labels.md185
-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/members/share_project_with_groups.md6
-rw-r--r--doc/user/project/merge_requests/code_quality.md80
-rw-r--r--doc/user/project/merge_requests/code_quality_diff.md1
-rw-r--r--doc/user/project/merge_requests/fast_forward_merge.md10
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration.pngbin11028 -> 10806 bytes
-rw-r--r--doc/user/project/merge_requests/img/allow_collaboration_after_save.pngbin5415 -> 5410 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_mr_widget.pngbin76524 -> 22175 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_premium_project_edit.pngbin47371 -> 14507 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_starter_project_edit.pngbin52442 -> 16217 bytes
-rw-r--r--doc/user/project/merge_requests/img/approvals_starter_project_empty.pngbin50820 -> 16982 bytes
-rw-r--r--doc/user/project/merge_requests/img/approve.pngbin19329 -> 6655 bytes
-rw-r--r--doc/user/project/merge_requests/img/approve_additionally.pngbin22700 -> 7803 bytes
-rw-r--r--doc/user/project/merge_requests/img/create_from_email.pngbin112256 -> 69480 bytes
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.pngbin0 -> 19461 bytes
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_edit_v12_2.pngbin0 -> 19302 bytes
-rw-r--r--doc/user/project/merge_requests/img/cross_project_dependencies_view_v12_2.pngbin0 -> 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/filter_approver_merge_requests.pngbin90764 -> 30525 bytes
-rw-r--r--doc/user/project/merge_requests/img/filter_wip_merge_requests.pngbin28572 -> 11017 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/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.pngbin19986 -> 7433 bytes
-rw-r--r--doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.pngbin20867 -> 7479 bytes
-rw-r--r--doc/user/project/merge_requests/img/remove_approval.pngbin21902 -> 7533 bytes
-rw-r--r--doc/user/project/merge_requests/img/squash_mr_message.pngbin150302 -> 37675 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_blocked_accept_button.pngbin7141 -> 4970 bytes
-rw-r--r--doc/user/project/merge_requests/index.md96
-rw-r--r--doc/user/project/merge_requests/merge_request_approvals.md48
-rw-r--r--doc/user/project/merge_requests/merge_request_dependencies.md142
-rw-r--r--doc/user/project/milestones/burndown_charts.md20
-rw-r--r--doc/user/project/milestones/index.md16
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md30
-rw-r--r--doc/user/project/operations/feature_flags.md75
-rw-r--r--doc/user/project/operations/img/error_tracking_list.pngbin230740 -> 60762 bytes
-rw-r--r--doc/user/project/operations/img/external_dashboard_link.pngbin63282 -> 20192 bytes
-rw-r--r--doc/user/project/operations/img/external_dashboard_settings.pngbin103474 -> 33248 bytes
-rw-r--r--doc/user/project/operations/img/feature_flags_list.pngbin14963 -> 22548 bytes
-rw-r--r--doc/user/project/operations/img/specs_list.pngbin43574 -> 70117 bytes
-rw-r--r--doc/user/project/operations/img/target_users_v12_2.pngbin0 -> 42768 bytes
-rw-r--r--doc/user/project/packages/img/npm_package_view.pngbin24443 -> 10349 bytes
-rw-r--r--doc/user/project/packages/npm_registry.md53
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.pngbin194433 -> 56899 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.pngbin35040 -> 11642 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.pngbin191712 -> 59544 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md19
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md25
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md2
-rw-r--r--doc/user/project/pages/getting_started_part_two.md44
-rw-r--r--doc/user/project/pages/img/pages_project_templates_v11_8.pngbin69702 -> 69675 bytes
-rw-r--r--doc/user/project/pages/img/remove_pages.pngbin58035 -> 12954 bytes
-rw-r--r--doc/user/project/pages/index.md2
-rw-r--r--doc/user/project/pages/introduction.md26
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedule_variables.pngbin17292 -> 6300 bytes
-rw-r--r--doc/user/project/pipelines/img/pipeline_schedules_new_form.pngbin62141 -> 20090 bytes
-rw-r--r--doc/user/project/pipelines/job_artifacts.md57
-rw-r--r--doc/user/project/pipelines/schedules.md22
-rw-r--r--doc/user/project/pipelines/settings.md27
-rw-r--r--doc/user/project/protected_branches.md39
-rw-r--r--doc/user/project/protected_tags.md34
-rw-r--r--doc/user/project/quick_actions.md60
-rw-r--r--doc/user/project/releases/img/releases.pngbin126093 -> 43776 bytes
-rw-r--r--doc/user/project/releases/index.md16
-rw-r--r--doc/user/project/repository/branches/img/compare_branches.png (renamed from doc/user/project/repository/img/compare_branches.png)bin131046 -> 131046 bytes
-rw-r--r--doc/user/project/repository/branches/index.md61
-rw-r--r--doc/user/project/repository/gpg_signed_commits/index.md75
-rw-r--r--doc/user/project/repository/img/download_source_code.pngbin61467 -> 19577 bytes
-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.md54
-rw-r--r--doc/user/project/repository/reducing_the_repo_size_using_git.md16
-rw-r--r--doc/user/project/repository/web_editor.md18
-rw-r--r--doc/user/project/settings/img/general_settings.pngbin154764 -> 46077 bytes
-rw-r--r--doc/user/project/settings/img/import_export_download_export.pngbin25905 -> 14867 bytes
-rw-r--r--doc/user/project/settings/img/import_export_export_button.pngbin25102 -> 14530 bytes
-rw-r--r--doc/user/project/settings/img/import_export_mail_link.pngbin7561 -> 6307 bytes
-rw-r--r--doc/user/project/settings/img/import_export_new_project.pngbin13202 -> 8011 bytes
-rw-r--r--doc/user/project/settings/img/import_export_select_file.pngbin20580 -> 11254 bytes
-rw-r--r--doc/user/project/settings/import_export.md6
-rw-r--r--doc/user/project/settings/index.md6
-rw-r--r--doc/user/project/web_ide/img/terminal_status.pngbin8129 -> 3709 bytes
-rw-r--r--doc/user/project/wiki/index.md9
-rw-r--r--doc/user/reserved_names.md24
-rw-r--r--doc/user/search/advanced_search_syntax.md6
-rw-r--r--doc/user/search/index.md10
-rw-r--r--doc/workflow/forking_workflow.md34
-rw-r--r--doc/workflow/git_annex.md30
-rw-r--r--doc/workflow/img/notification_global_settings.pngbin118914 -> 67652 bytes
-rw-r--r--doc/workflow/lfs/lfs_administration.md40
-rw-r--r--doc/workflow/lfs/manage_large_binaries_with_git_lfs.md31
-rw-r--r--doc/workflow/lfs/migrate_from_git_annex_to_git_lfs.md147
-rw-r--r--doc/workflow/notifications.md8
-rw-r--r--doc/workflow/repository_mirroring.md43
-rw-r--r--doc/workflow/shortcuts.md12
-rw-r--r--doc/workflow/time_tracking.md13
-rw-r--r--doc/workflow/todos.md4
-rw-r--r--doc/workflow/workflow.md38
-rw-r--r--lib/api/api.rb5
-rw-r--r--lib/api/award_emoji.rb8
-rw-r--r--lib/api/commit_statuses.rb4
-rw-r--r--lib/api/commits.rb18
-rw-r--r--lib/api/container_registry.rb149
-rw-r--r--lib/api/discussions.rb26
-rw-r--r--lib/api/entities.rb60
-rw-r--r--lib/api/entities/container_registry.rb10
-rw-r--r--lib/api/files.rb25
-rw-r--r--lib/api/group_container_repositories.rb39
-rw-r--r--lib/api/group_labels.rb2
-rw-r--r--lib/api/groups.rb5
-rw-r--r--lib/api/helpers.rb4
-rw-r--r--lib/api/helpers/label_helpers.rb6
-rw-r--r--lib/api/helpers/notes_helpers.rb12
-rw-r--r--lib/api/helpers/projects_helpers.rb1
-rw-r--r--lib/api/helpers/runner.rb2
-rw-r--r--lib/api/helpers/services_helpers.rb27
-rw-r--r--lib/api/helpers/variables_helpers.rb13
-rw-r--r--lib/api/issues.rb6
-rw-r--r--lib/api/job_artifacts.rb4
-rw-r--r--lib/api/labels.rb2
-rw-r--r--lib/api/project_container_repositories.rb152
-rw-r--r--lib/api/project_import.rb1
-rw-r--r--lib/api/projects.rb29
-rw-r--r--lib/api/settings.rb14
-rw-r--r--lib/api/todos.rb13
-rw-r--r--lib/api/triggers.rb21
-rw-r--r--lib/api/validations/types/labels_list.rb2
-rw-r--r--lib/api/variables.rb8
-rw-r--r--lib/backup/database.rb27
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb18
-rw-r--r--lib/banzai/filter/autolink_filter.rb11
-rw-r--r--lib/banzai/filter/base_sanitization_filter.rb32
-rw-r--r--lib/banzai/filter/commit_reference_filter.rb17
-rw-r--r--lib/banzai/filter/inline_embeds_filter.rb2
-rw-r--r--lib/banzai/filter/inline_metrics_filter.rb33
-rw-r--r--lib/banzai/filter/inline_metrics_redactor_filter.rb2
-rw-r--r--lib/banzai/filter/issuable_reference_filter.rb18
-rw-r--r--lib/banzai/filter/wiki_link_filter.rb15
-rw-r--r--lib/banzai/filter/wiki_link_filter/rewriter.rb8
-rw-r--r--lib/banzai/reference_redactor.rb2
-rw-r--r--lib/bitbucket/representation/comment.rb2
-rw-r--r--lib/bitbucket/representation/issue.rb2
-rw-r--r--lib/bitbucket/representation/pull_request.rb2
-rw-r--r--lib/bitbucket_server/client.rb3
-rw-r--r--lib/container_registry/client.rb14
-rw-r--r--lib/container_registry/tag.rb7
-rw-r--r--lib/expand_variables.rb18
-rw-r--r--lib/feature.rb7
-rw-r--r--lib/feature/gitaly.rb2
-rw-r--r--lib/forever.rb10
-rw-r--r--lib/gitlab.rb4
-rw-r--r--lib/gitlab/access.rb11
-rw-r--r--lib/gitlab/action_rate_limiter.rb40
-rw-r--r--lib/gitlab/auth/activity.rb7
-rw-r--r--lib/gitlab/auth/o_auth/auth_hash.rb3
-rw-r--r--lib/gitlab/auth/user_auth_finders.rb17
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb2
-rw-r--r--lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb17
-rw-r--r--lib/gitlab/background_migration/legacy_upload_mover.rb140
-rw-r--r--lib/gitlab/background_migration/legacy_uploads_migrator.rb23
-rw-r--r--lib/gitlab/background_migration/logger.rb12
-rw-r--r--lib/gitlab/background_migration/migrate_legacy_artifacts.rb18
-rw-r--r--lib/gitlab/background_migration/migrate_stage_index.rb42
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb15
-rw-r--r--lib/gitlab/background_migration/prepare_untracked_uploads.rb15
-rw-r--r--lib/gitlab/background_migration/remove_restricted_todos.rb17
-rw-r--r--lib/gitlab/badge/coverage/report.rb2
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb8
-rw-r--r--lib/gitlab/blob_helper.rb2
-rw-r--r--lib/gitlab/chaos.rb49
-rw-r--r--lib/gitlab/ci/ansi2html.rb29
-rw-r--r--lib/gitlab/ci/build/policy/variables.rb2
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb30
-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/charts.rb14
-rw-r--r--lib/gitlab/ci/config/entry/job.rb52
-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/config/normalizer.rb64
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb11
-rw-r--r--lib/gitlab/ci/pipeline/seed/base.rb4
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb127
-rw-r--r--lib/gitlab/ci/pipeline/seed/stage.rb25
-rw-r--r--lib/gitlab/ci/status/build/manual.rb2
-rw-r--r--lib/gitlab/ci/status/factory.rb6
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml5
-rw-r--r--lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml420
-rw-r--r--lib/gitlab/ci/templates/Maven.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Packer.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml7
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Serverless.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/trace.rb14
-rw-r--r--lib/gitlab/ci/trace/chunked_io.rb7
-rw-r--r--lib/gitlab/ci/yaml_processor.rb25
-rw-r--r--lib/gitlab/cluster/puma_worker_killer_observer.rb4
-rw-r--r--lib/gitlab/config/entry/attributable.rb4
-rw-r--r--lib/gitlab/config/entry/validators.rb33
-rw-r--r--lib/gitlab/content_security_policy/config_loader.rb43
-rw-r--r--lib/gitlab/contributions_calendar.rb6
-rw-r--r--lib/gitlab/current_settings.rb5
-rw-r--r--lib/gitlab/cycle_analytics/base_event_fetcher.rb12
-rw-r--r--lib/gitlab/cycle_analytics/base_query.rb25
-rw-r--r--lib/gitlab/cycle_analytics/base_stage.rb19
-rw-r--r--lib/gitlab/cycle_analytics/code_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/group_projects_provider.rb27
-rw-r--r--lib/gitlab/cycle_analytics/group_stage_summary.rb27
-rw-r--r--lib/gitlab/cycle_analytics/issue_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/issue_helper.rb16
-rw-r--r--lib/gitlab/cycle_analytics/metrics_tables.rb8
-rw-r--r--lib/gitlab/cycle_analytics/permissions.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/plan_helper.rb15
-rw-r--r--lib/gitlab/cycle_analytics/production_event_fetcher.rb5
-rw-r--r--lib/gitlab/cycle_analytics/review_event_fetcher.rb2
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/base.rb27
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/deploy.rb30
-rw-r--r--lib/gitlab/cycle_analytics/summary/group/issue.rb36
-rw-r--r--lib/gitlab/danger/helper.rb28
-rw-r--r--lib/gitlab/danger/teammate.rb2
-rw-r--r--lib/gitlab/data_builder/push.rb7
-rw-r--r--lib/gitlab/database.rb85
-rw-r--r--lib/gitlab/database/count.rb12
-rw-r--r--lib/gitlab/database/count/exact_count_strategy.rb4
-rw-r--r--lib/gitlab/database/count/reltuples_count_strategy.rb4
-rw-r--r--lib/gitlab/database/count/tablesample_count_strategy.rb4
-rw-r--r--lib/gitlab/database/date_time.rb18
-rw-r--r--lib/gitlab/database/grant.rb50
-rw-r--r--lib/gitlab/database/median.rb47
-rw-r--r--lib/gitlab/database/migration_helpers.rb205
-rw-r--r--lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb12
-rw-r--r--lib/gitlab/database/sha_attribute.rb11
-rw-r--r--lib/gitlab/database/subquery.rb6
-rw-r--r--lib/gitlab/ee_compat_check.rb2
-rw-r--r--lib/gitlab/etag_caching/router.rb4
-rw-r--r--lib/gitlab/exclusive_lease_helpers.rb5
-rw-r--r--lib/gitlab/gfm/uploads_rewriter.rb14
-rw-r--r--lib/gitlab/git.rb5
-rw-r--r--lib/gitlab/git/repository.rb8
-rw-r--r--lib/gitlab/git/rugged_impl/blob.rb2
-rw-r--r--lib/gitlab/git/rugged_impl/commit.rb6
-rw-r--r--lib/gitlab/git/rugged_impl/repository.rb2
-rw-r--r--lib/gitlab/git/rugged_impl/tree.rb2
-rw-r--r--lib/gitlab/git/rugged_impl/use_rugged.rb23
-rw-r--r--lib/gitlab/git_logger.rb8
-rw-r--r--lib/gitlab/git_post_receive.rb23
-rw-r--r--lib/gitlab/gitaly_client.rb27
-rw-r--r--lib/gitlab/gitaly_client/notification_service.rb22
-rw-r--r--lib/gitlab/gitaly_client/operation_service.rb9
-rw-r--r--lib/gitlab/github_import/client.rb4
-rw-r--r--lib/gitlab/github_import/importer/releases_importer.rb1
-rw-r--r--lib/gitlab/gon_helper.rb5
-rw-r--r--lib/gitlab/grape_logging/loggers/client_env_logger.rb16
-rw-r--r--lib/gitlab/grape_logging/loggers/perf_logger.rb19
-rw-r--r--lib/gitlab/health_checks/db_check.rb6
-rw-r--r--lib/gitlab/http_connection_adapter.rb4
-rw-r--r--lib/gitlab/import/database_helpers.rb10
-rw-r--r--lib/gitlab/import_export.rb12
-rw-r--r--lib/gitlab/import_export/attributes_finder.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml9
-rw-r--r--lib/gitlab/import_export/json_hash_builder.rb2
-rw-r--r--lib/gitlab/import_export/lfs_restorer.rb51
-rw-r--r--lib/gitlab/import_export/lfs_saver.rb47
-rw-r--r--lib/gitlab/import_export/members_mapper.rb4
-rw-r--r--lib/gitlab/import_export/project_tree_restorer.rb1
-rw-r--r--lib/gitlab/import_export/relation_factory.rb2
-rw-r--r--lib/gitlab/instrumentation_helper.rb25
-rw-r--r--lib/gitlab/kubernetes/default_namespace.rb58
-rw-r--r--lib/gitlab/kubernetes/helm.rb4
-rw-r--r--lib/gitlab/kubernetes/helm/delete_command.rb20
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb12
-rw-r--r--lib/gitlab/kubernetes/helm/reset_command.rb54
-rw-r--r--lib/gitlab/kubernetes/kube_client.rb2
-rw-r--r--lib/gitlab/kubernetes/kubectl_cmd.rb19
-rw-r--r--lib/gitlab/legacy_github_import/client.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/base_service.rb72
-rw-r--r--lib/gitlab/metrics/dashboard/defaults.rb14
-rw-r--r--lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb65
-rw-r--r--lib/gitlab/metrics/dashboard/errors.rb34
-rw-r--r--lib/gitlab/metrics/dashboard/finder.rb49
-rw-r--r--lib/gitlab/metrics/dashboard/project_dashboard_service.rb50
-rw-r--r--lib/gitlab/metrics/dashboard/service_selector.rb50
-rw-r--r--lib/gitlab/metrics/dashboard/stages/base_stage.rb4
-rw-r--r--lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/system_dashboard_service.rb49
-rw-r--r--lib/gitlab/metrics/dashboard/url.rb16
-rw-r--r--lib/gitlab/metrics/samplers/influx_sampler.rb22
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb3
-rw-r--r--lib/gitlab/metrics/samplers/ruby_sampler.rb56
-rw-r--r--lib/gitlab/metrics/subscribers/rails_cache.rb15
-rw-r--r--lib/gitlab/object_hierarchy.rb17
-rw-r--r--lib/gitlab/octokit/middleware.rb23
-rw-r--r--lib/gitlab/path_regex.rb4
-rw-r--r--lib/gitlab/performance_bar/peek_query_tracker.rb45
-rw-r--r--lib/gitlab/profiler.rb5
-rw-r--r--lib/gitlab/project_authorizations.rb121
-rw-r--r--lib/gitlab/project_authorizations/with_nested_groups.rb125
-rw-r--r--lib/gitlab/project_authorizations/without_nested_groups.rb35
-rw-r--r--lib/gitlab/project_search_results.rb21
-rw-r--r--lib/gitlab/project_template.rb18
-rw-r--r--lib/gitlab/prometheus/query_variables.rb5
-rw-r--r--lib/gitlab/prometheus_client.rb82
-rw-r--r--lib/gitlab/push_options.rb9
-rw-r--r--lib/gitlab/quick_actions/command_definition.rb21
-rw-r--r--lib/gitlab/quick_actions/commit_actions.rb7
-rw-r--r--lib/gitlab/quick_actions/dsl.rb37
-rw-r--r--lib/gitlab/quick_actions/issuable_actions.rb124
-rw-r--r--lib/gitlab/quick_actions/issue_actions.rb56
-rw-r--r--lib/gitlab/quick_actions/issue_and_merge_request_actions.rb107
-rw-r--r--lib/gitlab/quick_actions/merge_request_actions.rb28
-rw-r--r--lib/gitlab/regex.rb18
-rw-r--r--lib/gitlab/repository_cache_adapter.rb53
-rw-r--r--lib/gitlab/repository_set_cache.rb67
-rw-r--r--lib/gitlab/request_profiler.rb15
-rw-r--r--lib/gitlab/request_profiler/middleware.rb62
-rw-r--r--lib/gitlab/request_profiler/profile.rb42
-rw-r--r--lib/gitlab/rugged_instrumentation.rb47
-rw-r--r--lib/gitlab/search_results.rb23
-rw-r--r--lib/gitlab/sentry.rb17
-rw-r--r--lib/gitlab/sherlock/query.rb7
-rw-r--r--lib/gitlab/sidekiq_logging/structured_logger.rb21
-rw-r--r--lib/gitlab/sidekiq_middleware/instrumentation_logger.rb21
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb23
-rw-r--r--lib/gitlab/sidekiq_middleware/metrics.rb52
-rw-r--r--lib/gitlab/slug/environment.rb58
-rw-r--r--lib/gitlab/snippet_search_results.rb11
-rw-r--r--lib/gitlab/snowplow_tracker.rb35
-rw-r--r--lib/gitlab/submodule_links.rb11
-rw-r--r--lib/gitlab/url_blocker.rb45
-rw-r--r--lib/gitlab/usage_data.rb30
-rw-r--r--lib/gitlab/usage_data_counters/base_counter.rb39
-rw-r--r--lib/gitlab/usage_data_counters/cycle_analytics_counter.rb8
-rw-r--r--lib/gitlab/usage_data_counters/note_counter.rb39
-rw-r--r--lib/gitlab/usage_data_counters/redis_counter.rb10
-rw-r--r--lib/gitlab/usage_data_counters/search_counter.rb27
-rw-r--r--lib/gitlab/usage_data_counters/snippet_counter.rb8
-rw-r--r--lib/gitlab/usage_data_counters/source_code_counter.rb8
-rw-r--r--lib/gitlab/usage_data_counters/web_ide_commits_counter.rb13
-rw-r--r--lib/gitlab/usage_data_counters/web_ide_counter.rb47
-rw-r--r--lib/gitlab/usage_data_counters/wiki_page_counter.rb8
-rw-r--r--lib/gitlab/utils.rb9
-rw-r--r--lib/gitlab/utils/sanitize_node_link.rb61
-rw-r--r--lib/gitlab/workhorse.rb2
-rw-r--r--lib/gitlab/zoom_link_extractor.rb4
-rw-r--r--lib/gt_one_coercion.rb7
-rw-r--r--lib/mysql_zero_date.rb20
-rw-r--r--lib/peek/rblineprof/custom_controller_helpers.rb124
-rw-r--r--lib/peek/views/active_record.rb25
-rw-r--r--lib/peek/views/detailed_view.rb53
-rw-r--r--lib/peek/views/gitaly.rb30
-rw-r--r--lib/peek/views/redis.rb86
-rw-r--r--lib/peek/views/redis_detailed.rb73
-rw-r--r--lib/peek/views/rugged.rb46
-rw-r--r--lib/prometheus/cleanup_multiproc_dir_service.rb23
-rw-r--r--lib/prometheus/pid_provider.rb49
-rw-r--r--lib/quality/test_level.rb1
-rw-r--r--lib/sentry/client.rb2
-rw-r--r--lib/serializers/json.rb18
-rwxr-xr-xlib/support/deploy/deploy.sh2
-rwxr-xr-xlib/support/init.d/gitlab15
-rw-r--r--lib/system_check/app/git_version_check.rb2
-rw-r--r--lib/tasks/frontend.rake21
-rw-r--r--lib/tasks/gitlab/cleanup.rake52
-rw-r--r--lib/tasks/gitlab/db.rake23
-rw-r--r--lib/tasks/gitlab/setup.rake3
-rw-r--r--lib/tasks/gitlab/update_templates.rake103
-rw-r--r--lib/tasks/gitlab/uploads/legacy.rake27
-rw-r--r--lib/tasks/karma.rake11
-rw-r--r--lib/tasks/migrate/add_limits_mysql.rake17
-rw-r--r--lib/tasks/services.rake2
-rw-r--r--lib/tasks/spec.rake63
-rw-r--r--locale/gitlab.pot911
-rw-r--r--package.json39
-rw-r--r--qa/Dockerfile12
-rw-r--r--qa/Gemfile4
-rw-r--r--qa/Gemfile.lock20
-rw-r--r--qa/README.md5
-rw-r--r--qa/qa.rb7
-rw-r--r--qa/qa/ce/knapsack/nightly_master_report.json46
-rw-r--r--qa/qa/page/admin/menu.rb12
-rw-r--r--qa/qa/page/base.rb4
-rw-r--r--qa/qa/page/component/note.rb12
-rw-r--r--qa/qa/page/component/select2.rb4
-rw-r--r--qa/qa/page/main/login.rb39
-rw-r--r--qa/qa/page/main/menu.rb10
-rw-r--r--qa/qa/page/project/issue/new.rb4
-rw-r--r--qa/qa/page/project/issue/show.rb7
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb21
-rw-r--r--qa/qa/page/project/sub_menus/project.rb4
-rw-r--r--qa/qa/page/search/results.rb33
-rw-r--r--qa/qa/page/validator.rb6
-rw-r--r--qa/qa/resource/merge_request.rb25
-rw-r--r--qa/qa/resource/project.rb6
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb10
-rw-r--r--qa/qa/service/kubernetes_cluster.rb2
-rw-r--r--qa/qa/service/omnibus.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb21
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb10
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb3
-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/1_manage/project/create_project_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb12
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb28
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb41
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb33
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb14
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb13
-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/add_ssh_key_spec.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb34
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb23
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb21
-rw-r--r--qa/qa/vendor/saml_idp/page/login.rb8
-rw-r--r--qa/spec/resource/repository/push_spec.rb6
-rw-r--r--qa/spec/runtime/env_spec.rb24
-rw-r--r--qa/spec/spec_helper.rb2
-rw-r--r--qa/spec/specs/runner_spec.rb4
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb48
-rw-r--r--rubocop/cop/rspec/top_level_describe_path.rb35
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--scripts/create_mysql_user.sh7
-rwxr-xr-xscripts/frontend/file_test_coverage.js88
-rwxr-xr-xscripts/insert-rspec-profiling-data4
-rwxr-xr-xscripts/lint-changelog-yaml24
-rwxr-xr-xscripts/lint-rugged13
-rwxr-xr-xscripts/merge-html-reports84
-rwxr-xr-xscripts/review_apps/review-apps.sh39
-rwxr-xr-xscripts/static-analysis5
-rw-r--r--scripts/utils.sh2
-rw-r--r--spec/bin/changelog_spec.rb2
-rw-r--r--spec/controllers/admin/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/admin/groups_controller_spec.rb8
-rw-r--r--spec/controllers/admin/requests_profiles_controller_spec.rb65
-rw-r--r--spec/controllers/admin/users_controller_spec.rb6
-rw-r--r--spec/controllers/application_controller_spec.rb28
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb36
-rw-r--r--spec/controllers/boards/issues_controller_spec.rb2
-rw-r--r--spec/controllers/chaos_controller_spec.rb127
-rw-r--r--spec/controllers/concerns/confirm_email_warning_spec.rb98
-rw-r--r--spec/controllers/concerns/group_tree_spec.rb4
-rw-r--r--spec/controllers/concerns/issuable_actions_spec.rb69
-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/groups_controller_spec.rb2
-rw-r--r--spec/controllers/dashboard/milestones_controller_spec.rb15
-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/graphql_controller_spec.rb21
-rw-r--r--spec/controllers/groups/children_controller_spec.rb4
-rw-r--r--spec/controllers/groups/clusters/applications_controller_spec.rb2
-rw-r--r--spec/controllers/groups/group_members_controller_spec.rb35
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb4
-rw-r--r--spec/controllers/groups/milestones_controller_spec.rb2
-rw-r--r--spec/controllers/groups/uploads_controller_spec.rb5
-rw-r--r--spec/controllers/groups_controller_spec.rb6
-rw-r--r--spec/controllers/health_check_controller_spec.rb12
-rw-r--r--spec/controllers/help_controller_spec.rb4
-rw-r--r--spec/controllers/ide_controller_spec.rb17
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb6
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb14
-rw-r--r--spec/controllers/import/github_controller_spec.rb10
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb6
-rw-r--r--spec/controllers/metrics_controller_spec.rb9
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb6
-rw-r--r--spec/controllers/projects/badges_controller_spec.rb124
-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.rb64
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb18
-rw-r--r--spec/controllers/projects/environments_controller_spec.rb79
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb2
-rw-r--r--spec/controllers/projects/git_http_controller_spec.rb11
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb76
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb17
-rw-r--r--spec/controllers/projects/merge_requests/content_controller_spec.rb54
-rw-r--r--spec/controllers/projects/merge_requests/creations_controller_spec.rb50
-rw-r--r--spec/controllers/projects/merge_requests/diffs_controller_spec.rb2
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb127
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb4
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb21
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb18
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb2
-rw-r--r--spec/controllers/projects/raw_controller_spec.rb95
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb4
-rw-r--r--spec/controllers/projects/registry/tags_controller_spec.rb33
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb47
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb19
-rw-r--r--spec/controllers/projects/services_controller_spec.rb2
-rw-r--r--spec/controllers/projects/starrers_controller_spec.rb196
-rw-r--r--spec/controllers/projects/uploads_controller_spec.rb5
-rw-r--r--spec/controllers/projects/variables_controller_spec.rb65
-rw-r--r--spec/controllers/projects/wikis_controller_spec.rb118
-rw-r--r--spec/controllers/registrations_controller_spec.rb117
-rw-r--r--spec/controllers/search_controller_spec.rb202
-rw-r--r--spec/controllers/snippets/notes_controller_spec.rb6
-rw-r--r--spec/controllers/user_callouts_controller_spec.rb4
-rw-r--r--spec/controllers/users_controller_spec.rb6
-rw-r--r--spec/db/schema_spec.rb4
-rw-r--r--spec/factories/abuse_reports.rb2
-rw-r--r--spec/factories/appearances.rb2
-rw-r--r--spec/factories/application_settings.rb2
-rw-r--r--spec/factories/award_emoji.rb2
-rw-r--r--spec/factories/badge.rb2
-rw-r--r--spec/factories/boards.rb2
-rw-r--r--spec/factories/broadcast_messages.rb2
-rw-r--r--spec/factories/chat_names.rb2
-rw-r--r--spec/factories/chat_teams.rb2
-rw-r--r--spec/factories/ci/bridge.rb11
-rw-r--r--spec/factories/ci/build_need.rb8
-rw-r--r--spec/factories/ci/build_trace_chunks.rb2
-rw-r--r--spec/factories/ci/build_trace_section_names.rb2
-rw-r--r--spec/factories/ci/builds.rb2
-rw-r--r--spec/factories/ci/group_variables.rb2
-rw-r--r--spec/factories/ci/job_artifacts.rb2
-rw-r--r--spec/factories/ci/job_variables.rb10
-rw-r--r--spec/factories/ci/pipeline_schedule.rb2
-rw-r--r--spec/factories/ci/pipeline_schedule_variables.rb2
-rw-r--r--spec/factories/ci/pipeline_variables.rb2
-rw-r--r--spec/factories/ci/pipelines.rb2
-rw-r--r--spec/factories/ci/runner_projects.rb2
-rw-r--r--spec/factories/ci/runners.rb2
-rw-r--r--spec/factories/ci/stages.rb2
-rw-r--r--spec/factories/ci/trigger_requests.rb2
-rw-r--r--spec/factories/ci/triggers.rb2
-rw-r--r--spec/factories/ci/variables.rb2
-rw-r--r--spec/factories/clusters/applications/helm.rb18
-rw-r--r--spec/factories/clusters/clusters.rb9
-rw-r--r--spec/factories/clusters/kubernetes_namespaces.rb13
-rw-r--r--spec/factories/clusters/platforms/kubernetes.rb2
-rw-r--r--spec/factories/clusters/providers/gcp.rb2
-rw-r--r--spec/factories/commit_statuses.rb2
-rw-r--r--spec/factories/commits.rb2
-rw-r--r--spec/factories/container_repositories.rb4
-rw-r--r--spec/factories/conversational_development_index_metrics.rb2
-rw-r--r--spec/factories/deploy_keys_projects.rb2
-rw-r--r--spec/factories/deploy_tokens.rb2
-rw-r--r--spec/factories/deployments.rb2
-rw-r--r--spec/factories/emails.rb2
-rw-r--r--spec/factories/environments.rb2
-rw-r--r--spec/factories/events.rb2
-rw-r--r--spec/factories/file_uploaders.rb2
-rw-r--r--spec/factories/fork_network_members.rb2
-rw-r--r--spec/factories/fork_networks.rb2
-rw-r--r--spec/factories/gitaly/commit.rb2
-rw-r--r--spec/factories/gitaly/commit_author.rb2
-rw-r--r--spec/factories/gitaly/tag.rb2
-rw-r--r--spec/factories/gpg_key_subkeys.rb2
-rw-r--r--spec/factories/gpg_keys.rb2
-rw-r--r--spec/factories/gpg_signature.rb2
-rw-r--r--spec/factories/group_custom_attributes.rb2
-rw-r--r--spec/factories/group_members.rb10
-rw-r--r--spec/factories/groups.rb6
-rw-r--r--spec/factories/identities.rb2
-rw-r--r--spec/factories/import_export_uploads.rb2
-rw-r--r--spec/factories/import_states.rb2
-rw-r--r--spec/factories/instance_configuration.rb2
-rw-r--r--spec/factories/internal_ids.rb2
-rw-r--r--spec/factories/issues.rb2
-rw-r--r--spec/factories/keys.rb2
-rw-r--r--spec/factories/label_links.rb2
-rw-r--r--spec/factories/label_priorities.rb2
-rw-r--r--spec/factories/labels.rb2
-rw-r--r--spec/factories/lfs_file_locks.rb2
-rw-r--r--spec/factories/lfs_objects.rb3
-rw-r--r--spec/factories/lfs_objects_projects.rb2
-rw-r--r--spec/factories/lists.rb2
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/factories/merge_requests_closing_issues.rb2
-rw-r--r--spec/factories/milestones.rb2
-rw-r--r--spec/factories/namespaces.rb2
-rw-r--r--spec/factories/notes.rb2
-rw-r--r--spec/factories/notification_settings.rb2
-rw-r--r--spec/factories/oauth_access_grants.rb2
-rw-r--r--spec/factories/oauth_access_tokens.rb2
-rw-r--r--spec/factories/oauth_applications.rb2
-rw-r--r--spec/factories/pages_domains.rb86
-rw-r--r--spec/factories/personal_access_tokens.rb2
-rw-r--r--spec/factories/pool_repositories.rb2
-rw-r--r--spec/factories/programming_languages.rb2
-rw-r--r--spec/factories/project_auto_devops.rb2
-rw-r--r--spec/factories/project_custom_attributes.rb2
-rw-r--r--spec/factories/project_deploy_tokens.rb2
-rw-r--r--spec/factories/project_group_links.rb2
-rw-r--r--spec/factories/project_hooks.rb2
-rw-r--r--spec/factories/project_members.rb2
-rw-r--r--spec/factories/project_statistics.rb2
-rw-r--r--spec/factories/project_wikis.rb2
-rw-r--r--spec/factories/projects.rb6
-rw-r--r--spec/factories/protected_branches.rb2
-rw-r--r--spec/factories/protected_tags.rb2
-rw-r--r--spec/factories/redirect_routes.rb2
-rw-r--r--spec/factories/releases.rb2
-rw-r--r--spec/factories/remote_mirrors.rb2
-rw-r--r--spec/factories/repository_languages.rb2
-rw-r--r--spec/factories/sent_notifications.rb2
-rw-r--r--spec/factories/sequences.rb2
-rw-r--r--spec/factories/service_hooks.rb2
-rw-r--r--spec/factories/services.rb19
-rw-r--r--spec/factories/shards.rb2
-rw-r--r--spec/factories/snippets.rb2
-rw-r--r--spec/factories/spam_logs.rb2
-rw-r--r--spec/factories/subscriptions.rb2
-rw-r--r--spec/factories/system_hooks.rb2
-rw-r--r--spec/factories/system_note_metadata.rb2
-rw-r--r--spec/factories/term_agreements.rb2
-rw-r--r--spec/factories/terms.rb2
-rw-r--r--spec/factories/timelogs.rb2
-rw-r--r--spec/factories/todos.rb2
-rw-r--r--spec/factories/trending_project.rb2
-rw-r--r--spec/factories/u2f_registrations.rb2
-rw-r--r--spec/factories/uploads.rb7
-rw-r--r--spec/factories/user_agent_details.rb2
-rw-r--r--spec/factories/user_callouts.rb2
-rw-r--r--spec/factories/user_custom_attributes.rb2
-rw-r--r--spec/factories/users.rb2
-rw-r--r--spec/factories/users_star_projects.rb2
-rw-r--r--spec/factories/web_hook_log.rb2
-rw-r--r--spec/factories/wiki_directories.rb2
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/fast_spec_helper.rb1
-rw-r--r--spec/features/abuse_report_spec.rb2
-rw-r--r--spec/features/admin/admin_abuse_reports_spec.rb2
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb2
-rw-r--r--spec/features/admin/admin_browse_spam_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_browses_logs_spec.rb4
-rw-r--r--spec/features/admin/admin_builds_spec.rb2
-rw-r--r--spec/features/admin/admin_deploy_keys_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_git_access_protocol_spec.rb2
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb2
-rw-r--r--spec/features/admin/admin_groups_spec.rb11
-rw-r--r--spec/features/admin/admin_health_check_spec.rb2
-rw-r--r--spec/features/admin/admin_hook_logs_spec.rb2
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/admin/admin_labels_spec.rb2
-rw-r--r--spec/features/admin/admin_manage_applications_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb3
-rw-r--r--spec/features/admin/admin_requests_profiles_spec.rb117
-rw-r--r--spec/features/admin/admin_runners_spec.rb2
-rw-r--r--spec/features/admin/admin_settings_spec.rb43
-rw-r--r--spec/features/admin/admin_system_info_spec.rb2
-rw-r--r--spec/features/admin/admin_users_impersonation_tokens_spec.rb2
-rw-r--r--spec/features/admin/admin_users_spec.rb2
-rw-r--r--spec/features/admin/admin_uses_repository_checks_spec.rb2
-rw-r--r--spec/features/admin/dashboard_spec.rb8
-rw-r--r--spec/features/admin/services/admin_activates_prometheus_spec.rb2
-rw-r--r--spec/features/atom/dashboard_spec.rb2
-rw-r--r--spec/features/atom/issues_spec.rb2
-rw-r--r--spec/features/atom/users_spec.rb2
-rw-r--r--spec/features/boards/add_issues_modal_spec.rb2
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/boards/issue_ordering_spec.rb2
-rw-r--r--spec/features/boards/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/boards/modal_filter_spec.rb2
-rw-r--r--spec/features/boards/multiple_boards_spec.rb15
-rw-r--r--spec/features/boards/new_issue_spec.rb2
-rw-r--r--spec/features/boards/reload_boards_on_browser_back_spec.rb2
-rw-r--r--spec/features/boards/sidebar_spec.rb2
-rw-r--r--spec/features/boards/sub_group_project_spec.rb2
-rw-r--r--spec/features/calendar_spec.rb2
-rw-r--r--spec/features/commits_spec.rb2
-rw-r--r--spec/features/container_registry_spec.rb12
-rw-r--r--spec/features/contextual_sidebar_spec.rb10
-rw-r--r--spec/features/cycle_analytics_spec.rb2
-rw-r--r--spec/features/dashboard/active_tab_spec.rb2
-rw-r--r--spec/features/dashboard/activity_spec.rb2
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb2
-rw-r--r--spec/features/dashboard/datetime_on_tooltips_spec.rb2
-rw-r--r--spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb2
-rw-r--r--spec/features/dashboard/group_spec.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb8
-rw-r--r--spec/features/dashboard/help_spec.rb2
-rw-r--r--spec/features/dashboard/issuables_counter_spec.rb2
-rw-r--r--spec/features/dashboard/issues_filter_spec.rb2
-rw-r--r--spec/features/dashboard/issues_spec.rb2
-rw-r--r--spec/features/dashboard/label_filter_spec.rb2
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb2
-rw-r--r--spec/features/dashboard/milestone_tabs_spec.rb2
-rw-r--r--spec/features/dashboard/milestones_spec.rb2
-rw-r--r--spec/features/dashboard/project_member_activity_index_spec.rb2
-rw-r--r--spec/features/dashboard/projects_spec.rb2
-rw-r--r--spec/features/dashboard/shortcuts_spec.rb4
-rw-r--r--spec/features/dashboard/snippets_spec.rb2
-rw-r--r--spec/features/dashboard/todos/target_state_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb44
-rw-r--r--spec/features/dashboard/todos/todos_sorting_spec.rb2
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb2
-rw-r--r--spec/features/dashboard/user_filters_projects_spec.rb2
-rw-r--r--spec/features/discussion_comments/commit_spec.rb2
-rw-r--r--spec/features/discussion_comments/issue_spec.rb2
-rw-r--r--spec/features/discussion_comments/merge_request_spec.rb2
-rw-r--r--spec/features/discussion_comments/snippets_spec.rb2
-rw-r--r--spec/features/display_system_header_and_footer_bar_spec.rb2
-rw-r--r--spec/features/error_pages_spec.rb2
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb2
-rw-r--r--spec/features/explore/groups_list_spec.rb2
-rw-r--r--spec/features/explore/groups_spec.rb2
-rw-r--r--spec/features/explore/user_explores_projects_spec.rb2
-rw-r--r--spec/features/global_search_spec.rb15
-rw-r--r--spec/features/group_variables_spec.rb2
-rw-r--r--spec/features/groups/activity_spec.rb2
-rw-r--r--spec/features/groups/board_spec.rb2
-rw-r--r--spec/features/groups/empty_states_spec.rb4
-rw-r--r--spec/features/groups/group_settings_spec.rb12
-rw-r--r--spec/features/groups/issues_spec.rb4
-rw-r--r--spec/features/groups/labels/edit_spec.rb2
-rw-r--r--spec/features/groups/labels/subscription_spec.rb2
-rw-r--r--spec/features/groups/labels/user_sees_links_to_issuables.rb15
-rw-r--r--spec/features/groups/labels/user_sees_links_to_issuables_spec.rb19
-rw-r--r--spec/features/groups/members/filter_members_spec.rb8
-rw-r--r--spec/features/groups/members/leave_group_spec.rb2
-rw-r--r--spec/features/groups/members/list_members_spec.rb6
-rw-r--r--spec/features/groups/members/manage_members_spec.rb6
-rw-r--r--spec/features/groups/members/master_manages_access_requests_spec.rb2
-rw-r--r--spec/features/groups/members/request_access_spec.rb2
-rw-r--r--spec/features/groups/members/search_members_spec.rb6
-rw-r--r--spec/features/groups/members/sort_members_spec.rb20
-rw-r--r--spec/features/groups/milestone_spec.rb2
-rw-r--r--spec/features/groups/milestones_sorting_spec.rb2
-rw-r--r--spec/features/groups/settings/group_badges_spec.rb2
-rw-r--r--spec/features/groups/share_lock_spec.rb4
-rw-r--r--spec/features/groups/show_spec.rb90
-rw-r--r--spec/features/groups/user_browse_projects_group_page_spec.rb2
-rw-r--r--spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb2
-rw-r--r--spec/features/groups_spec.rb6
-rw-r--r--spec/features/ics/dashboard_issues_spec.rb4
-rw-r--r--spec/features/ics/group_issues_spec.rb4
-rw-r--r--spec/features/ics/project_issues_spec.rb4
-rw-r--r--spec/features/ide/user_opens_merge_request_spec.rb2
-rw-r--r--spec/features/ide_spec.rb4
-rw-r--r--spec/features/import/manifest_import_spec.rb4
-rw-r--r--spec/features/instance_statistics/cohorts_spec.rb2
-rw-r--r--spec/features/instance_statistics/instance_statistics.rb23
-rw-r--r--spec/features/instance_statistics/instance_statistics_spec.rb25
-rw-r--r--spec/features/invites_spec.rb75
-rw-r--r--spec/features/issuables/close_reopen_report_toggle_spec.rb2
-rw-r--r--spec/features/issuables/discussion_lock_spec.rb2
-rw-r--r--spec/features/issuables/issuable_list_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/internal_references_spec.rb2
-rw-r--r--spec/features/issuables/markdown_references/jira_spec.rb2
-rw-r--r--spec/features/issuables/shortcuts_issuable_spec.rb2
-rw-r--r--spec/features/issuables/sorting_list_spec.rb4
-rw-r--r--spec/features/issuables/user_sees_sidebar_spec.rb2
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_emoji_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/recent_searches_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb2
-rw-r--r--spec/features/issues/filtered_search/visual_tokens_spec.rb2
-rw-r--r--spec/features/issues/form_spec.rb2
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb2
-rw-r--r--spec/features/issues/group_label_sidebar_spec.rb2
-rw-r--r--spec/features/issues/issue_detail_spec.rb2
-rw-r--r--spec/features/issues/issue_sidebar_spec.rb2
-rw-r--r--spec/features/issues/keyboard_shortcut_spec.rb2
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb2
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues/note_polling_spec.rb2
-rw-r--r--spec/features/issues/notes_on_issues_spec.rb2
-rw-r--r--spec/features/issues/rss_spec.rb2
-rw-r--r--spec/features/issues/spam_issues_spec.rb2
-rw-r--r--spec/features/issues/todo_spec.rb2
-rw-r--r--spec/features/issues/update_issues_spec.rb2
-rw-r--r--spec/features/issues/user_comments_on_issue_spec.rb7
-rw-r--r--spec/features/issues/user_creates_branch_and_merge_request_spec.rb2
-rw-r--r--spec/features/issues/user_creates_confidential_merge_request_spec.rb2
-rw-r--r--spec/features/issues/user_creates_issue_spec.rb2
-rw-r--r--spec/features/issues/user_edits_issue_spec.rb2
-rw-r--r--spec/features/issues/user_interacts_with_awards_spec.rb2
-rw-r--r--spec/features/issues/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/issues/user_sorts_issues_spec.rb2
-rw-r--r--spec/features/issues/user_toggles_subscription_spec.rb12
-rw-r--r--spec/features/issues/user_views_issue_spec.rb2
-rw-r--r--spec/features/issues/user_views_issues_spec.rb2
-rw-r--r--spec/features/labels_hierarchy_spec.rb4
-rw-r--r--spec/features/markdown/copy_as_gfm_spec.rb10
-rw-r--r--spec/features/markdown/gitlab_flavored_markdown_spec.rb2
-rw-r--r--spec/features/markdown/markdown_spec.rb2
-rw-r--r--spec/features/markdown/math_spec.rb2
-rw-r--r--spec/features/markdown/mermaid_spec.rb2
-rw-r--r--spec/features/markdown/metrics_spec.rb78
-rw-r--r--spec/features/merge_request/maintainer_edits_fork_spec.rb2
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb2
-rw-r--r--spec/features/merge_request/user_assigns_themselves_spec.rb2
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb2
-rw-r--r--spec/features/merge_request/user_closes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_commit_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_comments_on_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_image_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_creates_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_edits_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_expands_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_locks_discussion_spec.rb2
-rw-r--r--spec/features/merge_request/user_manages_subscription_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb2
-rw-r--r--spec/features/merge_request/user_posts_diff_notes_spec.rb14
-rw-r--r--spec/features/merge_request/user_posts_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_rebases_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_reopens_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb2
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb8
-rw-r--r--spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_reverts_merge_request_spec.rb2
-rw-r--r--spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_closing_issues_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deleted_target_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_deployment_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_diff_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_empty_state_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_pipelines_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_system_notes_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_wip_help_message_spec.rb2
-rw-r--r--spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb2
-rw-r--r--spec/features/merge_request/user_toggles_whitespace_changes_spec.rb2
-rw-r--r--spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb56
-rw-r--r--spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb37
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb20
-rw-r--r--spec/features/merge_request/user_views_open_merge_request_spec.rb2
-rw-r--r--spec/features/merge_requests/filters_generic_behavior_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_assignees_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_labels_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_milestones_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb2
-rw-r--r--spec/features/merge_requests/user_filters_by_target_branch_spec.rb2
-rw-r--r--spec/features/merge_requests/user_lists_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_mass_updates_spec.rb2
-rw-r--r--spec/features/merge_requests/user_sorts_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_squashes_merge_request_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_all_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_closed_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_merged_merge_requests_spec.rb2
-rw-r--r--spec/features/merge_requests/user_views_open_merge_requests_spec.rb2
-rw-r--r--spec/features/milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_creates_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_deletes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_edits_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_promotes_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestone_spec.rb2
-rw-r--r--spec/features/milestones/user_views_milestones_spec.rb2
-rw-r--r--spec/features/oauth_login_spec.rb3
-rw-r--r--spec/features/participants_autocomplete_spec.rb2
-rw-r--r--spec/features/password_reset_spec.rb2
-rw-r--r--spec/features/profile_spec.rb2
-rw-r--r--spec/features/profiles/account_spec.rb2
-rw-r--r--spec/features/profiles/active_sessions_spec.rb2
-rw-r--r--spec/features/profiles/chat_names_spec.rb2
-rw-r--r--spec/features/profiles/emails_spec.rb2
-rw-r--r--spec/features/profiles/gpg_keys_spec.rb2
-rw-r--r--spec/features/profiles/keys_spec.rb2
-rw-r--r--spec/features/profiles/oauth_applications_spec.rb2
-rw-r--r--spec/features/profiles/password_spec.rb2
-rw-r--r--spec/features/profiles/personal_access_tokens_spec.rb2
-rw-r--r--spec/features/profiles/user_changes_notified_of_own_activity_spec.rb2
-rw-r--r--spec/features/profiles/user_edit_profile_spec.rb4
-rw-r--r--spec/features/profiles/user_manages_applications_spec.rb2
-rw-r--r--spec/features/profiles/user_manages_emails_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_notifications_tab_spec.rb10
-rw-r--r--spec/features/profiles/user_visits_profile_account_page_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_authentication_log_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb6
-rw-r--r--spec/features/profiles/user_visits_profile_spec.rb2
-rw-r--r--spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb2
-rw-r--r--spec/features/project_variables_spec.rb23
-rw-r--r--spec/features/projects/activity/rss_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_activity_spec.rb2
-rw-r--r--spec/features/projects/activity/user_sees_private_activity_spec.rb2
-rw-r--r--spec/features/projects/actve_tabs_spec.rb2
-rw-r--r--spec/features/projects/artifacts/file_spec.rb2
-rw-r--r--spec/features/projects/artifacts/raw_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_downloads_artifacts_spec.rb6
-rw-r--r--spec/features/projects/badges/coverage_spec.rb2
-rw-r--r--spec/features/projects/badges/list_spec.rb2
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_line_permalink_updater_spec.rb2
-rw-r--r--spec/features/projects/blobs/blob_show_spec.rb2
-rw-r--r--spec/features/projects/blobs/edit_spec.rb2
-rw-r--r--spec/features/projects/blobs/shortcuts_blob_spec.rb2
-rw-r--r--spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb2
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/branches/new_branch_ref_dropdown_spec.rb2
-rw-r--r--spec/features/projects/branches/user_creates_branch_spec.rb2
-rw-r--r--spec/features/projects/branches/user_deletes_branch_spec.rb2
-rw-r--r--spec/features/projects/branches/user_views_branches_spec.rb2
-rw-r--r--spec/features/projects/branches_spec.rb2
-rw-r--r--spec/features/projects/ci/lint_spec.rb2
-rw-r--r--spec/features/projects/clusters/applications_spec.rb14
-rw-r--r--spec/features/projects/clusters/gcp_spec.rb2
-rw-r--r--spec/features/projects/clusters/user_spec.rb2
-rw-r--r--spec/features/projects/commit/builds_spec.rb2
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_adds_comment_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_deletes_comments_spec.rb2
-rw-r--r--spec/features/projects/commit/comments/user_edits_comments_spec.rb2
-rw-r--r--spec/features/projects/commit/diff_notes_spec.rb2
-rw-r--r--spec/features/projects/commit/mini_pipeline_graph_spec.rb2
-rw-r--r--spec/features/projects/commit/user_comments_on_commit_spec.rb2
-rw-r--r--spec/features/projects/commit/user_reverts_commit_spec.rb2
-rw-r--r--spec/features/projects/commits/rss_spec.rb2
-rw-r--r--spec/features/projects/commits/user_browses_commits_spec.rb4
-rw-r--r--spec/features/projects/compare_spec.rb2
-rw-r--r--spec/features/projects/deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/diffs/diff_show_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_metrics_spec.rb2
-rw-r--r--spec/features/projects/environments/environment_spec.rb2
-rw-r--r--spec/features/projects/environments/environments_spec.rb2
-rw-r--r--spec/features/projects/features_visibility_spec.rb2
-rw-r--r--spec/features/projects/files/dockerfile_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/files/edit_file_soft_wrap_spec.rb2
-rw-r--r--spec/features/projects/files/editing_a_file_spec.rb2
-rw-r--r--spec/features/projects/files/files_sort_submodules_with_folders_spec.rb2
-rw-r--r--spec/features/projects/files/find_file_keyboard_spec.rb2
-rw-r--r--spec/features/projects/files/gitignore_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_creates_license_file_spec.rb2
-rw-r--r--spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb2
-rw-r--r--spec/features/projects/files/template_selector_menu_spec.rb2
-rw-r--r--spec/features/projects/files/template_type_dropdown_spec.rb2
-rw-r--r--spec/features/projects/files/undo_template_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb20
-rw-r--r--spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb20
-rw-r--r--spec/features/projects/files/user_browses_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_browses_lfs_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_directory_spec.rb2
-rw-r--r--spec/features/projects/files/user_creates_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_deletes_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_find_file_spec.rb2
-rw-r--r--spec/features/projects/files/user_reads_pipeline_status_spec.rb2
-rw-r--r--spec/features/projects/files/user_replaces_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_searches_for_files_spec.rb2
-rw-r--r--spec/features/projects/files/user_uploads_files_spec.rb2
-rw-r--r--spec/features/projects/fork_spec.rb2
-rw-r--r--spec/features/projects/forks/fork_list_spec.rb2
-rw-r--r--spec/features/projects/gfm_autocomplete_load_spec.rb2
-rw-r--r--spec/features/projects/graph_spec.rb2
-rw-r--r--spec/features/projects/hook_logs/user_reads_log_spec.rb2
-rw-r--r--spec/features/projects/import_export/export_file_spec.rb2
-rw-r--r--spec/features/projects/import_export/import_file_spec.rb2
-rw-r--r--spec/features/projects/issuable_templates_spec.rb2
-rw-r--r--spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb2
-rw-r--r--spec/features/projects/jobs/permissions_spec.rb2
-rw-r--r--spec/features/projects/jobs/user_browses_job_spec.rb16
-rw-r--r--spec/features/projects/jobs/user_browses_jobs_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb10
-rw-r--r--spec/features/projects/labels/issues_sorted_by_priority_spec.rb2
-rw-r--r--spec/features/projects/labels/subscription_spec.rb2
-rw-r--r--spec/features/projects/labels/update_prioritization_spec.rb2
-rw-r--r--spec/features/projects/labels/user_creates_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_edits_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_removes_labels_spec.rb2
-rw-r--r--spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb2
-rw-r--r--spec/features/projects/labels/user_sees_links_to_issuables.rb75
-rw-r--r--spec/features/projects/labels/user_sees_links_to_issuables_spec.rb89
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb2
-rw-r--r--spec/features/projects/members/group_members_spec.rb2
-rw-r--r--spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb2
-rw-r--r--spec/features/projects/members/groups_with_access_list_spec.rb10
-rw-r--r--spec/features/projects/members/invite_group_spec.rb6
-rw-r--r--spec/features/projects/members/list_spec.rb2
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb2
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb2
-rw-r--r--spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/member_leaves_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_leave_project_spec.rb2
-rw-r--r--spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb2
-rw-r--r--spec/features/projects/members/sorting_spec.rb20
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb2
-rw-r--r--spec/features/projects/merge_request_button_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestone_spec.rb2
-rw-r--r--spec/features/projects/milestones/milestones_sorting_spec.rb2
-rw-r--r--spec/features/projects/milestones/new_spec.rb2
-rw-r--r--spec/features/projects/milestones/user_interacts_with_labels_spec.rb2
-rw-r--r--spec/features/projects/network_graph_spec.rb2
-rw-r--r--spec/features/projects/new_project_spec.rb12
-rw-r--r--spec/features/projects/pages_lets_encrypt_spec.rb8
-rw-r--r--spec/features/projects/pipeline_schedules_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb2
-rw-r--r--spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb39
-rw-r--r--spec/features/projects/releases/user_views_releases_spec.rb2
-rw-r--r--spec/features/projects/remote_mirror_spec.rb2
-rw-r--r--spec/features/projects/serverless/functions_spec.rb12
-rw-r--r--spec/features/projects/services/disable_triggers_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_asana_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_assembla_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_emails_on_push_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_flowdock_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_irker_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_issue_tracker_spec.rb4
-rw-r--r--spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_jira_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_packagist_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_pivotaltracker_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_prometheus_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_pushover_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_slack_notifications_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_slack_slash_command_spec.rb2
-rw-r--r--spec/features/projects/services/user_activates_youtrack_spec.rb4
-rw-r--r--spec/features/projects/services/user_views_services_spec.rb2
-rw-r--r--spec/features/projects/settings/forked_project_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/integration_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/lfs_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb4
-rw-r--r--spec/features/projects/settings/project_badges_spec.rb2
-rw-r--r--spec/features/projects/settings/repository_settings_spec.rb10
-rw-r--r--spec/features/projects/settings/user_archives_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_changes_avatar_spec.rb2
-rw-r--r--spec/features/projects/settings/user_changes_default_branch_spec.rb2
-rw-r--r--spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_group_links_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb2
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/features/projects/settings/user_renames_a_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb2
-rw-r--r--spec/features/projects/settings/user_tags_project_spec.rb2
-rw-r--r--spec/features/projects/settings/user_transfers_a_project_spec.rb4
-rw-r--r--spec/features/projects/settings/visibility_settings_spec.rb14
-rw-r--r--spec/features/projects/show/developer_views_empty_project_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/show/no_password_spec.rb2
-rw-r--r--spec/features/projects/show/redirects_spec.rb2
-rw-r--r--spec/features/projects/show/rss_spec.rb2
-rw-r--r--spec/features/projects/show/user_interacts_with_stars_spec.rb2
-rw-r--r--spec/features/projects/show/user_manages_notifications_spec.rb10
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_deletion_failure_message_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_last_commit_ci_status_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_readme_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb2
-rw-r--r--spec/features/projects/snippets/create_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/show_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_comments_on_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_deletes_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_updates_snippet_spec.rb2
-rw-r--r--spec/features/projects/snippets/user_views_snippets_spec.rb2
-rw-r--r--spec/features/projects/sub_group_issuables_spec.rb4
-rw-r--r--spec/features/projects/tags/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb6
-rw-r--r--spec/features/projects/tree/create_file_spec.rb6
-rw-r--r--spec/features/projects/tree/rss_spec.rb2
-rw-r--r--spec/features/projects/tree/tree_show_spec.rb2
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb2
-rw-r--r--spec/features/projects/user_creates_project_spec.rb4
-rw-r--r--spec/features/projects/user_sees_sidebar_spec.rb2
-rw-r--r--spec/features/projects/user_uses_shortcuts_spec.rb2
-rw-r--r--spec/features/projects/user_views_empty_project_spec.rb2
-rw-r--r--spec/features/projects/view_on_env_spec.rb2
-rw-r--r--spec/features/projects/wiki/markdown_preview_spec.rb95
-rw-r--r--spec/features/projects/wiki/shortcuts_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_creates_wiki_page_spec.rb70
-rw-r--r--spec/features/projects/wiki/user_deletes_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_git_access_wiki_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_updates_wiki_page_spec.rb18
-rw-r--r--spec/features/projects/wiki/user_views_wiki_empty_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb2
-rw-r--r--spec/features/projects/wiki/user_views_wiki_page_spec.rb9
-rw-r--r--spec/features/projects/wiki/user_views_wiki_pages_spec.rb4
-rw-r--r--spec/features/projects_spec.rb4
-rw-r--r--spec/features/protected_branches_spec.rb2
-rw-r--r--spec/features/protected_tags_spec.rb8
-rw-r--r--spec/features/raven_js_spec.rb2
-rw-r--r--spec/features/read_only_spec.rb2
-rw-r--r--spec/features/reportable_note/commit_spec.rb2
-rw-r--r--spec/features/reportable_note/issue_spec.rb2
-rw-r--r--spec/features/reportable_note/merge_request_spec.rb2
-rw-r--r--spec/features/reportable_note/snippets_spec.rb2
-rw-r--r--spec/features/runners_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb101
-rw-r--r--spec/features/search/user_searches_for_comments_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_commits_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_projects_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb6
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/features/search/user_uses_header_search_field_spec.rb21
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb4
-rw-r--r--spec/features/security/admin_access_spec.rb2
-rw-r--r--spec/features/security/dashboard_access_spec.rb2
-rw-r--r--spec/features/security/group/internal_access_spec.rb2
-rw-r--r--spec/features/security/group/private_access_spec.rb2
-rw-r--r--spec/features/security/group/public_access_spec.rb2
-rw-r--r--spec/features/security/project/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/private_access_spec.rb4
-rw-r--r--spec/features/security/project/public_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/internal_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/private_access_spec.rb2
-rw-r--r--spec/features/security/project/snippet/public_access_spec.rb2
-rw-r--r--spec/features/signed_commits_spec.rb18
-rw-r--r--spec/features/snippets/embedded_snippet_spec.rb2
-rw-r--r--spec/features/snippets/explore_spec.rb2
-rw-r--r--spec/features/snippets/internal_snippet_spec.rb2
-rw-r--r--spec/features/snippets/notes_on_personal_snippets_spec.rb2
-rw-r--r--spec/features/snippets/public_snippets_spec.rb2
-rw-r--r--spec/features/snippets/search_snippets_spec.rb2
-rw-r--r--spec/features/snippets/show_spec.rb2
-rw-r--r--spec/features/snippets/user_creates_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_deletes_snippet_spec.rb2
-rw-r--r--spec/features/snippets/user_edits_snippet_spec.rb4
-rw-r--r--spec/features/snippets/user_sees_breadcrumb_links.rb17
-rw-r--r--spec/features/snippets/user_snippets_spec.rb2
-rw-r--r--spec/features/snippets_spec.rb2
-rw-r--r--spec/features/tags/developer_creates_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_deletes_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_updates_tag_spec.rb2
-rw-r--r--spec/features/tags/developer_views_tags_spec.rb2
-rw-r--r--spec/features/task_lists_spec.rb2
-rw-r--r--spec/features/triggers_spec.rb25
-rw-r--r--spec/features/u2f_spec.rb2
-rw-r--r--spec/features/unsubscribe_links_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_group_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_avatar_to_profile_spec.rb2
-rw-r--r--spec/features/uploads/user_uploads_file_to_note_spec.rb2
-rw-r--r--spec/features/usage_stats_consent_spec.rb10
-rw-r--r--spec/features/user_can_display_performance_bar_spec.rb2
-rw-r--r--spec/features/user_opens_link_to_comment.rb33
-rw-r--r--spec/features/user_opens_link_to_comment_spec.rb38
-rw-r--r--spec/features/user_sees_revert_modal_spec.rb2
-rw-r--r--spec/features/user_sorts_things_spec.rb2
-rw-r--r--spec/features/users/active_sessions_spec.rb2
-rw-r--r--spec/features/users/add_email_to_existing_account.rb15
-rw-r--r--spec/features/users/add_email_to_existing_account_spec.rb17
-rw-r--r--spec/features/users/login_spec.rb111
-rw-r--r--spec/features/users/logout_spec.rb2
-rw-r--r--spec/features/users/overview_spec.rb2
-rw-r--r--spec/features/users/rss_spec.rb2
-rw-r--r--spec/features/users/show_spec.rb2
-rw-r--r--spec/features/users/signup_spec.rb55
-rw-r--r--spec/features/users/snippets_spec.rb2
-rw-r--r--spec/features/users/terms_spec.rb2
-rw-r--r--spec/features/users/user_browses_projects_on_user_page_spec.rb15
-rw-r--r--spec/finders/autocomplete/move_to_project_finder_spec.rb40
-rw-r--r--spec/finders/autocomplete/users_finder_spec.rb2
-rw-r--r--spec/finders/award_emojis_finder_spec.rb49
-rw-r--r--spec/finders/awarded_emoji_finder_spec.rb25
-rw-r--r--spec/finders/cluster_ancestors_finder_spec.rb4
-rw-r--r--spec/finders/clusters/knative_services_finder_spec.rb22
-rw-r--r--spec/finders/clusters/kubernetes_namespace_finder_spec.rb110
-rw-r--r--spec/finders/container_repositories_finder_spec.rb44
-rw-r--r--spec/finders/group_descendants_finder_spec.rb6
-rw-r--r--spec/finders/group_members_finder_spec.rb4
-rw-r--r--spec/finders/group_projects_finder_spec.rb12
-rw-r--r--spec/finders/groups_finder_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb15
-rw-r--r--spec/finders/labels_finder_spec.rb6
-rw-r--r--spec/finders/members_finder_spec.rb8
-rw-r--r--spec/finders/merge_requests_finder_spec.rb2
-rw-r--r--spec/finders/notes_finder_spec.rb123
-rw-r--r--spec/finders/projects/serverless/functions_finder_spec.rb25
-rw-r--r--spec/finders/starred_projects_finder_spec.rb41
-rw-r--r--spec/finders/todos_finder_spec.rb2
-rw-r--r--spec/finders/users_star_projects_finder_spec.rb42
-rw-r--r--spec/fixtures/api/schemas/deployment.json2
-rw-r--r--spec/fixtures/api/schemas/entities/discussion.json67
-rw-r--r--spec/fixtures/api/schemas/entities/discussions.json4
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json46
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_poll_widget.json55
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_sidebar.json1
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json159
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json19
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/label.json11
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json16
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/project_label.json15
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json9
-rw-r--r--spec/fixtures/api/schemas/registry/repository.json6
-rw-r--r--spec/fixtures/clusters/sample_key.key9
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json6
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json3
-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.json944
-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.json947
-rw-r--r--spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json104
-rw-r--r--spec/fixtures/security-reports/remediations/remediation.patch180
-rw-r--r--spec/fixtures/security-reports/remediations/yarn.lock104
-rw-r--r--spec/frontend/autosave_spec.js11
-rw-r--r--spec/frontend/behaviors/markdown/render_metrics_spec.js37
-rw-r--r--spec/frontend/clusters/clusters_bundle_spec.js51
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js177
-rw-r--r--spec/frontend/environment.js8
-rw-r--r--spec/frontend/environments/confirm_rollback_modal_spec.js70
-rw-r--r--spec/frontend/environments/environment_rollback_spec.js53
-rw-r--r--spec/frontend/fixtures/abuse_reports.rb26
-rw-r--r--spec/frontend/fixtures/admin_users.rb28
-rw-r--r--spec/frontend/fixtures/application_settings.rb33
-rw-r--r--spec/frontend/fixtures/autocomplete_sources.rb39
-rw-r--r--spec/frontend/fixtures/blob.rb34
-rw-r--r--spec/frontend/fixtures/boards.rb28
-rw-r--r--spec/frontend/fixtures/branches.rb32
-rw-r--r--spec/frontend/fixtures/clusters.rb34
-rw-r--r--spec/frontend/fixtures/commit.rb33
-rw-r--r--spec/frontend/fixtures/deploy_keys.rb43
-rw-r--r--spec/frontend/fixtures/groups.rb35
-rw-r--r--spec/frontend/fixtures/issues.rb122
-rw-r--r--spec/frontend/fixtures/jobs.rb54
-rw-r--r--spec/frontend/fixtures/labels.rb58
-rw-r--r--spec/frontend/fixtures/merge_requests.rb134
-rw-r--r--spec/frontend/fixtures/merge_requests_diffs.rb69
-rw-r--r--spec/frontend/fixtures/pipeline_schedules.rb43
-rw-r--r--spec/frontend/fixtures/pipelines.rb34
-rw-r--r--spec/frontend/fixtures/projects.rb81
-rw-r--r--spec/frontend/fixtures/prometheus_service.rb34
-rw-r--r--spec/frontend/fixtures/raw.rb (renamed from spec/javascripts/fixtures/raw.rb)0
-rw-r--r--spec/frontend/fixtures/search.rb17
-rw-r--r--spec/frontend/fixtures/services.rb34
-rw-r--r--spec/frontend/fixtures/sessions.rb25
-rw-r--r--spec/frontend/fixtures/snippet.rb33
-rw-r--r--spec/frontend/fixtures/static/README.md3
-rw-r--r--spec/frontend/fixtures/static/ajax_loading_spinner.html (renamed from spec/javascripts/fixtures/static/ajax_loading_spinner.html)0
-rw-r--r--spec/frontend/fixtures/static/balsamiq_viewer.html (renamed from spec/javascripts/fixtures/static/balsamiq_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/create_item_dropdown.html (renamed from spec/javascripts/fixtures/static/create_item_dropdown.html)0
-rw-r--r--spec/frontend/fixtures/static/environments/table.html (renamed from spec/javascripts/fixtures/static/environments/table.html)0
-rw-r--r--spec/frontend/fixtures/static/environments_logs.html29
-rw-r--r--spec/frontend/fixtures/static/event_filter.html (renamed from spec/javascripts/fixtures/static/event_filter.html)0
-rw-r--r--spec/frontend/fixtures/static/gl_dropdown.html (renamed from spec/javascripts/fixtures/static/gl_dropdown.html)0
-rw-r--r--spec/frontend/fixtures/static/gl_field_errors.html (renamed from spec/javascripts/fixtures/static/gl_field_errors.html)0
-rw-r--r--spec/frontend/fixtures/static/images/green_box.png (renamed from spec/javascripts/fixtures/static/images/green_box.png)bin1306 -> 1306 bytes
-rw-r--r--spec/frontend/fixtures/static/images/one_white_pixel.png (renamed from spec/javascripts/fixtures/static/images/one_white_pixel.png)bin68 -> 68 bytes
-rw-r--r--spec/frontend/fixtures/static/images/red_box.png (renamed from spec/javascripts/fixtures/static/images/red_box.png)bin1305 -> 1305 bytes
-rw-r--r--spec/frontend/fixtures/static/issuable_filter.html (renamed from spec/javascripts/fixtures/static/issuable_filter.html)0
-rw-r--r--spec/frontend/fixtures/static/issue_sidebar_label.html (renamed from spec/javascripts/fixtures/static/issue_sidebar_label.html)0
-rw-r--r--spec/frontend/fixtures/static/line_highlighter.html (renamed from spec/javascripts/fixtures/static/line_highlighter.html)0
-rw-r--r--spec/frontend/fixtures/static/linked_tabs.html (renamed from spec/javascripts/fixtures/static/linked_tabs.html)0
-rw-r--r--spec/frontend/fixtures/static/merge_requests_show.html (renamed from spec/javascripts/fixtures/static/merge_requests_show.html)0
-rw-r--r--spec/frontend/fixtures/static/mini_dropdown_graph.html (renamed from spec/javascripts/fixtures/static/mini_dropdown_graph.html)0
-rw-r--r--spec/frontend/fixtures/static/notebook_viewer.html (renamed from spec/javascripts/fixtures/static/notebook_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/oauth_remember_me.html (renamed from spec/javascripts/fixtures/static/oauth_remember_me.html)0
-rw-r--r--spec/frontend/fixtures/static/pdf_viewer.html (renamed from spec/javascripts/fixtures/static/pdf_viewer.html)0
-rw-r--r--spec/frontend/fixtures/static/pipeline_graph.html (renamed from spec/javascripts/fixtures/static/pipeline_graph.html)0
-rw-r--r--spec/frontend/fixtures/static/pipelines.html (renamed from spec/javascripts/fixtures/static/pipelines.html)0
-rw-r--r--spec/frontend/fixtures/static/project_select_combo_button.html (renamed from spec/javascripts/fixtures/static/project_select_combo_button.html)0
-rw-r--r--spec/frontend/fixtures/static/projects.json (renamed from spec/javascripts/fixtures/static/projects.json)0
-rw-r--r--spec/frontend/fixtures/static/search_autocomplete.html (renamed from spec/javascripts/fixtures/static/search_autocomplete.html)0
-rw-r--r--spec/frontend/fixtures/static/signin_tabs.html (renamed from spec/javascripts/fixtures/static/signin_tabs.html)0
-rw-r--r--spec/frontend/fixtures/static/sketch_viewer.html (renamed from spec/javascripts/fixtures/static/sketch_viewer.html)0
-rw-r--r--spec/frontend/fixtures/todos.rb54
-rw-r--r--spec/frontend/fixtures/u2f.rb44
-rw-r--r--spec/frontend/helpers/fixtures.js7
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper.js18
-rw-r--r--spec/frontend/helpers/vue_test_utils_helper_spec.js (renamed from spec/javascripts/helpers/vue_test_utils_helper_spec.js)0
-rw-r--r--spec/frontend/ide/services/index_spec.js32
-rw-r--r--spec/frontend/jobs/components/empty_state_spec.js93
-rw-r--r--spec/frontend/lib/utils/color_utils_spec.js35
-rw-r--r--spec/frontend/lib/utils/forms_spec.js74
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js14
-rw-r--r--spec/frontend/lib/utils/url_utility_spec.js41
-rw-r--r--spec/frontend/matchers.js38
-rw-r--r--spec/frontend/mocks/mocks_helper_spec.js4
-rw-r--r--spec/frontend/monitoring/embed/embed_spec.js78
-rw-r--r--spec/frontend/monitoring/embed/mock_data.js87
-rw-r--r--spec/frontend/notes/components/discussion_jump_to_next_button_spec.js5
-rw-r--r--spec/frontend/notes/components/discussion_keyboard_navigator_spec.js104
-rw-r--r--spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap7
-rw-r--r--spec/frontend/pages/search/show/refresh_counts_spec.js35
-rw-r--r--spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js61
-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.js24
-rw-r--r--spec/frontend/tracking_spec.js116
-rw-r--r--spec/frontend/vue_shared/components/changed_file_icon_spec.js123
-rw-r--r--spec/frontend/vue_shared/components/markdown/header_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js9
-rw-r--r--spec/frontend/wikis_spec.js74
-rw-r--r--spec/graphql/resolvers/namespace_projects_resolver_spec.rb2
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb6
-rw-r--r--spec/helpers/dashboard_helper_spec.rb39
-rw-r--r--spec/helpers/groups_helper_spec.rb46
-rw-r--r--spec/helpers/labels_helper_spec.rb24
-rw-r--r--spec/helpers/namespaces_helper_spec.rb4
-rw-r--r--spec/helpers/notifications_helper_spec.rb11
-rw-r--r--spec/helpers/projects_helper_spec.rb25
-rw-r--r--spec/helpers/search_helper_spec.rb44
-rw-r--r--spec/helpers/sessions_helper_spec.rb17
-rw-r--r--spec/helpers/submodule_helper_spec.rb94
-rw-r--r--spec/helpers/tracking_helper_spec.rb28
-rw-r--r--spec/helpers/users_helper_spec.rb2
-rw-r--r--spec/helpers/wiki_helper_spec.rb2
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/boards/board_blank_state_spec.js19
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/boards/components/board_form_spec.js56
-rw-r--r--spec/javascripts/boards/components/boards_selector_spec.js206
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js2
-rw-r--r--spec/javascripts/ci_variable_list/ci_variable_list_spec.js2
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_expansion_cell_spec.js64
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js2
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js20
-rw-r--r--spec/javascripts/diffs/components/diff_line_gutter_content_spec.js15
-rw-r--r--spec/javascripts/diffs/components/diff_line_note_form_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_table_cell_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/inline_diff_table_row_spec.js4
-rw-r--r--spec/javascripts/diffs/components/inline_diff_view_spec.js8
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js31
-rw-r--r--spec/javascripts/diffs/components/parallel_diff_view_spec.js4
-rw-r--r--spec/javascripts/diffs/mock_data/diff_file.js2
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js6
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js5
-rw-r--r--spec/javascripts/diffs/store/utils_spec.js11
-rw-r--r--spec/javascripts/environments/confirm_rollback_modal_spec.js70
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js61
-rw-r--r--spec/javascripts/fixtures/.gitignore7
-rw-r--r--spec/javascripts/fixtures/abuse_reports.rb26
-rw-r--r--spec/javascripts/fixtures/admin_users.rb28
-rw-r--r--spec/javascripts/fixtures/application_settings.rb33
-rw-r--r--spec/javascripts/fixtures/autocomplete_sources.rb39
-rw-r--r--spec/javascripts/fixtures/blob.rb34
-rw-r--r--spec/javascripts/fixtures/boards.rb28
-rw-r--r--spec/javascripts/fixtures/branches.rb32
-rw-r--r--spec/javascripts/fixtures/clusters.rb34
-rw-r--r--spec/javascripts/fixtures/commit.rb33
-rw-r--r--spec/javascripts/fixtures/deploy_keys.rb43
-rw-r--r--spec/javascripts/fixtures/groups.rb35
-rw-r--r--spec/javascripts/fixtures/issues.rb122
-rw-r--r--spec/javascripts/fixtures/jobs.rb54
-rw-r--r--spec/javascripts/fixtures/labels.rb58
-rw-r--r--spec/javascripts/fixtures/merge_requests.rb134
-rw-r--r--spec/javascripts/fixtures/merge_requests_diffs.rb70
-rw-r--r--spec/javascripts/fixtures/pipeline_schedules.rb43
-rw-r--r--spec/javascripts/fixtures/pipelines.rb34
-rw-r--r--spec/javascripts/fixtures/projects.rb81
-rw-r--r--spec/javascripts/fixtures/prometheus_service.rb34
-rw-r--r--spec/javascripts/fixtures/search.rb17
-rw-r--r--spec/javascripts/fixtures/services.rb34
-rw-r--r--spec/javascripts/fixtures/sessions.rb25
-rw-r--r--spec/javascripts/fixtures/snippet.rb33
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/fixtures/todos.rb54
-rw-r--r--spec/javascripts/fixtures/u2f.rb44
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js24
-rw-r--r--spec/javascripts/ide/components/repo_editor_spec.js8
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js4
-rw-r--r--spec/javascripts/ide/stores/mutations/file_spec.js42
-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/issue_show/components/form_spec.js111
-rw-r--r--spec/javascripts/jobs/components/empty_state_spec.js141
-rw-r--r--spec/javascripts/jobs/components/job_app_spec.js684
-rw-r--r--spec/javascripts/jobs/components/job_log_spec.js24
-rw-r--r--spec/javascripts/jobs/components/manual_variables_form_spec.js88
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js7
-rw-r--r--spec/javascripts/monitoring/charts/empty_chart_spec.js29
-rw-r--r--spec/javascripts/monitoring/charts/time_series_spec.js335
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js588
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js475
-rw-r--r--spec/javascripts/monitoring/mock_data.js4
-rw-r--r--spec/javascripts/monitoring/panel_type_spec.js78
-rw-r--r--spec/javascripts/monitoring/store/actions_spec.js4
-rw-r--r--spec/javascripts/monitoring/utils_spec.js28
-rw-r--r--spec/javascripts/notes/components/note_actions/reply_button_spec.js5
-rw-r--r--spec/javascripts/notes/mock_data.js2
-rw-r--r--spec/javascripts/notes/stores/actions_spec.js27
-rw-r--r--spec/javascripts/notes/stores/getters_spec.js48
-rw-r--r--spec/javascripts/pdf/page_spec.js5
-rw-r--r--spec/javascripts/performance_bar/components/detailed_metric_spec.js27
-rw-r--r--spec/javascripts/persistent_user_callout_spec.js87
-rw-r--r--spec/javascripts/pipelines/pipelines_actions_spec.js42
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js175
-rw-r--r--spec/javascripts/registry/mock_data.js11
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js12
-rw-r--r--spec/javascripts/sidebar/assignees_spec.js206
-rw-r--r--spec/javascripts/test_constants.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js4
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js24
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/javascripts/vue_shared/components/changed_file_icon_spec.js63
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js2
-rw-r--r--spec/javascripts/vue_shared/directives/autofocusonshow_spec.js38
-rw-r--r--spec/lib/after_commit_queue_spec.rb2
-rw-r--r--spec/lib/api/api_spec.rb2
-rw-r--r--spec/lib/api/helpers/custom_validators_spec.rb2
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb2
-rw-r--r--spec/lib/api/helpers/related_resources_helpers_spec.rb2
-rw-r--r--spec/lib/api/helpers/version_spec.rb2
-rw-r--r--spec/lib/api/helpers_spec.rb2
-rw-r--r--spec/lib/backup/files_spec.rb2
-rw-r--r--spec/lib/backup/manager_spec.rb2
-rw-r--r--spec/lib/backup/repository_spec.rb2
-rw-r--r--spec/lib/backup/uploads_spec.rb2
-rw-r--r--spec/lib/banzai/color_parser_spec.rb2
-rw-r--r--spec/lib/banzai/commit_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/cross_project_reference_spec.rb2
-rw-r--r--spec/lib/banzai/filter/absolute_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/abstract_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/blockquote_fence_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/color_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/commit_reference_filter_spec.rb13
-rw-r--r--spec/lib/banzai/filter/commit_trailers_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/emoji_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/external_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/front_matter_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/gollum_tags_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/html_entity_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_lazy_load_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/image_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_diff_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/inline_metrics_filter_spec.rb37
-rw-r--r--spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb10
-rw-r--r--spec/lib/banzai/filter/issuable_state_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/markdown_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/math_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/merge_request_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/mermaid_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb6
-rw-r--r--spec/lib/banzai/filter/plantuml_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/reference_redactor_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/sanitization_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/snippet_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/spaced_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/table_of_contents_filter_spec.rb12
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/video_link_filter_spec.rb2
-rw-r--r--spec/lib/banzai/filter/wiki_link_filter_spec.rb44
-rw-r--r--spec/lib/banzai/filter_array_spec.rb2
-rw-r--r--spec/lib/banzai/issuable_extractor_spec.rb2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/description_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/email_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/full_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/gfm_pipeline_spec.rb2
-rw-r--r--spec/lib/banzai/pipeline/wiki_pipeline_spec.rb81
-rw-r--r--spec/lib/banzai/querying_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/base_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/issue_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb2
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb2
-rw-r--r--spec/lib/banzai/renderer_spec.rb2
-rw-r--r--spec/lib/bitbucket/collection_spec.rb2
-rw-r--r--spec/lib/bitbucket/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket/page_spec.rb2
-rw-r--r--spec/lib/bitbucket/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/comment_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/issue_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/pull_request_spec.rb4
-rw-r--r--spec/lib/bitbucket/representation/repo_spec.rb2
-rw-r--r--spec/lib/bitbucket/representation/user_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb13
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/page_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/paginator_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/activity_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/pull_request_spec.rb2
-rw-r--r--spec/lib/bitbucket_server/representation/repo_spec.rb2
-rw-r--r--spec/lib/constraints/feature_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/group_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/project_url_constrainer_spec.rb2
-rw-r--r--spec/lib/constraints/user_url_constrainer_spec.rb2
-rw-r--r--spec/lib/container_registry/blob_spec.rb21
-rw-r--r--spec/lib/container_registry/client_spec.rb38
-rw-r--r--spec/lib/container_registry/path_spec.rb2
-rw-r--r--spec/lib/container_registry/registry_spec.rb2
-rw-r--r--spec/lib/container_registry/tag_spec.rb4
-rw-r--r--spec/lib/event_filter_spec.rb2
-rw-r--r--spec/lib/expand_variables_spec.rb177
-rw-r--r--spec/lib/extracts_path_spec.rb2
-rw-r--r--spec/lib/feature/gitaly_spec.rb2
-rw-r--r--spec/lib/feature_spec.rb18
-rw-r--r--spec/lib/file_size_validator_spec.rb2
-rw-r--r--spec/lib/forever_spec.rb18
-rw-r--r--spec/lib/gitaly/server_spec.rb2
-rw-r--r--spec/lib/gitlab/action_rate_limiter_spec.rb103
-rw-r--r--spec/lib/gitlab/allowable_spec.rb2
-rw-r--r--spec/lib/gitlab/app_logger_spec.rb2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/activity_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/blocked_user_tracker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/adapter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/dn_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/ldap/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb21
-rw-r--r--spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/provider_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/request_authenticator_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/auth_hash_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/identity_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/saml/user_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/unique_ips_limiter_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_access_denied_reason_spec.rb2
-rw-r--r--spec/lib/gitlab/auth/user_auth_finders_spec.rb16
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_columns_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb6
-rw-r--r--spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb296
-rw-r--r--spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb63
-rw-r--r--spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/report_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/coverage/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/status_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb2
-rw-r--r--spec/lib/gitlab/badge/shared/metadata.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/importer_spec.rb18
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb43
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_server_import/importer_spec.rb2
-rw-r--r--spec/lib/gitlab/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/build_access_spec.rb2
-rw-r--r--spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb3
-rw-r--r--spec/lib/gitlab/cache/request_cache_spec.rb2
-rw-r--r--spec/lib/gitlab/changes_list_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_name_token_spec.rb2
-rw-r--r--spec/lib/gitlab/chat_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/force_push_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_created_spec.rb2
-rw-r--r--spec/lib/gitlab/checks/project_moved_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/ansi2html_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/path_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/credentials/registry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/changes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/refs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/policy/variables_spec.rb49
-rw-r--r--spec/lib/gitlab/ci/build/policy_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb81
-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/build/step_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/charts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/artifacts_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/commands_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/default_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/hidden_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/image_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb198
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/key_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/paths_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/config/entry/reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/retry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/root_spec.rb2
-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/config/entry/script_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/service_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/services_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stage_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/stages_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/variables_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable/entry_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/extendable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/normalizer_spec.rb59
-rw-r--r--spec/lib/gitlab/ci/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/cron_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/mask_secret_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/parsers/test/junit_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/create_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb10
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/duration_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/not_matches_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/expression/token_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb392
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb26
-rw-r--r--spec/lib/gitlab/ci/reports/test_case_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_reports_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/reports/test_suite_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/action_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/cancelable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/erased_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/play_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retried_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/retryable_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/stop_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/build/unschedule_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/canceled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/created_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/extended_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/external/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/failed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/group/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/manual_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pending_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/pipeline/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/running_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/scheduled_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/skipped_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/common_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/stage/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/status/success_warning_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/chunked_io_spec.rb14
-rw-r--r--spec/lib/gitlab/ci/trace/section_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/trace/stream_spec.rb16
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection/item_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/variables/collection_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/yaml_processor_spec.rb220
-rw-r--r--spec/lib/gitlab/ci_access_spec.rb2
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb4
-rw-r--r--spec/lib/gitlab/color_schemes_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/attributable_spec.rb5
-rw-r--r--spec/lib/gitlab/config/entry/boolean_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/factory_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/simplifiable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/undefined_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/unspecified_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validatable_spec.rb2
-rw-r--r--spec/lib/gitlab/config/entry/validator_spec.rb2
-rw-r--r--spec/lib/gitlab/config/loader/yaml_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/conflict/file_spec.rb2
-rw-r--r--spec/lib/gitlab/content_security_policy/config_loader_spec.rb61
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_collection_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/check_info_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access/class_methods_spec.rb2
-rw-r--r--spec/lib/gitlab/cross_project_access_spec.rb2
-rw-r--r--spec/lib/gitlab/crypto_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/current_settings_spec.rb12
-rw-r--r--spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb8
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/code_stage_spec.rb100
-rw-r--r--spec/lib/gitlab/cycle_analytics/events_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb114
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb98
-rw-r--r--spec/lib/gitlab/cycle_analytics/permissions_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb80
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/production_stage_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/review_stage_spec.rb70
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_event_spec.rb4
-rw-r--r--spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb72
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/test_stage_spec.rb6
-rw-r--r--spec/lib/gitlab/cycle_analytics/updater_spec.rb2
-rw-r--r--spec/lib/gitlab/cycle_analytics/usage_data_spec.rb82
-rw-r--r--spec/lib/gitlab/daemon_spec.rb2
-rw-r--r--spec/lib/gitlab/danger/helper_spec.rb46
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb8
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/note_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/pipeline_spec.rb2
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb36
-rw-r--r--spec/lib/gitlab/data_builder/wiki_page_spec.rb2
-rw-r--r--spec/lib/gitlab/database/count/exact_count_strategy_spec.rb16
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb18
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb22
-rw-r--r--spec/lib/gitlab/database/count_spec.rb17
-rw-r--r--spec/lib/gitlab/database/grant_spec.rb4
-rw-r--r--spec/lib/gitlab/database/median_spec.rb17
-rw-r--r--spec/lib/gitlab/database/migration_helpers_spec.rb523
-rw-r--r--spec/lib/gitlab/database/multi_threaded_migration_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb2
-rw-r--r--spec/lib/gitlab/database/sha_attribute_spec.rb8
-rw-r--r--spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/database_spec.rb203
-rw-r--r--spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/dependency_linker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/diff_refs_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/image_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/formatters/text_formatter_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/highlight_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_marker_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/inline_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_mapper_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/line_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parallel_diff_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/parser_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_spec.rb2
-rw-r--r--spec/lib/gitlab/diff/position_tracer_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check/message_spec.rb2
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb2
-rw-r--r--spec/lib/gitlab/email/attachment_uploader_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb2
-rw-r--r--spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb2
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb2
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb2
-rw-r--r--spec/lib/gitlab/email/reply_parser_spec.rb2
-rw-r--r--spec/lib/gitlab/encoding_helper_spec.rb14
-rw-r--r--spec/lib/gitlab/etag_caching/middleware_spec.rb2
-rw-r--r--spec/lib/gitlab/etag_caching/router_spec.rb2
-rw-r--r--spec/lib/gitlab/exclusive_lease_helpers_spec.rb17
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb11
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb6
-rw-r--r--spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb28
-rw-r--r--spec/lib/gitlab/git_post_receive_spec.rb138
-rw-r--r--spec/lib/gitlab/git_spec.rb20
-rw-r--r--spec/lib/gitlab/gitaly_client/notification_service_spec.rb17
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb23
-rw-r--r--spec/lib/gitlab/github_import/importer/releases_importer_spec.rb7
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/http_connection_adapter_spec.rb6
-rw-r--r--spec/lib/gitlab/http_spec.rb6
-rw-r--r--spec/lib/gitlab/import/database_helpers_spec.rb31
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml17
-rw-r--r--spec/lib/gitlab/import_export/lfs_restorer_spec.rb98
-rw-r--r--spec/lib/gitlab/import_export/lfs_saver_spec.rb49
-rw-r--r--spec/lib/gitlab/import_export/members_mapper_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/project.group.json6
-rw-r--r--spec/lib/gitlab/import_export/project.json79
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb43
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb18
-rw-r--r--spec/lib/gitlab/import_export/relation_rename_service_spec.rb1
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml25
-rw-r--r--spec/lib/gitlab/kubernetes/default_namespace_spec.rb85
-rw-r--r--spec/lib/gitlab/kubernetes/helm/pod_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb65
-rw-r--r--spec/lib/gitlab/kubernetes/kube_client_spec.rb2
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb48
-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/metrics/dashboard/defaults_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb36
-rw-r--r--spec/lib/gitlab/metrics/dashboard/finder_spec.rb76
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb89
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb80
-rw-r--r--spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb52
-rw-r--r--spec/lib/gitlab/metrics/dashboard/url_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb20
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb20
-rw-r--r--spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb10
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb2
-rw-r--r--spec/lib/gitlab/octokit/middleware_spec.rb68
-rw-r--r--spec/lib/gitlab/performance_bar_spec.rb4
-rw-r--r--spec/lib/gitlab/project_authorizations_spec.rb38
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb22
-rw-r--r--spec/lib/gitlab/project_template_spec.rb18
-rw-r--r--spec/lib/gitlab/prometheus/metric_group_spec.rb4
-rw-r--r--spec/lib/gitlab/prometheus/query_variables_spec.rb6
-rw-r--r--spec/lib/gitlab/prometheus_client_spec.rb47
-rw-r--r--spec/lib/gitlab/quick_actions/command_definition_spec.rb46
-rw-r--r--spec/lib/gitlab/quick_actions/dsl_spec.rb14
-rw-r--r--spec/lib/gitlab/regex_spec.rb8
-rw-r--r--spec/lib/gitlab/repository_cache_adapter_spec.rb7
-rw-r--r--spec/lib/gitlab/repository_set_cache_spec.rb73
-rw-r--r--spec/lib/gitlab/request_profiler/profile_spec.rb59
-rw-r--r--spec/lib/gitlab/request_profiler_spec.rb41
-rw-r--r--spec/lib/gitlab/rugged_instrumentation_spec.rb27
-rw-r--r--spec/lib/gitlab/search_results_spec.rb37
-rw-r--r--spec/lib/gitlab/sentry_spec.rb29
-rw-r--r--spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb68
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb8
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb57
-rw-r--r--spec/lib/gitlab/slug/environment_spec.rb38
-rw-r--r--spec/lib/gitlab/snippet_search_results_spec.rb18
-rw-r--r--spec/lib/gitlab/snowplow_tracker_spec.rb45
-rw-r--r--spec/lib/gitlab/sql/cte_spec.rb2
-rw-r--r--spec/lib/gitlab/sql/recursive_cte_spec.rb2
-rw-r--r--spec/lib/gitlab/submodule_links_spec.rb47
-rw-r--r--spec/lib/gitlab/url_blocker_spec.rb254
-rw-r--r--spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/note_counter_spec.rb88
-rw-r--r--spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb55
-rw-r--r--spec/lib/gitlab/usage_data_counters/search_counter_spec.rb13
-rw-r--r--spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb12
-rw-r--r--spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb14
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb62
-rw-r--r--spec/lib/gitlab/utils/sanitize_node_link_spec.rb72
-rw-r--r--spec/lib/gitlab/utils_spec.rb19
-rw-r--r--spec/lib/gitlab/workhorse_spec.rb1
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb10
-rw-r--r--spec/lib/gitlab_spec.rb6
-rw-r--r--spec/lib/peek/views/redis_detailed_spec.rb25
-rw-r--r--spec/lib/peek/views/rugged_spec.rb42
-rw-r--r--spec/lib/prometheus/cleanup_multiproc_dir_service_spec.rb51
-rw-r--r--spec/lib/prometheus/pid_provider_spec.rb117
-rw-r--r--spec/lib/quality/test_level_spec.rb4
-rw-r--r--spec/lib/sentry/client_spec.rb2
-rw-r--r--spec/lib/serializers/json_spec.rb84
-rw-r--r--spec/mailers/notify_spec.rb21
-rw-r--r--spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb21
-rw-r--r--spec/migrations/set_issue_id_for_all_versions_spec.rb38
-rw-r--r--spec/models/active_session_spec.rb2
-rw-r--r--spec/models/analytics/cycle_analytics/project_stage_spec.rb9
-rw-r--r--spec/models/application_setting_spec.rb11
-rw-r--r--spec/models/award_emoji_spec.rb23
-rw-r--r--spec/models/ci/bridge_spec.rb2
-rw-r--r--spec/models/ci/build_need_spec.rb13
-rw-r--r--spec/models/ci/build_spec.rb166
-rw-r--r--spec/models/ci/job_artifact_spec.rb25
-rw-r--r--spec/models/ci/job_variable_spec.rb12
-rw-r--r--spec/models/ci/pipeline_spec.rb24
-rw-r--r--spec/models/ci/runner_spec.rb6
-rw-r--r--spec/models/ci/variable_spec.rb1
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb52
-rw-r--r--spec/models/clusters/applications/helm_spec.rb67
-rw-r--r--spec/models/clusters/applications/knative_spec.rb46
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb17
-rw-r--r--spec/models/clusters/cluster_spec.rb125
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb84
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb213
-rw-r--r--spec/models/commit_spec.rb1
-rw-r--r--spec/models/concerns/awardable_spec.rb10
-rw-r--r--spec/models/concerns/cacheable_attributes_spec.rb45
-rw-r--r--spec/models/concerns/case_sensitivity_spec.rb27
-rw-r--r--spec/models/concerns/deployment_platform_spec.rb12
-rw-r--r--spec/models/concerns/group_descendant_spec.rb6
-rw-r--r--spec/models/concerns/has_environment_scope_spec.rb66
-rw-r--r--spec/models/concerns/issuable_spec.rb23
-rw-r--r--spec/models/concerns/project_api_compatibility_spec.rb51
-rw-r--r--spec/models/concerns/prometheus_adapter_spec.rb4
-rw-r--r--spec/models/concerns/relative_positioning_spec.rb242
-rw-r--r--spec/models/container_repository_spec.rb2
-rw-r--r--spec/models/cycle_analytics/code_spec.rb4
-rw-r--r--spec/models/cycle_analytics/group_level_spec.rb46
-rw-r--r--spec/models/cycle_analytics/issue_spec.rb2
-rw-r--r--spec/models/cycle_analytics/plan_spec.rb2
-rw-r--r--spec/models/cycle_analytics/production_spec.rb4
-rw-r--r--spec/models/cycle_analytics/project_level_spec.rb2
-rw-r--r--spec/models/cycle_analytics/review_spec.rb2
-rw-r--r--spec/models/cycle_analytics/staging_spec.rb4
-rw-r--r--spec/models/cycle_analytics/test_spec.rb8
-rw-r--r--spec/models/deployment_metrics_spec.rb12
-rw-r--r--spec/models/diff_note_spec.rb4
-rw-r--r--spec/models/environment_spec.rb114
-rw-r--r--spec/models/group_spec.rb73
-rw-r--r--spec/models/issue_spec.rb8
-rw-r--r--spec/models/label_spec.rb13
-rw-r--r--spec/models/lfs_download_object_spec.rb2
-rw-r--r--spec/models/member_spec.rb13
-rw-r--r--spec/models/members/group_member_spec.rb38
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request_diff_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb41
-rw-r--r--spec/models/namespace/aggregation_schedule_spec.rb34
-rw-r--r--spec/models/namespace/root_storage_statistics_spec.rb2
-rw-r--r--spec/models/namespace_spec.rb80
-rw-r--r--spec/models/note_spec.rb16
-rw-r--r--spec/models/notification_recipient_spec.rb34
-rw-r--r--spec/models/pages_domain_spec.rb24
-rw-r--r--spec/models/postgresql/replication_slot_spec.rb2
-rw-r--r--spec/models/project_auto_devops_spec.rb2
-rw-r--r--spec/models/project_feature_spec.rb6
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_services/chat_message/pipeline_message_spec.rb484
-rw-r--r--spec/models/project_services/emails_on_push_service_spec.rb20
-rw-r--r--spec/models/project_services/jira_service_spec.rb24
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb167
-rw-r--r--spec/models/project_services/microsoft_teams_service_spec.rb3
-rw-r--r--spec/models/project_services/prometheus_service_spec.rb4
-rw-r--r--spec/models/project_spec.rb434
-rw-r--r--spec/models/project_statistics_spec.rb13
-rw-r--r--spec/models/prometheus_metric_spec.rb13
-rw-r--r--spec/models/remote_mirror_spec.rb19
-rw-r--r--spec/models/repository_spec.rb97
-rw-r--r--spec/models/todo_spec.rb6
-rw-r--r--spec/models/user_spec.rb89
-rw-r--r--spec/models/wiki_page_spec.rb3
-rw-r--r--spec/policies/clusters/instance_policy_spec.rb4
-rw-r--r--spec/policies/global_policy_spec.rb28
-rw-r--r--spec/policies/group_member_policy_spec.rb2
-rw-r--r--spec/policies/group_policy_spec.rb142
-rw-r--r--spec/policies/project_policy_spec.rb120
-rw-r--r--spec/presenters/blobs/unfold_presenter_spec.rb95
-rw-r--r--spec/presenters/clusters/cluster_presenter_spec.rb2
-rw-r--r--spec/presenters/projects/settings/deploy_keys_presenter_spec.rb8
-rw-r--r--spec/requests/api/award_emoji_spec.rb16
-rw-r--r--spec/requests/api/boards_spec.rb2
-rw-r--r--spec/requests/api/commit_statuses_spec.rb24
-rw-r--r--spec/requests/api/commits_spec.rb211
-rw-r--r--spec/requests/api/container_registry_spec.rb245
-rw-r--r--spec/requests/api/discussions_spec.rb55
-rw-r--r--spec/requests/api/files_spec.rb162
-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/projects_spec.rb2
-rw-r--r--spec/requests/api/group_container_repositories_spec.rb57
-rw-r--r--spec/requests/api/group_labels_spec.rb19
-rw-r--r--spec/requests/api/groups_spec.rb14
-rw-r--r--spec/requests/api/issues/get_group_issues_spec.rb10
-rw-r--r--spec/requests/api/issues/get_project_issues_spec.rb1
-rw-r--r--spec/requests/api/issues/issues_spec.rb4
-rw-r--r--spec/requests/api/labels_spec.rb111
-rw-r--r--spec/requests/api/members_spec.rb6
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/api/project_clusters_spec.rb1
-rw-r--r--spec/requests/api/project_container_repositories_spec.rb228
-rw-r--r--spec/requests/api/projects_spec.rb103
-rw-r--r--spec/requests/api/resource_label_events_spec.rb45
-rw-r--r--spec/requests/api/services_spec.rb10
-rw-r--r--spec/requests/api/settings_spec.rb68
-rw-r--r--spec/requests/api/triggers_spec.rb28
-rw-r--r--spec/requests/api/variables_spec.rb24
-rw-r--r--spec/requests/openid_connect_spec.rb2
-rw-r--r--spec/requests/rack_attack_global_spec.rb12
-rw-r--r--spec/requests/request_profiler_spec.rb20
-rw-r--r--spec/routing/git_http_routing_spec.rb24
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb144
-rw-r--r--spec/rubocop/cop/rspec/top_level_describe_path_spec.rb67
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb8
-rw-r--r--spec/serializers/analytics_issue_serializer_spec.rb8
-rw-r--r--spec/serializers/analytics_merge_request_serializer_spec.rb8
-rw-r--r--spec/serializers/analytics_stage_serializer_spec.rb10
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb2
-rw-r--r--spec/serializers/deployment_entity_spec.rb4
-rw-r--r--spec/serializers/group_child_entity_spec.rb2
-rw-r--r--spec/serializers/group_child_serializer_spec.rb4
-rw-r--r--spec/serializers/issue_entity_spec.rb33
-rw-r--r--spec/serializers/merge_request_sidebar_basic_entity_spec.rb22
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb2
-rw-r--r--spec/serializers/user_serializer_spec.rb30
-rw-r--r--spec/serializers/variable_entity_spec.rb2
-rw-r--r--spec/services/application_settings/update_service_spec.rb64
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb13
-rw-r--r--spec/services/award_emojis/add_service_spec.rb103
-rw-r--r--spec/services/award_emojis/collect_user_emoji_service_spec.rb25
-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/boards/issues/list_service_spec.rb2
-rw-r--r--spec/services/ci/archive_trace_service_spec.rb38
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb57
-rw-r--r--spec/services/ci/play_build_service_spec.rb13
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb136
-rw-r--r--spec/services/ci/retry_build_service_spec.rb6
-rw-r--r--spec/services/clusters/build_kubernetes_namespace_service_spec.rb57
-rw-r--r--spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb14
-rw-r--r--spec/services/clusters/refresh_service_spec.rb113
-rw-r--r--spec/services/create_snippet_service_spec.rb16
-rw-r--r--spec/services/git/base_hooks_service_spec.rb72
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb84
-rw-r--r--spec/services/git/branch_push_service_spec.rb25
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb6
-rw-r--r--spec/services/git/tag_push_service_spec.rb4
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb2
-rw-r--r--spec/services/groups/create_service_spec.rb38
-rw-r--r--spec/services/groups/nested_create_service_spec.rb30
-rw-r--r--spec/services/groups/transfer_service_spec.rb17
-rw-r--r--spec/services/groups/update_service_spec.rb17
-rw-r--r--spec/services/issuable/bulk_update_service_spec.rb357
-rw-r--r--spec/services/issues/update_service_spec.rb15
-rw-r--r--spec/services/members/destroy_service_spec.rb6
-rw-r--r--spec/services/merge_requests/build_service_spec.rb37
-rw-r--r--spec/services/merge_requests/mergeability_check_service_spec.rb79
-rw-r--r--spec/services/merge_requests/push_options_handler_service_spec.rb230
-rw-r--r--spec/services/merge_requests/rebase_service_spec.rb2
-rw-r--r--spec/services/merge_requests/update_service_spec.rb5
-rw-r--r--spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb145
-rw-r--r--spec/services/metrics/dashboard/default_embed_service_spec.rb38
-rw-r--r--spec/services/metrics/dashboard/dynamic_embed_service_spec.rb151
-rw-r--r--spec/services/metrics/dashboard/project_dashboard_service_spec.rb89
-rw-r--r--spec/services/metrics/dashboard/system_dashboard_service_spec.rb52
-rw-r--r--spec/services/notes/create_service_spec.rb38
-rw-r--r--spec/services/notification_service_spec.rb421
-rw-r--r--spec/services/projects/autocomplete_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb1
-rw-r--r--spec/services/projects/destroy_service_spec.rb29
-rw-r--r--spec/services/projects/download_service_spec.rb9
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb4
-rw-r--r--spec/services/projects/lfs_pointers/lfs_download_service_spec.rb2
-rw-r--r--spec/services/projects/update_remote_mirror_service_spec.rb66
-rw-r--r--spec/services/projects/update_service_spec.rb27
-rw-r--r--spec/services/prometheus/proxy_service_spec.rb2
-rw-r--r--spec/services/quick_actions/interpret_service_spec.rb398
-rw-r--r--spec/services/self_monitoring/project/create_service_spec.rb83
-rw-r--r--spec/services/submodules/update_service_spec.rb2
-rw-r--r--spec/services/system_note_service_spec.rb24
-rw-r--r--spec/services/task_list_toggle_service_spec.rb17
-rw-r--r--spec/services/todos/destroy/entity_leave_service_spec.rb2
-rw-r--r--spec/services/todos/destroy/group_private_service_spec.rb2
-rw-r--r--spec/services/update_snippet_service_spec.rb17
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb4
-rw-r--r--spec/services/web_hook_service_spec.rb34
-rw-r--r--spec/services/wiki_pages/base_service_spec.rb27
-rw-r--r--spec/services/wiki_pages/create_service_spec.rb25
-rw-r--r--spec/services/wiki_pages/destroy_service_spec.rb12
-rw-r--r--spec/services/wiki_pages/update_service_spec.rb25
-rw-r--r--spec/services/zoom_notes_service_spec.rb81
-rw-r--r--spec/spec_helper.rb23
-rw-r--r--spec/support/api/boards_shared_examples.rb2
-rw-r--r--spec/support/api/issues_resolving_discussions_shared_examples.rb2
-rw-r--r--spec/support/api/members_shared_examples.rb2
-rw-r--r--spec/support/api/milestones_shared_examples.rb2
-rw-r--r--spec/support/api/repositories_shared_context.rb2
-rw-r--r--spec/support/api/schema_matcher.rb2
-rw-r--r--spec/support/api/scopes/read_user_shared_examples.rb2
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/banzai/reference_filter_shared_examples.rb2
-rw-r--r--spec/support/batch_loader.rb2
-rw-r--r--spec/support/capybara.rb3
-rw-r--r--spec/support/carrierwave.rb2
-rw-r--r--spec/support/chunked_io/chunked_io_helpers.rb2
-rw-r--r--spec/support/commit_trailers_spec_helper.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/githubish_import_controller_shared_examples.rb8
-rw-r--r--spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb2
-rw-r--r--spec/support/controllers/sessionless_auth_controller_shared_examples.rb2
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb14
-rw-r--r--spec/support/db_cleaner.rb2
-rw-r--r--spec/support/external_authorization_service_helpers.rb2
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb2
-rw-r--r--spec/support/features/reportable_note_shared_examples.rb2
-rw-r--r--spec/support/features/resolving_discussions_in_issues_shared_examples.rb2
-rw-r--r--spec/support/features/rss_shared_examples.rb2
-rw-r--r--spec/support/features/variable_list_shared_examples.rb2
-rw-r--r--spec/support/forgery_protection.rb2
-rw-r--r--spec/support/google_api/cloud_platform_helpers.rb2
-rw-r--r--spec/support/helpers/api_helpers.rb11
-rw-r--r--spec/support/helpers/assets_helpers.rb2
-rw-r--r--spec/support/helpers/bare_repo_operations.rb2
-rw-r--r--spec/support/helpers/board_helpers.rb2
-rw-r--r--spec/support/helpers/capybara_helpers.rb2
-rw-r--r--spec/support/helpers/ci_artifact_metadata_generator.rb2
-rw-r--r--spec/support/helpers/cookie_helper.rb2
-rw-r--r--spec/support/helpers/cycle_analytics_helpers.rb2
-rw-r--r--spec/support/helpers/database_connection_helpers.rb2
-rw-r--r--spec/support/helpers/devise_helpers.rb2
-rw-r--r--spec/support/helpers/drag_to_helper.rb2
-rw-r--r--spec/support/helpers/dropzone_helper.rb2
-rw-r--r--spec/support/helpers/email_helpers.rb6
-rw-r--r--spec/support/helpers/exclusive_lease_helpers.rb2
-rw-r--r--spec/support/helpers/expect_next_instance_of.rb2
-rw-r--r--spec/support/helpers/expect_offense.rb2
-rw-r--r--spec/support/helpers/expect_request_with_status.rb11
-rw-r--r--spec/support/helpers/fake_blob_helpers.rb2
-rw-r--r--spec/support/helpers/fake_migration_classes.rb2
-rw-r--r--spec/support/helpers/fake_u2f_device.rb2
-rw-r--r--spec/support/helpers/features/branches_helpers.rb2
-rw-r--r--spec/support/helpers/features/notes_helpers.rb10
-rw-r--r--spec/support/helpers/features/sorting_helpers.rb2
-rw-r--r--spec/support/helpers/filter_spec_helper.rb2
-rw-r--r--spec/support/helpers/filtered_search_helpers.rb2
-rw-r--r--spec/support/helpers/fixture_helpers.rb2
-rw-r--r--spec/support/helpers/git_http_helpers.rb2
-rw-r--r--spec/support/helpers/gitlab_verify_helpers.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb10
-rw-r--r--spec/support/helpers/import_spec_helper.rb2
-rw-r--r--spec/support/helpers/input_helper.rb2
-rw-r--r--spec/support/helpers/inspect_requests.rb2
-rw-r--r--spec/support/helpers/issue_helpers.rb2
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb4
-rw-r--r--spec/support/helpers/jira_service_helper.rb2
-rw-r--r--spec/support/helpers/key_generator_helper.rb4
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb2
-rw-r--r--spec/support/helpers/ldap_helpers.rb2
-rw-r--r--spec/support/helpers/live_debugger.rb2
-rw-r--r--spec/support/helpers/login_helpers.rb2
-rw-r--r--spec/support/helpers/markdown_feature.rb2
-rw-r--r--spec/support/helpers/merge_request_diff_helpers.rb2
-rw-r--r--spec/support/helpers/merge_request_helpers.rb2
-rw-r--r--spec/support/helpers/metrics_dashboard_helpers.rb8
-rw-r--r--spec/support/helpers/migrations_helpers.rb2
-rw-r--r--spec/support/helpers/mobile_helpers.rb2
-rw-r--r--spec/support/helpers/note_interaction_helpers.rb2
-rw-r--r--spec/support/helpers/notification_helpers.rb2
-rw-r--r--spec/support/helpers/project_forks_helper.rb2
-rw-r--r--spec/support/helpers/prometheus_helpers.rb10
-rw-r--r--spec/support/helpers/query_recorder.rb2
-rw-r--r--spec/support/helpers/quick_actions_helpers.rb2
-rw-r--r--spec/support/helpers/rake_helpers.rb2
-rw-r--r--spec/support/helpers/reactive_caching_helpers.rb2
-rw-r--r--spec/support/helpers/redis_without_keys.rb2
-rw-r--r--spec/support/helpers/reference_parser_helpers.rb2
-rw-r--r--spec/support/helpers/repo_helpers.rb2
-rw-r--r--spec/support/helpers/routes_helpers.rb2
-rw-r--r--spec/support/helpers/search_helpers.rb2
-rw-r--r--spec/support/helpers/seed_repo.rb2
-rw-r--r--spec/support/helpers/select2_helper.rb2
-rw-r--r--spec/support/helpers/selection_helper.rb2
-rw-r--r--spec/support/helpers/sorting_helper.rb2
-rw-r--r--spec/support/helpers/stub_configuration.rb2
-rw-r--r--spec/support/helpers/stub_env.rb2
-rw-r--r--spec/support/helpers/stub_feature_flags.rb2
-rw-r--r--spec/support/helpers/stub_gitlab_calls.rb6
-rw-r--r--spec/support/helpers/stub_gitlab_data.rb2
-rw-r--r--spec/support/helpers/stub_metrics.rb2
-rw-r--r--spec/support/helpers/stub_object_storage.rb2
-rw-r--r--spec/support/helpers/stub_requests.rb15
-rw-r--r--spec/support/helpers/stub_worker.rb2
-rw-r--r--spec/support/helpers/terms_helper.rb2
-rw-r--r--spec/support/helpers/test_env.rb18
-rw-r--r--spec/support/helpers/upload_helpers.rb2
-rw-r--r--spec/support/helpers/wait_for_requests.rb2
-rw-r--r--spec/support/helpers/wait_helpers.rb2
-rw-r--r--spec/support/helpers/wiki_helpers.rb2
-rw-r--r--spec/support/helpers/workhorse_helpers.rb2
-rw-r--r--spec/support/http_io/http_io_helpers.rb2
-rw-r--r--spec/support/import_export/common_util.rb2
-rw-r--r--spec/support/import_export/configuration_helper.rb2
-rw-r--r--spec/support/import_export/export_file_helper.rb2
-rw-r--r--spec/support/inspect_squelch.rb2
-rw-r--r--spec/support/issuables_requiring_filter_shared_examples.rb2
-rw-r--r--spec/support/json_response.rb2
-rw-r--r--spec/support/matchers/abort_matcher.rb2
-rw-r--r--spec/support/matchers/access_matchers.rb2
-rw-r--r--spec/support/matchers/access_matchers_for_controller.rb2
-rw-r--r--spec/support/matchers/background_migrations_matchers.rb2
-rw-r--r--spec/support/matchers/be_a_binary_string.rb2
-rw-r--r--spec/support/matchers/be_like_time.rb2
-rw-r--r--spec/support/matchers/be_url.rb6
-rw-r--r--spec/support/matchers/be_utf8.rb2
-rw-r--r--spec/support/matchers/be_valid_commit.rb2
-rw-r--r--spec/support/matchers/disallow_request_matchers.rb2
-rw-r--r--spec/support/matchers/exceed_query_limit.rb2
-rw-r--r--spec/support/matchers/execute_check.rb2
-rw-r--r--spec/support/matchers/gitaly_matchers.rb2
-rw-r--r--spec/support/matchers/gitlab_git_matchers.rb2
-rw-r--r--spec/support/matchers/graphql_matchers.rb2
-rw-r--r--spec/support/matchers/have_emoji.rb2
-rw-r--r--spec/support/matchers/have_gitlab_http_status.rb2
-rw-r--r--spec/support/matchers/have_issuable_counts.rb2
-rw-r--r--spec/support/matchers/include_module.rb2
-rw-r--r--spec/support/matchers/issuable_matchers.rb2
-rw-r--r--spec/support/matchers/markdown_matchers.rb2
-rw-r--r--spec/support/matchers/match_file.rb2
-rw-r--r--spec/support/matchers/match_ids.rb2
-rw-r--r--spec/support/matchers/metric_counter_matcher.rb2
-rw-r--r--spec/support/matchers/navigation_matcher.rb2
-rw-r--r--spec/support/matchers/pagination_matcher.rb2
-rw-r--r--spec/support/matchers/query_matcher.rb2
-rw-r--r--spec/support/matchers/satisfy_matchers.rb2
-rw-r--r--spec/support/matchers/security_header_matcher.rb2
-rw-r--r--spec/support/migrations_helpers/track_untracked_uploads_helpers.rb2
-rw-r--r--spec/support/omni_auth.rb2
-rw-r--r--spec/support/prometheus/additional_metrics_shared_examples.rb4
-rw-r--r--spec/support/prometheus/metric_builders.rb2
-rw-r--r--spec/support/protected_tags/access_control_ce_shared_examples.rb2
-rw-r--r--spec/support/redis/redis_helpers.rb2
-rw-r--r--spec/support/redis/redis_shared_examples.rb2
-rw-r--r--spec/support/rspec.rb2
-rw-r--r--spec/support/seed.rb2
-rw-r--r--spec/support/services/clusters/create_service_shared.rb69
-rw-r--r--spec/support/services/issuable_create_service_slash_commands_shared_examples.rb2
-rw-r--r--spec/support/services/issuable_update_service_shared_examples.rb2
-rw-r--r--spec/support/services/migrate_to_ghost_user_service_shared_examples.rb2
-rw-r--r--spec/support/setup_builds_storage.rb2
-rw-r--r--spec/support/shared_contexts/email_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb2
-rw-r--r--spec/support/shared_contexts/json_response_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/merge_requests_allowing_collaboration.rb2
-rw-r--r--spec/support/shared_contexts/policies/group_policy_shared_context.rb6
-rw-r--r--spec/support/shared_contexts/services_shared_context.rb2
-rw-r--r--spec/support/shared_contexts/url_shared_context.rb2
-rw-r--r--spec/support/shared_examples/application_setting_examples.rb164
-rw-r--r--spec/support/shared_examples/award_emoji_todo_shared_examples.rb59
-rw-r--r--spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb144
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/ci_trace_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/common_system_notes_examples.rb2
-rw-r--r--spec/support/shared_examples/container_repositories_shared_examples.rb58
-rw-r--r--spec/support/shared_examples/controllers/external_authorization_service_shared_examples.rb4
-rw-r--r--spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/set_sort_order_from_user_preference_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/controllers/todos_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb12
-rw-r--r--spec/support/shared_examples/controllers/variables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/dirty_submit_form_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/discussions_provider_shared_examples.rb15
-rw-r--r--spec/support/shared_examples/email_format_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/fast_destroy_all.rb2
-rw-r--r--spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/editable_merge_request_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb2
-rw-r--r--spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/features/protected_branches_access_control_ce.rb14
-rw-r--r--spec/support/shared_examples/features/search_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/file_finder.rb2
-rw-r--r--spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb2
-rw-r--r--spec/support/shared_examples/gitlab_verify.rb2
-rw-r--r--spec/support/shared_examples/graphql/issuable_state_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/group_members_shared_example.rb2
-rw-r--r--spec/support/shared_examples/helm_generated_script.rb2
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issuables_list_metadata_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/issue_tracker_service_shared_example.rb2
-rw-r--r--spec/support/shared_examples/ldap_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/legacy_path_redirect_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb2
-rw-r--r--spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb57
-rw-r--r--spec/support/shared_examples/malicious_regexp_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/mentionable_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/milestone_tabs_examples.rb2
-rw-r--r--spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/chat_service_shared_examples.rb5
-rw-r--r--spec/support/shared_examples/models/cluster_application_core_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb52
-rw-r--r--spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/members_notifications_shared_example.rb2
-rw-r--r--spec/support/shared_examples/models/project_hook_data_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/update_project_statistics_shared_examples.rb40
-rw-r--r--spec/support/shared_examples/models/with_uploads_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/notify_shared_examples.rb35
-rw-r--r--spec/support/shared_examples/policies/clusterable_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/position_formatters.rb2
-rw-r--r--spec/support/shared_examples/project_latest_successful_build_for_examples.rb63
-rw-r--r--spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb22
-rw-r--r--spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb8
-rw-r--r--spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/reference_parser_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/relative_positioning_shared_examples.rb283
-rw-r--r--spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/diff_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issuable_participants_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/api/notes.rb2
-rw-r--r--spec/support/shared_examples/requests/api/resolvable_discussions.rb2
-rw-r--r--spec/support/shared_examples/requests/api/status_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/requests/graphql_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/resource_label_events_api.rb44
-rw-r--r--spec/support/shared_examples/serializers/note_entity_examples.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_create_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/boards_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/issues_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_destroy_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_list_service.rb2
-rw-r--r--spec/support/shared_examples/services/boards/lists_move_service.rb2
-rw-r--r--spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/services/notification_service_shared_examples.rb54
-rw-r--r--spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb6
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/snippets_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/taskable_shared_examples.rb23
-rw-r--r--spec/support/shared_examples/throttled_touch.rb2
-rw-r--r--spec/support/shared_examples/unique_ip_check_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/update_invalid_issuable.rb2
-rw-r--r--spec/support/shared_examples/updating_mentions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/uploaders/object_storage_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/url_validator_examples.rb59
-rw-r--r--spec/support/sidekiq.rb2
-rw-r--r--spec/support/stored_repositories.rb2
-rw-r--r--spec/support/test_reports/test_reports_helper.rb2
-rw-r--r--spec/support/trace/trace_helpers.rb2
-rw-r--r--spec/support/webmock.rb2
-rw-r--r--spec/tasks/gitlab/cleanup_rake_spec.rb30
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb27
-rw-r--r--spec/tasks/gitlab/update_templates_rake_spec.rb34
-rw-r--r--spec/uploaders/records_uploads_spec.rb21
-rw-r--r--spec/validators/public_url_validator_spec.rb24
-rw-r--r--spec/validators/qualified_domain_array_validator_spec.rb103
-rw-r--r--spec/validators/system_hook_url_validator_spec.rb8
-rw-r--r--spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb (renamed from spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb)0
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml.rb17
-rw-r--r--spec/views/dashboard/projects/_nav.html.haml_spec.rb17
-rw-r--r--spec/views/groups/edit.html.haml_spec.rb2
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb17
-rw-r--r--spec/views/layouts/header/_new_dropdown.haml_spec.rb2
-rw-r--r--spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb13
-rw-r--r--spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb (renamed from spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb)0
-rw-r--r--spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/merge_requests/edit.html.haml_spec.rb1
-rw-r--r--spec/views/projects/pages_domains/show.html.haml_spec.rb66
-rw-r--r--spec/views/search/_filter.html.haml_spec.rb17
-rw-r--r--spec/views/search/_form.html.haml_spec.rb14
-rw-r--r--spec/views/search/show.html.haml_spec.rb37
-rw-r--r--spec/views/shared/_label_row.html.haml.rb32
-rw-r--r--spec/views/shared/_label_row.html.haml_spec.rb43
-rw-r--r--spec/views/shared/milestones/_issuable.html.haml_spec.rb (renamed from spec/views/shared/milestones/_issuable.html.haml.rb)0
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml.rb32
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml_spec.rb32
-rw-r--r--spec/views/shared/milestones/_top.html.haml.rb35
-rw-r--r--spec/views/shared/milestones/_top.html.haml_spec.rb36
-rw-r--r--spec/workers/archive_trace_worker_spec.rb2
-rw-r--r--spec/workers/background_migration_worker_spec.rb20
-rw-r--r--spec/workers/ci/archive_traces_cron_worker_spec.rb7
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb71
-rw-r--r--spec/workers/cluster_project_configure_worker_spec.rb14
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb11
-rw-r--r--spec/workers/namespaces/schedule_aggregation_worker_spec.rb10
-rw-r--r--spec/workers/pipeline_process_worker_spec.rb11
-rw-r--r--spec/workers/post_receive_spec.rb152
-rw-r--r--spec/workers/process_commit_worker_spec.rb40
-rw-r--r--spec/workers/project_cache_worker_spec.rb10
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb111
-rw-r--r--spec/workers/stuck_ci_jobs_worker_spec.rb4
-rw-r--r--vendor/jupyter/values.yaml4
-rw-r--r--vendor/licenses.csv2
-rw-r--r--vendor/project_templates/rails.tar.gzbin25490 -> 25668 bytes
-rw-r--r--yarn.lock2955
4400 files changed, 63178 insertions, 32321 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/.eslintrc.yml b/.eslintrc.yml
index 2612fd3371d..95cbc602d5b 100644
--- a/.eslintrc.yml
+++ b/.eslintrc.yml
@@ -10,12 +10,15 @@ plugins:
- import
- html
- "@gitlab/i18n"
+ - "@gitlab/vue-i18n"
settings:
import/resolver:
webpack:
config: './config/webpack.config.js'
rules:
"@gitlab/i18n/no-non-i18n-strings": error
+ "@gitlab/vue-i18n/no-bare-strings": error
+ "@gitlab/vue-i18n/no-bare-attribute-strings": error
import/no-commonjs: error
no-underscore-dangle:
- error
@@ -36,9 +39,6 @@ rules:
vue/use-v-on-exact: off
overrides:
files:
- # Vue is temporarily being disabled until the autofix errors are resolved
- # Follow up issue https://gitlab.com/gitlab-org/gitlab-ce/issues/57969
- - '*.vue'
- '**/spec/**/*'
rules:
"@gitlab/i18n/no-non-i18n-strings": off
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5b39304444c..f926cbc2939 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,4 +1,4 @@
-image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.29"
+image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-9.6-graphicsmagick-1.3.33"
variables:
RAILS_ENV: "test"
@@ -29,7 +29,6 @@ stages:
- qa
- post-test
- pages
- - post-cleanup
include:
- local: .gitlab/ci/global.gitlab-ci.yml
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index b865b212ac0..dae3c349ff4 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -1,16 +1,20 @@
# Backend Maintainers are the default for all ruby files
-*.rb @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
-*.rake @ashmckenzie @ayufan @dbalexandre @DouweM @dzaporozhets @godfat @grzesiek @mkozono @mayra-cabrera @nick.thomas @rspeicher @rymai @reprazent @smcgivern @tkuah
+*.rb @gitlab-org/maintainers/rails-backend
+*.rake @gitlab-org/maintainers/rails-backend
# Technical writing team are the default reviewers for everything in `doc/`
/doc/ @axil @marcia @eread @mikelewis
# Frontend maintainers should see everything in `app/assets/`
-app/assets/ @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
-*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @iamphill @mikegreiling @timzallmann @kushalpandya @pslaughter
+app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter
+*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter
-# Someone from the database team should review changes in `db/`
-db/ @abrandl @NikolayS
+# Maintainers from the Database team should review changes in `db/`
+db/ @gl-database
+lib/gitlab/background_migration/ @gl-database
+lib/gitlab/database/ @gl-database
+lib/gitlab/sql/ @gl-database
+/ee/db/ @gl-database
# Feature specific owners
/ee/lib/gitlab/code_owners/ @reprazent
diff --git a/.gitlab/ci/docs.gitlab-ci.yml b/.gitlab/ci/docs.gitlab-ci.yml
index 5bc109f2b7f..39ae62a43c9 100644
--- a/.gitlab/ci/docs.gitlab-ci.yml
+++ b/.gitlab/ci/docs.gitlab-ci.yml
@@ -15,7 +15,7 @@ review-docs-deploy-manual:
extends:
- .review-docs
- .no-docs-and-no-qa
- stage: build
+ stage: review
script:
- gem install gitlab --no-document
- ./$SCRIPT_NAME deploy
@@ -28,20 +28,20 @@ review-docs-deploy-manual:
# Useful to preview the docs changes live.
review-docs-deploy:
<<: *review-docs
- stage: post-test
+ stage: review
script:
- gem install gitlab --no-document
- ./$SCRIPT_NAME deploy
only:
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ce
- - /(^docs[\/-].*|.*-docs$)/@gitlab-org/gitlab-ee
+ - /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ce
+ - /(^docs[\/-].+|.+-docs$)/@gitlab-org/gitlab-ee
except:
- /(^qa[\/-].*|.*-qa$)/
# Cleanup remote environment of gitlab-docs
review-docs-cleanup:
<<: *review-docs
- stage: post-cleanup
+ stage: review
environment:
name: review-docs/$CI_COMMIT_REF_SLUG
action: stop
@@ -62,7 +62,6 @@ docs lint:
before_script: []
script:
- scripts/lint-doc.sh
- - scripts/lint-changelog-yaml
- mv doc/ /tmp/gitlab-docs/content/$DOCS_GITLAB_REPO_SUFFIX
- cd /tmp/gitlab-docs
# Lint Markdown
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index 45a6a177943..5c3278fcf53 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -8,18 +8,18 @@
.use-pg: &use-pg
services:
- - name: postgres:9.6.11
+ - name: postgres:9.6.14
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
.gitlab:assets:compile-metadata:
<<: *assets-compile-cache
extends: .dedicated-no-docs-pull-cache-job
- image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.21-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.29-docker-18.06.1
+ image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-git-2.22-chrome-73.0-node-12.x-yarn-1.16-graphicsmagick-1.3.33-docker-18.06.1
dependencies:
- setup-test-env
services:
- - docker:stable-dind
+ - docker:19.03.0-dind
variables:
NODE_ENV: "production"
RAILS_ENV: "production"
@@ -73,7 +73,7 @@ gitlab:assets:compile pull-cache:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.compile-assets-metadata:
extends: .dedicated-runner
@@ -111,38 +111,7 @@ compile-assets pull-cache:
refs:
- master@gitlab-org/gitlab-ce
- master@gitlab-org/gitlab-ee
- - /(^docs[\/-].*|.*-docs$)/
-
-gitlab:ui:visual:
- extends: .dedicated-runner
- before_script: []
- allow_failure: true
- dependencies:
- - compile-assets
- - compile-assets pull-cache
- script:
- # Remove node modules from GitLab that may conflict with gitlab-ui
- - rm -r node_modules
- - git clone https://gitlab.com/gitlab-org/gitlab-ui.git
- - cp public/assets/application-*.css gitlab-ui/styles/application.css
- - cd gitlab-ui
- - yarn install
- - CSS_URL=./application.css yarn test
- only:
- changes:
- - app/assets/stylesheets/*.scss
- - app/assets/stylesheets/**/*.scss
- - app/assets/stylesheets/**/**/*.scss
- except:
- refs:
- - /(^docs[\/-].*|.*-docs$)/
- - master
- variables:
- - $CI_COMMIT_MESSAGE =~ /\[skip visual\]/i
- artifacts:
- paths:
- - gitlab-ui/tests/__image_snapshots__/
- when: always
+ - /(^docs[\/-].+|.+-docs$)/
karma:
extends: .dedicated-no-docs-pull-cache-job
@@ -168,8 +137,9 @@ karma:
paths:
- chrome_debug.log
- coverage-javascript/
-# reports:
-# junit: junit_karma.xml
+ - tmp/tests/frontend/
+ reports:
+ junit: junit_karma.xml
jest:
extends: .dedicated-no-docs-and-no-qa-pull-cache-job
@@ -181,7 +151,7 @@ jest:
script:
- scripts/gitaly-test-spawn
- date
- - bundle exec rake karma:fixtures
+ - bundle exec rake frontend:fixtures
- date
- yarn jest --ci --coverage
artifacts:
@@ -191,8 +161,9 @@ jest:
paths:
- coverage-frontend/
- junit_jest.xml
-# reports:
-# junit: junit_jest.xml
+ - tmp/tests/frontend/
+ reports:
+ junit: junit_jest.xml
cache:
key: jest
paths:
@@ -237,15 +208,15 @@ qa:selectors:
qa-frontend-node:8:
<<: *qa-frontend-node
- image: node:8-alpine
+ image: node:carbon
qa-frontend-node:10:
<<: *qa-frontend-node
- image: node:10-alpine
+ image: node:dubnium
qa-frontend-node:latest:
<<: *qa-frontend-node
- image: node:alpine
+ image: node:latest
allow_failure: true
lint:javascript:report:
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 4da7f404767..78ef346d417 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -31,12 +31,12 @@
.no-docs:
except:
refs:
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.no-docs-and-no-qa:
except:
refs:
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
.dedicated-no-docs-pull-cache-job:
diff --git a/.gitlab/ci/memory.gitlab-ci.yml b/.gitlab/ci/memory.gitlab-ci.yml
index ffe5dbdc31b..9923732e587 100644
--- a/.gitlab/ci/memory.gitlab-ci.yml
+++ b/.gitlab/ci/memory.gitlab-ci.yml
@@ -33,7 +33,7 @@ memory-on-boot:
NODE_OPTIONS: --max_old_space_size=3584
script:
# Both bootsnap and derailed monkey-patch Kernel#require, which leads to circular dependency
- - DISABLE_BOOTSNAP=true PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
+ - ENABLE_BOOTSNAP=false PATH_TO_HIT="/users/sign_in" CUT_OFF=0.3 bundle exec derailed exec perf:mem >> 'tmp/memory_on_boot.txt'
- scripts/generate-memory-metrics-on-boot tmp/memory_on_boot.txt >> 'tmp/memory_on_boot_metrics.txt'
artifacts:
paths:
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index ee9e467886a..dcc681294d2 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -1,7 +1,6 @@
-package-and-qa:
+.package-and-qa-base:
image: ruby:2.6-alpine
stage: review # So even if review-deploy failed we can still run this
- when: manual
before_script: []
dependencies: []
cache: {}
@@ -13,5 +12,18 @@ package-and-qa:
- install_gitlab_gem
- ./scripts/trigger-build omnibus
only:
- - /.+/@gitlab-org/gitlab-ce
- - /.+/@gitlab-org/gitlab-ee
+ - branches@gitlab-org/gitlab-ce
+ - branches@gitlab-org/gitlab-ee
+
+package-and-qa:
+ extends: .package-and-qa-base
+ when: manual
+ except:
+ - /(^qa[\/-].*|.*-qa$)/
+
+package-and-qa-always:
+ extends: .package-and-qa-base
+ allow_failure: true
+ only:
+ - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ce
+ - /(^qa[\/-].*|.*-qa$)/@gitlab-org/gitlab-ee
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml
index c7a51beeee5..50476b43dd6 100644
--- a/.gitlab/ci/rails.gitlab-ci.yml
+++ b/.gitlab/ci/rails.gitlab-ci.yml
@@ -1,12 +1,12 @@
.use-pg: &use-pg
services:
- - name: postgres:9.6.11
+ - name: postgres:9.6.14
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
.use-pg-10: &use-pg-10
services:
- - name: postgres:10.7
+ - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
@@ -80,8 +80,8 @@
- rspec_profiling/
- tmp/capybara/
- tmp/memory_test/
-# reports:
-# junit: junit_rspec.xml
+ reports:
+ junit: junit_rspec.xml
.rspec-metadata-pg: &rspec-metadata-pg
<<: *rspec-metadata
@@ -90,7 +90,7 @@
.rspec-metadata-pg-10: &rspec-metadata-pg-10
<<: *rspec-metadata
<<: *use-pg-10
- image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.21-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.29"
+ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.6.3-golang-1.11-git-2.22-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
# DB migration, rollback, and seed jobs
.db-migrate-reset: &db-migrate-reset
@@ -207,7 +207,7 @@ downtime_check:
- master
- tags
- /^[\d-]+-stable(-ee)?$/
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
dependencies:
- setup-test-env
@@ -223,6 +223,7 @@ ee_compat_check:
- /^security-/
- branches@gitlab-org/gitlab-ee
- branches@gitlab/gitlab-ee
+ - /(^docs[\/-].+|.+-docs$)/
retry: 0
artifacts:
name: "${CI_JOB_NAME}_${CI_COMIT_REF_NAME}_${CI_COMMIT_SHA}"
diff --git a/.gitlab/ci/reports.gitlab-ci.yml b/.gitlab/ci/reports.gitlab-ci.yml
index ca55bbd32a7..fc0e93ee9f1 100644
--- a/.gitlab/ci/reports.gitlab-ci.yml
+++ b/.gitlab/ci/reports.gitlab-ci.yml
@@ -2,6 +2,7 @@ include:
- template: Code-Quality.gitlab-ci.yml
- template: Security/SAST.gitlab-ci.yml
- template: Security/Dependency-Scanning.gitlab-ci.yml
+ - template: Security/DAST.gitlab-ci.yml
code_quality:
extends: .dedicated-no-docs
@@ -24,3 +25,12 @@ dependency_scanning:
tags: []
before_script: []
cache: {}
+
+dast:
+ extends: .dedicated-no-docs
+ stage: qa
+ dependencies:
+ - review-deploy
+ before_script:
+ - export DAST_WEBSITE="$(cat review_app_url.txt)"
+ cache: {}
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index ce019de213b..3cbfa32d9a5 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -7,7 +7,8 @@
except:
refs:
- master
- - /(^docs[\/-].*|.*-docs$)/
+ - /^\d+-\d+-auto-deploy-\d+$/
+ - /(^docs[\/-].+|.+-docs$)/
.review-schedules-only: &review-schedules-only
only:
@@ -20,7 +21,7 @@
except:
refs:
- tags
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
.review-base: &review-base
extends: .dedicated-runner
@@ -35,7 +36,7 @@
<<: *review-base
image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-qa-alpine
services:
- - docker:stable-dind
+ - docker:19.03.0-dind
tags:
- gitlab-org
- docker
@@ -49,7 +50,7 @@ build-qa-image:
<<: *review-docker
stage: test
script:
- - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
+ - time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} --file ./qa/Dockerfile ./
- echo "${CI_JOB_TOKEN}" | docker login --username gitlab-ci-token --password-stdin ${CI_REGISTRY}
- time docker push ${QA_IMAGE}
@@ -101,8 +102,7 @@ schedule:review-build-cng:
- install_tiller
- install_external_dns
- download_chart
- - deploy || display_deployment_debug
- - wait_for_review_app_to_be_accessible
+ - deploy || (display_deployment_debug && exit 1)
- add_license
artifacts:
paths: [review_app_url.txt]
@@ -117,17 +117,21 @@ schedule:review-deploy:
<<: *review-schedules-only
review-stop:
- <<: *review-base
+ <<: *review-only
+ extends: .single-script-job-dedicated-runner
+ image: registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-charts-build-base
stage: review
when: manual
allow_failure: true
variables:
- GIT_DEPTH: "1"
+ SCRIPT_NAME: review_apps/review-apps.sh
environment:
<<: *review-environment
action: stop
script:
- - source scripts/review_apps/review-apps.sh
+ - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/utils.sh
+ - source utils.sh
+ - source $(basename $SCRIPT_NAME)
- delete
.review-qa-base: &review-qa-base
@@ -174,7 +178,38 @@ review-qa-all:
script:
- export KNAPSACK_REPORT_PATH=knapsack/${CI_PROJECT_NAME}/review-qa-all_master_report.json
- export KNAPSACK_TEST_FILE_PATTERN=qa/specs/features/**/*_spec.rb
- - gitlab-qa Test::Instance::Any "${QA_IMAGE}" "${CI_ENVIRONMENT_URL}"
+ - 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
+ dependencies:
+ - review-qa-all
+ image: ruby:2.6-alpine
+ services: []
+ before_script: []
+ variables:
+ SETUP_DB: "false"
+ NEW_PARALLEL_SPECS_REPORT: qa/report-new.html
+ BASE_ARTIFACT_URL: "${CI_PROJECT_URL}/-/jobs/${CI_JOB_ID}/artifacts/file/qa/"
+ stage: post-test
+ allow_failure: true
+ when: manual
+ retry: 0
+ artifacts:
+ when: always
+ paths:
+ - qa/report-new.html
+ - qa/gitlab-qa-run-*
+ reports:
+ junit: qa/gitlab-qa-run-*/**/rspec-*.xml
+ script:
+ - apk add --update build-base libxml2-dev libxslt-dev && rm -rf /var/cache/apk/*
+ - gem install nokogiri
+ - cd qa/gitlab-qa-run-*/gitlab-*
+ - ARTIFACT_DIRS=$(pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_)
+ - cd ../../..
+ - '[[ -f $NEW_PARALLEL_SPECS_REPORT ]] || echo "{}" > ${NEW_PARALLEL_SPECS_REPORT}'
+ - scripts/merge-html-reports ${NEW_PARALLEL_SPECS_REPORT} ${BASE_ARTIFACT_URL}${ARTIFACT_DIRS} qa/gitlab-qa-run-*/**/rspec.htm
.review-performance-base: &review-performance-base
<<: *review-qa-base
@@ -230,6 +265,8 @@ danger-review:
except:
refs:
- master
+ - /^\d+-\d+-auto-deploy-\d+$/
+ - /^[\d-]+-stable(-ee)?$/
variables:
- $CI_COMMIT_REF_NAME =~ /^ce-to-ee-.*/
- $CI_COMMIT_REF_NAME =~ /.*-stable(-ee)?-prepare-.*/
diff --git a/.gitlab/ci/test-metadata.gitlab-ci.yml b/.gitlab/ci/test-metadata.gitlab-ci.yml
index 2454ea85652..4c97a4feb18 100644
--- a/.gitlab/ci/test-metadata.gitlab-ci.yml
+++ b/.gitlab/ci/test-metadata.gitlab-ci.yml
@@ -39,6 +39,7 @@ update-tests-metadata:
policy: push
script:
- retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
+ - echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec_*_pg_node_*.json
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $KNAPSACK_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
@@ -70,7 +71,7 @@ flaky-examples-check:
except:
refs:
- master
- - /(^docs[\/-].*|.*-docs$)/
+ - /(^docs[\/-].+|.+-docs$)/
- /(^qa[\/-].*|.*-qa$)/
artifacts:
expire_in: 30d
diff --git a/.gitlab/ci/yaml.gitlab-ci.yml b/.gitlab/ci/yaml.gitlab-ci.yml
index 401318d2df2..b7aa418d8f7 100644
--- a/.gitlab/ci/yaml.gitlab-ci.yml
+++ b/.gitlab/ci/yaml.gitlab-ci.yml
@@ -6,4 +6,4 @@ lint-ci-gitlab:
dependencies: []
image: sdesbure/yamllint:latest
script:
- - yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates
+ - yamllint .gitlab-ci.yml .gitlab/ci lib/gitlab/ci/templates changelogs
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
index b7db5a33faf..0cac769bd55 100644
--- a/.gitlab/issue_templates/Feature Flag Roll Out.md
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -39,5 +39,6 @@ If applicable, any groups/projects that are happy to have this feature turned on
- [ ] Cross post chatops slack command to `#support_gitlab-com` and in your team channel
- [ ] Announce on the issue that the flag has been enabled
- [ ] Remove feature flag and add changelog entry
+- [ ] After the flag removal is deployed, [clean up the feature flag](https://docs.gitlab.com/ee/development/feature_flags/controls.html#cleaning-up) by running chatops command in `#production` channel
/label ~"feature flag"
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 8a49715e0e8..2ba6b68a53e 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -26,7 +26,7 @@ Add all known Documentation Requirements here, per https://docs.gitlab.com/ee/de
### Testing
-<!-- What risks does this change pose? How might it affect the quality of the product? What additional test coverage or changes to tests will be needed? Will it require cross-browser testing? See the test engineering process for further guidelines: https://about.gitlab.com/handbook/engineering/quality/guidelines/test-engineering/ -->
+<!-- What risks does this change pose? How might it affect the quality of the product? What additional test coverage or changes to tests will be needed? Will it require cross-browser testing? See the test engineering process for further help: https://about.gitlab.com/handbook/engineering/quality/test-engineering/ -->
### What does success look like, and how can we measure that?
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md
index ae469d3b125..3e60274623e 100644
--- a/.gitlab/issue_templates/Security Release.md
+++ b/.gitlab/issue_templates/Security Release.md
@@ -1,7 +1,7 @@
<!--
# Read me first!
-Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
+Set the title to: `Security Release: 12.2.X, 12.1.X, and 12.0.X`
-->
## Releases tasks
@@ -12,9 +12,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
## Version issues:
-* 11.4.X: {release task link}
-* 11.3.X: {release task link}
-* 11.2.X: {release task link}
+* 12.2.X: {release task link}
+* 12.1.X: {release task link}
+* 12.0.X: {release task link}
## Security Issues:
@@ -34,9 +34,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
| Version | MR |
|---------|----|
-| 11.4 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
-| 11.3 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
-| 11.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
+| 12.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
+| 12.1 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
+| 12.0 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
| master | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} |
@@ -48,9 +48,9 @@ Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
| Version | MR |
|---------|----|
-| 11.4| {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
-| 11.3 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
-| 11.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
+| 12.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
+| 12.1 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
+| 12.0 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
| master | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} |
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 7857afb66c2..3e634de4f0c 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -17,7 +17,7 @@ Set the title to: `Description of the original issue`
#### Backports
-- [ ] Once the MR is ready to be merged, create MRs targeting the last 3 releases, plus the current RC if between the 7th and 22nd of the month.
+- [ ] Once the MR is ready to be merged, create MRs targeting the latest 3 stable branches
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the "Security Release" merge request template.
diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md
index 3aedd5859d3..f194adebc87 100644
--- a/.gitlab/issue_templates/Test plan.md
+++ b/.gitlab/issue_templates/Test plan.md
@@ -89,7 +89,7 @@ New end-to-end and integration tests (Selenium and API) should be added to the
Please note if automated tests already exist.
-When adding new automated tests, please keep [testing levels](https://docs.gitlab.com/ce/development/testing_guide/testing_levels.html)
+When adding new automated tests, please keep [testing levels](https://docs.gitlab.com/ee/development/testing_guide/testing_levels.html)
in mind.
-->
diff --git a/.gitlab/merge_request_templates/Database changes.md b/.gitlab/merge_request_templates/Database changes.md
index 3f457174492..2077997a0cb 100644
--- a/.gitlab/merge_request_templates/Database changes.md
+++ b/.gitlab/merge_request_templates/Database changes.md
@@ -39,20 +39,11 @@ When adding tables:
- [ ] Ordered columns based on the [Ordering Table Columns](https://docs.gitlab.com/ee/development/ordering_table_columns.html#ordering-table-columns) guidelines
- [ ] Added foreign keys to any columns pointing to data in other tables
-- [ ] Added indexes for fields that are used in statements such as WHERE, ORDER BY, GROUP BY, and JOINs
+- [ ] Added indexes for fields that are used in statements such as `WHERE`, `ORDER BY`, `GROUP BY`, and `JOIN`s
When removing columns, tables, indexes or other structures:
- [ ] Removed these in a post-deployment migration
- [ ] Made sure the application no longer uses (or ignores) these structures
-## General checklist
-
-- [ ] [Changelog entry](https://docs.gitlab.com/ee/development/changelog.html) added, if necessary
-- [ ] [Documentation created/updated](https://docs.gitlab.com/ee/development/documentation/)
-- [ ] [Tests added for this feature/bug](https://docs.gitlab.com/ee/development/testing_guide/index.html)
-- [ ] Conforms to the [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html)
-- [ ] Conforms to the [merge request performance guidelines](https://docs.gitlab.com/ee/development/merge_request_performance_guidelines.html)
-- [ ] Conforms to the [style guides](https://docs.gitlab.com/ee/development/contributing/style_guides.html)
-
-/label ~database
+/label ~database ~"database::review pending"
diff --git a/.haml-lint.yml b/.haml-lint.yml
index 0412b24a48c..399fa9656a0 100644
--- a/.haml-lint.yml
+++ b/.haml-lint.yml
@@ -118,7 +118,6 @@ linters:
- Lint/ParenthesesAsGroupedExpression
- Lint/RedundantWithIndex
- Lint/SafeNavigationConsistency
- - Lint/Syntax
- Metrics/BlockNesting
- Naming/VariableName
- Performance/RedundantMatch
@@ -155,6 +154,9 @@ linters:
enabled: true
style: space
+ Syntax:
+ enabled: true
+
Indentation:
enabled: true
character: space # or tab
diff --git a/.haml-lint_todo.yml b/.haml-lint_todo.yml
index 61805c2d8d2..d29cb8aa0b0 100644
--- a/.haml-lint_todo.yml
+++ b/.haml-lint_todo.yml
@@ -7,517 +7,517 @@
# versions of Haml-Lint, may require this file to be generated again.
linters:
-
# Offense count: 2075
NoPlainNodes:
enabled: true
exclude:
- - "app/views/admin/abuse_reports/_abuse_report.html.haml"
- - "app/views/admin/abuse_reports/index.html.haml"
- - "app/views/admin/appearances/_form.html.haml"
- - "app/views/admin/application_settings/_abuse.html.haml"
- - "app/views/admin/application_settings/_diff_limits.html.haml"
- - "app/views/admin/application_settings/_gitaly.html.haml"
- - "app/views/admin/application_settings/_influx.html.haml"
- - "app/views/admin/application_settings/_ip_limits.html.haml"
- - "app/views/admin/application_settings/_logging.html.haml"
- - "app/views/admin/application_settings/_performance.html.haml"
- - "app/views/admin/application_settings/_plantuml.html.haml"
- - "app/views/admin/application_settings/_prometheus.html.haml"
- - "app/views/admin/application_settings/_realtime.html.haml"
- - "app/views/admin/application_settings/_repository_check.html.haml"
- - "app/views/admin/application_settings/_repository_storage.html.haml"
- - "app/views/admin/application_settings/_signin.html.haml"
- - "app/views/admin/application_settings/_signup.html.haml"
- - "app/views/admin/application_settings/_spam.html.haml"
- - "app/views/admin/application_settings/_terminal.html.haml"
- - "app/views/admin/application_settings/_usage.html.haml"
- - "app/views/admin/application_settings/_visibility_and_access.html.haml"
- - "app/views/admin/applications/_delete_form.html.haml"
- - "app/views/admin/applications/_form.html.haml"
- - "app/views/admin/applications/edit.html.haml"
- - "app/views/admin/applications/index.html.haml"
- - "app/views/admin/applications/new.html.haml"
- - "app/views/admin/applications/show.html.haml"
- - "app/views/admin/background_jobs/show.html.haml"
- - "app/views/admin/broadcast_messages/index.html.haml"
- - "app/views/admin/dashboard/index.html.haml"
- - "app/views/admin/deploy_keys/new.html.haml"
- - "app/views/admin/groups/show.html.haml"
- - "app/views/admin/health_check/show.html.haml"
- - "app/views/admin/hook_logs/_index.html.haml"
- - "app/views/admin/hook_logs/show.html.haml"
- - "app/views/admin/hooks/_form.html.haml"
- - "app/views/admin/hooks/edit.html.haml"
- - "app/views/admin/hooks/index.html.haml"
- - "app/views/admin/labels/_form.html.haml"
- - "app/views/admin/logs/show.html.haml"
- - "app/views/admin/projects/_projects.html.haml"
- - "app/views/admin/projects/show.html.haml"
- - "app/views/admin/requests_profiles/index.html.haml"
- - "app/views/admin/runners/_runner.html.haml"
- - "app/views/admin/runners/index.html.haml"
- - "app/views/admin/runners/show.html.haml"
- - "app/views/admin/services/_form.html.haml"
- - "app/views/admin/services/index.html.haml"
- - "app/views/admin/spam_logs/_spam_log.html.haml"
- - "app/views/admin/spam_logs/index.html.haml"
- - "app/views/admin/system_info/show.html.haml"
- - "app/views/admin/users/_access_levels.html.haml"
- - "app/views/admin/users/_form.html.haml"
- - "app/views/admin/users/_head.html.haml"
- - "app/views/admin/users/_profile.html.haml"
- - "app/views/admin/users/_projects.html.haml"
- - "app/views/admin/users/new.html.haml"
- - "app/views/admin/users/projects.html.haml"
- - "app/views/admin/users/show.html.haml"
- - "app/views/clusters/clusters/_cluster.html.haml"
- - "app/views/clusters/clusters/_form.html.haml"
- - "app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml"
- - "app/views/clusters/clusters/gcp/_form.html.haml"
- - "app/views/clusters/clusters/new.html.haml"
- - "app/views/dashboard/milestones/index.html.haml"
- - "app/views/dashboard/projects/_blank_state_admin_welcome.html.haml"
- - "app/views/dashboard/projects/_blank_state_welcome.html.haml"
- - "app/views/dashboard/projects/_zero_authorized_projects.html.haml"
- - "app/views/dashboard/snippets/index.html.haml"
- - "app/views/dashboard/todos/_todo.html.haml"
- - "app/views/dashboard/todos/index.html.haml"
- - "app/views/devise/confirmations/almost_there.haml"
- - "app/views/devise/mailer/_confirmation_instructions_account.html.haml"
- - "app/views/devise/mailer/_confirmation_instructions_secondary.html.haml"
- - "app/views/devise/mailer/email_changed.html.haml"
- - "app/views/devise/mailer/password_change.html.haml"
- - "app/views/devise/mailer/reset_password_instructions.html.haml"
- - "app/views/devise/mailer/unlock_instructions.html.haml"
- - "app/views/devise/passwords/edit.html.haml"
- - "app/views/devise/sessions/_new_base.html.haml"
- - "app/views/devise/sessions/_new_crowd.html.haml"
- - "app/views/devise/sessions/_new_ldap.html.haml"
- - "app/views/devise/sessions/new.html.haml"
- - "app/views/devise/sessions/two_factor.html.haml"
- - "app/views/devise/shared/_omniauth_box.html.haml"
- - "app/views/devise/shared/_sign_in_link.html.haml"
- - "app/views/devise/shared/_signup_box.html.haml"
- - "app/views/devise/shared/_tabs_normal.html.haml"
- - "app/views/discussions/_discussion.html.haml"
- - "app/views/discussions/_headline.html.haml"
- - "app/views/discussions/_notes.html.haml"
- - "app/views/discussions/_resolve_all.html.haml"
- - "app/views/doorkeeper/applications/_delete_form.html.haml"
- - "app/views/doorkeeper/authorized_applications/_delete_form.html.haml"
- - "app/views/errors/encoding.html.haml"
- - "app/views/errors/git_not_found.html.haml"
- - "app/views/errors/omniauth_error.html.haml"
- - "app/views/errors/precondition_failed.html.haml"
- - "app/views/events/_commit.html.haml"
- - "app/views/events/_event_push.atom.haml"
- - "app/views/events/event/_push.html.haml"
- - "app/views/groups/_create_chat_team.html.haml"
- - "app/views/groups/_group_admin_settings.html.haml"
- - "app/views/groups/group_members/_new_group_member.html.haml"
- - "app/views/groups/group_members/index.html.haml"
- - "app/views/groups/labels/edit.html.haml"
- - "app/views/groups/labels/new.html.haml"
- - "app/views/groups/milestones/edit.html.haml"
- - "app/views/groups/milestones/index.html.haml"
- - "app/views/groups/milestones/new.html.haml"
- - "app/views/groups/projects.html.haml"
- - "app/views/groups/runners/edit.html.haml"
- - "app/views/groups/settings/_advanced.html.haml"
- - "app/views/groups/settings/_lfs.html.haml"
- - "app/views/help/_shortcuts.html.haml"
- - "app/views/help/index.html.haml"
- - "app/views/help/instance_configuration.html.haml"
- - "app/views/help/instance_configuration/_gitlab_ci.html.haml"
- - "app/views/help/instance_configuration/_gitlab_pages.html.haml"
- - "app/views/help/instance_configuration/_ssh_info.html.haml"
- - "app/views/help/ui.html.haml"
- - "app/views/import/bitbucket/status.html.haml"
- - "app/views/import/bitbucket_server/status.html.haml"
- - "app/views/instance_statistics/cohorts/_cohorts_table.html.haml"
- - "app/views/instance_statistics/cohorts/_usage_ping.html.haml"
- - "app/views/invites/show.html.haml"
- - "app/views/layouts/_mailer.html.haml"
- - "app/views/layouts/header/_default.html.haml"
- - "app/views/layouts/header/_new_dropdown.haml"
- - "app/views/layouts/mailer/devise.html.haml"
- - "app/views/layouts/nav/sidebar/_profile.html.haml"
- - "app/views/layouts/notify.html.haml"
- - "app/views/notify/_failed_builds.html.haml"
- - "app/views/notify/_reassigned_issuable_email.html.haml"
- - "app/views/notify/_removal_notification.html.haml"
- - "app/views/notify/autodevops_disabled_email.html.haml"
- - "app/views/notify/changed_milestone_email.html.haml"
- - "app/views/notify/import_issues_csv_email.html.haml"
- - "app/views/notify/issue_moved_email.html.haml"
- - "app/views/notify/member_access_denied_email.html.haml"
- - "app/views/notify/member_invite_accepted_email.html.haml"
- - "app/views/notify/member_invite_declined_email.html.haml"
- - "app/views/notify/member_invited_email.html.haml"
- - "app/views/notify/new_gpg_key_email.html.haml"
- - "app/views/notify/new_mention_in_issue_email.html.haml"
- - "app/views/notify/new_ssh_key_email.html.haml"
- - "app/views/notify/new_user_email.html.haml"
- - "app/views/notify/pages_domain_disabled_email.html.haml"
- - "app/views/notify/pages_domain_enabled_email.html.haml"
- - "app/views/notify/pages_domain_verification_failed_email.html.haml"
- - "app/views/notify/pages_domain_verification_succeeded_email.html.haml"
- - "app/views/notify/pipeline_failed_email.html.haml"
- - "app/views/notify/pipeline_success_email.html.haml"
- - "app/views/notify/project_was_exported_email.html.haml"
- - "app/views/notify/project_was_moved_email.html.haml"
- - "app/views/notify/project_was_not_exported_email.html.haml"
- - "app/views/notify/push_to_merge_request_email.html.haml"
- - "app/views/notify/remote_mirror_update_failed_email.html.haml"
- - "app/views/notify/removed_milestone_issue_email.html.haml"
- - "app/views/notify/removed_milestone_merge_request_email.html.haml"
- - "app/views/notify/repository_push_email.html.haml"
- - "app/views/peek/views/_gc.html.haml"
- - "app/views/peek/views/_redis.html.haml"
- - "app/views/peek/views/_sidekiq.html.haml"
- - "app/views/profiles/_event_table.html.haml"
- - "app/views/profiles/active_sessions/_active_session.html.haml"
- - "app/views/profiles/active_sessions/index.html.haml"
- - "app/views/profiles/audit_log.html.haml"
- - "app/views/profiles/chat_names/_chat_name.html.haml"
- - "app/views/profiles/chat_names/index.html.haml"
- - "app/views/profiles/chat_names/new.html.haml"
- - "app/views/profiles/emails/index.html.haml"
- - "app/views/profiles/gpg_keys/_key.html.haml"
- - "app/views/profiles/gpg_keys/index.html.haml"
- - "app/views/profiles/keys/_key.html.haml"
- - "app/views/profiles/keys/_key_details.html.haml"
- - "app/views/profiles/keys/index.html.haml"
- - "app/views/profiles/notifications/show.html.haml"
- - "app/views/profiles/passwords/edit.html.haml"
- - "app/views/profiles/personal_access_tokens/index.html.haml"
- - "app/views/profiles/preferences/show.html.haml"
- - "app/views/profiles/show.html.haml"
- - "app/views/profiles/two_factor_auths/_codes.html.haml"
- - "app/views/profiles/two_factor_auths/codes.html.haml"
- - "app/views/profiles/two_factor_auths/create.html.haml"
- - "app/views/profiles/two_factor_auths/show.html.haml"
- - "app/views/projects/_bitbucket_import_modal.html.haml"
- - "app/views/projects/_customize_workflow.html.haml"
- - "app/views/projects/_deletion_failed.html.haml"
- - "app/views/projects/_fork_suggestion.html.haml"
- - "app/views/projects/_gitlab_import_modal.html.haml"
- - "app/views/projects/_home_panel.html.haml"
- - "app/views/projects/_import_project_pane.html.haml"
- - "app/views/projects/_issuable_by_email.html.haml"
- - "app/views/projects/_md_preview.html.haml"
- - "app/views/projects/_new_project_fields.html.haml"
- - "app/views/projects/_readme.html.haml"
- - "app/views/projects/artifacts/_tree_file.html.haml"
- - "app/views/projects/artifacts/browse.html.haml"
- - "app/views/projects/blame/_age_map_legend.html.haml"
- - "app/views/projects/blame/show.html.haml"
- - "app/views/projects/blob/_editor.html.haml"
- - "app/views/projects/blob/_header_content.html.haml"
- - "app/views/projects/blob/_new_dir.html.haml"
- - "app/views/projects/blob/_remove.html.haml"
- - "app/views/projects/blob/_render_error.html.haml"
- - "app/views/projects/blob/_template_selectors.html.haml"
- - "app/views/projects/blob/_upload.html.haml"
- - "app/views/projects/blob/edit.html.haml"
- - "app/views/projects/blob/new.html.haml"
- - "app/views/projects/blob/preview.html.haml"
- - "app/views/projects/blob/viewers/_empty.html.haml"
- - "app/views/projects/blob/viewers/_stl.html.haml"
- - "app/views/projects/branches/_branch.html.haml"
- - "app/views/projects/branches/_commit.html.haml"
- - "app/views/projects/branches/_delete_protected_modal.html.haml"
- - "app/views/projects/branches/new.html.haml"
- - "app/views/projects/ci/builds/_build.html.haml"
- - "app/views/projects/ci/lints/_create.html.haml"
- - "app/views/projects/commit/_change.html.haml"
- - "app/views/projects/commits/_commit.html.haml"
- - "app/views/projects/commits/_inline_commit.html.haml"
- - "app/views/projects/compare/_form.html.haml"
- - "app/views/projects/compare/index.html.haml"
- - "app/views/projects/cycle_analytics/_empty_stage.html.haml"
- - "app/views/projects/cycle_analytics/_no_access.html.haml"
- - "app/views/projects/cycle_analytics/_overview.html.haml"
- - "app/views/projects/cycle_analytics/show.html.haml"
- - "app/views/projects/deploy_keys/_form.html.haml"
- - "app/views/projects/deploy_keys/_index.html.haml"
- - "app/views/projects/deploy_keys/edit.html.haml"
- - "app/views/projects/deploy_tokens/_revoke_modal.html.haml"
- - "app/views/projects/deploy_tokens/_table.html.haml"
- - "app/views/projects/deployments/_deployment.html.haml"
- - "app/views/projects/diffs/_file_header.html.haml"
- - "app/views/projects/diffs/_replaced_image_diff.html.haml"
- - "app/views/projects/diffs/_stats.html.haml"
- - "app/views/projects/empty.html.haml"
- - "app/views/projects/environments/show.html.haml"
- - "app/views/projects/forks/error.html.haml"
- - "app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml"
- - "app/views/projects/graphs/charts.html.haml"
- - "app/views/projects/hook_logs/_index.html.haml"
- - "app/views/projects/hook_logs/show.html.haml"
- - "app/views/projects/hooks/_index.html.haml"
- - "app/views/projects/hooks/edit.html.haml"
- - "app/views/projects/imports/new.html.haml"
- - "app/views/projects/imports/show.html.haml"
- - "app/views/projects/issues/_issue.html.haml"
- - "app/views/projects/issues/_new_branch.html.haml"
- - "app/views/projects/issues/import_csv/_modal.html.haml"
- - "app/views/projects/issues/show.html.haml"
- - "app/views/projects/jobs/_header.html.haml"
- - "app/views/projects/jobs/_table.html.haml"
- - "app/views/projects/jobs/index.html.haml"
- - "app/views/projects/labels/edit.html.haml"
- - "app/views/projects/labels/new.html.haml"
- - "app/views/projects/mattermosts/_no_teams.html.haml"
- - "app/views/projects/mattermosts/_team_selection.html.haml"
- - "app/views/projects/mattermosts/new.html.haml"
- - "app/views/projects/merge_requests/_commits.html.haml"
- - "app/views/projects/merge_requests/_discussion.html.haml"
- - "app/views/projects/merge_requests/_how_to_merge.html.haml"
- - "app/views/projects/merge_requests/_merge_request.html.haml"
- - "app/views/projects/merge_requests/_mr_title.html.haml"
- - "app/views/projects/merge_requests/conflicts/_commit_stats.html.haml"
- - "app/views/projects/merge_requests/conflicts/_file_actions.html.haml"
- - "app/views/projects/merge_requests/conflicts/_submit_form.html.haml"
- - "app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml"
- - "app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml"
- - "app/views/projects/merge_requests/conflicts/show.html.haml"
- - "app/views/projects/merge_requests/creations/_diffs.html.haml"
- - "app/views/projects/merge_requests/creations/_new_compare.html.haml"
- - "app/views/projects/merge_requests/creations/_new_submit.html.haml"
- - "app/views/projects/merge_requests/diffs/_different_base.html.haml"
- - "app/views/projects/merge_requests/diffs/_diffs.html.haml"
- - "app/views/projects/merge_requests/diffs/_version_controls.html.haml"
- - "app/views/projects/merge_requests/invalid.html.haml"
- - "app/views/projects/merge_requests/widget/open/_error.html.haml"
- - "app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml"
- - "app/views/projects/mirrors/_ssh_host_keys.html.haml"
- - "app/views/projects/new.html.haml"
- - "app/views/projects/no_repo.html.haml"
- - "app/views/projects/pages/_access.html.haml"
- - "app/views/projects/pages/_destroy.haml"
- - "app/views/projects/pages/_https_only.html.haml"
- - "app/views/projects/pages/_list.html.haml"
- - "app/views/projects/pages/_no_domains.html.haml"
- - "app/views/projects/pages/_use.html.haml"
- - "app/views/projects/pages/show.html.haml"
- - "app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml"
- - "app/views/projects/pipelines/_info.html.haml"
- - "app/views/projects/pipelines/charts/_pipelines.haml"
- - "app/views/projects/protected_branches/shared/_branches_list.html.haml"
- - "app/views/projects/protected_branches/shared/_create_protected_branch.html.haml"
- - "app/views/projects/protected_branches/shared/_dropdown.html.haml"
- - "app/views/projects/protected_branches/shared/_index.html.haml"
- - "app/views/projects/protected_branches/shared/_matching_branch.html.haml"
- - "app/views/projects/protected_branches/shared/_protected_branch.html.haml"
- - "app/views/projects/protected_branches/show.html.haml"
- - "app/views/projects/protected_tags/shared/_create_protected_tag.html.haml"
- - "app/views/projects/protected_tags/shared/_dropdown.html.haml"
- - "app/views/projects/protected_tags/shared/_index.html.haml"
- - "app/views/projects/protected_tags/shared/_matching_tag.html.haml"
- - "app/views/projects/protected_tags/shared/_protected_tag.html.haml"
- - "app/views/projects/protected_tags/shared/_tags_list.html.haml"
- - "app/views/projects/protected_tags/show.html.haml"
- - "app/views/projects/registry/repositories/_tag.html.haml"
- - "app/views/projects/repositories/_feed.html.haml"
- - "app/views/projects/runners/_shared_runners.html.haml"
- - "app/views/projects/runners/edit.html.haml"
- - "app/views/projects/services/_form.html.haml"
- - "app/views/projects/services/_index.html.haml"
- - "app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml"
- - "app/views/projects/services/mattermost_slash_commands/_help.html.haml"
- - "app/views/projects/services/prometheus/_metrics.html.haml"
- - "app/views/projects/services/slack_slash_commands/_help.html.haml"
- - "app/views/projects/settings/ci_cd/_badge.html.haml"
- - "app/views/projects/settings/ci_cd/_form.html.haml"
- - "app/views/projects/stage/_stage.html.haml"
- - "app/views/projects/tags/index.html.haml"
- - "app/views/projects/tags/new.html.haml"
- - "app/views/projects/tags/releases/edit.html.haml"
- - "app/views/projects/tree/_tree_row.html.haml"
- - "app/views/projects/tree/_truncated_notice_tree_row.html.haml"
- - "app/views/projects/triggers/_content.html.haml"
- - "app/views/projects/triggers/_form.html.haml"
- - "app/views/projects/triggers/_index.html.haml"
- - "app/views/projects/triggers/_trigger.html.haml"
- - "app/views/projects/triggers/edit.html.haml"
- - "app/views/projects/wikis/_new.html.haml"
- - "app/views/projects/wikis/_pages_wiki_page.html.haml"
- - "app/views/projects/wikis/edit.html.haml"
- - "app/views/projects/wikis/history.html.haml"
- - "app/views/repository_check_mailer/notify.html.haml"
- - "app/views/search/_form.html.haml"
- - "app/views/search/results/_issue.html.haml"
- - "app/views/search/results/_note.html.haml"
- - "app/views/search/results/_snippet_blob.html.haml"
- - "app/views/search/results/_snippet_title.html.haml"
- - "app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml"
- - "app/views/shared/_commit_message_container.html.haml"
- - "app/views/shared/_confirm_modal.html.haml"
- - "app/views/shared/_delete_label_modal.html.haml"
- - "app/views/shared/_group_form.html.haml"
- - "app/views/shared/_group_tips.html.haml"
- - "app/views/shared/_milestone_expired.html.haml"
- - "app/views/shared/_no_password.html.haml"
- - "app/views/shared/_no_ssh.html.haml"
- - "app/views/shared/_outdated_browser.html.haml"
- - "app/views/shared/_personal_access_tokens_created_container.html.haml"
- - "app/views/shared/_personal_access_tokens_table.html.haml"
- - "app/views/shared/_ping_consent.html.haml"
- - "app/views/shared/_project_limit.html.haml"
- - "app/views/shared/_service_settings.html.haml"
- - "app/views/shared/boards/components/_board.html.haml"
- - "app/views/shared/boards/components/_sidebar.html.haml"
- - "app/views/shared/boards/components/sidebar/_due_date.html.haml"
- - "app/views/shared/boards/components/sidebar/_labels.html.haml"
- - "app/views/shared/boards/components/sidebar/_milestone.html.haml"
- - "app/views/shared/empty_states/_priority_labels.html.haml"
- - "app/views/shared/hook_logs/_content.html.haml"
- - "app/views/shared/issuable/_assignees.html.haml"
- - "app/views/shared/issuable/_board_create_list_dropdown.html.haml"
- - "app/views/shared/issuable/_bulk_update_sidebar.html.haml"
- - "app/views/shared/issuable/_close_reopen_report_toggle.html.haml"
- - "app/views/shared/issuable/_form.html.haml"
- - "app/views/shared/issuable/_search_bar.html.haml"
- - "app/views/shared/issuable/_sidebar.html.haml"
- - "app/views/shared/issuable/form/_default_templates.html.haml"
- - "app/views/shared/issuable/form/_issue_assignee.html.haml"
- - "app/views/shared/issuable/form/_template_selector.html.haml"
- - "app/views/shared/issuable/form/_title.html.haml"
- - "app/views/shared/labels/_form.html.haml"
- - "app/views/shared/members/_member.html.haml"
- - "app/views/shared/milestones/_form_dates.html.haml"
- - "app/views/shared/milestones/_issuable.html.haml"
- - "app/views/shared/milestones/_milestone.html.haml"
- - "app/views/shared/milestones/_sidebar.html.haml"
- - "app/views/shared/milestones/_top.html.haml"
- - "app/views/shared/notes/_hints.html.haml"
- - "app/views/shared/notes/_note.html.haml"
- - "app/views/shared/notifications/_button.html.haml"
- - "app/views/shared/notifications/_custom_notifications.html.haml"
- - "app/views/shared/notifications/_new_button.html.haml"
- - "app/views/shared/notifications/_notification_dropdown.html.haml"
- - "app/views/shared/plugins/_index.html.haml"
- - "app/views/shared/projects/_dropdown.html.haml"
- - "app/views/shared/projects/_list.html.haml"
- - "app/views/shared/projects/_project.html.haml"
- - "app/views/shared/runners/_runner_description.html.haml"
- - "app/views/shared/runners/show.html.haml"
- - "app/views/shared/snippets/_embed.html.haml"
- - "app/views/shared/snippets/_header.html.haml"
- - "app/views/shared/snippets/_snippet.html.haml"
- - "app/views/shared/tokens/_scopes_list.html.haml"
- - "app/views/shared/web_hooks/_form.html.haml"
- - "app/views/shared/web_hooks/_test_button.html.haml"
- - "app/views/u2f/_authenticate.html.haml"
- - "app/views/u2f/_register.html.haml"
- - "app/views/users/_deletion_guidance.html.haml"
- - "ee/app/views/admin/_namespace_plan_info.html.haml"
- - "ee/app/views/admin/application_settings/_elasticsearch_form.html.haml"
- - "ee/app/views/admin/application_settings/_slack.html.haml"
- - "ee/app/views/admin/application_settings/_snowplow.html.haml"
- - "ee/app/views/admin/application_settings/_templates.html.haml"
- - "ee/app/views/admin/audit_logs/index.html.haml"
- - "ee/app/views/admin/dashboard/stats.html.haml"
- - "ee/app/views/admin/emails/show.html.haml"
- - "ee/app/views/admin/geo/nodes/edit.html.haml"
- - "ee/app/views/admin/geo/nodes/new.html.haml"
- - "ee/app/views/admin/geo/projects/_registry_failed.html.haml"
- - "ee/app/views/admin/geo/projects/_registry_never.html.haml"
- - "ee/app/views/admin/licenses/_breakdown.html.haml"
- - "ee/app/views/admin/licenses/_upload_trial_license.html.haml"
- - "ee/app/views/admin/licenses/missing.html.haml"
- - "ee/app/views/admin/licenses/new.html.haml"
- - "ee/app/views/admin/licenses/show.html.haml"
- - "ee/app/views/admin/monitoring/ee/_nav.html.haml"
- - "ee/app/views/admin/projects/_shared_runner_status.html.haml"
- - "ee/app/views/admin/push_rules/show.html.haml"
- - "ee/app/views/admin/users/_limits.html.haml"
- - "ee/app/views/admin/users/_user_detail_note.html.haml"
- - "ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml"
- - "ee/app/views/errors/kerberos_denied.html.haml"
- - "ee/app/views/groups/analytics/show.html.haml"
- - "ee/app/views/groups/audit_events/index.html.haml"
- - "ee/app/views/groups/ee/_settings_nav.html.haml"
- - "ee/app/views/groups/epics/_epic.html.haml"
- - "ee/app/views/groups/group_members/_ldap_sync.html.haml"
- - "ee/app/views/groups/group_members/_sync_button.html.haml"
- - "ee/app/views/groups/hooks/_project_hook.html.haml"
- - "ee/app/views/groups/hooks/index.html.haml"
- - "ee/app/views/groups/ldap_group_links/index.html.haml"
- - "ee/app/views/groups/pipeline_quota/index.html.haml"
- - "ee/app/views/jira_connect/subscriptions/index.html.haml"
- - "ee/app/views/layouts/jira_connect.html.haml"
- - "ee/app/views/layouts/nav/ee/_epic_link.html.haml"
- - "ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml"
- - "ee/app/views/layouts/service_desk.html.haml"
- - "ee/app/views/ldap_group_links/_form.html.haml"
- - "ee/app/views/ldap_group_links/_ldap_group_link.html.haml"
- - "ee/app/views/ldap_group_links/_ldap_group_links.html.haml"
- - "ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml"
- - "ee/app/views/ldap_group_links/_ldap_group_links_synchronizations.html.haml"
- - "ee/app/views/namespaces/_shared_runner_status.html.haml"
- - "ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml"
- - "ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml"
- - "ee/app/views/namespaces/pipelines_quota/_list.haml"
- - "ee/app/views/notify/approved_merge_request_email.html.haml"
- - "ee/app/views/notify/epic_status_changed_email.html.haml"
- - "ee/app/views/notify/issues_csv_email.html.haml"
- - "ee/app/views/notify/new_review_email.html.haml"
- - "ee/app/views/notify/prometheus_alert_fired_email.html.haml"
- - "ee/app/views/notify/send_admin_notification.html.haml"
- - "ee/app/views/notify/send_unsubscribed_notification.html.haml"
- - "ee/app/views/notify/unapproved_merge_request_email.html.haml"
- - "ee/app/views/oauth/geo_auth/error.html.haml"
- - "ee/app/views/profiles/pipeline_quota/index.haml"
- - "ee/app/views/projects/audit_events/index.html.haml"
- - "ee/app/views/projects/blob/_owners.html.haml"
- - "ee/app/views/projects/commits/_mirror_status.html.haml"
- - "ee/app/views/projects/feature_flags/_configure_feature_flags_modal.html.haml"
- - "ee/app/views/projects/issues/_issue_weight.html.haml"
- - "ee/app/views/projects/issues/_related_issues.html.haml"
- - "ee/app/views/projects/issues/export_csv/_modal.html.haml"
- - "ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml"
- - "ee/app/views/projects/merge_requests/_approvals_count.html.haml"
- - "ee/app/views/projects/merge_requests/widget/open/_geo.html.haml"
- - "ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml"
- - "ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml"
- - "ee/app/views/projects/protected_branches/ee/_dropdown.html.haml"
- - "ee/app/views/projects/protected_branches/ee/_fallback_update_protected_branch.html.haml"
- - "ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml"
- - "ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml"
- - "ee/app/views/projects/push_rules/_index.html.haml"
- - "ee/app/views/projects/services/gitlab_slack_application/_help.html.haml"
- - "ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml"
- - "ee/app/views/projects/services/prometheus/_metrics.html.haml"
- - "ee/app/views/projects/settings/slacks/edit.html.haml"
- - "ee/app/views/shared/_additional_email_text.html.haml"
- - "ee/app/views/shared/_geo_info_modal.html.haml"
- - "ee/app/views/shared/_mirror_update_button.html.haml"
- - "ee/app/views/shared/_shared_runners_minutes_limit.html.haml"
- - "ee/app/views/shared/audit_events/_event_table.html.haml"
- - "ee/app/views/shared/boards/components/_list_weight.html.haml"
- - "ee/app/views/shared/boards/components/sidebar/_epic.html.haml"
- - "ee/app/views/shared/ee/_import_form.html.haml"
- - "ee/app/views/shared/epic/_search_bar.html.haml"
- - "ee/app/views/shared/issuable/_approvals.html.haml"
- - "ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml"
- - "ee/app/views/shared/issuable/_filter_weight.html.haml"
- - "ee/app/views/shared/issuable/_sidebar_item_epic.haml"
- - "ee/app/views/shared/members/ee/_ldap_tag.html.haml"
- - "ee/app/views/shared/members/ee/_override_member_buttons.html.haml"
- - "ee/app/views/shared/members/ee/_sso_badge.html.haml"
- - "ee/app/views/shared/milestones/_burndown.html.haml"
- - "ee/app/views/shared/milestones/_weight.html.haml"
- - "ee/app/views/shared/promotions/_promote_audit_events.html.haml"
- - "ee/app/views/shared/promotions/_promote_burndown_charts.html.haml"
- - "ee/app/views/shared/promotions/_promote_csv_export.html.haml"
- - "ee/app/views/shared/promotions/_promote_issue_weights.html.haml"
- - "ee/app/views/shared/promotions/_promote_repository_features.html.haml"
- - "ee/app/views/shared/promotions/_promote_servicedesk.html.haml"
- - "ee/app/views/shared/push_rules/_form.html.haml"
- - "ee/app/views/unsubscribes/show.html.haml"
- - "ee/app/views/admin/users/_auditor_access_level_radio.html.haml"
- - "ee/app/views/admin/users/_auditor_user_badge.html.haml"
- - "ee/app/views/projects/protected_branches/_update_protected_branch.html.haml"
+ - 'app/views/admin/abuse_reports/_abuse_report.html.haml'
+ - 'app/views/admin/abuse_reports/index.html.haml'
+ - 'app/views/admin/appearances/_form.html.haml'
+ - 'app/views/admin/application_settings/_abuse.html.haml'
+ - 'app/views/admin/application_settings/_diff_limits.html.haml'
+ - 'app/views/admin/application_settings/_gitaly.html.haml'
+ - 'app/views/admin/application_settings/_influx.html.haml'
+ - 'app/views/admin/application_settings/_ip_limits.html.haml'
+ - 'app/views/admin/application_settings/_logging.html.haml'
+ - 'app/views/admin/application_settings/_performance.html.haml'
+ - 'app/views/admin/application_settings/_plantuml.html.haml'
+ - 'app/views/admin/application_settings/_prometheus.html.haml'
+ - 'app/views/admin/application_settings/_realtime.html.haml'
+ - 'app/views/admin/application_settings/_repository_check.html.haml'
+ - 'app/views/admin/application_settings/_repository_storage.html.haml'
+ - 'app/views/admin/application_settings/_signin.html.haml'
+ - 'app/views/admin/application_settings/_signup.html.haml'
+ - 'app/views/admin/application_settings/_spam.html.haml'
+ - 'app/views/admin/application_settings/_terminal.html.haml'
+ - 'app/views/admin/application_settings/_usage.html.haml'
+ - 'app/views/admin/application_settings/_visibility_and_access.html.haml'
+ - 'app/views/admin/applications/_delete_form.html.haml'
+ - 'app/views/admin/applications/_form.html.haml'
+ - 'app/views/admin/applications/edit.html.haml'
+ - 'app/views/admin/applications/index.html.haml'
+ - 'app/views/admin/applications/new.html.haml'
+ - 'app/views/admin/applications/show.html.haml'
+ - 'app/views/admin/background_jobs/show.html.haml'
+ - 'app/views/admin/broadcast_messages/index.html.haml'
+ - 'app/views/admin/dashboard/index.html.haml'
+ - 'app/views/admin/deploy_keys/new.html.haml'
+ - 'app/views/admin/groups/show.html.haml'
+ - 'app/views/admin/health_check/show.html.haml'
+ - 'app/views/admin/hook_logs/_index.html.haml'
+ - 'app/views/admin/hook_logs/show.html.haml'
+ - 'app/views/admin/hooks/_form.html.haml'
+ - 'app/views/admin/hooks/edit.html.haml'
+ - 'app/views/admin/hooks/index.html.haml'
+ - 'app/views/admin/labels/_form.html.haml'
+ - 'app/views/admin/logs/show.html.haml'
+ - 'app/views/admin/projects/_projects.html.haml'
+ - 'app/views/admin/projects/show.html.haml'
+ - 'app/views/admin/requests_profiles/index.html.haml'
+ - 'app/views/admin/runners/_runner.html.haml'
+ - 'app/views/admin/runners/index.html.haml'
+ - 'app/views/admin/runners/show.html.haml'
+ - 'app/views/admin/services/_form.html.haml'
+ - 'app/views/admin/services/index.html.haml'
+ - 'app/views/admin/spam_logs/_spam_log.html.haml'
+ - 'app/views/admin/spam_logs/index.html.haml'
+ - 'app/views/admin/system_info/show.html.haml'
+ - 'app/views/admin/users/_access_levels.html.haml'
+ - 'app/views/admin/users/_form.html.haml'
+ - 'app/views/admin/users/_head.html.haml'
+ - 'app/views/admin/users/_profile.html.haml'
+ - 'app/views/admin/users/_projects.html.haml'
+ - 'app/views/admin/users/new.html.haml'
+ - 'app/views/admin/users/projects.html.haml'
+ - 'app/views/admin/users/show.html.haml'
+ - 'app/views/clusters/clusters/_cluster.html.haml'
+ - 'app/views/clusters/clusters/_form.html.haml'
+ - 'app/views/clusters/clusters/_gcp_signup_offer_banner.html.haml'
+ - 'app/views/clusters/clusters/gcp/_form.html.haml'
+ - 'app/views/clusters/clusters/new.html.haml'
+ - 'app/views/dashboard/milestones/index.html.haml'
+ - 'app/views/dashboard/projects/_blank_state_admin_welcome.html.haml'
+ - 'app/views/dashboard/projects/_blank_state_welcome.html.haml'
+ - 'app/views/dashboard/projects/_zero_authorized_projects.html.haml'
+ - 'app/views/dashboard/snippets/index.html.haml'
+ - 'app/views/dashboard/todos/_todo.html.haml'
+ - 'app/views/dashboard/todos/index.html.haml'
+ - 'app/views/devise/confirmations/almost_there.haml'
+ - 'app/views/devise/mailer/_confirmation_instructions_account.html.haml'
+ - 'app/views/devise/mailer/_confirmation_instructions_secondary.html.haml'
+ - 'app/views/devise/mailer/email_changed.html.haml'
+ - 'app/views/devise/mailer/password_change.html.haml'
+ - 'app/views/devise/mailer/reset_password_instructions.html.haml'
+ - 'app/views/devise/mailer/unlock_instructions.html.haml'
+ - 'app/views/devise/passwords/edit.html.haml'
+ - 'app/views/devise/sessions/_new_base.html.haml'
+ - 'app/views/devise/sessions/_new_crowd.html.haml'
+ - 'app/views/devise/sessions/_new_ldap.html.haml'
+ - 'app/views/devise/sessions/new.html.haml'
+ - 'app/views/devise/sessions/two_factor.html.haml'
+ - 'app/views/devise/shared/_omniauth_box.html.haml'
+ - 'app/views/devise/shared/_sign_in_link.html.haml'
+ - 'app/views/devise/shared/_signup_box.html.haml'
+ - 'app/views/devise/shared/_tabs_normal.html.haml'
+ - 'app/views/discussions/_discussion.html.haml'
+ - 'app/views/discussions/_headline.html.haml'
+ - 'app/views/discussions/_notes.html.haml'
+ - 'app/views/discussions/_resolve_all.html.haml'
+ - 'app/views/doorkeeper/applications/_delete_form.html.haml'
+ - 'app/views/doorkeeper/authorized_applications/_delete_form.html.haml'
+ - 'app/views/errors/encoding.html.haml'
+ - 'app/views/errors/git_not_found.html.haml'
+ - 'app/views/errors/omniauth_error.html.haml'
+ - 'app/views/errors/precondition_failed.html.haml'
+ - 'app/views/events/_commit.html.haml'
+ - 'app/views/events/_event_push.atom.haml'
+ - 'app/views/events/event/_push.html.haml'
+ - 'app/views/groups/_create_chat_team.html.haml'
+ - 'app/views/groups/_group_admin_settings.html.haml'
+ - 'app/views/groups/group_members/_new_group_member.html.haml'
+ - 'app/views/groups/group_members/index.html.haml'
+ - 'app/views/groups/labels/edit.html.haml'
+ - 'app/views/groups/labels/new.html.haml'
+ - 'app/views/groups/milestones/edit.html.haml'
+ - 'app/views/groups/milestones/index.html.haml'
+ - 'app/views/groups/milestones/new.html.haml'
+ - 'app/views/groups/projects.html.haml'
+ - 'app/views/groups/runners/edit.html.haml'
+ - 'app/views/groups/settings/_advanced.html.haml'
+ - 'app/views/groups/settings/_lfs.html.haml'
+ - 'app/views/help/_shortcuts.html.haml'
+ - 'app/views/help/index.html.haml'
+ - 'app/views/help/instance_configuration.html.haml'
+ - 'app/views/help/instance_configuration/_gitlab_ci.html.haml'
+ - 'app/views/help/instance_configuration/_gitlab_pages.html.haml'
+ - 'app/views/help/instance_configuration/_ssh_info.html.haml'
+ - 'app/views/help/ui.html.haml'
+ - 'app/views/import/bitbucket/status.html.haml'
+ - 'app/views/import/bitbucket_server/status.html.haml'
+ - 'app/views/instance_statistics/cohorts/_cohorts_table.html.haml'
+ - 'app/views/instance_statistics/cohorts/_usage_ping.html.haml'
+ - 'app/views/invites/show.html.haml'
+ - 'app/views/layouts/_mailer.html.haml'
+ - 'app/views/layouts/header/_default.html.haml'
+ - 'app/views/layouts/header/_new_dropdown.haml'
+ - 'app/views/layouts/mailer/devise.html.haml'
+ - 'app/views/layouts/nav/sidebar/_profile.html.haml'
+ - 'app/views/layouts/notify.html.haml'
+ - 'app/views/notify/_failed_builds.html.haml'
+ - 'app/views/notify/_reassigned_issuable_email.html.haml'
+ - 'app/views/notify/_removal_notification.html.haml'
+ - 'app/views/notify/autodevops_disabled_email.html.haml'
+ - 'app/views/notify/changed_milestone_email.html.haml'
+ - 'app/views/notify/import_issues_csv_email.html.haml'
+ - 'app/views/notify/issue_moved_email.html.haml'
+ - 'app/views/notify/member_access_denied_email.html.haml'
+ - 'app/views/notify/member_invite_accepted_email.html.haml'
+ - 'app/views/notify/member_invite_declined_email.html.haml'
+ - 'app/views/notify/member_invited_email.html.haml'
+ - 'app/views/notify/new_gpg_key_email.html.haml'
+ - 'app/views/notify/new_mention_in_issue_email.html.haml'
+ - 'app/views/notify/new_ssh_key_email.html.haml'
+ - 'app/views/notify/new_user_email.html.haml'
+ - 'app/views/notify/pages_domain_disabled_email.html.haml'
+ - 'app/views/notify/pages_domain_enabled_email.html.haml'
+ - 'app/views/notify/pages_domain_verification_failed_email.html.haml'
+ - 'app/views/notify/pages_domain_verification_succeeded_email.html.haml'
+ - 'app/views/notify/pipeline_failed_email.html.haml'
+ - 'app/views/notify/pipeline_success_email.html.haml'
+ - 'app/views/notify/project_was_exported_email.html.haml'
+ - 'app/views/notify/project_was_moved_email.html.haml'
+ - 'app/views/notify/project_was_not_exported_email.html.haml'
+ - 'app/views/notify/push_to_merge_request_email.html.haml'
+ - 'app/views/notify/remote_mirror_update_failed_email.html.haml'
+ - 'app/views/notify/removed_milestone_issue_email.html.haml'
+ - 'app/views/notify/removed_milestone_merge_request_email.html.haml'
+ - 'app/views/notify/repository_push_email.html.haml'
+ - 'app/views/peek/views/_gc.html.haml'
+ - 'app/views/peek/views/_redis.html.haml'
+ - 'app/views/peek/views/_sidekiq.html.haml'
+ - 'app/views/profiles/_event_table.html.haml'
+ - 'app/views/profiles/active_sessions/_active_session.html.haml'
+ - 'app/views/profiles/active_sessions/index.html.haml'
+ - 'app/views/profiles/audit_log.html.haml'
+ - 'app/views/profiles/chat_names/_chat_name.html.haml'
+ - 'app/views/profiles/chat_names/index.html.haml'
+ - 'app/views/profiles/chat_names/new.html.haml'
+ - 'app/views/profiles/emails/index.html.haml'
+ - 'app/views/profiles/gpg_keys/_key.html.haml'
+ - 'app/views/profiles/gpg_keys/index.html.haml'
+ - 'app/views/profiles/keys/_key.html.haml'
+ - 'app/views/profiles/keys/_key_details.html.haml'
+ - 'app/views/profiles/keys/index.html.haml'
+ - 'app/views/profiles/notifications/show.html.haml'
+ - 'app/views/profiles/passwords/edit.html.haml'
+ - 'app/views/profiles/personal_access_tokens/index.html.haml'
+ - 'app/views/profiles/preferences/show.html.haml'
+ - 'app/views/profiles/show.html.haml'
+ - 'app/views/profiles/two_factor_auths/_codes.html.haml'
+ - 'app/views/profiles/two_factor_auths/codes.html.haml'
+ - 'app/views/profiles/two_factor_auths/create.html.haml'
+ - 'app/views/profiles/two_factor_auths/show.html.haml'
+ - 'app/views/projects/_bitbucket_import_modal.html.haml'
+ - 'app/views/projects/_customize_workflow.html.haml'
+ - 'app/views/projects/_deletion_failed.html.haml'
+ - 'app/views/projects/_fork_suggestion.html.haml'
+ - 'app/views/projects/_gitlab_import_modal.html.haml'
+ - 'app/views/projects/_home_panel.html.haml'
+ - 'app/views/projects/_import_project_pane.html.haml'
+ - 'app/views/projects/_issuable_by_email.html.haml'
+ - 'app/views/projects/_md_preview.html.haml'
+ - 'app/views/projects/_new_project_fields.html.haml'
+ - 'app/views/projects/_readme.html.haml'
+ - 'app/views/projects/artifacts/_tree_file.html.haml'
+ - 'app/views/projects/artifacts/browse.html.haml'
+ - 'app/views/projects/blame/_age_map_legend.html.haml'
+ - 'app/views/projects/blame/show.html.haml'
+ - 'app/views/projects/blob/_editor.html.haml'
+ - 'app/views/projects/blob/_header_content.html.haml'
+ - 'app/views/projects/blob/_new_dir.html.haml'
+ - 'app/views/projects/blob/_remove.html.haml'
+ - 'app/views/projects/blob/_render_error.html.haml'
+ - 'app/views/projects/blob/_template_selectors.html.haml'
+ - 'app/views/projects/blob/_upload.html.haml'
+ - 'app/views/projects/blob/edit.html.haml'
+ - 'app/views/projects/blob/new.html.haml'
+ - 'app/views/projects/blob/preview.html.haml'
+ - 'app/views/projects/blob/viewers/_empty.html.haml'
+ - 'app/views/projects/blob/viewers/_stl.html.haml'
+ - 'app/views/projects/branches/_branch.html.haml'
+ - 'app/views/projects/branches/_commit.html.haml'
+ - 'app/views/projects/branches/_delete_protected_modal.html.haml'
+ - 'app/views/projects/branches/new.html.haml'
+ - 'app/views/projects/ci/builds/_build.html.haml'
+ - 'app/views/projects/ci/lints/_create.html.haml'
+ - 'app/views/projects/commit/_change.html.haml'
+ - 'app/views/projects/commits/_commit.html.haml'
+ - 'app/views/projects/commits/_inline_commit.html.haml'
+ - 'app/views/projects/compare/_form.html.haml'
+ - 'app/views/projects/compare/index.html.haml'
+ - 'app/views/projects/cycle_analytics/_empty_stage.html.haml'
+ - 'app/views/projects/cycle_analytics/_no_access.html.haml'
+ - 'app/views/projects/cycle_analytics/_overview.html.haml'
+ - 'app/views/projects/cycle_analytics/show.html.haml'
+ - 'app/views/projects/deploy_keys/_form.html.haml'
+ - 'app/views/projects/deploy_keys/_index.html.haml'
+ - 'app/views/projects/deploy_keys/edit.html.haml'
+ - 'app/views/projects/deploy_tokens/_revoke_modal.html.haml'
+ - 'app/views/projects/deploy_tokens/_table.html.haml'
+ - 'app/views/projects/deployments/_deployment.html.haml'
+ - 'app/views/projects/diffs/_file_header.html.haml'
+ - 'app/views/projects/diffs/_replaced_image_diff.html.haml'
+ - 'app/views/projects/diffs/_stats.html.haml'
+ - 'app/views/projects/empty.html.haml'
+ - 'app/views/projects/environments/show.html.haml'
+ - 'app/views/projects/forks/error.html.haml'
+ - 'app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml'
+ - 'app/views/projects/graphs/charts.html.haml'
+ - 'app/views/projects/hook_logs/_index.html.haml'
+ - 'app/views/projects/hook_logs/show.html.haml'
+ - 'app/views/projects/hooks/_index.html.haml'
+ - 'app/views/projects/hooks/edit.html.haml'
+ - 'app/views/projects/imports/new.html.haml'
+ - 'app/views/projects/imports/show.html.haml'
+ - 'app/views/projects/issues/_issue.html.haml'
+ - 'app/views/projects/issues/_new_branch.html.haml'
+ - 'app/views/projects/issues/import_csv/_modal.html.haml'
+ - 'app/views/projects/issues/show.html.haml'
+ - 'app/views/projects/jobs/_header.html.haml'
+ - 'app/views/projects/jobs/_table.html.haml'
+ - 'app/views/projects/jobs/index.html.haml'
+ - 'app/views/projects/labels/edit.html.haml'
+ - 'app/views/projects/labels/new.html.haml'
+ - 'app/views/projects/mattermosts/_no_teams.html.haml'
+ - 'app/views/projects/mattermosts/_team_selection.html.haml'
+ - 'app/views/projects/mattermosts/new.html.haml'
+ - 'app/views/projects/merge_requests/_commits.html.haml'
+ - 'app/views/projects/merge_requests/_discussion.html.haml'
+ - 'app/views/projects/merge_requests/_how_to_merge.html.haml'
+ - 'app/views/projects/merge_requests/_merge_request.html.haml'
+ - 'app/views/projects/merge_requests/_mr_title.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/_commit_stats.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/_file_actions.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/_submit_form.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/components/_diff_file_editor.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/components/_inline_conflict_lines.html.haml'
+ - 'app/views/projects/merge_requests/conflicts/show.html.haml'
+ - 'app/views/projects/merge_requests/creations/_diffs.html.haml'
+ - 'app/views/projects/merge_requests/creations/_new_compare.html.haml'
+ - 'app/views/projects/merge_requests/creations/_new_submit.html.haml'
+ - 'app/views/projects/merge_requests/diffs/_different_base.html.haml'
+ - 'app/views/projects/merge_requests/diffs/_diffs.html.haml'
+ - 'app/views/projects/merge_requests/diffs/_version_controls.html.haml'
+ - 'app/views/projects/merge_requests/invalid.html.haml'
+ - 'app/views/projects/merge_requests/widget/open/_error.html.haml'
+ - 'app/views/projects/mirrors/_regenerate_public_ssh_key_confirm_modal.html.haml'
+ - 'app/views/projects/mirrors/_ssh_host_keys.html.haml'
+ - 'app/views/projects/new.html.haml'
+ - 'app/views/projects/no_repo.html.haml'
+ - 'app/views/projects/pages/_access.html.haml'
+ - 'app/views/projects/pages/_destroy.haml'
+ - 'app/views/projects/pages/_https_only.html.haml'
+ - 'app/views/projects/pages/_list.html.haml'
+ - 'app/views/projects/pages/_no_domains.html.haml'
+ - 'app/views/projects/pages/_use.html.haml'
+ - 'app/views/projects/pages/show.html.haml'
+ - 'app/views/projects/pipeline_schedules/_pipeline_schedule.html.haml'
+ - 'app/views/projects/pipelines/_info.html.haml'
+ - 'app/views/projects/pipelines/charts/_pipelines.haml'
+ - 'app/views/projects/protected_branches/shared/_branches_list.html.haml'
+ - 'app/views/projects/protected_branches/shared/_create_protected_branch.html.haml'
+ - 'app/views/projects/protected_branches/shared/_dropdown.html.haml'
+ - 'app/views/projects/protected_branches/shared/_index.html.haml'
+ - 'app/views/projects/protected_branches/shared/_matching_branch.html.haml'
+ - 'app/views/projects/protected_branches/shared/_protected_branch.html.haml'
+ - 'app/views/projects/protected_branches/show.html.haml'
+ - 'app/views/projects/protected_tags/shared/_create_protected_tag.html.haml'
+ - 'app/views/projects/protected_tags/shared/_dropdown.html.haml'
+ - 'app/views/projects/protected_tags/shared/_index.html.haml'
+ - 'app/views/projects/protected_tags/shared/_matching_tag.html.haml'
+ - 'app/views/projects/protected_tags/shared/_protected_tag.html.haml'
+ - 'app/views/projects/protected_tags/shared/_tags_list.html.haml'
+ - 'app/views/projects/protected_tags/show.html.haml'
+ - 'app/views/projects/registry/repositories/_tag.html.haml'
+ - 'app/views/projects/repositories/_feed.html.haml'
+ - 'app/views/projects/runners/_shared_runners.html.haml'
+ - 'app/views/projects/runners/edit.html.haml'
+ - 'app/views/projects/services/_form.html.haml'
+ - 'app/views/projects/services/_index.html.haml'
+ - 'app/views/projects/services/mattermost_slash_commands/_detailed_help.html.haml'
+ - 'app/views/projects/services/mattermost_slash_commands/_help.html.haml'
+ - 'app/views/projects/services/prometheus/_metrics.html.haml'
+ - 'app/views/projects/services/slack_slash_commands/_help.html.haml'
+ - 'app/views/projects/settings/ci_cd/_badge.html.haml'
+ - 'app/views/projects/settings/ci_cd/_form.html.haml'
+ - 'app/views/projects/stage/_stage.html.haml'
+ - 'app/views/projects/tags/index.html.haml'
+ - 'app/views/projects/tags/new.html.haml'
+ - 'app/views/projects/tags/releases/edit.html.haml'
+ - 'app/views/projects/tree/_tree_row.html.haml'
+ - 'app/views/projects/tree/_truncated_notice_tree_row.html.haml'
+ - 'app/views/projects/triggers/_content.html.haml'
+ - 'app/views/projects/triggers/_form.html.haml'
+ - 'app/views/projects/triggers/_index.html.haml'
+ - 'app/views/projects/triggers/_trigger.html.haml'
+ - 'app/views/projects/triggers/edit.html.haml'
+ - 'app/views/projects/wikis/_new.html.haml'
+ - 'app/views/projects/wikis/_pages_wiki_page.html.haml'
+ - 'app/views/projects/wikis/edit.html.haml'
+ - 'app/views/projects/wikis/history.html.haml'
+ - 'app/views/repository_check_mailer/notify.html.haml'
+ - 'app/views/search/_form.html.haml'
+ - 'app/views/search/results/_issue.html.haml'
+ - 'app/views/search/results/_note.html.haml'
+ - 'app/views/search/results/_snippet_blob.html.haml'
+ - 'app/views/search/results/_snippet_title.html.haml'
+ - 'app/views/shared/_auto_devops_implicitly_enabled_banner.html.haml'
+ - 'app/views/shared/_commit_message_container.html.haml'
+ - 'app/views/shared/_confirm_modal.html.haml'
+ - 'app/views/shared/_delete_label_modal.html.haml'
+ - 'app/views/shared/_group_form.html.haml'
+ - 'app/views/shared/_group_tips.html.haml'
+ - 'app/views/shared/_milestone_expired.html.haml'
+ - 'app/views/shared/_no_password.html.haml'
+ - 'app/views/shared/_no_ssh.html.haml'
+ - 'app/views/shared/_outdated_browser.html.haml'
+ - 'app/views/shared/_personal_access_tokens_created_container.html.haml'
+ - 'app/views/shared/_personal_access_tokens_table.html.haml'
+ - 'app/views/shared/_ping_consent.html.haml'
+ - 'app/views/shared/_project_limit.html.haml'
+ - 'app/views/shared/_service_settings.html.haml'
+ - 'app/views/shared/boards/components/_board.html.haml'
+ - 'app/views/shared/boards/components/_sidebar.html.haml'
+ - 'app/views/shared/boards/components/sidebar/_due_date.html.haml'
+ - 'app/views/shared/boards/components/sidebar/_labels.html.haml'
+ - 'app/views/shared/boards/components/sidebar/_milestone.html.haml'
+ - 'app/views/shared/empty_states/_priority_labels.html.haml'
+ - 'app/views/shared/hook_logs/_content.html.haml'
+ - 'app/views/shared/issuable/_assignees.html.haml'
+ - 'app/views/shared/issuable/_board_create_list_dropdown.html.haml'
+ - 'app/views/shared/issuable/_bulk_update_sidebar.html.haml'
+ - 'app/views/shared/issuable/_close_reopen_report_toggle.html.haml'
+ - 'app/views/shared/issuable/_form.html.haml'
+ - 'app/views/shared/issuable/_search_bar.html.haml'
+ - 'app/views/shared/issuable/_sidebar.html.haml'
+ - 'app/views/shared/issuable/form/_default_templates.html.haml'
+ - 'app/views/shared/issuable/form/_issue_assignee.html.haml'
+ - 'app/views/shared/issuable/form/_template_selector.html.haml'
+ - 'app/views/shared/issuable/form/_title.html.haml'
+ - 'app/views/shared/labels/_form.html.haml'
+ - 'app/views/shared/members/_member.html.haml'
+ - 'app/views/shared/milestones/_form_dates.html.haml'
+ - 'app/views/shared/milestones/_issuable.html.haml'
+ - 'app/views/shared/milestones/_milestone.html.haml'
+ - 'app/views/shared/milestones/_sidebar.html.haml'
+ - 'app/views/shared/milestones/_top.html.haml'
+ - 'app/views/shared/notes/_hints.html.haml'
+ - 'app/views/shared/notes/_note.html.haml'
+ - 'app/views/shared/notifications/_button.html.haml'
+ - 'app/views/shared/notifications/_custom_notifications.html.haml'
+ - 'app/views/shared/notifications/_new_button.html.haml'
+ - 'app/views/shared/notifications/_notification_dropdown.html.haml'
+ - 'app/views/shared/plugins/_index.html.haml'
+ - 'app/views/shared/projects/_dropdown.html.haml'
+ - 'app/views/shared/projects/_list.html.haml'
+ - 'app/views/shared/projects/_project.html.haml'
+ - 'app/views/shared/runners/_runner_description.html.haml'
+ - 'app/views/shared/runners/show.html.haml'
+ - 'app/views/shared/snippets/_embed.html.haml'
+ - 'app/views/shared/snippets/_header.html.haml'
+ - 'app/views/shared/snippets/_snippet.html.haml'
+ - 'app/views/shared/tokens/_scopes_list.html.haml'
+ - 'app/views/shared/web_hooks/_form.html.haml'
+ - 'app/views/shared/web_hooks/_test_button.html.haml'
+ - 'app/views/u2f/_authenticate.html.haml'
+ - 'app/views/u2f/_register.html.haml'
+ - 'app/views/users/_deletion_guidance.html.haml'
+ - 'ee/app/views/admin/_namespace_plan_info.html.haml'
+ - 'ee/app/views/admin/application_settings/_elasticsearch_form.html.haml'
+ - 'ee/app/views/admin/application_settings/_slack.html.haml'
+ - 'ee/app/views/admin/application_settings/_snowplow.html.haml'
+ - 'ee/app/views/admin/application_settings/_templates.html.haml'
+ - 'ee/app/views/admin/audit_logs/index.html.haml'
+ - 'ee/app/views/admin/dashboard/stats.html.haml'
+ - 'ee/app/views/admin/emails/show.html.haml'
+ - 'ee/app/views/admin/geo/nodes/edit.html.haml'
+ - 'ee/app/views/admin/geo/nodes/new.html.haml'
+ - 'ee/app/views/admin/geo/projects/_registry_failed.html.haml'
+ - 'ee/app/views/admin/geo/projects/_registry_never.html.haml'
+ - 'ee/app/views/admin/licenses/_breakdown.html.haml'
+ - 'ee/app/views/admin/licenses/_upload_trial_license.html.haml'
+ - 'ee/app/views/admin/licenses/missing.html.haml'
+ - 'ee/app/views/admin/licenses/new.html.haml'
+ - 'ee/app/views/admin/licenses/show.html.haml'
+ - 'ee/app/views/admin/monitoring/ee/_nav.html.haml'
+ - 'ee/app/views/admin/projects/_shared_runner_status.html.haml'
+ - 'ee/app/views/admin/push_rules/show.html.haml'
+ - 'ee/app/views/admin/users/_limits.html.haml'
+ - 'ee/app/views/admin/users/_user_detail_note.html.haml'
+ - 'ee/app/views/dashboard/projects/_blank_state_ee_trial.html.haml'
+ - 'ee/app/views/errors/kerberos_denied.html.haml'
+ - 'ee/app/views/groups/analytics/show.html.haml'
+ - 'ee/app/views/groups/audit_events/index.html.haml'
+ - 'ee/app/views/groups/ee/_settings_nav.html.haml'
+ - 'ee/app/views/groups/epics/_epic.html.haml'
+ - 'ee/app/views/groups/group_members/_ldap_sync.html.haml'
+ - 'ee/app/views/groups/group_members/_sync_button.html.haml'
+ - 'ee/app/views/groups/hooks/_project_hook.html.haml'
+ - 'ee/app/views/groups/hooks/index.html.haml'
+ - 'ee/app/views/groups/ldap_group_links/index.html.haml'
+ - 'ee/app/views/groups/pipeline_quota/index.html.haml'
+ - 'ee/app/views/jira_connect/subscriptions/index.html.haml'
+ - 'ee/app/views/layouts/jira_connect.html.haml'
+ - 'ee/app/views/layouts/nav/ee/_epic_link.html.haml'
+ - 'ee/app/views/layouts/nav/ee/admin/_new_monitoring_sidebar.html.haml'
+ - 'ee/app/views/layouts/service_desk.html.haml'
+ - 'ee/app/views/ldap_group_links/_form.html.haml'
+ - 'ee/app/views/ldap_group_links/_ldap_group_link.html.haml'
+ - 'ee/app/views/ldap_group_links/_ldap_group_links.html.haml'
+ - 'ee/app/views/ldap_group_links/_ldap_group_links_show.html.haml'
+ - 'ee/app/views/ldap_group_links/_ldap_group_links_synchronizations.html.haml'
+ - 'ee/app/views/namespaces/_shared_runner_status.html.haml'
+ - 'ee/app/views/namespaces/_shared_runners_minutes_setting.html.haml'
+ - 'ee/app/views/namespaces/pipelines_quota/_extra_shared_runners_minutes_quota.html.haml'
+ - 'ee/app/views/namespaces/pipelines_quota/_list.haml'
+ - 'ee/app/views/notify/approved_merge_request_email.html.haml'
+ - 'ee/app/views/notify/epic_status_changed_email.html.haml'
+ - 'ee/app/views/notify/issues_csv_email.html.haml'
+ - 'ee/app/views/notify/new_review_email.html.haml'
+ - 'ee/app/views/notify/prometheus_alert_fired_email.html.haml'
+ - 'ee/app/views/notify/send_admin_notification.html.haml'
+ - 'ee/app/views/notify/send_unsubscribed_notification.html.haml'
+ - 'ee/app/views/notify/unapproved_merge_request_email.html.haml'
+ - 'ee/app/views/oauth/geo_auth/error.html.haml'
+ - 'ee/app/views/profiles/pipeline_quota/index.haml'
+ - 'ee/app/views/projects/audit_events/index.html.haml'
+ - 'ee/app/views/projects/blob/_owners.html.haml'
+ - 'ee/app/views/projects/commits/_mirror_status.html.haml'
+ - 'ee/app/views/projects/feature_flags/_configure_feature_flags_modal.html.haml'
+ - 'ee/app/views/projects/issues/_issue_weight.html.haml'
+ - 'ee/app/views/projects/issues/_related_issues.html.haml'
+ - 'ee/app/views/projects/issues/export_csv/_modal.html.haml'
+ - 'ee/app/views/projects/jobs/_shared_runner_limit_warning.html.haml'
+ - 'ee/app/views/projects/merge_requests/_approvals_count.html.haml'
+ - 'ee/app/views/projects/merge_requests/widget/open/_geo.html.haml'
+ - 'ee/app/views/projects/mirrors/_mirrored_repositories_count.html.haml'
+ - 'ee/app/views/projects/protected_branches/ee/_create_protected_branch.html.haml'
+ - 'ee/app/views/projects/protected_branches/ee/_dropdown.html.haml'
+ - 'ee/app/views/projects/protected_branches/ee/_fallback_update_protected_branch.html.haml'
+ - 'ee/app/views/projects/protected_tags/_protected_tag_extra_create_access_levels.haml'
+ - 'ee/app/views/projects/protected_tags/ee/_create_protected_tag.html.haml'
+ - 'ee/app/views/projects/push_rules/_index.html.haml'
+ - 'ee/app/views/projects/services/gitlab_slack_application/_help.html.haml'
+ - 'ee/app/views/projects/services/gitlab_slack_application/_slack_integration_form.html.haml'
+ - 'ee/app/views/projects/services/prometheus/_metrics.html.haml'
+ - 'ee/app/views/projects/settings/slacks/edit.html.haml'
+ - 'ee/app/views/shared/_additional_email_text.html.haml'
+ - 'ee/app/views/shared/_geo_info_modal.html.haml'
+ - 'ee/app/views/shared/_mirror_update_button.html.haml'
+ - 'ee/app/views/shared/_shared_runners_minutes_limit.html.haml'
+ - 'ee/app/views/shared/audit_events/_event_table.html.haml'
+ - 'ee/app/views/shared/boards/components/_list_weight.html.haml'
+ - 'ee/app/views/shared/boards/components/sidebar/_epic.html.haml'
+ - 'ee/app/views/shared/ee/_import_form.html.haml'
+ - 'ee/app/views/shared/epic/_search_bar.html.haml'
+ - 'ee/app/views/shared/issuable/_approvals.html.haml'
+ - 'ee/app/views/shared/issuable/_board_create_list_dropdown.html.haml'
+ - 'ee/app/views/shared/issuable/_filter_weight.html.haml'
+ - 'ee/app/views/shared/issuable/_sidebar_item_epic.haml'
+ - 'ee/app/views/shared/members/ee/_ldap_tag.html.haml'
+ - 'ee/app/views/shared/members/ee/_override_member_buttons.html.haml'
+ - 'ee/app/views/shared/members/ee/_sso_badge.html.haml'
+ - 'ee/app/views/shared/milestones/_burndown.html.haml'
+ - 'ee/app/views/shared/milestones/_weight.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_audit_events.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_burndown_charts.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_csv_export.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_issue_weights.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_repository_features.html.haml'
+ - 'ee/app/views/shared/promotions/_promote_servicedesk.html.haml'
+ - 'ee/app/views/shared/push_rules/_form.html.haml'
+ - 'ee/app/views/unsubscribes/show.html.haml'
+ - 'ee/app/views/admin/users/_auditor_access_level_radio.html.haml'
+ - 'ee/app/views/admin/users/_auditor_user_badge.html.haml'
+ - 'ee/app/views/projects/protected_branches/_update_protected_branch.html.haml'
+ - 'ee/app/views/analytics/cycle_analytics/show.html.haml'
diff --git a/.mdlrc.style b/.mdlrc.style
index 0ca3611df0b..b315c99e3fc 100644
--- a/.mdlrc.style
+++ b/.mdlrc.style
@@ -5,12 +5,23 @@
# for more detailed information on the rules and styles.
rule "MD001"
+rule "MD002"
rule "MD003", :style => :atx
+rule "MD006"
+rule "MD010"
rule "MD011"
+rule "MD012"
+rule "MD019"
+rule "MD022"
rule "MD023"
+rule "MD025"
+rule "MD028"
+rule "MD029", :style => :one
+rule "MD030"
rule "MD032"
rule "MD034"
rule "MD037"
+rule "MD038"
# Should not be used currently:
diff --git a/.prettierignore b/.prettierignore
index dc9e572ab54..c9b945ac96d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -7,3 +7,4 @@
# ignore stylesheets for now as this clashes with our linter
*.css
*.scss
+*.md
diff --git a/.rubocop.yml b/.rubocop.yml
index e5fe527e611..b75c63e1f58 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,7 +8,7 @@ require:
- rubocop-rspec
AllCops:
- TargetRubyVersion: 2.5
+ TargetRubyVersion: 2.6
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
@@ -60,8 +60,8 @@ Style/FrozenStringLiteralComment:
RSpec/FilePath:
Exclude:
- 'qa/**/*'
- - 'spec/javascripts/fixtures/*'
- - 'ee/spec/javascripts/fixtures/*'
+ - 'spec/frontend/fixtures/*'
+ - 'ee/spec/frontend/fixtures/*'
- 'spec/requests/api/v3/*'
Naming/FileName:
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 41714eefa97..3898206e3b5 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -262,25 +262,6 @@ Naming/HeredocDelimiterNaming:
Naming/RescuedExceptionsVariableName:
Enabled: false
-# Offense count: 6
-# Cop supports --auto-correct.
-Performance/InefficientHashSearch:
- Exclude:
- - 'app/controllers/concerns/sessionless_authentication.rb'
- - 'app/models/note.rb'
- - 'app/models/user_preference.rb'
- - 'ee/app/models/ee/project.rb'
- - 'lib/gitlab/import_export/members_mapper.rb'
- - 'qa/spec/spec_helper.rb'
-
-# Offense count: 3
-# Cop supports --auto-correct.
-Performance/ReverseEach:
- Exclude:
- - 'app/models/commit.rb'
- - 'db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb'
- - 'lib/gitlab/profiler.rb'
-
# Offense count: 7081
# Configuration parameters: Prefixes.
# Prefixes: when, with, without
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0b73585722f..267a1caafec 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,346 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 12.1.5
+
+### Security (2 changes)
+
+- Upgrade Gitaly to 1.53.2 to prevent revision flag injection exploits.
+- Upgrade pages to 1.7.1 to prevent gitlab api token recovery from cookie.
+
+
+## 12.1.4
+
+### Fixed (3 changes, 1 of them is from the community)
+
+- Properly translate term in projects list. !30958
+- Add exclusive lease to mergeability check process. !31082
+- Fix Docker in Docker (DIND) listen port behavior change by adding DOCKER_TLS_CERTDIR in CI job templates. !31201 (Cameron Boulton)
+
+### Performance (1 change)
+
+- Improve job log rendering performance. !31262
+
+
+## 12.1.3
+
+### Fixed (11 changes)
+
+- Prevent multiple confirmation modals from opening when deleting a repository. !30532
+- Fix the project auto devops API. !30946
+- Fix "Certificate misses intermediates" UI error when enabling Let's Encrypt integration for pages domain. !30995
+- Fix xterm css not loading for environment terminal. !31023
+- Set DOCKER_TLS_CERTDIR in Auto Dev-Ops CI template to fix jobs using Docker-in-Docker. !31078
+- Set DOCKER_TLS_CERTDIR in CI job templates to fix Docker-in-Docker service. !31080
+- Support Docker OCI images. !31127
+- Fix error rendering submodules in MR diffs when there is no .gitmodules. !31162
+- Fix pdf.js rendering pages in the wrong order. !31222
+- Fix exception handling in Gitaly autodetection. !31285
+- Fix bug that caused diffs not to show on MRs with changes to submodules.
+
+### Performance (1 change)
+
+- Optimise import performance. !31045
+
+
+## 12.1.2
+
+### Security (1 change)
+
+- Use source project as permissions reference for MergeRequestsController#pipelines.
+
+### Security (9 changes)
+
+- Restrict slash commands to users who can log in.
+- Patch XSS issue in wiki links.
+- Queries for Upload should be scoped by model.
+- Filter merge request params on the new merge request page.
+- Fix Server Side Request Forgery mitigation bypass.
+- Show badges if pipelines are public otherwise default to project permissions.
+- Do not allow localhost url redirection in GitHub Integration.
+- Do not show moved issue id for users that cannot read issue.
+- Drop feature to take ownership of trigger token.
+
+
+## 12.1.1
+
+- No changes.
+
+## 12.1.0
+
+### Security (11 changes, 2 of them are from the community)
+
+- Update tar to 2.2.2. !29949 (Takuya Noguchi)
+- Update lodash to 4.7.14 and lodash.mergewith to 4.6.2. !30602 (Takuya Noguchi)
+- Correctly check permissions when creating snippet notes.
+- Gate MR head_pipeline behind read_pipeline ability.
+- Prevent Billion Laughs attack.
+- Add missing authorizations in GraphQL.
+- Fix Denial of Service for comments when rendering issues/MR comments.
+- Expose merge requests count based on user access.
+- Fix DoS vulnerability in color validation regex.
+- Prevent the detection of merge request templates by unauthorized users.
+- Persist tmp snippet uploads at users.
+
+### Removed (7 changes)
+
+- Disable Kubernetes credential passthrough for managed project-level clusters. !29262
+- Remove deprecated group routes. !29351
+- Remove support for creating non-RBAC kubernetes clusters. !29614
+- Remove Kubernetes service integration and Kubernetes service template from available deployment platforms. !29786
+- Remove MySQL support. !29790
+- Remove depreated /u/:username routing. !30044
+- Remove support for legacy pipeline triggers. !30133
+
+### Fixed (84 changes, 14 of them are from the community)
+
+- Update a user's routes after updating their name. !23272
+- Show poper panel when validation error occurs in admin settings panels. !25434
+- Expect bytes from Gitaly RPC GetRawChanges. !28164
+- Sanitize LDAP output in Rake tasks. !28427
+- Left align mr widget icons and text. !28561
+- Keep the empty folders in the tree. !29196
+- Fix incorrect emoji placement in commit diff discussion. !29445
+- Fix favicon path with uploads of object store. !29482 (Roger Meier)
+- Remove duplicate trailing +/- char in merge request discussions. !29518
+- Fix the signup form's username validation messages not displaying. !29678 (Jiaan Louw)
+- Fix broken environment selector and always display it on monitoring dashboard. !29705
+- Fix Container Scanning job timeout when using the kubernetes executor. !29706
+- Look for new branches more carefully. !29761
+- Fix nested lists unnecessary margin. !29775 (Kuba Kopeć)
+- Fix reports jobs timing out because of cache. !29780
+- Fix Double Border in Profile Page. !29784 (Yoginth <@yo>)
+- Remove minimum character limits for fuzzy searches when using a CTE. !29810
+- Set default sort method for dashboard projects list. !29830 (David Palubin)
+- Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option. !29836 (Nikolay Novikov, Raphael Tweitmann)
+- Fix pipeline schedule does not run correctly when it's scheduled at the same time with the cron worker. !29848
+- Always shows author of created issue/started discussion/comment in HTML body and text of email. !29886 (Frank van Rest)
+- Build correct basenames for title search results. !29898
+- Resolve "500 error when forking via the web IDE button". !29909
+- Turn commit sha in monitor charts popover to link. !29914
+- Fix broken URLs for uploads with a plus in the filename. !29915
+- Retry fetching Kubernetes Secret#token (#63507). !29922
+- Enforce presence of pipeline when "Pipeline must succeed" project setting is enabled. !29926
+- Fix unresponsive reply button in discussions. !29936
+- Allow asynchronous rebase operations to be monitored. !29940
+- Resolve Avatar in Please sign in pattern too large. !29944
+- Persist the cluster a deployment was deployed to. !29960
+- Fix runner tags search dropdown being empty when there are tags. !29985
+- Display the correct amount of projects being migrated/rolled-back to Hashed Storage when specifying ranges. !29996
+- Resolve Environment details header border misaligned. !30011
+- Correct link to docs for External Dashboard. !30019
+- Fix Jupyter-Git integration. !30020 (Amit Rathi)
+- Update Mermaid to 8.1.0. !30036
+- Fix background migrations failing with unused replication slot. !30042
+- Disable Rails SQL query cache when applying service templates. !30060
+- Set higher TTL for write lock of trace to prevent concurrent archiving. !30064
+- Fix charts on Cluster health page. !30073
+- Display boards filter bar on mobile. !30120
+- Fix IDE editor not showing when switching back from preview. !30135
+- Support note position tracing on an image. !30158
+- Replace slugifyWithHyphens with improved slugify function. !30172 (Luke Ward)
+- 'Open' and 'Closed' issue board lists no longer display a redundant tooltip. !30187
+- Fix pipelines table to update without refreshing after action. !30190
+- Change ruby_process_start_time_seconds metric to unix timestamp instead of seconds from boot. !30195
+- Fix attachments using the wrong URLs in e-mails. !30197
+- Make sure UnicornSampler is started only in master process. !30215
+- Don't show image diff note on text file. !30221
+- Fix median counting for cycle analytics. !30229
+- In WebIDE allow adding new entries of the same name as deleted entry. !30239
+- Don't let logged out user do manual order. !30264
+- Skip spam check for task list updates. !30279
+- Make Housekeeping button do a full garbage collection. !30289
+- Removing an image should not output binary data. !30314
+- Fix spacing issues for toasts. !30345
+- Fix race in forbid_sidekiq_in_transactions.rb. !30359
+- Fixed back navigation for projects filter. !30373
+- Fix environments broken terminal. !30401
+- Fix invalid SSL certificate errors on Drone CI service. !30422
+- Fix subgroup url in search drop down. !30457
+- Make unicorn_workers to return meaningful results. !30506
+- Fix wrong URL when creating milestones from instance milestones dashboard. !30512
+- Fixed incorrect line wrap for assignee label in issues. !30523 (Marc Schwede)
+- Improves section header whitespace on the CI/CD Charts page. !30531
+- Prevent multiple confirmation modals from opening when deleting a repository. !30532
+- Aligns CI icon in Merge Request dashboard. !30558
+- Add text-secondary to controls in project list. !30567
+- Review Tools: Add large z-index to toolbar. !30583
+- Hide restricted and disallowed visibility radios. !30590
+- Resolve Label picker: Line break on long label titles. !30610
+- Fix a bug that prevented projects containing merge request diff comments from being imported. !30630
+- I fixed z index bug in diff page. !30657 (Faruk Can)
+- Allow client authentication method to be configured for OpenID Connect. !30683 (Vincent Fazio)
+- Fix commenting before discussions are loaded. !30724
+- Fix linebreak rendering in Mermaid flowcharts. !30730
+- Make httpclient respect system SSL configuration. !30749
+- Bump fog-aws to v3.5.2. !30803
+- API: Allow changing only ci_default_git_depth. !30888 (Mathieu Parent)
+- Search issuables by iids. (Riccardo Padovani)
+- Fix broken warnings while Editing Issues and Edit File on MR.
+- Make sure we are receiving the proper information on the MR Popover by updating the IID in the graphql query.
+
+### Changed (39 changes, 8 of them are from the community)
+
+- Improve group list UI. !26542
+- Backport and Docs for Paginate license management and add license search. !27602
+- Update merge requests section description text on project settings page. !27838
+- Knative version bump 0.5 -> 0.6. !28798 (Chris Baumbauer)
+- Add salesforce logo for salesforce SSO. !28857
+- Enforced requirements for UltraAuth users. !28941 (Kartikey Tanna)
+- Return 400 when deleting tags more often than once per hour. !29448
+- Add identity information to external authorization requests. !29461
+- Enable just-in-time Kubernetes resource creation for project-level clusters. !29515
+- renamed discussion to thread in merge-request and issue timeline. !29553 (Michel Engelen)
+- Changed HTTP Status Code for disabled repository on /branches and /commits to 404. !29585 (Sam Battalio)
+- Enable Git object pools. !29595 (jramsay)
+- Updated container registry to display error message when special characters in path. Documentation has also been updated. !29616
+- Allow developers to delete tags. !29668
+- Will not update issue timestamps when changing positions in a list. !29677
+- Include a link back to the MR for Visual Review feedback form. !29719
+- Improve discussion reply buttons layout and how jump to next discussion button appears. !29779
+- Renders a pre-release tag for releases. !29797
+- Migrate NULL values for users.private_profile column and update users API to reject null value for private_profile. !29888
+- Re-name files in Web IDE in a more natural way. !29948
+- Include events from subgroups in group's activity. !29953 (Fabian Schneider @fabsrc)
+- Upgrade to Gitaly v1.49.0. !29990
+- Remove group and instance clusters feature flag. !30124
+- Add support for creating random passwords in user creation API. !30138
+- Support CIDR notation in IP rate limiter. !30146
+- Add Redis call details in Peek performance bar. !30191
+- Create Knative role and binding with service account. !30235
+- Add cleanup migration for MR's multiple assignees. !30261
+- Updates PHP template to php:latest to ensure always targeting latest stable. !30319 (Paul Giberson)
+- Format `from` and `to` fields in JSON audit log. !30333
+- Upgrade to Gitaly v1.51.0. !30353
+- Modify cycle analytics on project level. !30356
+- Extract clair version as CLAIR_EXECUTABLE_VERSION variable and update clair executable from v8 to v11. !30396
+- Upgrade Rouge to 3.5.1. !30431
+- Move multiple issue boards to core. !30503
+- Upgrade to Gitaly v1.52.0. !30568
+- Upgrade to Gitaly v1.53.0. !30614
+- Open WebIDE in fork when user doesn't have access. !30642
+- Propagate python version variable. (Can Eldem)
+
+### Performance (25 changes, 1 of them is from the community)
+
+- Remove tooltip directive on project avatar image component. !29631 (George Tsiolis)
+- Use Rugged if we detect storage is NFS and we can access the disk. !29725
+- Add endpoint for fetching diverging commit counts. !29802
+- Cache feature flag names in Redis for a minute. !29816
+- Avoid storing backtraces from Bitbucket Cloud imports in the database. !29862
+- Remove import columns from projects table. !29863
+- Enable Gitaly ref name caching for discussions.json. !29951
+- Allow caching of negative FindCommit matches. !29952
+- Eliminate N+1 queries in Dashboard::TodosController. !29954
+- Memoize non-existent custom appearances. !29957
+- Add a separate endpoint for fetching MRs serialized as widgets. !29979
+- Use CTE to fetch clusters hierarchy in single query. !30063
+- Enable Gitaly ref caching for SearchController. !30105
+- Avoid loading pipeline status in search results. !30111
+- Improve performance of MergeRequestsController#ci_environment_status endpoint. !30224
+- Add a memory cache local to the thread to reduce Redis load. !30233
+- Cache Flipper persisted names directly to local memory storage. !30265
+- Limit amount of JUnit tests returned. !30274
+- Cache Flipper feature flags in L1 and L2 caches. !30276
+- Prevent amplification of ReactiveCachingWorker jobs upon failures. !30432
+- Allow ReactiveCaching to support nil value. !30456
+- Improve performance of fetching environments statuses. !30560
+- Do Redis lookup in batches in ActiveSession.sessions_from_ids. !30561
+- Remove catfile cache feature flag. !30750
+- Fix Gitaly auto-detection caching. !30954
+
+### Added (46 changes, 12 of them are from the community)
+
+- Document the negative commit message push rule for the API. !14004 (Maikel Vlasman)
+- Expose saml_provider_id in the users API. !14045
+- Improve Project API. !28327 (Mathieu Parent)
+- Remove Sentry from application settings. !28447 (Roger Meier)
+- Implement borderless discussion design with new reply field. !28580
+- Enable terminals for instance and group clusters. !28613
+- Resolve Multiple discussions per line in merge request diffs. !28748
+- Adds link to Grafana in Admin > Monitoring settings when grafana is enabled in config. !28937 (Romain Maneschi)
+- Bring Manual Ordering on Issue List. !29410
+- Added commit type to tree GraphQL response. !29412
+- New API for User Counts, updates on success of an MR the count on top and in other tabs. !29441
+- Add option to limit time tracking units to hours. !29469 (Jon Kolb)
+- Add confirmation for registry image deletion. !29505
+- Sync merge ref upon mergeability check. !29569
+- Show an Upcoming Status for Releases. !29577
+- Add order_by and sort params to list runner jobs api. !29629 (Sujay Patel)
+- Allow custom username for deploy tokens. !29639
+- Add a verified pill next to email addresses under the admin users section. !29669
+- Add rake task to clean orphan artifact files. !29681
+- Render GFM in GraphQL. !29700
+- Upgrade asciidoctor version to 2.0.10. !29741 (Rajendra Kadam)
+- Allow auto-completing scoped labels. !29749
+- Enable syntax highlighting for AsciiDoc. !29835 (Guillaume Grossetie)
+- Expose placeholder element for metrics charts in GFM. !29861
+- Added a min schema version check to db:migrate. !29882
+- Extract zoom link from issue and pass to frontend. !29910 (raju249)
+- GraphQL mutations for add, remove and toggle emoji. !29919
+- Labeled issue boards can now collapse. !29955
+- Allow Ingress to be uninstalled from the UI. !29977
+- Add permission check to metrics dashboards endpoint. !30017
+- Allow JupyterHub to be uninstalled from the UI. !30097
+- Allow GitLab Runner to be uninstalled from the UI. !30176
+- GraphQL mutations for managing Notes. !30210
+- Add API for CRUD group clusters. !30213
+- Add endpoint to move multiple issues in boards. !30216
+- Enable terminals button for group clusters. !30255
+- Prevent excessive sanitization of AsciiDoc ouptut. !30290 (Guillaume Grossetie)
+- Extend `MergeToRefService` to create merge ref from an arbitrary ref. !30361
+- Add CI variable to provide GitLab HOST. !30417
+- Add migration for adding rule_type to approval_project_rules. !30575
+- Enable section anchors in Asciidoctor. !30666 (Guillaume Grossetie)
+- Preserve footnote link ids in Asciidoctor. !30790 (Guillaume Grossetie)
+- Add support for generating SSL certificates for custon pages domains through Let's Encrypt.
+- Introduce default: for gitlab-ci.yml.
+- Move Multiple Issue Boards for Projects to Core.
+- Add Gitaly data to the usage ping.
+
+### Other (35 changes, 15 of them are from the community)
+
+- Remove unresolved class and fixed height in discussion header. !28440 (David Palubin)
+- Moved EE/CE code differences for file `app/views/search/_category.html.haml` into CE. !28755 (Michel Engelen)
+- Changes "Todo" to "To Do" in the UI for clarity. !28844
+- Migrate GitLab managed project-level clusters to unmanaged if a Kubernetes namespace was unable to be created. !29251
+- Migrate GitLab managed project-level clusters to unmanaged if they are missing a Kubernetes service account token. !29648
+- Add strategies column to operations_feature_flag_scopes table. !29808
+- Disallow `NULL` values for `geo_nodes.primary` column. !29818 (Arun Kumar Mohan)
+- Replace 'JIRA' with 'Jira'. !29849 (Takuya Noguchi)
+- Support jsonb default in add_column_with_default migration helper. !29871
+- Update pagination prev and next texts. !29911
+- Adds metrics to measure cost of expensive operations. !29928
+- Always allow access to health endpoints from localhost in dev. !29930
+- Update GitLab Runner Helm Chart to 0.6.0. !29982
+- Use darker gray color for system note metadata and edited text. !30054
+- Fix typo in docs about Elasticsearch. !30162 (Takuya Noguchi)
+- Fix typo in code comments about Elasticsearch. !30163 (Takuya Noguchi)
+- Update mixin-deep to 1.3.2. !30223 (Takuya Noguchi)
+- Migrate markdown header_spec.js to Jest. !30228 (Martin Hobert)
+- Remove istanbul JavaScript package. !30232 (Takuya Noguchi)
+- Centralize markdownlint configuration. !30263
+- Use PostgreSQL 9.6.11 in CI tests. !30270 (Takuya Noguchi)
+- Fix typo in updateResolvableDiscussionsCounts action. !30278 (Frank van Rest)
+- Change color for namespace in commit search. !30312
+- Remove applySuggestion from notes service. !30399 (Frank van Rest)
+- Improved readability of storage statistics in group / project admin area. !30406
+- Alignign empty container registry message with design guidelines. !30502
+- Remove toggleAward from notes service. !30536 (Frank van Rest)
+- Remove deleteNote from notes service. !30537 (Frank van Rest)
+- change the use of boardService in favor of boardsStore on footer for the board component. !30616 (eduarmreyes)
+- Update example Prometheus scrape config. !30739
+- Update GitLab Pages to v1.7.0.
+- Add token_encrypted column to operations_feature_flags_clients table.
+- Removes EE diff for app/views/profiles/preferences/show.html.haml.
+- Removes EE differences for app/views/layouts/fullscreen.html.haml.
+- Removes EE differences for app/views/admin/users/show.html.haml.
+
+
+## 12.0.6
+
+- No changes.
+
## 12.0.3 (2019-06-27)
- No changes.
@@ -350,6 +690,29 @@ entry.
- Moves snowplow to CE repo.
+## 11.11.8
+
+### Security (2 changes)
+
+- Upgrade Gitaly to 1.42.7 to prevent revision flag injection exploits.
+- Upgrade pages to 1.5.1 to prevent gitlab api token recovery from cookie.
+
+
+## 11.11.7
+
+### Security (9 changes)
+
+- Restrict slash commands to users who can log in.
+- Patch XSS issue in wiki links.
+- Filter merge request params on the new merge request page.
+- Fix Server Side Request Forgery mitigation bypass.
+- Show badges if pipelines are public otherwise default to project permissions.
+- Do not allow localhost url redirection in GitHub Integration.
+- Do not show moved issue id for users that cannot read issue.
+- Use source project as permissions reference for MergeRequestsController#pipelines.
+- Drop feature to take ownership of trigger token.
+
+
## 11.11.4 (2019-06-26)
### Fixed (3 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 3f4830156cb..bb120e876c6 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.53.0
+1.59.0
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index bd8bf882d06..943f9cbc4ec 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.7.0
+1.7.1
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/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION
index df5119ec64e..3b6825376ad 100644
--- a/GITLAB_WORKHORSE_VERSION
+++ b/GITLAB_WORKHORSE_VERSION
@@ -1 +1 @@
-8.7.0
+8.8.0
diff --git a/Gemfile b/Gemfile
index 8bffc2a973d..116c94bdaf8 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,8 @@ source 'https://rubygems.org'
gem 'rails', '5.2.3'
+gem 'bootsnap', '~> 1.4'
+
# Improves copy-on-write performance for MRI
gem 'nakayoshi_fork', '~> 0.0.4'
@@ -14,8 +16,7 @@ gem 'sprockets', '~> 3.7.0'
gem 'default_value_for', '~> 3.2.0'
# Supported DBs
-gem 'mysql2', '~> 0.4.10', group: :mysql
-gem 'pg', '~> 1.1', group: :postgres
+gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.1'
@@ -50,6 +51,7 @@ gem 'jwt', '~> 2.1.0'
# Spam and anti-bot protection
gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
+gem 'invisible_captcha', '~> 0.12.1'
# Two-factor authentication
gem 'devise-two-factor', '~> 3.0.0'
@@ -133,10 +135,10 @@ gem 'wikicloth', '0.8.1'
gem 'asciidoctor', '~> 2.0.10'
gem 'asciidoctor-include-ext', '~> 0.3.1', require: false
gem 'asciidoctor-plantuml', '0.0.9'
-gem 'rouge', '~> 3.5'
+gem 'rouge', '~> 3.7'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
-gem 'nokogiri', '~> 1.10.3'
+gem 'nokogiri', '~> 1.10.4'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -169,7 +171,7 @@ gem 'acts-as-taggable-on', '~> 6.0'
gem 'sidekiq', '~> 5.2.7'
gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
-gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
+gem 'gitlab-sidekiq-fetcher', '0.5.1', require: 'sidekiq-reliable-fetch'
# Cron Parser
gem 'fugit', '~> 1.2.1'
@@ -199,13 +201,13 @@ gem 'js_regex', '~> 3.1'
# User agent parsing
gem 'device_detector'
-# Cache
-gem 'redis-rails', '~> 5.0.2'
-
# Redis
-gem 'redis', '~> 3.2'
+gem 'redis', '~> 4.0'
gem 'connection_pool', '~> 2.0'
+# Redis session store
+gem 'redis-rails', '~> 5.0.2'
+
# Discord integration
gem 'discordrb-webhooks-blackst0ne', '~> 3.3', require: false
@@ -273,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
@@ -282,7 +283,7 @@ gem 'sentry-raven', '~> 2.9'
gem 'premailer-rails', '~> 1.9.7'
# LabKit: Tracing and Correlation
-gem 'gitlab-labkit', '~> 0.3.0'
+gem 'gitlab-labkit', '~> 0.4.2'
# I18n
gem 'ruby_parser', '~> 3.8', require: false
@@ -295,11 +296,9 @@ gem 'batch-loader', '~> 1.4.0'
# Perf bar
gem 'peek', '~> 1.0.1'
-gem 'peek-gc', '~> 0.0.2'
-gem 'peek-mysql2', '~> 1.2.0', group: :mysql
-gem 'peek-pg', '~> 1.3.0', group: :postgres
-gem 'peek-rblineprof', '~> 0.2.0'
-gem 'peek-redis', '~> 1.2.0'
+
+# Snowplow events tracking
+gem 'snowplow-tracker', '~> 0.6.1'
# Memory benchmarks
gem 'derailed_benchmarks', require: false
@@ -330,7 +329,6 @@ group :development do
end
group :development, :test do
- gem 'bootsnap', '~> 1.4'
gem 'bullet', '~> 5.5.0', require: !!ENV['ENABLE_BULLET']
gem 'pry-byebug', '~> 3.5.1', platform: :mri
gem 'pry-rails', '~> 0.3.4'
@@ -376,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
@@ -391,7 +387,6 @@ group :test do
gem 'json-schema', '~> 2.8.0'
gem 'webmock', '~> 3.5.1'
gem 'rails-controller-testing'
- gem 'sham_rack', '~> 1.3.6'
gem 'concurrent-ruby', '~> 1.1'
gem 'test-prof', '~> 0.2.5'
gem 'rspec_junit_formatter'
@@ -430,8 +425,8 @@ group :ed25519 do
gem 'bcrypt_pbkdf', '~> 1.0'
end
-# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.37.0', require: 'gitaly'
+# Gitaly GRPC protocol definitions
+gem 'gitaly', '~> 1.58.0'
gem 'grpc', '~> 1.19.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 60939ae918c..0ecf3aa2840 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -41,8 +41,6 @@ GEM
activerecord-explain-analyze (0.1.0)
activerecord (>= 4)
pg
- activerecord_sane_schema_dumper (1.0)
- rails (>= 5, < 6)
activestorage (5.2.3)
actionpack (= 5.2.3)
activerecord (= 5.2.3)
@@ -76,7 +74,6 @@ GEM
asciidoctor-plantuml (0.0.9)
asciidoctor (>= 1.5.6, < 3.0.0)
ast (2.4.0)
- atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
attr_required (1.0.1)
@@ -101,7 +98,7 @@ GEM
binding_ninja (0.2.3)
binding_of_caller (0.8.0)
debug_inspector (>= 0.0.1)
- bootsnap (1.4.1)
+ bootsnap (1.4.4)
msgpack (~> 1.0)
bootstrap_form (4.2.0)
actionpack (>= 5.0)
@@ -153,6 +150,7 @@ GEM
concurrent-ruby-ext (1.1.5)
concurrent-ruby (= 1.1.5)
connection_pool (2.2.2)
+ contracts (0.11.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
@@ -237,7 +235,7 @@ GEM
fast_blank (1.0.0)
fast_gettext (1.6.0)
ffaker (2.10.0)
- ffi (1.10.0)
+ ffi (1.11.1)
flipper (0.13.0)
flipper-active_record (0.13.0)
activerecord (>= 3.2, < 6)
@@ -310,19 +308,19 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.37.0)
+ gitaly (1.58.0)
grpc (~> 1.0)
github-markup (1.7.0)
- gitlab-labkit (0.3.0)
+ gitlab-labkit (0.4.2)
actionpack (~> 5)
activesupport (~> 5)
- grpc (~> 1.19.0)
+ grpc (~> 1.19)
jaeger-client (~> 0.10)
opentracing (~> 0.4)
gitlab-markup (1.7.0)
- gitlab-sidekiq-fetcher (0.4.0)
+ gitlab-sidekiq-fetcher (0.5.1)
sidekiq (~> 5)
- gitlab-styles (2.7.0)
+ gitlab-styles (2.8.0)
rubocop (~> 0.69.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-performance (~> 1.1.0)
@@ -438,11 +436,13 @@ GEM
influxdb (0.2.3)
cause
json
+ invisible_captcha (0.12.1)
+ rails (>= 3.2.0)
ipaddress (0.8.3)
jaeger-client (0.10.0)
opentracing (~> 0.3)
thrift
- jaro_winkler (1.5.2)
+ jaro_winkler (1.5.3)
jira-ruby (1.4.1)
activesupport
multipart-post
@@ -523,27 +523,26 @@ GEM
mime-types-data (~> 3.2015)
mime-types-data (3.2019.0331)
mimemagic (0.3.2)
- mini_magick (4.8.0)
+ mini_magick (4.9.5)
mini_mime (1.0.1)
mini_portile2 (2.4.0)
minitest (5.11.3)
mixlib-cli (1.7.0)
mixlib-config (2.2.18)
tomlrb
- msgpack (1.2.10)
+ msgpack (1.3.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
mustermann (1.0.3)
mustermann-grape (1.0.0)
mustermann (~> 1.0.0)
- mysql2 (0.4.10)
nakayoshi_fork (0.0.4)
net-ldap (0.16.0)
net-ssh (5.2.0)
netrc (0.11.0)
nio4r (2.3.1)
- nokogiri (1.10.3)
+ nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0)
nokogiri
@@ -643,25 +642,6 @@ GEM
concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0)
railties (>= 4.0.0)
- peek-gc (0.0.2)
- peek
- peek-mysql2 (1.2.0)
- concurrent-ruby
- concurrent-ruby-ext
- mysql2
- peek
- peek-pg (1.3.0)
- concurrent-ruby
- concurrent-ruby-ext
- peek
- pg
- peek-rblineprof (0.2.0)
- peek
- rblineprof
- peek-redis (1.2.0)
- atomic (>= 1.0.0)
- peek
- redis
pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
@@ -760,17 +740,17 @@ GEM
recaptcha (4.13.1)
json
recursive-open-struct (1.1.0)
- redis (3.3.5)
+ redis (4.1.2)
redis-actionpack (5.0.2)
actionpack (>= 4.0, < 6)
redis-rack (>= 1, < 3)
redis-store (>= 1.1.0, < 2)
- redis-activesupport (5.0.4)
+ redis-activesupport (5.0.7)
activesupport (>= 3, < 6)
redis-store (>= 1.3, < 2)
redis-namespace (1.6.0)
redis (>= 3.0.4)
- redis-rack (2.0.4)
+ redis-rack (2.0.5)
rack (>= 1.5, < 3)
redis-store (>= 1.2, < 2)
redis-rails (5.0.2)
@@ -796,7 +776,7 @@ GEM
retriable (3.1.2)
rinku (2.0.0)
rotp (2.1.2)
- rouge (3.5.1)
+ rouge (3.7.0)
rqrcode (0.7.0)
chunky_png
rqrcode-rails3 (0.1.7)
@@ -856,7 +836,7 @@ GEM
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.17.0)
- ruby-progressbar (1.10.0)
+ ruby-progressbar (1.10.1)
ruby-saml (1.7.2)
nokogiri (>= 1.5.10)
ruby_parser (3.13.1)
@@ -864,7 +844,7 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rugged (0.28.1)
+ rugged (0.28.3.1)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -900,8 +880,6 @@ GEM
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.12.0)
- sham_rack (1.3.6)
- rack
shoulda-matchers (4.0.1)
activesupport (>= 4.2.0)
sidekiq (5.2.7)
@@ -924,6 +902,8 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-notifier (1.5.1)
+ snowplow-tracker (0.6.1)
+ contracts (~> 0.7, <= 0.11)
spring (2.0.2)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
@@ -1046,7 +1026,6 @@ DEPENDENCIES
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.2)
activerecord-explain-analyze (~> 0.1)
- activerecord_sane_schema_dumper (= 1.0)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
@@ -1119,11 +1098,11 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.37.0)
+ gitaly (~> 1.58.0)
github-markup (~> 1.7.0)
- gitlab-labkit (~> 0.3.0)
+ gitlab-labkit (~> 0.4.2)
gitlab-markup (~> 1.7.0)
- gitlab-sidekiq-fetcher (~> 0.4.0)
+ gitlab-sidekiq-fetcher (= 0.5.1)
gitlab-styles (~> 2.7)
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
@@ -1149,6 +1128,7 @@ DEPENDENCIES
httparty (~> 0.16.4)
icalendar
influxdb (~> 0.2)
+ invisible_captcha (~> 0.12.1)
jira-ruby (~> 1.4)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
@@ -1168,11 +1148,10 @@ DEPENDENCIES
mimemagic (~> 0.3.2)
mini_magick
minitest (~> 5.11.0)
- mysql2 (~> 0.4.10)
nakayoshi_fork (~> 0.0.4)
net-ldap
net-ssh (~> 5.2)
- nokogiri (~> 1.10.3)
+ nokogiri (~> 1.10.4)
oauth2 (~> 1.4)
octokit (~> 4.9)
omniauth (~> 1.8)
@@ -1195,11 +1174,6 @@ DEPENDENCIES
omniauth_openid_connect (~> 0.3.1)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
- peek-gc (~> 0.0.2)
- peek-mysql2 (~> 1.2.0)
- peek-pg (~> 1.3.0)
- peek-rblineprof (~> 0.2.0)
- peek-redis (~> 1.2.0)
pg (~> 1.1)
premailer-rails (~> 1.9.7)
prometheus-client-mmap (~> 0.9.8)
@@ -1223,12 +1197,12 @@ DEPENDENCIES
rdoc (~> 6.0)
re2 (~> 1.1.1)
recaptcha (~> 4.11)
- redis (~> 3.2)
+ redis (~> 4.0)
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
request_store (~> 1.3)
responders (~> 2.0)
- rouge (~> 3.5)
+ rouge (~> 3.7)
rqrcode-rails3 (~> 0.1.7)
rspec-parameterized
rspec-rails (~> 3.8.0)
@@ -1252,13 +1226,13 @@ DEPENDENCIES
selenium-webdriver (~> 3.141)
sentry-raven (~> 2.9)
settingslogic (~> 2.0.9)
- sham_rack (~> 1.3.6)
shoulda-matchers (~> 4.0.1)
sidekiq (~> 5.2.7)
sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1)
+ snowplow-tracker (~> 0.6.1)
spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4)
sprockets (~> 3.7.0)
@@ -1278,7 +1252,6 @@ DEPENDENCIES
unicorn-worker-killer (~> 0.4.4)
validates_hostname (~> 1.0.6)
version_sorter (~> 2.2.4)
- virtus (~> 1.0.1)
vmstat (~> 2.3.0)
webmock (~> 3.5.1)
webpack-rails (~> 0.9.10)
diff --git a/LICENSE b/LICENSE
index a90ea939517..216125d93c7 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,12 @@
-Copyright GitLab B.V.
+Copyright (c) 2011-present GitLab B.V.
+
+Portions of this software are licensed as follows:
+
+* All content residing under the "doc/" directory of this repository is licensed under "Creative Commons: CC BY-SA 4.0 license".
+* All content that resides under the "ee/" directory of this repository, if that directory exists, is licensed under the license defined in "ee/LICENSE".
+* All client-side JavaScript (when served directly or after being compiled, arranged, augmented, or combined), is licensed under the "MIT Expat" license.
+* All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
+* Content outside of the above mentioned directories or restrictions above is available under the "MIT Expat" license as defined below.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 51dc87ac653..054e2d02461 100644
--- a/README.md
+++ b/README.md
@@ -20,10 +20,6 @@ To see how GitLab looks please see the [features page on our website](https://ab
- Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises
- Completely free and open source (MIT Expat license)
-## Hiring
-
-We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/).
-
## Editions
There are two editions of GitLab:
@@ -31,6 +27,15 @@ There are two editions of GitLab:
- GitLab Community Edition (CE) is available freely under the MIT Expat license.
- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/pricing/#compare-options) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
+## Licensing
+
+See the [LICENSE](LICENSE) file for licensing information as it pertains to
+files in this repository.
+
+## Hiring
+
+We're hiring developers, support people, and production engineers all the time, please see our [jobs page](https://about.gitlab.com/jobs/).
+
## Website
On [about.gitlab.com](https://about.gitlab.com/) you can find more information about:
@@ -58,14 +63,6 @@ There are various other options to install GitLab, please refer to the [installa
GitLab is an open source project and we are very happy to accept community contributions. Please refer to [Contributing to GitLab page](https://about.gitlab.com/contributing/) for more details.
-## Licensing
-
-GitLab Community Edition (CE) is available freely under the MIT Expat license.
-
-All third party components incorporated into the GitLab Software are licensed under the original license provided by the owner of the applicable component.
-
-All Documentation content that resides under the `doc/` directory of this repository is licensed under Creative Commons: CC BY-SA 4.0.
-
## Install a development environment
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
@@ -81,7 +78,7 @@ Instructions on how to start GitLab and how to run the tests can be found in the
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL/OpenSUSE
-- Ruby (MRI) 2.4
+- Ruby (MRI) 2.6.3
- Git 2.8.4+
- Redis 2.8+
- PostgreSQL (preferred) or MySQL
diff --git a/VERSION b/VERSION
index 6edc7fa8d35..4dd919b3b06 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-12.1.0-pre
+12.2.0-pre
diff --git a/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js
new file mode 100644
index 00000000000..ff8b4c56321
--- /dev/null
+++ b/app/assets/javascripts/analytics/cycle_analytics/mixins/filter_mixins.js
@@ -0,0 +1 @@
+export default {};
diff --git a/app/assets/javascripts/badges/components/badge.vue b/app/assets/javascripts/badges/components/badge.vue
index ae2916e3a3b..eb720f5380b 100644
--- a/app/assets/javascripts/badges/components/badge.vue
+++ b/app/assets/javascripts/badges/components/badge.vue
@@ -3,6 +3,8 @@ import Icon from '~/vue_shared/components/icon.vue';
import { GlLoadingIcon, GlTooltipDirective } from '@gitlab/ui';
export default {
+ // name: 'Badge' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'Badge',
components: {
Icon,
diff --git a/app/assets/javascripts/behaviors/markdown/render_gfm.js b/app/assets/javascripts/behaviors/markdown/render_gfm.js
index bfb073fdcdc..137cc7b4669 100644
--- a/app/assets/javascripts/behaviors/markdown/render_gfm.js
+++ b/app/assets/javascripts/behaviors/markdown/render_gfm.js
@@ -2,6 +2,7 @@ import $ from 'jquery';
import syntaxHighlight from '~/syntax_highlight';
import renderMath from './render_math';
import renderMermaid from './render_mermaid';
+import renderMetrics from './render_metrics';
import highlightCurrentUser from './highlight_current_user';
import initUserPopovers from '../../user_popovers';
import initMRPopovers from '../../mr_popover';
@@ -17,6 +18,7 @@ $.fn.renderGFM = function renderGFM() {
highlightCurrentUser(this.find('.gfm-project_member').get());
initUserPopovers(this.find('.gfm-project_member').get());
initMRPopovers(this.find('.gfm-merge_request').get());
+ renderMetrics(this.find('.js-render-metrics').get());
return this;
};
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index dbc28beffbe..27708504791 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -33,6 +33,7 @@ export default function renderMermaid($els) {
flowchart: {
htmlLabels: false,
},
+ securityLevel: 'strict',
});
$els.each((i, el) => {
diff --git a/app/assets/javascripts/behaviors/markdown/render_metrics.js b/app/assets/javascripts/behaviors/markdown/render_metrics.js
new file mode 100644
index 00000000000..252b98610b6
--- /dev/null
+++ b/app/assets/javascripts/behaviors/markdown/render_metrics.js
@@ -0,0 +1,24 @@
+import Vue from 'vue';
+import Metrics from '~/monitoring/components/embed.vue';
+import { createStore } from '~/monitoring/stores';
+
+// TODO: Handle copy-pasting - https://gitlab.com/gitlab-org/gitlab-ce/issues/64369.
+export default function renderMetrics(elements) {
+ if (!elements.length) {
+ return;
+ }
+
+ elements.forEach(element => {
+ const { dashboardUrl } = element.dataset;
+ const MetricsComponent = Vue.extend(Metrics);
+
+ // eslint-disable-next-line no-new
+ new MetricsComponent({
+ el: element,
+ store: createStore(),
+ propsData: {
+ dashboardUrl,
+ },
+ });
+ });
+}
diff --git a/app/assets/javascripts/behaviors/preview_markdown.js b/app/assets/javascripts/behaviors/preview_markdown.js
index 35874140bf9..b2571fb840c 100644
--- a/app/assets/javascripts/behaviors/preview_markdown.js
+++ b/app/assets/javascripts/behaviors/preview_markdown.js
@@ -36,6 +36,10 @@ MarkdownPreview.prototype.showPreview = function($form) {
mdText = $form.find('textarea.markdown-area').val();
+ if (mdText === undefined) {
+ return;
+ }
+
if (mdText.trim().length === 0) {
preview.text(this.emptyMessage);
this.hideReferencedUsers($form);
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
index eade1283513..7e3515b1f4b 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts.js
@@ -4,7 +4,7 @@ import Mousetrap from 'mousetrap';
import axios from '../../lib/utils/axios_utils';
import { refreshCurrentPage, visitUrl } from '../../lib/utils/url_utility';
import findAndFollowLink from '../../lib/utils/navigation_utility';
-import { parseBoolean } from '~/lib/utils/common_utils';
+import { parseBoolean, getCspNonceValue } from '~/lib/utils/common_utils';
const defaultStopCallback = Mousetrap.stopCallback;
Mousetrap.stopCallback = (e, element, combo) => {
@@ -94,7 +94,7 @@ export default class Shortcuts {
responseType: 'text',
})
.then(({ data }) => {
- $.globalEval(data);
+ $.globalEval(data, { nonce: getCspNonceValue() });
if (location && location.length > 0) {
const results = [];
diff --git a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
index 8b7e6a56d25..208c91a1f08 100644
--- a/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
+++ b/app/assets/javascripts/behaviors/shortcuts/shortcuts_wiki.js
@@ -6,6 +6,8 @@ export default class ShortcutsWiki extends ShortcutsNavigation {
constructor() {
super();
Mousetrap.bind('e', ShortcutsWiki.editWiki);
+
+ this.enabledHelp.push('.hidden-shortcut.wiki');
}
static editWiki() {
diff --git a/app/assets/javascripts/boards/components/board_blank_state.vue b/app/assets/javascripts/boards/components/board_blank_state.vue
index d8b0b60c183..9f26337d153 100644
--- a/app/assets/javascripts/boards/components/board_blank_state.vue
+++ b/app/assets/javascripts/boards/components/board_blank_state.vue
@@ -1,6 +1,6 @@
<script>
import { __ } from '~/locale';
-/* global ListLabel */
+import ListLabel from '~/boards/models/label';
import Cookies from 'js-cookie';
import boardsStore from '../stores/boards_store';
@@ -30,13 +30,17 @@ export default {
});
// Save the labels
- gl.boardService
+ boardsStore
.generateDefaultLists()
.then(res => res.data)
.then(data => {
data.forEach(listObj => {
const list = boardsStore.findList('title', listObj.title);
+ if (!list) {
+ return;
+ }
+
list.id = listObj.id;
list.label.id = listObj.label.id;
list.getIssues().catch(() => {
@@ -69,8 +73,7 @@ export default {
<span
:style="{ backgroundColor: label.color }"
class="label-color position-relative d-inline-block rounded"
- >
- </span>
+ ></span>
{{ label.title }}
</li>
</ul>
diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue
new file mode 100644
index 00000000000..ebf48cee2ae
--- /dev/null
+++ b/app/assets/javascripts/boards/components/board_form.vue
@@ -0,0 +1,219 @@
+<script>
+import { __ } from '~/locale';
+import Flash from '~/flash';
+import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue';
+import { visitUrl } from '~/lib/utils/url_utility';
+import boardsStore from '~/boards/stores/boards_store';
+
+const boardDefaults = {
+ id: false,
+ name: '',
+ labels: [],
+ milestone_id: undefined,
+ assignee: {},
+ assignee_id: undefined,
+ weight: null,
+};
+
+export default {
+ components: {
+ BoardScope: () => import('ee_component/boards/components/board_scope.vue'),
+ DeprecatedModal,
+ },
+ props: {
+ canAdminBoard: {
+ type: Boolean,
+ required: true,
+ },
+ milestonePath: {
+ type: String,
+ required: true,
+ },
+ labelsPath: {
+ type: String,
+ required: true,
+ },
+ scopedIssueBoardFeatureEnabled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ projectId: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ groupId: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ weights: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
+ enableScopedLabels: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ required: false,
+ default: '#',
+ },
+ },
+ data() {
+ return {
+ board: { ...boardDefaults, ...this.currentBoard },
+ currentBoard: boardsStore.state.currentBoard,
+ currentPage: boardsStore.state.currentPage,
+ isLoading: false,
+ };
+ },
+ computed: {
+ isNewForm() {
+ return this.currentPage === 'new';
+ },
+ isDeleteForm() {
+ return this.currentPage === 'delete';
+ },
+ isEditForm() {
+ return this.currentPage === 'edit';
+ },
+ isVisible() {
+ return this.currentPage !== '';
+ },
+ buttonText() {
+ if (this.isNewForm) {
+ return __('Create board');
+ }
+ if (this.isDeleteForm) {
+ return __('Delete');
+ }
+ return __('Save changes');
+ },
+ buttonKind() {
+ if (this.isNewForm) {
+ return 'success';
+ }
+ if (this.isDeleteForm) {
+ return 'danger';
+ }
+ return 'info';
+ },
+ title() {
+ if (this.isNewForm) {
+ return __('Create new board');
+ }
+ if (this.isDeleteForm) {
+ return __('Delete board');
+ }
+ if (this.readonly) {
+ return __('Board scope');
+ }
+ return __('Edit board');
+ },
+ readonly() {
+ return !this.canAdminBoard;
+ },
+ submitDisabled() {
+ return this.isLoading || this.board.name.length === 0;
+ },
+ },
+ mounted() {
+ this.resetFormState();
+ if (this.$refs.name) {
+ this.$refs.name.focus();
+ }
+ },
+ methods: {
+ submit() {
+ if (this.board.name.length === 0) return;
+ this.isLoading = true;
+ if (this.isDeleteForm) {
+ gl.boardService
+ .deleteBoard(this.currentBoard)
+ .then(() => {
+ visitUrl(boardsStore.rootPath);
+ })
+ .catch(() => {
+ Flash(__('Failed to delete board. Please try again.'));
+ this.isLoading = false;
+ });
+ } else {
+ gl.boardService
+ .createBoard(this.board)
+ .then(resp => resp.data)
+ .then(data => {
+ visitUrl(data.board_path);
+ })
+ .catch(() => {
+ Flash(__('Unable to save your changes. Please try again.'));
+ this.isLoading = false;
+ });
+ }
+ },
+ cancel() {
+ boardsStore.showPage('');
+ },
+ resetFormState() {
+ if (this.isNewForm) {
+ // Clear the form when we open the "New board" modal
+ this.board = { ...boardDefaults };
+ } else if (this.currentBoard && Object.keys(this.currentBoard).length) {
+ this.board = { ...boardDefaults, ...this.currentBoard };
+ }
+ },
+ },
+};
+</script>
+
+<template>
+ <deprecated-modal
+ v-show="isVisible"
+ :hide-footer="readonly"
+ :title="title"
+ :primary-button-label="buttonText"
+ :kind="buttonKind"
+ :submit-disabled="submitDisabled"
+ modal-dialog-class="board-config-modal"
+ @cancel="cancel"
+ @submit="submit"
+ >
+ <template slot="body">
+ <p v-if="isDeleteForm">{{ __('Are you sure you want to delete this board?') }}</p>
+ <form v-else class="js-board-config-modal" @submit.prevent>
+ <div v-if="!readonly" class="append-bottom-20">
+ <label class="form-section-title label-bold" for="board-new-name">{{
+ __('Board name')
+ }}</label>
+ <input
+ id="board-new-name"
+ ref="name"
+ v-model="board.name"
+ class="form-control"
+ type="text"
+ :placeholder="__('Enter board name')"
+ @keyup.enter="submit"
+ />
+ </div>
+
+ <board-scope
+ v-if="scopedIssueBoardFeatureEnabled"
+ :collapse-scope="isNewForm"
+ :board="board"
+ :can-admin-board="canAdminBoard"
+ :milestone-path="milestonePath"
+ :labels-path="labelsPath"
+ :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
+ :enable-scoped-labels="enableScopedLabels"
+ :project-id="projectId"
+ :group-id="groupId"
+ :weights="weights"
+ />
+ </form>
+ </template>
+ </deprecated-modal>
+</template>
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index 787ff110bf8..03a8a92575e 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import Sortable from 'sortablejs';
import { GlLoadingIcon } from '@gitlab/ui';
import boardNewIssue from './board_new_issue.vue';
diff --git a/app/assets/javascripts/boards/components/boards_selector.vue b/app/assets/javascripts/boards/components/boards_selector.vue
new file mode 100644
index 00000000000..b05de4538f2
--- /dev/null
+++ b/app/assets/javascripts/boards/components/boards_selector.vue
@@ -0,0 +1,334 @@
+<script>
+import { throttle } from 'underscore';
+import {
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownHeader,
+ GlDropdownItem,
+} from '@gitlab/ui';
+
+import Icon from '~/vue_shared/components/icon.vue';
+import httpStatusCodes from '~/lib/utils/http_status';
+import boardsStore from '../stores/boards_store';
+import BoardForm from './board_form.vue';
+
+const MIN_BOARDS_TO_VIEW_RECENT = 10;
+
+export default {
+ name: 'BoardsSelector',
+ components: {
+ Icon,
+ BoardForm,
+ GlLoadingIcon,
+ GlSearchBoxByType,
+ GlDropdown,
+ GlDropdownDivider,
+ GlDropdownHeader,
+ GlDropdownItem,
+ },
+ props: {
+ currentBoard: {
+ type: Object,
+ required: true,
+ },
+ milestonePath: {
+ type: String,
+ required: true,
+ },
+ throttleDuration: {
+ type: Number,
+ default: 200,
+ },
+ boardBaseUrl: {
+ type: String,
+ required: true,
+ },
+ hasMissingBoards: {
+ type: Boolean,
+ required: true,
+ },
+ canAdminBoard: {
+ type: Boolean,
+ required: true,
+ },
+ multipleIssueBoardsAvailable: {
+ type: Boolean,
+ required: true,
+ },
+ labelsPath: {
+ type: String,
+ required: true,
+ },
+ projectId: {
+ type: Number,
+ required: true,
+ },
+ groupId: {
+ type: Number,
+ required: true,
+ },
+ scopedIssueBoardFeatureEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ weights: {
+ type: Array,
+ required: true,
+ },
+ enabledScopedLabels: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ scopedLabelsDocumentationLink: {
+ type: String,
+ required: false,
+ default: '#',
+ },
+ },
+ data() {
+ return {
+ loading: true,
+ hasScrollFade: false,
+ scrollFadeInitialized: false,
+ boards: [],
+ recentBoards: [],
+ state: boardsStore.state,
+ throttledSetScrollFade: throttle(this.setScrollFade, this.throttleDuration),
+ contentClientHeight: 0,
+ maxPosition: 0,
+ store: boardsStore,
+ filterTerm: '',
+ };
+ },
+ computed: {
+ currentPage() {
+ return this.state.currentPage;
+ },
+ filteredBoards() {
+ return this.boards.filter(board =>
+ board.name.toLowerCase().includes(this.filterTerm.toLowerCase()),
+ );
+ },
+ reload: {
+ get() {
+ return this.state.reload;
+ },
+ set(newValue) {
+ this.state.reload = newValue;
+ },
+ },
+ board() {
+ return this.state.currentBoard;
+ },
+ showDelete() {
+ return this.boards.length > 1;
+ },
+ scrollFadeClass() {
+ return {
+ 'fade-out': !this.hasScrollFade,
+ };
+ },
+ showRecentSection() {
+ return (
+ this.recentBoards.length &&
+ this.boards.length > MIN_BOARDS_TO_VIEW_RECENT &&
+ !this.filterTerm.length
+ );
+ },
+ },
+ watch: {
+ filteredBoards() {
+ this.scrollFadeInitialized = false;
+ this.$nextTick(this.setScrollFade);
+ },
+ reload() {
+ if (this.reload) {
+ this.boards = [];
+ this.recentBoards = [];
+ this.loading = true;
+ this.reload = false;
+
+ this.loadBoards(false);
+ }
+ },
+ },
+ created() {
+ boardsStore.setCurrentBoard(this.currentBoard);
+ },
+ methods: {
+ showPage(page) {
+ boardsStore.showPage(page);
+ },
+ loadBoards(toggleDropdown = true) {
+ if (toggleDropdown && this.boards.length > 0) {
+ return;
+ }
+
+ const recentBoardsPromise = new Promise((resolve, reject) =>
+ gl.boardService
+ .recentBoards()
+ .then(resolve)
+ .catch(err => {
+ /**
+ * If user is unauthorized we'd still want to resolve the
+ * request to display all boards.
+ */
+ if (err.response.status === httpStatusCodes.UNAUTHORIZED) {
+ resolve({ data: [] }); // recent boards are empty
+ return;
+ }
+ reject(err);
+ }),
+ );
+
+ Promise.all([gl.boardService.allBoards(), recentBoardsPromise])
+ .then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
+ .then(([allBoardsJson, recentBoardsJson]) => {
+ this.loading = false;
+ this.boards = allBoardsJson;
+ this.recentBoards = recentBoardsJson;
+ })
+ .then(() => this.$nextTick()) // Wait for boards list in DOM
+ .then(() => {
+ this.setScrollFade();
+ })
+ .catch(() => {
+ this.loading = false;
+ });
+ },
+ isScrolledUp() {
+ const { content } = this.$refs;
+ const currentPosition = this.contentClientHeight + content.scrollTop;
+
+ return content && currentPosition < this.maxPosition;
+ },
+ initScrollFade() {
+ this.scrollFadeInitialized = true;
+
+ const { content } = this.$refs;
+
+ this.contentClientHeight = content.clientHeight;
+ this.maxPosition = content.scrollHeight;
+ },
+ setScrollFade() {
+ if (!this.scrollFadeInitialized) this.initScrollFade();
+
+ this.hasScrollFade = this.isScrolledUp();
+ },
+ },
+};
+</script>
+
+<template>
+ <div class="boards-switcher js-boards-selector append-right-10">
+ <span class="boards-selector-wrapper js-boards-selector-wrapper">
+ <gl-dropdown
+ toggle-class="dropdown-menu-toggle js-dropdown-toggle"
+ menu-class="flex-column dropdown-extended-height"
+ :text="board.name"
+ @show="loadBoards"
+ >
+ <div>
+ <div class="dropdown-title mb-0" @mousedown.prevent>
+ {{ s__('IssueBoards|Switch board') }}
+ </div>
+ </div>
+
+ <gl-dropdown-header class="mt-0">
+ <gl-search-box-by-type ref="searchBox" v-model="filterTerm" />
+ </gl-dropdown-header>
+
+ <div
+ v-if="!loading"
+ ref="content"
+ class="dropdown-content flex-fill"
+ @scroll.passive="throttledSetScrollFade"
+ >
+ <gl-dropdown-item
+ v-show="filteredBoards.length === 0"
+ class="no-pointer-events text-secondary"
+ >
+ {{ s__('IssueBoards|No matching boards found') }}
+ </gl-dropdown-item>
+
+ <h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
+ {{ __('Recent') }}
+ </h6>
+
+ <template v-if="showRecentSection">
+ <gl-dropdown-item
+ v-for="recentBoard in recentBoards"
+ :key="`recent-${recentBoard.id}`"
+ class="js-dropdown-item"
+ :href="`${boardBaseUrl}/${recentBoard.id}`"
+ >
+ {{ recentBoard.name }}
+ </gl-dropdown-item>
+ </template>
+
+ <hr v-if="showRecentSection" class="my-1" />
+
+ <h6 v-if="showRecentSection" class="dropdown-bold-header my-0">
+ {{ __('All') }}
+ </h6>
+
+ <gl-dropdown-item
+ v-for="otherBoard in filteredBoards"
+ :key="otherBoard.id"
+ class="js-dropdown-item"
+ :href="`${boardBaseUrl}/${otherBoard.id}`"
+ >
+ {{ otherBoard.name }}
+ </gl-dropdown-item>
+ <gl-dropdown-item v-if="hasMissingBoards" class="small unclickable">
+ {{
+ s__(
+ 'IssueBoards|Some of your boards are hidden, activate a license to see them again.',
+ )
+ }}
+ </gl-dropdown-item>
+ </div>
+
+ <div
+ v-show="filteredBoards.length > 0"
+ class="dropdown-content-faded-mask"
+ :class="scrollFadeClass"
+ ></div>
+
+ <gl-loading-icon v-if="loading" />
+
+ <div v-if="canAdminBoard">
+ <gl-dropdown-divider />
+
+ <gl-dropdown-item v-if="multipleIssueBoardsAvailable" @click.prevent="showPage('new')">
+ {{ s__('IssueBoards|Create new board') }}
+ </gl-dropdown-item>
+
+ <gl-dropdown-item
+ v-if="showDelete"
+ class="text-danger"
+ @click.prevent="showPage('delete')"
+ >
+ {{ s__('IssueBoards|Delete board') }}
+ </gl-dropdown-item>
+ </div>
+ </gl-dropdown>
+
+ <board-form
+ v-if="currentPage"
+ :milestone-path="milestonePath"
+ :labels-path="labelsPath"
+ :project-id="projectId"
+ :group-id="groupId"
+ :can-admin-board="canAdminBoard"
+ :scoped-issue-board-feature-enabled="scopedIssueBoardFeatureEnabled"
+ :weights="weights"
+ :enable-scoped-labels="enabledScopedLabels"
+ :scoped-labels-documentation-link="scopedLabelsDocumentationLink"
+ />
+ </span>
+ </div>
+</template>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index 7a696035dc8..8cd4840d3d6 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { __ } from '~/locale';
import ModalFilters from './filters';
import ModalTabs from './tabs.vue';
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
index 2d2920e312e..7430fc96654 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.vue
+++ b/app/assets/javascripts/boards/components/modal/tabs.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
diff --git a/app/assets/javascripts/boards/components/project_select.vue b/app/assets/javascripts/boards/components/project_select.vue
index a1cf1866faf..e8d25e84be1 100644
--- a/app/assets/javascripts/boards/components/project_select.vue
+++ b/app/assets/javascripts/boards/components/project_select.vue
@@ -68,13 +68,15 @@ export default {
<li>
<a href='#' class='dropdown-menu-link' data-project-id="${
project.id
- }" data-project-name="${project.name}">
- ${_.escape(project.name)}
+ }" data-project-name="${project.name}" data-project-name-with-namespace="${
+ project.name_with_namespace
+ }">
+ ${_.escape(project.name_with_namespace)}
</a>
</li>
`;
},
- text: project => project.name,
+ text: project => project.name_with_namespace,
});
},
};
diff --git a/app/assets/javascripts/boards/ee_functions.js b/app/assets/javascripts/boards/ee_functions.js
new file mode 100644
index 00000000000..583270fcae5
--- /dev/null
+++ b/app/assets/javascripts/boards/ee_functions.js
@@ -0,0 +1,7 @@
+export const setPromotionState = () => {};
+
+export const setWeigthFetchingState = () => {};
+export const setEpicFetchingState = () => {};
+
+export const getMilestoneTitle = () => ({});
+export const getBoardsModalData = () => ({});
diff --git a/app/assets/javascripts/boards/index.js b/app/assets/javascripts/boards/index.js
index d6a5372b22d..3bded4a3258 100644
--- a/app/assets/javascripts/boards/index.js
+++ b/app/assets/javascripts/boards/index.js
@@ -1,7 +1,6 @@
import $ from 'jquery';
import Vue from 'vue';
-import mountMultipleBoardsSwitcher from 'ee_else_ce/boards/mount_multiple_boards_switcher';
import Flash from '~/flash';
import { __ } from '~/locale';
import './models/label';
@@ -31,6 +30,14 @@ import {
} from '~/lib/utils/common_utils';
import boardConfigToggle from 'ee_else_ce/boards/config_toggle';
import toggleFocusMode from 'ee_else_ce/boards/toggle_focus';
+import {
+ setPromotionState,
+ setWeigthFetchingState,
+ setEpicFetchingState,
+ getMilestoneTitle,
+ getBoardsModalData,
+} from 'ee_else_ce/boards/ee_functions';
+import mountMultipleBoardsSwitcher from './mount_multiple_boards_switcher';
let issueBoardsApp;
@@ -129,6 +136,7 @@ export default () => {
});
boardsStore.addBlankState();
+ setPromotionState(boardsStore);
this.loading = false;
})
.catch(() => {
@@ -143,6 +151,8 @@ export default () => {
const { sidebarInfoEndpoint } = newIssue;
if (sidebarInfoEndpoint && newIssue.subscribed === undefined) {
newIssue.setFetchingState('subscriptions', true);
+ setWeigthFetchingState(newIssue, true);
+ setEpicFetchingState(newIssue, true);
BoardService.getIssueInfo(sidebarInfoEndpoint)
.then(res => res.data)
.then(data => {
@@ -157,6 +167,8 @@ export default () => {
} = convertObjectPropsToCamelCase(data);
newIssue.setFetchingState('subscriptions', false);
+ setWeigthFetchingState(newIssue, false);
+ setEpicFetchingState(newIssue, false);
newIssue.updateData({
humanTimeSpent: humanTotalTimeSpent,
timeSpent: totalTimeSpent,
@@ -169,6 +181,7 @@ export default () => {
})
.catch(() => {
newIssue.setFetchingState('subscriptions', false);
+ setWeigthFetchingState(newIssue, false);
Flash(__('An error occurred while fetching sidebar data'));
});
}
@@ -203,6 +216,7 @@ export default () => {
el: document.getElementById('js-add-list'),
data: {
filters: boardsStore.state.filters,
+ ...getMilestoneTitle($boardApp),
},
mounted() {
initNewListDropdown();
@@ -222,6 +236,7 @@ export default () => {
return {
modal: ModalStore.store,
store: boardsStore.state,
+ ...getBoardsModalData($boardApp),
canAdminList: this.$options.el.hasAttribute('data-can-admin-list'),
};
},
@@ -285,6 +300,6 @@ export default () => {
});
}
- toggleFocusMode(ModalStore, boardsStore);
+ toggleFocusMode(ModalStore, boardsStore, $boardApp);
mountMultipleBoardsSwitcher();
};
diff --git a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
index bdb14a7f2f2..8d22f009784 100644
--- a/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
+++ b/app/assets/javascripts/boards/mount_multiple_boards_switcher.js
@@ -1,2 +1,35 @@
-// this will be moved from EE to CE as part of https://gitlab.com/gitlab-org/gitlab-ce/issues/53811
-export default () => {};
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import BoardsSelector from '~/boards/components/boards_selector.vue';
+
+export default () => {
+ const boardsSwitcherElement = document.getElementById('js-multiple-boards-switcher');
+ return new Vue({
+ el: boardsSwitcherElement,
+ components: {
+ BoardsSelector,
+ },
+ data() {
+ const { dataset } = boardsSwitcherElement;
+
+ const boardsSelectorProps = {
+ ...dataset,
+ currentBoard: JSON.parse(dataset.currentBoard),
+ hasMissingBoards: parseBoolean(dataset.hasMissingBoards),
+ canAdminBoard: parseBoolean(dataset.canAdminBoard),
+ multipleIssueBoardsAvailable: parseBoolean(dataset.multipleIssueBoardsAvailable),
+ projectId: Number(dataset.projectId),
+ groupId: Number(dataset.groupId),
+ scopedIssueBoardFeatureEnabled: parseBoolean(dataset.scopedIssueBoardFeatureEnabled),
+ weights: JSON.parse(dataset.weights),
+ };
+
+ return { boardsSelectorProps };
+ },
+ render(createElement) {
+ return createElement(BoardsSelector, {
+ props: this.boardsSelectorProps,
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/boards/services/board_service.js b/app/assets/javascripts/boards/services/board_service.js
index 5202620057c..56a6cab6c73 100644
--- a/app/assets/javascripts/boards/services/board_service.js
+++ b/app/assets/javascripts/boards/services/board_service.js
@@ -1,4 +1,9 @@
/* eslint-disable class-methods-use-this */
+/**
+ * This file is intended to be deleted.
+ * The existing functions will removed one by one in favor of using the board store directly.
+ * see https://gitlab.com/gitlab-org/gitlab-ce/issues/61621
+ */
import boardsStore from '~/boards/stores/boards_store';
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index aacfa0d87e6..5f5c8044b49 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -48,6 +48,9 @@ export default class Clusters {
} = document.querySelector('.js-edit-cluster-form').dataset;
this.clusterId = clusterId;
+ this.clusterNewlyCreatedKey = `cluster_${this.clusterId}_newly_created`;
+ this.clusterBannerDismissedKey = `cluster_${this.clusterId}_banner_dismissed`;
+
this.store = new ClustersStore();
this.store.setHelpPaths(helpPath, ingressHelpPath, ingressDnsHelpPath);
this.store.setManagePrometheusPath(managePrometheusPath);
@@ -81,18 +84,19 @@ export default class Clusters {
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
- this.ingressDomainSnippet = this.ingressDomainHelpText.querySelector(
- '.js-ingress-domain-snippet',
- );
+ this.ingressDomainSnippet =
+ this.ingressDomainHelpText &&
+ this.ingressDomainHelpText.querySelector('.js-ingress-domain-snippet');
Clusters.initDismissableCallout();
initSettingsPanels();
- setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area'));
+ const toggleButtonsContainer = document.querySelector('.js-cluster-enable-toggle-area');
+ if (toggleButtonsContainer) {
+ setupToggleButtons(toggleButtonsContainer);
+ }
this.initApplications(clusterType);
- if (this.store.state.status !== 'created') {
- this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
- }
+ this.updateContainer(null, this.store.state.status, this.store.state.statusReason);
this.addListeners();
if (statusPath) {
@@ -247,35 +251,56 @@ export default class Clusters {
setBannerDismissedState(status, isDismissed) {
if (AccessorUtilities.isLocalStorageAccessSafe()) {
- window.localStorage.setItem(
- `cluster_${this.clusterId}_banner_dismissed`,
- `${status}_${isDismissed}`,
- );
+ window.localStorage.setItem(this.clusterBannerDismissedKey, `${status}_${isDismissed}`);
}
}
isBannerDismissed(status) {
let bannerState;
if (AccessorUtilities.isLocalStorageAccessSafe()) {
- bannerState = window.localStorage.getItem(`cluster_${this.clusterId}_banner_dismissed`);
+ bannerState = window.localStorage.getItem(this.clusterBannerDismissedKey);
}
return bannerState === `${status}_true`;
}
- updateContainer(prevStatus, status, error) {
- this.hideAll();
+ setClusterNewlyCreated(state) {
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ window.localStorage.setItem(this.clusterNewlyCreatedKey, Boolean(state));
+ }
+ }
+
+ isClusterNewlyCreated() {
+ // once this is true, it will always be true for a given page load
+ if (!this.isNewlyCreated) {
+ let newlyCreated;
+ if (AccessorUtilities.isLocalStorageAccessSafe()) {
+ newlyCreated = window.localStorage.getItem(this.clusterNewlyCreatedKey);
+ }
+
+ this.isNewlyCreated = newlyCreated === 'true';
+ }
+ return this.isNewlyCreated;
+ }
- if (this.isBannerDismissed(status)) {
+ updateContainer(prevStatus, status, error) {
+ if (status !== 'created' && this.isBannerDismissed(status)) {
return;
}
this.setBannerDismissedState(status, false);
- // We poll all the time but only want the `created` banner to show when newly created
- if (this.store.state.status !== 'created' || prevStatus !== this.store.state.status) {
+ if (prevStatus !== status) {
+ this.hideAll();
+
switch (status) {
case 'created':
- this.successContainer.classList.remove('hidden');
+ if (this.isClusterNewlyCreated()) {
+ this.setClusterNewlyCreated(false);
+ this.successContainer.classList.remove('hidden');
+ } else if (prevStatus) {
+ this.setClusterNewlyCreated(true);
+ window.location.reload();
+ }
break;
case 'errored':
this.errorContainer.classList.remove('hidden');
@@ -292,7 +317,6 @@ export default class Clusters {
this.creatingContainer.classList.remove('hidden');
break;
default:
- this.hideAll();
}
}
}
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index cd2121db3b2..64364092016 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -1,5 +1,6 @@
<script>
/* eslint-disable vue/require-default-prop */
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlLink, GlModalDirective } from '@gitlab/ui';
import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
import { s__, __, sprintf } from '~/locale';
diff --git a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
index 920439ebb23..4f60e543666 100644
--- a/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
+++ b/app/assets/javascripts/clusters/components/uninstall_application_confirmation_modal.vue
@@ -2,18 +2,23 @@
import { GlModal } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import trackUninstallButtonClickMixin from 'ee_else_ce/clusters/mixins/track_uninstall_button_click';
-import { INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
+import { HELM, INGRESS, CERT_MANAGER, PROMETHEUS, RUNNER, KNATIVE, JUPYTER } from '../constants';
const CUSTOM_APP_WARNING_TEXT = {
+ [HELM]: s__(
+ 'ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored.',
+ ),
[INGRESS]: s__(
'ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored.',
),
[CERT_MANAGER]: s__(
- 'ClusterIntegration|The associated certifcate will be deleted and cannot be restored.',
+ 'ClusterIntegration|The associated private key will be deleted and cannot be restored.',
),
[PROMETHEUS]: s__('ClusterIntegration|All data will be deleted and cannot be restored.'),
[RUNNER]: s__('ClusterIntegration|Any running pipelines will be canceled.'),
- [KNATIVE]: s__('ClusterIntegration|The associated IP will be deleted and cannot be restored.'),
+ [KNATIVE]: s__(
+ 'ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications.',
+ ),
[JUPYTER]: s__(
'ClusterIntegration|All data not committed to GitLab will be deleted and cannot be restored.',
),
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index f64f0ca616f..ada5a49e246 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -171,6 +171,7 @@ export default class ClusterStore {
this.state.applications.cert_manager.email || serverAppEntry.email;
} else if (appId === JUPYTER) {
this.state.applications.jupyter.hostname =
+ this.state.applications.jupyter.hostname ||
serverAppEntry.hostname ||
(this.state.applications.ingress.externalIp
? `jupyter.${this.state.applications.ingress.externalIp}.nip.io`
diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js
index 54e2589c707..7dd75d03ab9 100644
--- a/app/assets/javascripts/commits.js
+++ b/app/assets/javascripts/commits.js
@@ -1,5 +1,5 @@
import $ from 'jquery';
-import { pluralize } from './lib/utils/text_utility';
+import { n__ } from '~/locale';
import { localTimeAgo } from './lib/utils/datetime_utility';
import Pager from './pager';
import axios from './lib/utils/axios_utils';
@@ -90,9 +90,10 @@ export default class CommitsList {
.first()
.find('li.commit').length,
);
+
$commitsHeadersLast
.find('span.commits-count')
- .text(`${commitsCount} ${pluralize('commit', commitsCount)}`);
+ .text(n__('%d commit', '%d commits', commitsCount));
}
localTimeAgo($processedData.find('.js-timeago'));
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index a4394ab7e92..7a6ad3dc771 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -13,6 +13,7 @@ import 'core-js/es/string/code-point-at';
import 'core-js/es/string/from-code-point';
import 'core-js/es/string/includes';
import 'core-js/es/string/starts-with';
+import 'core-js/es/string/ends-with';
import 'core-js/es/symbol';
import 'core-js/es/map';
import 'core-js/es/weak-map';
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 d4b994d4922..b3ae47af750 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js
@@ -1,7 +1,10 @@
import $ from 'jquery';
import Vue from 'vue';
import Cookies from 'js-cookie';
+import { GlEmptyState } from '@gitlab/ui';
+import filterMixins from 'ee_else_ce/analytics/cycle_analytics/mixins/filter_mixins';
import Flash from '../flash';
+import { __ } from '~/locale';
import Translate from '../vue_shared/translate';
import banner from './components/banner.vue';
import stageCodeComponent from './components/stage_code_component.vue';
@@ -9,9 +12,9 @@ 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';
-import { __ } from '~/locale';
Vue.use(Translate);
@@ -24,6 +27,7 @@ export default () => {
el: '#cycle-analytics',
name: 'CycleAnalytics',
components: {
+ GlEmptyState,
banner,
'stage-issue-component': stageComponent,
'stage-plan-component': stageComponent,
@@ -32,12 +36,16 @@ export default () => {
'stage-review-component': stageReviewComponent,
'stage-staging-component': stageStagingComponent,
'stage-production-component': stageComponent,
+ GroupsDropdownFilter: () =>
+ import('ee_component/analytics/shared/components/groups_dropdown_filter.vue'),
+ ProjectsDropdownFilter: () =>
+ 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() {
- const cycleAnalyticsService = new CycleAnalyticsService({
- requestPath: cycleAnalyticsEl.dataset.requestPath,
- });
-
return {
store: CycleAnalyticsStore,
state: CycleAnalyticsStore.state,
@@ -47,7 +55,7 @@ export default () => {
hasError: false,
startDate: 30,
isOverviewDialogDismissed: Cookies.get(OVERVIEW_DIALOG_COOKIE),
- service: cycleAnalyticsService,
+ service: this.createCycleAnalyticsService(cycleAnalyticsEl.dataset.requestPath),
};
},
computed: {
@@ -124,6 +132,7 @@ export default () => {
.fetchStageData({
stage,
startDate: this.startDate,
+ projectIds: this.selectedProjectIds,
})
.then(response => {
this.isEmptyStage = !response.events.length;
@@ -139,6 +148,11 @@ export default () => {
this.isOverviewDialogDismissed = true;
Cookies.set(OVERVIEW_DIALOG_COOKIE, '1', { expires: 365 });
},
+ createCycleAnalyticsService(requestPath) {
+ return new CycleAnalyticsService({
+ requestPath,
+ });
+ },
},
});
};
diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
index a0426301a0a..babbfe93082 100644
--- a/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
+++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_service.js
@@ -8,22 +8,26 @@ export default class CycleAnalyticsService {
}
fetchCycleAnalyticsData(options = { startDate: 30 }) {
+ const { startDate, projectIds } = options;
+
return this.axios
.get('', {
params: {
- 'cycle_analytics[start_date]': options.startDate,
+ 'cycle_analytics[start_date]': startDate,
+ 'cycle_analytics[project_ids]': projectIds,
},
})
.then(x => x.data);
}
fetchStageData(options) {
- const { stage, startDate } = options;
+ const { stage, startDate, projectIds } = options;
return this.axios
.get(`events/${stage.name}.json`, {
params: {
'cycle_analytics[start_date]': startDate,
+ 'cycle_analytics[project_ids]': projectIds,
},
})
.then(x => x.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/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue
index 58d5b658b17..c82b4a7abc6 100644
--- a/app/assets/javascripts/diffs/components/commit_item.vue
+++ b/app/assets/javascripts/diffs/components/commit_item.vue
@@ -49,8 +49,8 @@ export default {
return this.author.id ? this.author.id : '';
},
authorUrl() {
- // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings
// name: 'mailto:' 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
return this.author.web_url || `mailto:${this.commit.author_email}`;
},
authorAvatar() {
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 363ebad1594..2e57a47f2f7 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlTooltipDirective, GlLink, GlButton } from '@gitlab/ui';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/diffs/components/diff_discussion_reply.vue b/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
index 2aa5e9b3339..531ebaddacd 100644
--- a/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussion_reply.vue
@@ -44,7 +44,6 @@ export default {
class="d-none d-sm-block"
/>
<reply-placeholder
- class="qa-discussion-reply"
:button-text="__('Start a new discussion...')"
@onClick="$emit('showNewDiscussionForm')"
/>
diff --git a/app/assets/javascripts/diffs/components/diff_expansion_cell.vue b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
new file mode 100644
index 00000000000..839ab542377
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/diff_expansion_cell.vue
@@ -0,0 +1,244 @@
+<script>
+import createFlash from '~/flash';
+import { s__ } from '~/locale';
+import { mapState, mapActions } from 'vuex';
+import Icon from '~/vue_shared/components/icon.vue';
+import { UNFOLD_COUNT } from '../constants';
+import * as utils from '../store/utils';
+import tooltip from '../../vue_shared/directives/tooltip';
+
+const EXPAND_ALL = 0;
+const EXPAND_UP = 1;
+const EXPAND_DOWN = 2;
+
+export default {
+ directives: {
+ tooltip,
+ },
+ components: {
+ Icon,
+ },
+ props: {
+ fileHash: {
+ type: String,
+ required: true,
+ },
+ contextLinesPath: {
+ type: String,
+ required: true,
+ },
+ line: {
+ type: Object,
+ required: true,
+ },
+ isTop: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isBottom: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ colspan: {
+ type: Number,
+ required: false,
+ default: 3,
+ },
+ },
+ computed: {
+ ...mapState({
+ diffViewType: state => state.diffs.diffViewType,
+ diffFiles: state => state.diffs.diffFiles,
+ }),
+ canExpandUp() {
+ return !this.isBottom;
+ },
+ canExpandDown() {
+ return this.isBottom || !this.isTop;
+ },
+ },
+ created() {
+ this.EXPAND_DOWN = EXPAND_DOWN;
+ this.EXPAND_UP = EXPAND_UP;
+ },
+ methods: {
+ ...mapActions('diffs', ['loadMoreLines']),
+ getPrevLineNumber(oldLineNumber, newLineNumber) {
+ const diffFile = utils.findDiffFile(this.diffFiles, this.fileHash);
+ const indexForInline = utils.findIndexInInlineLines(diffFile.highlighted_diff_lines, {
+ oldLineNumber,
+ newLineNumber,
+ });
+ const prevLine = diffFile.highlighted_diff_lines[indexForInline - 2];
+ return (prevLine && prevLine.new_line) || 0;
+ },
+ callLoadMoreLines(
+ endpoint,
+ params,
+ lineNumbers,
+ fileHash,
+ isExpandDown = false,
+ nextLineNumbers = {},
+ ) {
+ this.loadMoreLines({ endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers })
+ .then(() => {
+ this.isRequesting = false;
+ })
+ .catch(() => {
+ createFlash(s__('Diffs|Something went wrong while fetching diff lines.'));
+ this.isRequesting = false;
+ });
+ },
+ handleExpandLines(type = EXPAND_ALL) {
+ if (this.isRequesting) {
+ return;
+ }
+
+ this.isRequesting = true;
+ const endpoint = this.contextLinesPath;
+ const { fileHash } = this;
+ const view = this.diffViewType;
+ const oldLineNumber = this.line.meta_data.old_pos || 0;
+ const newLineNumber = this.line.meta_data.new_pos || 0;
+ const offset = newLineNumber - oldLineNumber;
+
+ const expandOptions = { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset };
+
+ if (type === EXPAND_UP) {
+ this.handleExpandUpLines(expandOptions);
+ } else if (type === EXPAND_DOWN) {
+ this.handleExpandDownLines(expandOptions);
+ } else {
+ this.handleExpandAllLines(expandOptions);
+ }
+ },
+ handleExpandUpLines(expandOptions = EXPAND_ALL) {
+ const { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset } = expandOptions;
+
+ const bottom = this.isBottom;
+ const lineNumber = newLineNumber - 1;
+ const to = lineNumber;
+ let since = lineNumber - UNFOLD_COUNT;
+ let unfold = true;
+
+ const prevLineNumber = this.getPrevLineNumber(oldLineNumber, newLineNumber);
+ if (since <= prevLineNumber + 1) {
+ since = prevLineNumber + 1;
+ unfold = false;
+ }
+
+ const params = { since, to, bottom, offset, unfold, view };
+ const lineNumbers = { oldLineNumber, newLineNumber };
+ this.callLoadMoreLines(endpoint, params, lineNumbers, fileHash);
+ },
+ handleExpandDownLines(expandOptions) {
+ const {
+ endpoint,
+ fileHash,
+ view,
+ oldLineNumber: metaOldPos,
+ newLineNumber: metaNewPos,
+ offset,
+ } = expandOptions;
+
+ const bottom = true;
+ const nextLineNumbers = {
+ old_line: metaOldPos,
+ new_line: metaNewPos,
+ };
+
+ let unfold = true;
+ let isExpandDown = false;
+ let oldLineNumber = metaOldPos;
+ let newLineNumber = metaNewPos;
+ let lineNumber = metaNewPos + 1;
+ let since = lineNumber;
+ let to = lineNumber + UNFOLD_COUNT;
+
+ if (!this.isBottom) {
+ const prevLineNumber = this.getPrevLineNumber(oldLineNumber, newLineNumber);
+
+ isExpandDown = true;
+ oldLineNumber = prevLineNumber - offset;
+ newLineNumber = prevLineNumber;
+ lineNumber = prevLineNumber + 1;
+ since = lineNumber;
+ to = lineNumber + UNFOLD_COUNT;
+
+ if (to >= metaNewPos) {
+ to = metaNewPos - 1;
+ unfold = false;
+ }
+ }
+
+ const params = { since, to, bottom, offset, unfold, view };
+ const lineNumbers = { oldLineNumber, newLineNumber };
+ this.callLoadMoreLines(
+ endpoint,
+ params,
+ lineNumbers,
+ fileHash,
+ isExpandDown,
+ nextLineNumbers,
+ );
+ },
+ handleExpandAllLines(expandOptions) {
+ const { endpoint, fileHash, view, oldLineNumber, newLineNumber, offset } = expandOptions;
+ const bottom = this.isBottom;
+ const unfold = false;
+ let since;
+ let to;
+
+ if (this.isTop) {
+ since = 1;
+ to = newLineNumber - 1;
+ } else if (bottom) {
+ since = newLineNumber + 1;
+ to = -1;
+ } else {
+ const prevLineNumber = this.getPrevLineNumber(oldLineNumber, newLineNumber);
+ since = prevLineNumber + 1;
+ to = newLineNumber - 1;
+ }
+
+ const params = { since, to, bottom, offset, unfold, view };
+ const lineNumbers = { oldLineNumber, newLineNumber };
+ this.callLoadMoreLines(endpoint, params, lineNumbers, fileHash);
+ },
+ },
+};
+</script>
+
+<template>
+ <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 d-inline-block pt-2 pb-2"
+ data-placement="top"
+ data-container="body"
+ :title="__('Expand up')"
+ @click="handleExpandLines(EXPAND_UP)"
+ >
+ <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>
+ </a>
+ <a
+ v-if="canExpandDown"
+ v-tooltip
+ 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)"
+ >
+ <icon :size="12" name="expand-down" aria-hidden="true" />
+ </a>
+ </div>
+ </td>
+</template>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index 4b226e30699..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;
@@ -263,6 +263,7 @@ export default {
:disabled="!diffHasDiscussions(diffFile)"
:class="{ active: hasExpandedDiscussions }"
class="js-btn-vue-toggle-comments btn"
+ data-qa-selector="toggle_comments_button"
type="button"
@click="handleToggleDiscussions"
>
diff --git a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
index af5550aec3b..7ede7a4f430 100644
--- a/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
+++ b/app/assets/javascripts/diffs/components/diff_gutter_avatars.vue
@@ -1,6 +1,7 @@
<script>
+import { n__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import { pluralize, truncate } from '~/lib/utils/text_utility';
+import { truncate } from '~/lib/utils/text_utility';
import UserAvatarImage from '~/vue_shared/components/user_avatar/user_avatar_image.vue';
import { GlTooltipDirective } from '@gitlab/ui';
import { COUNT_OF_AVATARS_IN_GUTTER, LENGTH_OF_AVATAR_TOOLTIP } from '../constants';
@@ -42,7 +43,7 @@ export default {
return '';
}
- return pluralize(`${this.moreCount} more comment`, this.moreCount);
+ return n__('%d more comment', '%d more comments', this.moreCount);
},
},
methods: {
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index 351110f0a87..434d554d148 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -1,11 +1,8 @@
<script>
-import createFlash from '~/flash';
-import { s__ } from '~/locale';
import { mapState, mapGetters, mapActions } from 'vuex';
import Icon from '~/vue_shared/components/icon.vue';
import DiffGutterAvatars from './diff_gutter_avatars.vue';
-import { LINE_POSITION_RIGHT, UNFOLD_COUNT } from '../constants';
-import * as utils from '../store/utils';
+import { LINE_POSITION_RIGHT } from '../constants';
export default {
components: {
@@ -115,89 +112,36 @@ export default {
handleCommentButton() {
this.showCommentForm({ lineCode: this.line.line_code, fileHash: this.fileHash });
},
- handleLoadMoreLines() {
- if (this.isRequesting) {
- return;
- }
-
- this.isRequesting = true;
- const endpoint = this.contextLinesPath;
- const oldLineNumber = this.line.meta_data.old_pos || 0;
- const newLineNumber = this.line.meta_data.new_pos || 0;
- const offset = newLineNumber - oldLineNumber;
- const bottom = this.isBottom;
- const { fileHash } = this;
- const view = this.diffViewType;
- let unfold = true;
- let lineNumber = newLineNumber - 1;
- let since = lineNumber - UNFOLD_COUNT;
- let to = lineNumber;
-
- if (bottom) {
- lineNumber = newLineNumber + 1;
- since = lineNumber;
- to = lineNumber + UNFOLD_COUNT;
- } else {
- const diffFile = utils.findDiffFile(this.diffFiles, this.fileHash);
- const indexForInline = utils.findIndexInInlineLines(diffFile.highlighted_diff_lines, {
- oldLineNumber,
- newLineNumber,
- });
- const prevLine = diffFile.highlighted_diff_lines[indexForInline - 2];
- const prevLineNumber = (prevLine && prevLine.new_line) || 0;
-
- if (since <= prevLineNumber + 1) {
- since = prevLineNumber + 1;
- unfold = false;
- }
- }
-
- const params = { since, to, bottom, offset, unfold, view };
- const lineNumbers = { oldLineNumber, newLineNumber };
- this.loadMoreLines({ endpoint, params, lineNumbers, fileHash })
- .then(() => {
- this.isRequesting = false;
- })
- .catch(() => {
- createFlash(s__('Diffs|Something went wrong while fetching diff lines.'));
- this.isRequesting = false;
- });
- },
},
};
</script>
<template>
<div>
- <span v-if="isMatchLine" class="context-cell" role="button" @click="handleLoadMoreLines"
- >...</span
+ <button
+ v-if="shouldRenderCommentButton"
+ v-show="shouldShowCommentButton"
+ type="button"
+ class="add-diff-note js-add-diff-note-button qa-diff-comment"
+ title="Add a comment to this line"
+ @click="handleCommentButton"
+ >
+ <icon :size="12" name="comment" />
+ </button>
+ <a
+ v-if="lineNumber"
+ :data-linenumber="lineNumber"
+ :href="lineHref"
+ @click="setHighlightedRow(lineCode)"
>
- <template v-else>
- <button
- v-if="shouldRenderCommentButton"
- v-show="shouldShowCommentButton"
- type="button"
- class="add-diff-note js-add-diff-note-button qa-diff-comment"
- title="Add a comment to this line"
- @click="handleCommentButton"
- >
- <icon :size="12" name="comment" />
- </button>
- <a
- v-if="lineNumber"
- :data-linenumber="lineNumber"
- :href="lineHref"
- @click="setHighlightedRow(lineCode)"
- >
- </a>
- <diff-gutter-avatars
- v-if="shouldShowAvatarsOnGutter"
- :discussions="line.discussions"
- :discussions-expanded="line.discussionsExpanded"
- @toggleLineDiscussions="
- toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
- "
- />
- </template>
+ </a>
+ <diff-gutter-avatars
+ v-if="shouldShowAvatarsOnGutter"
+ :discussions="line.discussions"
+ :discussions-expanded="line.discussionsExpanded"
+ @toggleLineDiscussions="
+ toggleLineDiscussions({ lineCode, fileHash, expanded: !line.discussionsExpanded })
+ "
+ />
</div>
</template>
diff --git a/app/assets/javascripts/diffs/components/hidden_files_warning.vue b/app/assets/javascripts/diffs/components/hidden_files_warning.vue
index 119e139de21..035c2b3b11e 100644
--- a/app/assets/javascripts/diffs/components/hidden_files_warning.vue
+++ b/app/assets/javascripts/diffs/components/hidden_files_warning.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
export default {
props: {
total: {
diff --git a/app/assets/javascripts/diffs/components/inline_diff_expansion_row.vue b/app/assets/javascripts/diffs/components/inline_diff_expansion_row.vue
new file mode 100644
index 00000000000..6e732727f42
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/inline_diff_expansion_row.vue
@@ -0,0 +1,53 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import DiffExpansionCell from './diff_expansion_cell.vue';
+import { MATCH_LINE_TYPE } from '../constants';
+
+export default {
+ components: {
+ Icon,
+ DiffExpansionCell,
+ },
+ props: {
+ fileHash: {
+ type: String,
+ required: true,
+ },
+ contextLinesPath: {
+ type: String,
+ required: true,
+ },
+ line: {
+ type: Object,
+ required: true,
+ },
+ isTop: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isBottom: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ isMatchLine() {
+ return this.line.type === MATCH_LINE_TYPE;
+ },
+ },
+};
+</script>
+
+<template>
+ <tr v-if="isMatchLine" class="line_expansion match">
+ <diff-expansion-cell
+ :file-hash="fileHash"
+ :context-lines-path="contextLinesPath"
+ :line="line"
+ :is-top="isTop"
+ :is-bottom="isBottom"
+ />
+ </tr>
+</template>
diff --git a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
index 2d5262baeec..55a8df43c62 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_table_row.vue
@@ -2,6 +2,7 @@
import { mapActions, mapState } from 'vuex';
import DiffTableCell from './diff_table_cell.vue';
import {
+ MATCH_LINE_TYPE,
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
@@ -58,6 +59,9 @@ export default {
inlineRowId() {
return this.line.line_code || `${this.fileHash}_${this.line.old_line}_${this.line.new_line}`;
},
+ isMatchLine() {
+ return this.line.type === MATCH_LINE_TYPE;
+ },
},
created() {
this.newLineType = NEW_LINE_TYPE;
@@ -81,6 +85,7 @@ export default {
<template>
<tr
+ v-if="!isMatchLine"
:id="inlineRowId"
:class="classNameMap"
class="line_holder"
diff --git a/app/assets/javascripts/diffs/components/inline_diff_view.vue b/app/assets/javascripts/diffs/components/inline_diff_view.vue
index b2bc3d9914f..aee01409db7 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_view.vue
@@ -3,6 +3,7 @@ import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import inlineDiffTableRow from './inline_diff_table_row.vue';
import inlineDiffCommentRow from './inline_diff_comment_row.vue';
+import inlineDiffExpansionRow from './inline_diff_expansion_row.vue';
export default {
components: {
@@ -10,6 +11,7 @@ export default {
inlineDiffTableRow,
InlineDraftCommentRow: () =>
import('ee_component/batch_comments/components/inline_draft_comment_row.vue'),
+ inlineDiffExpansionRow,
},
mixins: [draftCommentsMixin],
props: {
@@ -43,10 +45,24 @@ export default {
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file js-diff-inline-view"
>
+ <!-- Need to insert an empty row to solve "table-layout:fixed" equal width when expansion row is the first line -->
+ <tr>
+ <td style="width: 50px;"></td>
+ <td style="width: 50px;"></td>
+ <td></td>
+ </tr>
<tbody>
<template v-for="(line, index) in diffLines">
+ <inline-diff-expansion-row
+ :key="`expand-${index}`"
+ :file-hash="diffFile.file_hash"
+ :context-lines-path="diffFile.context_lines_path"
+ :line="line"
+ :is-top="index === 0"
+ :is-bottom="index + 1 === diffLinesLength"
+ />
<inline-diff-table-row
- :key="line.line_code"
+ :key="`${line.line_code || index}`"
:file-hash="diffFile.file_hash"
:context-lines-path="diffFile.context_lines_path"
:line="line"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_expansion_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_expansion_row.vue
new file mode 100644
index 00000000000..c1b30eab199
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/parallel_diff_expansion_row.vue
@@ -0,0 +1,56 @@
+<script>
+import { MATCH_LINE_TYPE } from '../constants';
+import DiffExpansionCell from './diff_expansion_cell.vue';
+
+export default {
+ components: {
+ DiffExpansionCell,
+ },
+ props: {
+ fileHash: {
+ type: String,
+ required: true,
+ },
+ contextLinesPath: {
+ type: String,
+ required: true,
+ },
+ line: {
+ type: Object,
+ required: true,
+ },
+ isTop: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isBottom: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ computed: {
+ isMatchLineLeft() {
+ return this.line.left && this.line.left.type === MATCH_LINE_TYPE;
+ },
+ isMatchLineRight() {
+ return this.line.right && this.line.right.type === MATCH_LINE_TYPE;
+ },
+ },
+};
+</script>
+<template>
+ <tr class="line_expansion match">
+ <template v-if="isMatchLineLeft || isMatchLineRight">
+ <diff-expansion-cell
+ :file-hash="fileHash"
+ :context-lines-path="contextLinesPath"
+ :line="line.left"
+ :is-top="isTop"
+ :is-bottom="isBottom"
+ :colspan="4"
+ />
+ </template>
+ </tr>
+</template>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index c60246bf8ef..4c95d618b0f 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -3,6 +3,7 @@ import { mapActions, mapState } from 'vuex';
import $ from 'jquery';
import DiffTableCell from './diff_table_cell.vue';
import {
+ MATCH_LINE_TYPE,
NEW_LINE_TYPE,
OLD_LINE_TYPE,
CONTEXT_LINE_TYPE,
@@ -75,6 +76,12 @@ export default {
},
];
},
+ isMatchLineLeft() {
+ return this.line.left && this.line.left.type === MATCH_LINE_TYPE;
+ },
+ isMatchLineRight() {
+ return this.line.right && this.line.right.type === MATCH_LINE_TYPE;
+ },
},
created() {
this.newLineType = NEW_LINE_TYPE;
@@ -122,7 +129,7 @@ export default {
@mouseover="handleMouseMove"
@mouseout="handleMouseMove"
>
- <template v-if="line.left">
+ <template v-if="line.left && !isMatchLineLeft">
<diff-table-cell
:file-hash="fileHash"
:context-lines-path="contextLinesPath"
@@ -148,7 +155,7 @@ export default {
<td class="diff-line-num old_line empty-cell"></td>
<td class="line_content parallel left-side empty-cell"></td>
</template>
- <template v-if="line.right">
+ <template v-if="line.right && !isMatchLineRight">
<diff-table-cell
:file-hash="fileHash"
:context-lines-path="contextLinesPath"
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_view.vue b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
index c477e68c33c..d400eb2c586 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_view.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_view.vue
@@ -3,9 +3,11 @@ import { mapGetters } from 'vuex';
import draftCommentsMixin from 'ee_else_ce/diffs/mixins/draft_comments';
import parallelDiffTableRow from './parallel_diff_table_row.vue';
import parallelDiffCommentRow from './parallel_diff_comment_row.vue';
+import parallelDiffExpansionRow from './parallel_diff_expansion_row.vue';
export default {
components: {
+ parallelDiffExpansionRow,
parallelDiffTableRow,
parallelDiffCommentRow,
ParallelDraftCommentRow: () =>
@@ -43,8 +45,23 @@ export default {
:data-commit-id="commitId"
class="code diff-wrap-lines js-syntax-highlight text-file"
>
+ <!-- Need to insert an empty row to solve "table-layout:fixed" equal width when expansion row is the first line -->
+ <tr>
+ <td style="width: 50px;"></td>
+ <td></td>
+ <td style="width: 50px;"></td>
+ <td></td>
+ </tr>
<tbody>
<template v-for="(line, index) in diffLines">
+ <parallel-diff-expansion-row
+ :key="`expand-${index}`"
+ :file-hash="diffFile.file_hash"
+ :context-lines-path="diffFile.context_lines_path"
+ :line="line"
+ :is-top="index === 0"
+ :is-bottom="index + 1 === diffLinesLength"
+ />
<parallel-diff-table-row
:key="line.line_code"
:file-hash="diffFile.file_hash"
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 32e0d8f42ee..6695d9fe96c 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -109,7 +109,7 @@ export const toggleLineDiscussions = ({ commit }, options) => {
export const renderFileForDiscussionId = ({ commit, rootState, state }, discussionId) => {
const discussion = rootState.notes.discussions.find(d => d.id === discussionId);
- if (discussion) {
+ if (discussion && discussion.diff_file) {
const file = state.diffFiles.find(f => f.file_hash === discussion.diff_file.file_hash);
if (file) {
@@ -183,7 +183,7 @@ export const cancelCommentForm = ({ commit }, { lineCode, fileHash }) => {
};
export const loadMoreLines = ({ commit }, options) => {
- const { endpoint, params, lineNumbers, fileHash } = options;
+ const { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers } = options;
params.from_merge_request = true;
@@ -195,6 +195,8 @@ export const loadMoreLines = ({ commit }, options) => {
contextLines,
params,
fileHash,
+ isExpandDown,
+ nextLineNumbers,
});
});
};
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index a66f205bbbd..a6915a46c00 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -71,18 +71,30 @@ export default {
},
[types.ADD_CONTEXT_LINES](state, options) {
- const { lineNumbers, contextLines, fileHash } = options;
+ const { lineNumbers, contextLines, fileHash, isExpandDown, nextLineNumbers } = options;
const { bottom } = options.params;
const diffFile = findDiffFile(state.diffFiles, fileHash);
removeMatchLine(diffFile, lineNumbers, bottom);
- const lines = addLineReferences(contextLines, lineNumbers, bottom).map(line => ({
- ...line,
- line_code: line.line_code || `${fileHash}_${line.old_line}_${line.new_line}`,
- discussions: line.discussions || [],
- hasForm: false,
- }));
+ const lines = addLineReferences(
+ contextLines,
+ lineNumbers,
+ bottom,
+ isExpandDown,
+ nextLineNumbers,
+ ).map(line => {
+ const lineCode =
+ line.type === 'match'
+ ? `${fileHash}_${line.meta_data.old_pos}_${line.meta_data.new_pos}_match`
+ : line.line_code || `${fileHash}_${line.old_line}_${line.new_line}`;
+ return {
+ ...line,
+ line_code: lineCode,
+ discussions: line.discussions || [],
+ hasForm: false,
+ };
+ });
addContextLines({
inlineLines: diffFile.highlighted_diff_lines,
@@ -90,6 +102,7 @@ export default {
contextLines: lines,
bottom,
lineNumbers,
+ isExpandDown,
});
},
diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js
index 1c3ed84001c..d46bdea9b50 100644
--- a/app/assets/javascripts/diffs/store/utils.js
+++ b/app/assets/javascripts/diffs/store/utils.js
@@ -121,7 +121,7 @@ export function removeMatchLine(diffFile, lineNumbers, bottom) {
diffFile.parallel_diff_lines.splice(indexForParallel + factor, 1);
}
-export function addLineReferences(lines, lineNumbers, bottom) {
+export function addLineReferences(lines, lineNumbers, bottom, isExpandDown, nextLineNumbers) {
const { oldLineNumber, newLineNumber } = lineNumbers;
const lineCount = lines.length;
let matchLineIndex = -1;
@@ -135,15 +135,20 @@ export function addLineReferences(lines, lineNumbers, bottom) {
new_line: bottom ? newLineNumber + index + 1 : newLineNumber + index - lineCount,
});
}
-
return l;
});
if (matchLineIndex > -1) {
const line = linesWithNumbers[matchLineIndex];
- const targetLine = bottom
- ? linesWithNumbers[matchLineIndex - 1]
- : linesWithNumbers[matchLineIndex + 1];
+ let targetLine;
+
+ if (isExpandDown) {
+ targetLine = nextLineNumbers;
+ } else if (bottom) {
+ targetLine = linesWithNumbers[matchLineIndex - 1];
+ } else {
+ targetLine = linesWithNumbers[matchLineIndex + 1];
+ }
Object.assign(line, {
meta_data: {
@@ -152,26 +157,27 @@ export function addLineReferences(lines, lineNumbers, bottom) {
},
});
}
-
return linesWithNumbers;
}
export function addContextLines(options) {
- const { inlineLines, parallelLines, contextLines, lineNumbers } = options;
+ const { inlineLines, parallelLines, contextLines, lineNumbers, isExpandDown } = options;
const normalizedParallelLines = contextLines.map(line => ({
left: line,
right: line,
line_code: line.line_code,
}));
+ const factor = isExpandDown ? 1 : 0;
- if (options.bottom) {
+ if (!isExpandDown && options.bottom) {
inlineLines.push(...contextLines);
parallelLines.push(...normalizedParallelLines);
} else {
const inlineIndex = findIndexInInlineLines(inlineLines, lineNumbers);
const parallelIndex = findIndexInParallelLines(parallelLines, lineNumbers);
- inlineLines.splice(inlineIndex, 0, ...contextLines);
- parallelLines.splice(parallelIndex, 0, ...normalizedParallelLines);
+
+ inlineLines.splice(inlineIndex + factor, 0, ...contextLines);
+ parallelLines.splice(parallelIndex + factor, 0, ...normalizedParallelLines);
}
}
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 813045cb5e4..95e1e8af9b3 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { __, sprintf } from '~/locale';
import Timeago from 'timeago.js';
import _ from 'underscore';
@@ -286,9 +287,9 @@ export default {
* @returns {Boolean|Undefined}
*/
isLastDeployment() {
- // TODO: when the vue i18n rules are merged need to disable @gitlab/i18n/no-non-i18n-strings
// name: 'last?' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26#possible-false-positives
// Vue i18n ESLint rules issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/63560
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return this.model && this.model.last_deployment && this.model.last_deployment['last?'];
},
diff --git a/app/assets/javascripts/environments/components/stop_environment_modal.vue b/app/assets/javascripts/environments/components/stop_environment_modal.vue
index c78d86e9b97..2cc3412e075 100644
--- a/app/assets/javascripts/environments/components/stop_environment_modal.vue
+++ b/app/assets/javascripts/environments/components/stop_environment_modal.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlTooltipDirective } from '@gitlab/ui';
import GlModal from '~/vue_shared/components/gl_modal.vue';
import { s__, sprintf } from '~/locale';
diff --git a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
index ef1d1e49320..a734e8527dd 100644
--- a/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
+++ b/app/assets/javascripts/error_tracking_settings/components/error_tracking_form.vue
@@ -36,12 +36,14 @@ export default {
<label class="label-bold" for="error-tracking-api-host">{{ __('Sentry API URL') }}</label>
<div class="row">
<div class="col-8 col-md-9 gl-pr-0">
+ <!-- eslint-disable @gitlab/vue-i18n/no-bare-attribute-strings -->
<gl-form-input
id="error-tracking-api-host"
:value="apiHost"
placeholder="https://mysentryserver.com"
@input="$emit('update-api-host', $event)"
/>
+ <!-- eslint-enable @gitlab/vue-i18n/no-bare-attribute-strings -->
</div>
</div>
<p class="form-text text-muted">
diff --git a/app/assets/javascripts/graphql_shared/fragments/pageInfo.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/pageInfo.fragment.graphql
new file mode 100644
index 00000000000..7403fd6d3c2
--- /dev/null
+++ b/app/assets/javascripts/graphql_shared/fragments/pageInfo.fragment.graphql
@@ -0,0 +1,4 @@
+fragment PageInfo on PageInfo {
+ hasNextPage
+ endCursor
+}
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 9909f437fc8..830385941d8 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -129,7 +129,7 @@ export default {
<item-stats-value
:icon-name="visibilityIcon"
:title="visibilityTooltip"
- css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4"
+ css-class="item-visibility d-inline-flex align-items-center prepend-top-8 append-right-4 text-secondary"
/>
<span v-if="group.permission" class="user-access-role prepend-top-8">
{{ group.permission }}
diff --git a/app/assets/javascripts/ide/components/branches/item.vue b/app/assets/javascripts/ide/components/branches/item.vue
index 4dff3f7e755..5c048749060 100644
--- a/app/assets/javascripts/ide/components/branches/item.vue
+++ b/app/assets/javascripts/ide/components/branches/item.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import Icon from '~/vue_shared/components/icon.vue';
import Timeago from '~/vue_shared/components/time_ago_tooltip.vue';
import router from '../../ide_router';
diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue
index 3cfdc1a367a..db8365a08e0 100644
--- a/app/assets/javascripts/ide/components/branches/search_list.vue
+++ b/app/assets/javascripts/ide/components/branches/search_list.vue
@@ -58,26 +58,24 @@ export default {
<template>
<div>
- <div class="dropdown-input mt-3 pb-3 mb-0 border-bottom">
- <div class="position-relative">
- <input
- ref="searchInput"
- v-model="search"
- :placeholder="__('Search branches')"
- type="search"
- class="form-control dropdown-input-field"
- @input="searchBranches"
- />
- <icon :size="18" name="search" class="input-icon" />
- </div>
- </div>
+ <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block position-relative" @click.stop>
+ <input
+ ref="searchInput"
+ v-model="search"
+ :placeholder="__('Search branches')"
+ type="search"
+ class="form-control dropdown-input-field"
+ @input="searchBranches"
+ />
+ <icon :size="18" name="search" class="ml-3 input-icon" />
+ </label>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<gl-loading-icon
v-if="isLoading"
:size="2"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
/>
- <ul v-else class="mb-3 w-100">
+ <ul v-else class="mb-0 w-100">
<template v-if="hasBranches">
<li v-for="item in branches" :key="item.name">
<item :item="item" :project-id="currentProjectId" :is-active="isActiveBranch(item)" />
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
index c8fbc3cb9f1..302adccd759 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/list_item.vue
@@ -45,6 +45,8 @@ export default {
},
computed: {
iconName() {
+ // name: '-solid' 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
const suffix = this.stagedList ? '-solid' : '';
return `${getCommitIconMap(this.file).icon}${suffix}`;
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
index b1d5de8682d..137f8bb18c7 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/success_message.vue
@@ -10,7 +10,9 @@ export default {
<template>
<div class="multi-file-commit-panel-success-message" aria-live="assertive">
- <div class="svg-content svg-80"><img :src="committedStateSvgPath" alt="" /></div>
+ <div class="svg-content svg-80">
+ <img :src="committedStateSvgPath" :alt="s__('IDE|Successful commit')" />
+ </div>
<div class="append-right-default prepend-left-default">
<div class="text-content text-center">
<h4>{{ __('All changes are committed') }}</h4>
diff --git a/app/assets/javascripts/ide/components/file_row_extra.vue b/app/assets/javascripts/ide/components/file_row_extra.vue
index 80a6ab9598a..7254c50a568 100644
--- a/app/assets/javascripts/ide/components/file_row_extra.vue
+++ b/app/assets/javascripts/ide/components/file_row_extra.vue
@@ -87,7 +87,6 @@ export default {
:file="file"
:show-tooltip="true"
:show-staged-icon="true"
- :force-modified-icon="true"
/>
<new-dropdown
:type="file.type"
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index 206b8341aad..326589fa50f 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapActions, mapState, mapGetters } from 'vuex';
import IdeStatusList from 'ee_else_ce/ide/components/ide_status_list.vue';
import icon from '~/vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index 2d55ffb3c65..5daf2d1422c 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -76,19 +76,17 @@ export default {
<template>
<div>
- <div class="dropdown-input mt-3 pb-3 mb-0 border-bottom">
- <div class="position-relative">
- <tokened-input
- v-model="search"
- :tokens="searchTokens"
- :placeholder="__('Search merge requests')"
- @focus="onSearchFocus"
- @input="searchMergeRequests"
- @removeToken="setSearchType(null)"
- />
- <icon :size="18" name="search" class="input-icon" />
- </div>
- </div>
+ <label class="dropdown-input pt-3 pb-3 mb-0 border-bottom block" @click.stop>
+ <tokened-input
+ v-model="search"
+ :tokens="searchTokens"
+ :placeholder="__('Search merge requests')"
+ @focus="onSearchFocus"
+ @input="searchMergeRequests"
+ @removeToken="setSearchType(null)"
+ />
+ <icon :size="18" name="search" class="ml-3 input-icon" />
+ </label>
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<gl-loading-icon
v-if="isLoading"
@@ -96,7 +94,7 @@ export default {
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
/>
<template v-else>
- <ul class="mb-3 w-100">
+ <ul class="mb-0 w-100">
<template v-if="showSearchTypes">
<li v-for="searchType in $options.searchTypes" :key="searchType.type">
<button
@@ -107,7 +105,7 @@ export default {
<span class="d-flex append-right-default ide-search-list-current-icon">
<icon :size="18" name="search" />
</span>
- <span> {{ searchType.label }} </span>
+ <span>{{ searchType.label }}</span>
</button>
</li>
</template>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index 03756a634d5..802b7f1fa6f 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -4,7 +4,12 @@ import { viewerInformationForPath } from '~/vue_shared/components/content_viewer
import flash from '~/flash';
import ContentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
-import { activityBarViews, viewerTypes } from '../constants';
+import {
+ activityBarViews,
+ viewerTypes,
+ FILE_VIEW_MODE_EDITOR,
+ FILE_VIEW_MODE_PREVIEW,
+} from '../constants';
import Editor from '../lib/editor';
import ExternalLink from './external_link.vue';
import FileTemplatesBar from './file_templates/bar.vue';
@@ -49,10 +54,10 @@ export default {
return this.shouldHideEditor && this.file.mrChange && this.viewer === viewerTypes.mr;
},
isEditorViewMode() {
- return this.file.viewMode === 'editor';
+ return this.file.viewMode === FILE_VIEW_MODE_EDITOR;
},
isPreviewViewMode() {
- return this.file.viewMode === 'preview';
+ return this.file.viewMode === FILE_VIEW_MODE_PREVIEW;
},
editTabCSS() {
return {
@@ -85,7 +90,7 @@ export default {
if (this.currentActivityView !== activityBarViews.edit) {
this.setFileViewMode({
file: this.file,
- viewMode: 'editor',
+ viewMode: FILE_VIEW_MODE_EDITOR,
});
}
}
@@ -94,7 +99,7 @@ export default {
if (this.currentActivityView !== activityBarViews.edit) {
this.setFileViewMode({
file: this.file,
- viewMode: 'editor',
+ viewMode: FILE_VIEW_MODE_EDITOR,
});
}
},
@@ -244,6 +249,8 @@ export default {
},
},
viewerTypes,
+ FILE_VIEW_MODE_EDITOR,
+ FILE_VIEW_MODE_PREVIEW,
};
</script>
@@ -255,7 +262,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'editor' })"
+ @click.prevent="setFileViewMode({ file, viewMode: $options.FILE_VIEW_MODE_EDITOR })"
>
<template v-if="viewer === $options.viewerTypes.edit">{{ __('Edit') }}</template>
<template v-else>{{ __('Review') }}</template>
@@ -265,7 +272,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'preview' })"
+ @click.prevent="setFileViewMode({ file, viewMode: $options.FILE_VIEW_MODE_PREVIEW })"
>{{ file.previewMode.previewTitle }}</a
>
</li>
diff --git a/app/assets/javascripts/ide/constants.js b/app/assets/javascripts/ide/constants.js
index e30670e119f..673ac1bfa9a 100644
--- a/app/assets/javascripts/ide/constants.js
+++ b/app/assets/javascripts/ide/constants.js
@@ -4,6 +4,10 @@ export const MAX_WINDOW_HEIGHT_COMPACT = 750;
export const MAX_TITLE_LENGTH = 50;
export const MAX_BODY_LENGTH = 72;
+// File view modes
+export const FILE_VIEW_MODE_EDITOR = 'editor';
+export const FILE_VIEW_MODE_PREVIEW = 'preview';
+
export const activityBarViews = {
edit: 'ide-tree',
commit: 'commit-section',
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 840761f68db..ba33b6826d6 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -56,13 +56,7 @@ export default {
return Api.branchSingle(projectId, currentBranchId);
},
commit(projectId, payload) {
- // Currently the `commit` endpoint does not support `start_sha` so we
- // have to make the request in the FE. This is not ideal and will be
- // resolved soon. https://gitlab.com/gitlab-org/gitlab-ce/issues/59023
- const { branch, start_sha: ref } = payload;
- const branchPromise = ref ? Api.createBranch(projectId, { ref, branch }) : Promise.resolve();
-
- return branchPromise.then(() => Api.commitMultiple(projectId, payload));
+ return Api.commitMultiple(projectId, payload);
},
getFiles(projectUrl, branchId) {
const url = `${projectUrl}/files/${branchId}`;
diff --git a/app/assets/javascripts/ide/stores/mutations/file.js b/app/assets/javascripts/ide/stores/mutations/file.js
index a52f1e235ed..1442ea7dbfa 100644
--- a/app/assets/javascripts/ide/stores/mutations/file.js
+++ b/app/assets/javascripts/ide/stores/mutations/file.js
@@ -43,10 +43,14 @@ export default {
[stateEntry, stagedFile, openFile, changedFile].forEach(f => {
if (f) {
- Object.assign(f, convertObjectPropsToCamelCase(data, { dropKeys: ['raw', 'baseRaw'] }), {
- raw: (stateEntry && stateEntry.raw) || null,
- baseRaw: null,
- });
+ Object.assign(
+ f,
+ convertObjectPropsToCamelCase(data, { dropKeys: ['path', 'name', 'raw', 'baseRaw'] }),
+ {
+ raw: (stateEntry && stateEntry.raw) || null,
+ baseRaw: null,
+ },
+ );
}
});
},
diff --git a/app/assets/javascripts/ide/stores/utils.js b/app/assets/javascripts/ide/stores/utils.js
index 01f78a29cf6..52200ce7847 100644
--- a/app/assets/javascripts/ide/stores/utils.js
+++ b/app/assets/javascripts/ide/stores/utils.js
@@ -1,4 +1,4 @@
-import { commitActionTypes } from '../constants';
+import { commitActionTypes, FILE_VIEW_MODE_EDITOR } from '../constants';
export const dataStructure = () => ({
id: '',
@@ -43,7 +43,7 @@ export const dataStructure = () => ({
editorColumn: 1,
fileLanguage: '',
eol: '',
- viewMode: 'editor',
+ viewMode: FILE_VIEW_MODE_EDITOR,
previewMode: null,
size: 0,
parentPath: null,
@@ -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,
@@ -155,11 +155,11 @@ export const createCommitPayload = ({
last_commit_id:
newBranch || f.deleted || f.prevPath || f.replaces ? undefined : f.lastCommitSha,
})),
- start_sha: newBranch ? rootGetters.lastCommit.short_id : undefined,
+ start_sha: newBranch ? rootGetters.lastCommit.id : undefined,
});
export const createNewMergeRequestUrl = (projectUrl, source, target) =>
- `${projectUrl}/merge_requests/new?merge_request[source_branch]=${source}&merge_request[target_branch]=${target}`;
+ `${projectUrl}/merge_requests/new?merge_request[source_branch]=${source}&merge_request[target_branch]=${target}&nav_source=webide`;
const sortTreesByTypeAndName = (a, b) => {
if (a.type === 'tree' && b.type === 'blob') {
diff --git a/app/assets/javascripts/issuable_suggestions/components/item.vue b/app/assets/javascripts/issuable_suggestions/components/item.vue
index 9a16b486bf5..7629e04684c 100644
--- a/app/assets/javascripts/issuable_suggestions/components/item.vue
+++ b/app/assets/javascripts/issuable_suggestions/components/item.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore';
import { GlLink, GlTooltip, GlTooltipDirective } from '@gitlab/ui';
import { __ } from '~/locale';
diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue
index b2f9296c68b..eb51a074f84 100644
--- a/app/assets/javascripts/issue_show/components/edit_actions.vue
+++ b/app/assets/javascripts/issue_show/components/edit_actions.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { __, sprintf } from '~/locale';
import updateMixin from '../mixins/update';
import eventHub from '../event_hub';
diff --git a/app/assets/javascripts/issue_show/components/edited.vue b/app/assets/javascripts/issue_show/components/edited.vue
index 14ad8d3b7c9..2c92324d292 100644
--- a/app/assets/javascripts/issue_show/components/edited.vue
+++ b/app/assets/javascripts/issue_show/components/edited.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default {
diff --git a/app/assets/javascripts/issue_show/components/fields/description_template.vue b/app/assets/javascripts/issue_show/components/fields/description_template.vue
index 6f955928d8e..bc3c81d479e 100644
--- a/app/assets/javascripts/issue_show/components/fields/description_template.vue
+++ b/app/assets/javascripts/issue_show/components/fields/description_template.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import $ from 'jquery';
import IssuableTemplateSelectors from '../../../templates/issuable_template_selectors';
diff --git a/app/assets/javascripts/issue_show/components/form.vue b/app/assets/javascripts/issue_show/components/form.vue
index 528ccb77efc..d48bf1fe7a9 100644
--- a/app/assets/javascripts/issue_show/components/form.vue
+++ b/app/assets/javascripts/issue_show/components/form.vue
@@ -70,6 +70,9 @@ export default {
hasIssuableTemplates() {
return this.issuableTemplates.length;
},
+ showLockedWarning() {
+ return this.formState.lockedWarningVisible && !this.formState.updateLoading;
+ },
},
created() {
eventHub.$on('delete.issuable', this.resetAutosave);
@@ -117,7 +120,7 @@ export default {
<template>
<form>
- <locked-warning v-if="formState.lockedWarningVisible" />
+ <locked-warning v-if="showLockedWarning" />
<div class="row">
<div v-if="hasIssuableTemplates" class="col-sm-4 col-lg-3">
<description-template
diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue
index b651a6e4bfb..9fac880c5f8 100644
--- a/app/assets/javascripts/jobs/components/commit_block.vue
+++ b/app/assets/javascripts/jobs/components/commit_block.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlLink } from '@gitlab/ui';
import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue
index 04f910b6b80..e2bc413e3ce 100644
--- a/app/assets/javascripts/jobs/components/empty_state.vue
+++ b/app/assets/javascripts/jobs/components/empty_state.vue
@@ -1,9 +1,11 @@
<script>
import { GlLink } from '@gitlab/ui';
+import ManualVariablesForm from './manual_variables_form.vue';
export default {
components: {
GlLink,
+ ManualVariablesForm,
},
props: {
illustrationPath: {
@@ -23,6 +25,21 @@ export default {
required: false,
default: null,
},
+ playable: {
+ type: Boolean,
+ required: true,
+ default: false,
+ },
+ scheduled: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ variablesSettingsUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
action: {
type: Object,
required: false,
@@ -37,28 +54,40 @@ export default {
},
},
},
+ computed: {
+ shouldRenderManualVariables() {
+ return this.playable && !this.scheduled;
+ },
+ },
};
</script>
<template>
<div class="row empty-state">
<div class="col-12">
- <div :class="illustrationSizeClass" class="svg-content"><img :src="illustrationPath" /></div>
+ <div :class="illustrationSizeClass" class="svg-content">
+ <img :src="illustrationPath" />
+ </div>
</div>
<div class="col-12">
<div class="text-content">
<h4 class="js-job-empty-state-title text-center">{{ title }}</h4>
- <p v-if="content" class="js-job-empty-state-content text-center">{{ content }}</p>
-
- <div v-if="action" class="text-center">
+ <p v-if="content" class="js-job-empty-state-content">{{ content }}</p>
+ </div>
+ <manual-variables-form
+ v-if="shouldRenderManualVariables"
+ :action="action"
+ :variables-settings-url="variablesSettingsUrl"
+ />
+ <div class="text-content">
+ <div v-if="action && !shouldRenderManualVariables" class="text-center">
<gl-link
:href="action.path"
:data-method="action.method"
class="js-job-empty-state-action btn btn-primary"
+ >{{ action.button_title }}</gl-link
>
- {{ action.button_title }}
- </gl-link>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index 79fb67d38cd..8da87f424c4 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -45,6 +45,11 @@ export default {
required: false,
default: null,
},
+ variablesSettingsUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
runnerHelpUrl: {
type: String,
required: false,
@@ -68,6 +73,10 @@ export default {
type: String,
required: true,
},
+ projectPath: {
+ type: String,
+ required: true,
+ },
logState: {
type: String,
required: true,
@@ -253,6 +262,7 @@ export default {
:quota-used="job.runners.quota.used"
:quota-limit="job.runners.quota.limit"
:runners-path="runnerHelpUrl"
+ :project-path="projectPath"
/>
<environments-block
@@ -313,6 +323,9 @@ export default {
:title="emptyStateTitle"
:content="emptyStateIllustration.content"
:action="emptyStateAction"
+ :playable="job.playable"
+ :scheduled="job.scheduled"
+ :variables-settings-url="variablesSettingsUrl"
/>
<!-- EO empty state -->
diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue
index d611b370ab9..a3fbe9338ee 100644
--- a/app/assets/javascripts/jobs/components/job_log.vue
+++ b/app/assets/javascripts/jobs/components/job_log.vue
@@ -48,9 +48,14 @@ export default {
}
},
removeEventListener() {
- this.$el
- .querySelectorAll('.js-section-start')
- .forEach(el => el.removeEventListener('click', this.handleSectionClick));
+ this.$el.querySelectorAll('.js-section-start').forEach(el => {
+ const titleSection = el.nextSibling;
+ titleSection.removeEventListener(
+ 'click',
+ this.handleHeaderClick.bind(this, el, el.dataset.section),
+ );
+ el.removeEventListener('click', this.handleSectionClick);
+ });
},
/**
* The collapsible rows are sent in HTML from the backend
@@ -58,9 +63,28 @@ export default {
*
*/
handleCollapsibleRows() {
- this.$el
- .querySelectorAll('.js-section-start')
- .forEach(el => el.addEventListener('click', this.handleSectionClick));
+ this.$el.querySelectorAll('.js-section-start').forEach(el => {
+ const titleSection = el.nextSibling;
+ titleSection.addEventListener(
+ 'click',
+ this.handleHeaderClick.bind(this, el, el.dataset.section),
+ );
+ el.addEventListener('click', this.handleSectionClick);
+ });
+ },
+
+ handleHeaderClick(arrowElement, section) {
+ this.updateToggleSection(arrowElement, section);
+ },
+
+ updateToggleSection(arrow, section) {
+ // toggle the arrow class
+ arrow.classList.toggle('fa-caret-right');
+ arrow.classList.toggle('fa-caret-down');
+
+ // hide the sections
+ const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`);
+ sibilings.forEach(row => row.classList.toggle('hidden'));
},
/**
* On click, we toggle the hidden class of
@@ -68,14 +92,7 @@ export default {
*/
handleSectionClick(evt) {
const clickedArrow = evt.currentTarget;
- // toggle the arrow class
- clickedArrow.classList.toggle('fa-caret-right');
- clickedArrow.classList.toggle('fa-caret-down');
-
- const { section } = clickedArrow.dataset;
- const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`);
-
- sibilings.forEach(row => row.classList.toggle('hidden'));
+ this.updateToggleSection(clickedArrow, clickedArrow.dataset.section);
},
},
};
diff --git a/app/assets/javascripts/jobs/components/manual_variables_form.vue b/app/assets/javascripts/jobs/components/manual_variables_form.vue
new file mode 100644
index 00000000000..c32a3cac7be
--- /dev/null
+++ b/app/assets/javascripts/jobs/components/manual_variables_form.vue
@@ -0,0 +1,179 @@
+<script>
+import _ from 'underscore';
+import { mapActions } from 'vuex';
+import { GlButton } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ name: 'ManualVariablesForm',
+ components: {
+ GlButton,
+ Icon,
+ },
+ props: {
+ action: {
+ type: Object,
+ required: false,
+ default: null,
+ validator(value) {
+ return (
+ value === null ||
+ (_.has(value, 'path') && _.has(value, 'method') && _.has(value, 'button_title'))
+ );
+ },
+ },
+ variablesSettingsUrl: {
+ type: String,
+ required: true,
+ default: '',
+ },
+ },
+ inputTypes: {
+ key: 'key',
+ value: 'value',
+ },
+ i18n: {
+ keyPlaceholder: s__('CiVariables|Input variable key'),
+ valuePlaceholder: s__('CiVariables|Input variable value'),
+ },
+ data() {
+ return {
+ variables: [],
+ key: '',
+ secretValue: '',
+ };
+ },
+ computed: {
+ helpText() {
+ return sprintf(
+ s__(
+ 'CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default',
+ ),
+ {
+ linkStart: `<a href="${this.variablesSettingsUrl}">`,
+ linkEnd: '</a>',
+ },
+ false,
+ );
+ },
+ },
+ watch: {
+ key(newVal) {
+ this.handleValueChange(newVal, this.$options.inputTypes.key);
+ },
+ secretValue(newVal) {
+ this.handleValueChange(newVal, this.$options.inputTypes.value);
+ },
+ },
+ methods: {
+ ...mapActions(['triggerManualJob']),
+ handleValueChange(newValue, type) {
+ if (newValue !== '') {
+ this.createNewVariable(type);
+ this.resetForm();
+ }
+ },
+ createNewVariable(type) {
+ const newVariable = {
+ key: this.key,
+ secret_value: this.secretValue,
+ id: _.uniqueId(),
+ };
+
+ this.variables.push(newVariable);
+
+ return this.$nextTick().then(() => {
+ this.$refs[`${this.$options.inputTypes[type]}-${newVariable.id}`][0].focus();
+ });
+ },
+ resetForm() {
+ this.key = '';
+ this.secretValue = '';
+ },
+ deleteVariable(id) {
+ this.variables.splice(this.variables.findIndex(el => el.id === id), 1);
+ },
+ },
+};
+</script>
+<template>
+ <div class="js-manual-vars-form col-12">
+ <label>{{ s__('CiVariables|Variables') }}</label>
+
+ <div class="ci-table">
+ <div class="gl-responsive-table-row table-row-header pb-0 pt-0 border-0" role="row">
+ <div class="table-section section-50" role="rowheader">{{ s__('CiVariables|Key') }}</div>
+ <div class="table-section section-50" role="rowheader">{{ s__('CiVariables|Value') }}</div>
+ </div>
+
+ <div v-for="variable in variables" :key="variable.id" class="gl-responsive-table-row">
+ <div class="table-section section-50">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
+ <div class="table-mobile-content append-right-10">
+ <input
+ :ref="`${$options.inputTypes.key}-${variable.id}`"
+ v-model="variable.key"
+ :placeholder="$options.i18n.keyPlaceholder"
+ class="ci-variable-body-item form-control"
+ />
+ </div>
+ </div>
+
+ <div class="table-section section-50">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
+ <div class="table-mobile-content append-right-10">
+ <input
+ :ref="`${$options.inputTypes.value}-${variable.id}`"
+ v-model="variable.secret_value"
+ :placeholder="$options.i18n.valuePlaceholder"
+ class="ci-variable-body-item form-control"
+ />
+ </div>
+ </div>
+
+ <div class="table-section section-10">
+ <div class="table-mobile-header" role="rowheader"></div>
+ <div class="table-mobile-content justify-content-end">
+ <gl-button class="btn-transparent btn-blank w-25" @click="deleteVariable(variable.id)">
+ <icon name="clear" />
+ </gl-button>
+ </div>
+ </div>
+ </div>
+ <div class="gl-responsive-table-row">
+ <div class="table-section section-50">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Key') }}</div>
+ <div class="table-mobile-content append-right-10">
+ <input
+ ref="inputKey"
+ v-model="key"
+ class="js-input-key form-control"
+ :placeholder="$options.i18n.keyPlaceholder"
+ />
+ </div>
+ </div>
+
+ <div class="table-section section-50">
+ <div class="table-mobile-header" role="rowheader">{{ s__('Pipeline|Value') }}</div>
+ <div class="table-mobile-content append-right-10">
+ <input
+ ref="inputSecretValue"
+ v-model="secretValue"
+ class="ci-variable-body-item form-control"
+ :placeholder="$options.i18n.valuePlaceholder"
+ />
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="d-flex prepend-top-default justify-content-center">
+ <p class="text-muted" v-html="helpText"></p>
+ </div>
+ <div class="d-flex justify-content-center">
+ <gl-button variant="primary" @click="triggerManualJob(variables)">
+ {{ action.button_title }}
+ </gl-button>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index e9704584c9f..06477477aad 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -73,15 +73,14 @@ export default {
},
renderBlock() {
return (
- this.job.merge_request ||
this.job.duration ||
- this.job.finished_data ||
+ this.job.finished_at ||
this.job.erased_at ||
this.job.queued ||
+ this.hasTimeout ||
this.job.runner ||
this.job.coverage ||
- this.job.tags.length ||
- this.job.cancel_path
+ this.job.tags.length
);
},
hasArtifact() {
@@ -160,7 +159,7 @@ export default {
</gl-link>
</div>
- <div :class="{ block: renderBlock }">
+ <div v-if="renderBlock" class="block">
<detail-row
v-if="job.duration"
:value="duration"
diff --git a/app/assets/javascripts/jobs/index.js b/app/assets/javascripts/jobs/index.js
index 25132449458..add7f9b710a 100644
--- a/app/assets/javascripts/jobs/index.js
+++ b/app/assets/javascripts/jobs/index.js
@@ -10,15 +10,29 @@ export default () => {
JobApp,
},
render(createElement) {
+ const {
+ deploymentHelpUrl,
+ runnerHelpUrl,
+ runnerSettingsUrl,
+ variablesSettingsUrl,
+ endpoint,
+ pagePath,
+ logState,
+ buildStatus,
+ projectPath,
+ } = element.dataset;
+
return createElement('job-app', {
props: {
- deploymentHelpUrl: element.dataset.deploymentHelpUrl,
- runnerHelpUrl: element.dataset.runnerHelpUrl,
- runnerSettingsUrl: element.dataset.runnerSettingsUrl,
- endpoint: element.dataset.endpoint,
- pagePath: element.dataset.buildOptionsPagePath,
- logState: element.dataset.buildOptionsLogState,
- buildStatus: element.dataset.buildOptionsBuildStatus,
+ deploymentHelpUrl,
+ runnerHelpUrl,
+ runnerSettingsUrl,
+ variablesSettingsUrl,
+ endpoint,
+ pagePath,
+ logState,
+ buildStatus,
+ projectPath,
},
});
},
diff --git a/app/assets/javascripts/jobs/store/actions.js b/app/assets/javascripts/jobs/store/actions.js
index 12d67a43599..a2daef96a2d 100644
--- a/app/assets/javascripts/jobs/store/actions.js
+++ b/app/assets/javascripts/jobs/store/actions.js
@@ -209,5 +209,19 @@ export const receiveJobsForStageError = ({ commit }) => {
flash(__('An error occurred while fetching the jobs.'));
};
+export const triggerManualJob = ({ state }, variables) => {
+ const parsedVariables = variables.map(variable => {
+ const copyVar = Object.assign({}, variable);
+ delete copyVar.id;
+ return copyVar;
+ });
+
+ axios
+ .post(state.job.status.action.path, {
+ job_variables_attributes: parsedVariables,
+ })
+ .catch(() => flash(__('An error occurred while triggering the job.')));
+};
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/lib/utils/color_utils.js b/app/assets/javascripts/lib/utils/color_utils.js
new file mode 100644
index 00000000000..07fb2915ca7
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/color_utils.js
@@ -0,0 +1,25 @@
+/**
+ * Convert hex color to rgb array
+ *
+ * @param hex string
+ * @returns array|null
+ */
+export const hexToRgb = hex => {
+ // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
+ const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
+ const fullHex = hex.replace(shorthandRegex, (_m, r, g, b) => r + r + g + g + b + b);
+
+ const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(fullHex);
+ return result
+ ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]
+ : null;
+};
+
+export const textColorForBackground = backgroundColor => {
+ const [r, g, b] = hexToRgb(backgroundColor);
+
+ if (r + g + b > 500) {
+ return '#333333';
+ }
+ return '#FFFFFF';
+};
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 5e90893b684..31c4a920bbe 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -44,6 +44,11 @@ export const isInIssuePage = () => checkPageAndAction('issues', 'show');
export const isInMRPage = () => checkPageAndAction('merge_requests', 'show');
export const isInEpicPage = () => checkPageAndAction('epics', 'show');
+export const getCspNonceValue = () => {
+ const metaTag = document.querySelector('meta[name=csp-nonce]');
+ return metaTag && metaTag.content;
+};
+
export const ajaxGet = url =>
axios
.get(url, {
@@ -51,7 +56,7 @@ export const ajaxGet = url =>
responseType: 'text',
})
.then(({ data }) => {
- $.globalEval(data);
+ $.globalEval(data, { nonce: getCspNonceValue() });
});
export const rstrip = val => {
diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js
index 062d21ed247..a4715789337 100644
--- a/app/assets/javascripts/lib/utils/datetime_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime_utility.js
@@ -2,8 +2,7 @@ import $ from 'jquery';
import _ from 'underscore';
import timeago from 'timeago.js';
import dateFormat from 'dateformat';
-import { pluralize } from './text_utility';
-import { languageCode, s__, __ } from '../../locale';
+import { languageCode, s__, __, n__ } from '../../locale';
window.timeago = timeago;
@@ -231,14 +230,10 @@ export const timeIntervalInWords = intervalInSeconds => {
const secondsInteger = parseInt(intervalInSeconds, 10);
const minutes = Math.floor(secondsInteger / 60);
const seconds = secondsInteger - minutes * 60;
- let text = '';
-
- if (minutes >= 1) {
- text = `${minutes} ${pluralize('minute', minutes)} ${seconds} ${pluralize('second', seconds)}`;
- } else {
- text = `${seconds} ${pluralize('second', seconds)}`;
- }
- return text;
+ const secondsText = n__('%d second', '%d seconds', seconds);
+ return minutes >= 1
+ ? [n__('%d minute', '%d minutes', minutes), secondsText].join(' ')
+ : secondsText;
};
export const dateInWords = (date, abbreviated = false, hideYear = false) => {
diff --git a/app/assets/javascripts/lib/utils/forms.js b/app/assets/javascripts/lib/utils/forms.js
new file mode 100644
index 00000000000..106209a2f3a
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/forms.js
@@ -0,0 +1,12 @@
+export const serializeFormEntries = entries =>
+ entries.reduce((acc, { name, value }) => Object.assign(acc, { [name]: value }), {});
+
+export const serializeForm = form => {
+ const fdata = new FormData(form);
+ const entries = Array.from(fdata.keys()).map(key => {
+ const val = fdata.getAll(key);
+ return { name: key, value: val.length === 1 ? val[0] : val };
+ });
+
+ return serializeFormEntries(entries);
+};
diff --git a/app/assets/javascripts/lib/utils/icons_path.js b/app/assets/javascripts/lib/utils/icons_path.js
new file mode 100644
index 00000000000..1a1c3c8e7b3
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/icons_path.js
@@ -0,0 +1,3 @@
+// any import of '@gitlab/svgs/dist/icons.svg' will be overridden with this
+// to avoid asset duplication between sprockets and webpack
+export default gon && gon.sprite_icons;
diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js
index d38f59b5861..d13fbeb5fc7 100644
--- a/app/assets/javascripts/lib/utils/text_utility.js
+++ b/app/assets/javascripts/lib/utils/text_utility.js
@@ -29,14 +29,6 @@ export const humanize = string =>
string.charAt(0).toUpperCase() + string.replace(/_/g, ' ').slice(1);
/**
- * Adds an 's' to the end of the string when count is bigger than 0
- * @param {String} str
- * @param {Number} count
- * @returns {String}
- */
-export const pluralize = (str, count) => str + (count > 1 || count === 0 ? 's' : '');
-
-/**
* Replaces underscores with dashes
* @param {*} str
* @returns {String}
diff --git a/app/assets/javascripts/lib/utils/url_utility.js b/app/assets/javascripts/lib/utils/url_utility.js
index 32fd0990374..7ead9d46fbb 100644
--- a/app/assets/javascripts/lib/utils/url_utility.js
+++ b/app/assets/javascripts/lib/utils/url_utility.js
@@ -1,9 +1,15 @@
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) {
- const sPageURL = decodeURIComponent(window.location.search.substring(1));
+export function getParameterValues(sParam, url = window.location) {
+ const sPageURL = decodeURIComponent(new URL(url).search.substring(1));
return sPageURL.split('&').reduce((acc, urlParam) => {
const sParameterName = urlParam.split('=');
@@ -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 9e97f345717..ba33d72b1f3 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -107,6 +107,7 @@ function deferredInitialisation() {
.then(() => {
$('select.select2').select2({
width: 'resolve',
+ minimumResultsForSearch: 10,
dropdownAutoWidth: true,
});
diff --git a/app/assets/javascripts/manual_ordering.js b/app/assets/javascripts/manual_ordering.js
index 012d1e70410..29a0e5a904a 100644
--- a/app/assets/javascripts/manual_ordering.js
+++ b/app/assets/javascripts/manual_ordering.js
@@ -21,7 +21,7 @@ const updateIssue = (url, issueList, { move_before_id, move_after_id }) =>
const initManualOrdering = () => {
const issueList = document.querySelector('.manual-ordering');
- if (!issueList || !(gon.features && gon.features.manualSorting) || !(gon.current_user_id > 0)) {
+ if (!issueList || !(gon.current_user_id > 0)) {
return;
}
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 454ff4f284e..78d97a3c122 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,
@@ -37,7 +40,18 @@ export default {
},
projectPath: {
type: String,
- required: true,
+ required: false,
+ default: () => '',
+ },
+ showBorder: {
+ type: Boolean,
+ required: false,
+ default: () => false,
+ },
+ singleEmbed: {
+ type: Boolean,
+ required: false,
+ default: false,
},
thresholds: {
type: Array,
@@ -234,52 +248,57 @@ export default {
</script>
<template>
- <div class="prometheus-graph col-12 col-lg-6">
- <div class="prometheus-graph-header">
- <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
- </div>
- <gl-area-chart
- ref="areaChart"
- 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
+ class="prometheus-graph col-12"
+ :class="[showBorder ? 'p-2' : 'p-0', { 'col-lg-6': !singleEmbed }]"
+ >
+ <div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
+ </div>
+ <gl-area-chart
+ ref="areaChart"
+ 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 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 }}
+ <template v-else>
+ <template slot="tooltipTitle">
+ <div class="text-nowrap">
+ {{ tooltip.title }}
</div>
- </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>
- </template>
- </gl-area-chart>
+ </gl-area-chart>
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/charts/empty_chart.vue b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
new file mode 100644
index 00000000000..73682adc4ee
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/empty_chart.vue
@@ -0,0 +1,41 @@
+<script>
+import chartEmptyStateIllustration from '@gitlab/svgs/dist/illustrations/chart-empty-state.svg';
+import { chartHeight } from '../../constants';
+
+export default {
+ props: {
+ graphTitle: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ height: chartHeight,
+ };
+ },
+ computed: {
+ svgContainerStyle() {
+ return {
+ height: `${this.height}px`,
+ };
+ },
+ },
+ created() {
+ this.chartEmptyStateIllustration = chartEmptyStateIllustration;
+ },
+};
+</script>
+<template>
+ <div class="prometheus-graph col-12 col-lg-6 d-flex flex-column justify-content-center">
+ <div class="prometheus-graph-header">
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphTitle }}</h5>
+ </div>
+ <div
+ class="prepend-top-8 svg-w-100 d-flex align-items-center"
+ :style="svgContainerStyle"
+ v-html="chartEmptyStateIllustration"
+ ></div>
+ <h5 class="text-center prepend-top-8">{{ __('No data to display') }}</h5>
+ </div>
+</template>
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..2fdc75f63ca
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -0,0 +1,334 @@
+<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,
+ },
+ 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 col-lg-6" :class="[showBorder ? 'p-2' : 'p-0']">
+ <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 6eaced0c108..d330ceb836c 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -1,25 +1,32 @@
<script>
-import { GlButton, GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui';
+import {
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ GlFormGroup,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+} from '@gitlab/ui';
import _ from 'underscore';
import { mapActions, mapState } from 'vuex';
-import { s__ } from '~/locale';
+import { __, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
-import { getParameterValues } from '~/lib/utils/url_utility';
+import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
-import 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 { timeWindows, timeWindowsKeyNames } from '../constants';
-import { getTimeDiff } from '../utils';
+import { sidebarAnimationDuration, timeWindows } from '../constants';
+import { getTimeDiff, getTimeWindow } from '../utils';
-const sidebarAnimationDuration = 150;
let sidebarMutationObserver;
export default {
components: {
- MonitorAreaChart,
+ MonitorTimeSeriesChart,
MonitorSingleStatChart,
PanelType,
GraphGroup,
@@ -28,10 +35,12 @@ export default {
GlButton,
GlDropdown,
GlDropdownItem,
+ GlFormGroup,
GlModal,
},
directives: {
- GlModalDirective,
+ GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
props: {
externalDashboardUrl: {
@@ -132,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 {
@@ -140,6 +159,7 @@ export default {
selectedTimeWindow: '',
selectedTimeWindowKey: '',
formIsValid: null,
+ timeWindows: {},
};
},
computed: {
@@ -158,11 +178,11 @@ export default {
'multipleDashboardsEnabled',
'additionalPanelTypesEnabled',
]),
- groupsWithData() {
- return this.groups.filter(group => this.chartsWithData(group.metrics).length > 0);
+ firstDashboard() {
+ return this.allDashboards[0] || {};
},
selectedDashboardText() {
- return this.currentDashboard || (this.allDashboards[0] && this.allDashboards[0].display_name);
+ return this.currentDashboard || this.firstDashboard.display_name;
},
addingMetricsAvailable() {
return IS_EE && this.canAddMetrics && !this.showEmptyState;
@@ -180,17 +200,6 @@ export default {
currentDashboard: this.currentDashboard,
projectPath: this.projectPath,
});
-
- this.timeWindows = timeWindows;
- this.selectedTimeWindowKey =
- _.escape(getParameterValues('time_window')[0]) || timeWindowsKeyNames.eightHours;
-
- // Set default time window if the selectedTimeWindowKey is bogus
- if (!Object.keys(this.timeWindows).includes(this.selectedTimeWindowKey)) {
- this.selectedTimeWindowKey = timeWindowsKeyNames.eightHours;
- }
-
- this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
},
beforeDestroy() {
if (sidebarMutationObserver) {
@@ -201,7 +210,20 @@ export default {
if (!this.hasMetrics) {
this.setGettingStartedEmptyState();
} else {
- this.fetchData(getTimeDiff(this.selectedTimeWindow));
+ const defaultRange = getTimeDiff();
+ const start = getParameterValues('start')[0] || defaultRange.start;
+ const end = getParameterValues('end')[0] || defaultRange.end;
+
+ const range = {
+ start,
+ end,
+ };
+
+ this.timeWindows = timeWindows;
+ this.selectedTimeWindowKey = getTimeWindow(range);
+ this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
+
+ this.fetchData(range);
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -226,6 +248,19 @@ export default {
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
+ csvText(graphData) {
+ const chartData = graphData.queries[0].result[0].values;
+ const yLabel = graphData.y_label;
+ const header = `timestamp,${yLabel}\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);
+ },
+ downloadCsv(graphData) {
+ const data = new Blob([this.csvText(graphData)], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
// TODO: BEGIN, Duplicated code with panel_type until feature flag is removed
// Issue number: https://gitlab.com/gitlab-org/gitlab-ce/issues/63845
getGraphAlerts(queries) {
@@ -236,7 +271,15 @@ export default {
getGraphAlertValues(queries) {
return Object.values(this.getGraphAlerts(queries));
},
+ showToast() {
+ this.$toast.show(__('Link copied to clipboard'));
+ },
// TODO: END
+ generateLink(group, title, yLabel) {
+ const dashboard = this.currentDashboard || this.firstDashboard.path;
+ const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
+ return mergeUrlParams(params, window.location.href);
+ },
hideAddMetricModal() {
this.$refs.addMetricModal.hide();
},
@@ -255,7 +298,11 @@ export default {
return this.timeWindows[key] === this.selectedTimeWindow;
},
setTimeWindowParameter(key) {
- return `?time_window=${key}`;
+ const { start, end } = getTimeDiff(key);
+ return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
+ },
+ groupHasData(group) {
+ return this.chartsWithData(group.metrics).length > 0;
},
},
addMetric: {
@@ -267,124 +314,158 @@ export default {
<template>
<div class="prometheus-graphs">
- <div class="gl-p-3 border-bottom bg-gray-light d-flex justify-content-between">
- <div
- v-if="environmentsEndpoint"
- class="dropdowns d-flex align-items-center justify-content-between"
- >
- <div v-if="multipleDashboardsEnabled" class="d-flex align-items-center">
- <label class="mb-0">{{ __('Dashboard') }}</label>
- <gl-dropdown
- class="ml-2 mr-3 js-dashboards-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedDashboardText"
+ <div class="gl-p-3 pb-0 border-bottom bg-gray-light">
+ <div class="row">
+ <template v-if="environmentsEndpoint">
+ <gl-form-group
+ v-if="multipleDashboardsEnabled"
+ :label="__('Dashboard')"
+ label-size="sm"
+ label-for="monitor-dashboards-dropdown"
+ class="col-sm-12 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="dashboard in allDashboards"
- :key="dashboard.path"
- :active="dashboard.path === currentDashboard"
- active-class="is-active"
- :href="`?dashboard=${dashboard.path}`"
+ <gl-dropdown
+ id="monitor-dashboards-dropdown"
+ class="mb-0 d-flex js-dashboards-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="selectedDashboardText"
>
- {{ dashboard.display_name || dashboard.path }}
- </gl-dropdown-item>
- </gl-dropdown>
- </div>
- <div class="d-flex align-items-center">
- <strong>{{ s__('Metrics|Environment') }}</strong>
- <gl-dropdown
- class="prepend-left-10 js-environments-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="currentEnvironmentName"
- :disabled="environments.length === 0"
+ <gl-dropdown-item
+ v-for="dashboard in allDashboards"
+ :key="dashboard.path"
+ :active="dashboard.path === currentDashboard"
+ active-class="is-active"
+ :href="`?dashboard=${dashboard.path}`"
+ >{{ dashboard.display_name || dashboard.path }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </gl-form-group>
+
+ <gl-form-group
+ :label="s__('Metrics|Environment')"
+ label-size="sm"
+ label-for="monitor-environments-dropdown"
+ class="col-sm-6 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="environment in environments"
- :key="environment.id"
- :active="environment.name === currentEnvironmentName"
- active-class="is-active"
- :href="environment.metrics_path"
- >{{ environment.name }}</gl-dropdown-item
+ <gl-dropdown
+ id="monitor-environments-dropdown"
+ class="mb-0 d-flex js-environments-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="currentEnvironmentName"
+ :disabled="environments.length === 0"
>
- </gl-dropdown>
- </div>
- <div v-if="!showEmptyState" class="d-flex align-items-center prepend-left-8">
- <strong>{{ s__('Metrics|Show last') }}</strong>
- <gl-dropdown
- class="prepend-left-10 js-time-window-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedTimeWindow"
+ <gl-dropdown-item
+ v-for="environment in environments"
+ :key="environment.id"
+ :active="environment.name === currentEnvironmentName"
+ active-class="is-active"
+ :href="environment.metrics_path"
+ >{{ environment.name }}</gl-dropdown-item
+ >
+ </gl-dropdown>
+ </gl-form-group>
+
+ <gl-form-group
+ v-if="!showEmptyState"
+ :label="s__('Metrics|Show last')"
+ label-size="sm"
+ label-for="monitor-time-window-dropdown"
+ class="col-sm-6 col-md-4 col-lg-2"
>
- <gl-dropdown-item
- v-for="(value, key) in timeWindows"
- :key="key"
- :active="activeTimeWindow(key)"
- :href="setTimeWindowParameter(key)"
- active-class="active"
- >{{ value }}</gl-dropdown-item
+ <gl-dropdown
+ id="monitor-time-window-dropdown"
+ class="mb-0 d-flex js-time-window-dropdown"
+ toggle-class="dropdown-menu-toggle"
+ :text="selectedTimeWindow"
>
- </gl-dropdown>
- </div>
- </div>
- <div class="d-flex">
- <div v-if="addingMetricsAvailable">
- <gl-button
- v-gl-modal-directive="$options.addMetric.modalId"
- class="js-add-metric-button text-success border-success"
- >{{ $options.addMetric.title }}</gl-button
- >
- <gl-modal
- ref="addMetricModal"
- :modal-id="$options.addMetric.modalId"
- :title="$options.addMetric.title"
- >
- <form ref="customMetricsForm" :action="customMetricsPath" method="post">
- <custom-metrics-form-fields
- :validate-query-path="validateQueryPath"
- form-operation="post"
- @formValidation="setFormValidity"
- />
- </form>
- <div slot="modal-footer">
- <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button>
- <gl-button
- :disabled="!formIsValid"
- variant="success"
- @click="submitCustomMetricsForm"
- >{{ __('Save changes') }}</gl-button
+ <gl-dropdown-item
+ v-for="(value, key) in timeWindows"
+ :key="key"
+ :active="activeTimeWindow(key)"
+ :href="setTimeWindowParameter(key)"
+ active-class="active"
+ >{{ value }}</gl-dropdown-item
>
- </div>
- </gl-modal>
- </div>
- <gl-button
- v-if="externalDashboardUrl.length"
- class="js-external-dashboard-link prepend-left-8"
- variant="primary"
- :href="externalDashboardUrl"
- target="_blank"
+ </gl-dropdown>
+ </gl-form-group>
+ </template>
+
+ <gl-form-group
+ v-if="addingMetricsAvailable || externalDashboardUrl.length"
+ label-for="prometheus-graphs-dropdown-buttons"
+ class="dropdown-buttons col-lg d-lg-flex align-items-end"
>
- {{ __('View full dashboard') }}
- <icon name="external-link" />
- </gl-button>
+ <div id="prometheus-graphs-dropdown-buttons">
+ <gl-button
+ v-if="addingMetricsAvailable"
+ v-gl-modal="$options.addMetric.modalId"
+ class="mr-2 mt-1 js-add-metric-button text-success border-success"
+ >
+ {{ $options.addMetric.title }}
+ </gl-button>
+ <gl-modal
+ v-if="addingMetricsAvailable"
+ ref="addMetricModal"
+ :modal-id="$options.addMetric.modalId"
+ :title="$options.addMetric.title"
+ >
+ <form ref="customMetricsForm" :action="customMetricsPath" method="post">
+ <custom-metrics-form-fields
+ :validate-query-path="validateQueryPath"
+ form-operation="post"
+ @formValidation="setFormValidity"
+ />
+ </form>
+ <div slot="modal-footer">
+ <gl-button @click="hideAddMetricModal">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ :disabled="!formIsValid"
+ variant="success"
+ @click="submitCustomMetricsForm"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ </div>
+ </gl-modal>
+
+ <gl-button
+ v-if="externalDashboardUrl.length"
+ class="mt-1 js-external-dashboard-link"
+ variant="primary"
+ :href="externalDashboardUrl"
+ target="_blank"
+ rel="noopener noreferrer"
+ >
+ {{ __('View full dashboard') }}
+ <icon name="external-link" />
+ </gl-button>
+ </div>
+ </gl-form-group>
</div>
</div>
+
<div v-if="!showEmptyState">
<graph-group
- v-for="(groupData, index) in groupsWithData"
- :key="index"
+ v-for="(groupData, index) in groups"
+ :key="`${groupData.group}.${groupData.priority}`"
:name="groupData.group"
:show-panels="showPanels"
+ :collapse-group="groupHasData(groupData)"
>
<template v-if="additionalPanelTypesEnabled">
<panel-type
- v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
+ v-for="(graphData, graphIndex) in groupData.metrics"
:key="`panel-type-${graphIndex}`"
+ :clipboard-text="generateLink(groupData.group, graphData.title, graphData.y_label)"
:graph-data="graphData"
:dashboard-width="elWidth"
+ :alerts-endpoint="alertsEndpoint"
+ :prometheus-alerts-available="prometheusAlertsAvailable"
+ :index="`${index}-${graphIndex}`"
/>
</template>
<template v-else>
- <monitor-area-chart
+ <monitor-time-series-chart
v-for="(graphData, graphIndex) in chartsWithData(groupData.metrics)"
:key="graphIndex"
:graph-data="graphData"
@@ -392,16 +473,49 @@ export default {
:thresholds="getGraphAlertValues(graphData.queries)"
:container-width="elWidth"
:project-path="projectPath"
- group-id="monitor-area-chart"
+ group-id="monitor-time-series-chart"
>
- <alert-widget
- v-if="alertWidgetAvailable && graphData"
- :alerts-endpoint="alertsEndpoint"
- :relevant-queries="graphData.queries"
- :alerts-to-manage="getGraphAlerts(graphData.queries)"
- @setAlerts="setAlerts"
- />
- </monitor-area-chart>
+ <div class="d-flex align-items-center">
+ <alert-widget
+ v-if="alertWidgetAvailable && graphData"
+ :modal-id="`alert-modal-${index}-${graphIndex}`"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ <gl-dropdown
+ v-gl-tooltip
+ class="mx-2"
+ toggle-class="btn btn-transparent border-0"
+ :right="true"
+ :no-caret="true"
+ :title="__('More actions')"
+ >
+ <template slot="button-content">
+ <icon name="ellipsis_v" class="text-secondary" />
+ </template>
+ <gl-dropdown-item :href="downloadCsv(graphData)" download="chart_metrics.csv">
+ {{ __('Download CSV') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ class="js-chart-link"
+ :data-clipboard-text="
+ generateLink(groupData.group, graphData.title, graphData.y_label)
+ "
+ @click="showToast"
+ >
+ {{ __('Generate link to chart') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ v-if="alertWidgetAvailable"
+ v-gl-modal="`alert-modal-${index}-${graphIndex}`"
+ >
+ {{ __('Alerts') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+ </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
new file mode 100644
index 00000000000..e3256147618
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/embed.vue
@@ -0,0 +1,107 @@
+<script>
+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 { sidebarAnimationDuration } from '../constants';
+import { getTimeDiff } from '../utils';
+
+let sidebarMutationObserver;
+
+export default {
+ components: {
+ GraphGroup,
+ MonitorAreaChart,
+ },
+ props: {
+ dashboardUrl: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ const defaultRange = getTimeDiff();
+ const start = getParameterValues('start', this.dashboardUrl)[0] || defaultRange.start;
+ const end = getParameterValues('end', this.dashboardUrl)[0] || defaultRange.end;
+
+ const params = {
+ start,
+ end,
+ };
+
+ return {
+ params,
+ elWidth: 0,
+ };
+ },
+ computed: {
+ ...mapState('monitoringDashboard', ['groups', 'metricsWithData']),
+ charts() {
+ const groupWithMetrics = this.groups.find(group =>
+ group.metrics.find(chart => this.chartHasData(chart)),
+ ) || { metrics: [] };
+
+ return groupWithMetrics.metrics.filter(chart => this.chartHasData(chart));
+ },
+ isSingleChart() {
+ return this.charts.length === 1;
+ },
+ },
+ mounted() {
+ this.setInitialState();
+ this.fetchMetricsData(this.params);
+ sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
+ sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
+ attributes: true,
+ childList: false,
+ subtree: false,
+ });
+ },
+ beforeDestroy() {
+ if (sidebarMutationObserver) {
+ sidebarMutationObserver.disconnect();
+ }
+ },
+ methods: {
+ ...mapActions('monitoringDashboard', [
+ 'fetchMetricsData',
+ 'setEndpoints',
+ 'setFeatureFlags',
+ 'setShowErrorBanner',
+ ]),
+ chartHasData(chart) {
+ return chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id));
+ },
+ onSidebarMutation() {
+ setTimeout(() => {
+ this.elWidth = this.$el.clientWidth;
+ }, sidebarAnimationDuration);
+ },
+ setInitialState() {
+ this.setFeatureFlags({
+ prometheusEndpointEnabled: true,
+ });
+ this.setEndpoints({
+ dashboardEndpoint: removeParams(['start', 'end'], this.dashboardUrl),
+ });
+ this.setShowErrorBanner(false);
+ },
+ },
+};
+</script>
+<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
+ v-for="graphData in charts"
+ :key="graphData.title"
+ :graph-data="graphData"
+ :container-width="elWidth"
+ group-id="monitor-area-chart"
+ :project-path="null"
+ :show-border="true"
+ :single-embed="isSingleChart"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/graph_group.vue b/app/assets/javascripts/monitoring/components/graph_group.vue
index b20ad1802f3..0f5c5b3d60f 100644
--- a/app/assets/javascripts/monitoring/components/graph_group.vue
+++ b/app/assets/javascripts/monitoring/components/graph_group.vue
@@ -10,6 +10,10 @@ export default {
required: false,
default: true,
},
+ collapseGroup: {
+ type: Boolean,
+ required: true,
+ },
},
};
</script>
@@ -19,7 +23,7 @@ export default {
<div class="card-header">
<h4>{{ name }}</h4>
</div>
- <div class="card-body prometheus-graph-group"><slot></slot></div>
+ <div v-if="collapseGroup" class="card-body prometheus-graph-group"><slot></slot></div>
</div>
<div v-else class="prometheus-graph-group"><slot></slot></div>
</template>
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 45ee067de83..96f62bc85ee 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -1,15 +1,38 @@
<script>
import { mapState } from 'vuex';
import _ from 'underscore';
+import { __ } from '~/locale';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
import MonitorAreaChart from './charts/area.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
+import MonitorEmptyChart from './charts/empty_chart.vue';
export default {
components: {
MonitorAreaChart,
MonitorSingleStatChart,
+ MonitorEmptyChart,
+ Icon,
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ },
+ directives: {
+ GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
props: {
+ clipboardText: {
+ type: String,
+ required: true,
+ },
graphData: {
type: Object,
required: true,
@@ -18,12 +41,33 @@ export default {
type: Number,
required: true,
},
+ index: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState('monitoringDashboard', ['deploymentData', 'projectPath']),
alertWidgetAvailable() {
return IS_EE && this.prometheusAlertsAvailable && this.alertsEndpoint && this.graphData;
},
+ graphDataHasMetrics() {
+ return this.graphData.queries[0].result.length > 0;
+ },
+ csvText() {
+ const chartData = this.graphData.queries[0].result[0].values;
+ const yLabel = this.graphData.y_label;
+ const header = `timestamp,${yLabel}\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);
+ },
+ downloadCsv() {
+ const data = new Blob([this.csvText], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
},
methods: {
getGraphAlerts(queries) {
@@ -37,13 +81,19 @@ export default {
isPanelType(type) {
return this.graphData.type && this.graphData.type === type;
},
+ showToast() {
+ this.$toast.show(__('Link copied to clipboard'));
+ },
},
};
</script>
<template>
- <monitor-single-stat-chart v-if="isPanelType('single-stat')" :graph-data="graphData" />
+ <monitor-single-stat-chart
+ v-if="isPanelType('single-stat') && graphDataHasMetrics"
+ :graph-data="graphData"
+ />
<monitor-area-chart
- v-else
+ v-else-if="graphDataHasMetrics"
:graph-data="graphData"
:deployment-data="deploymentData"
:project-path="projectPath"
@@ -51,12 +101,41 @@ export default {
:container-width="dashboardWidth"
group-id="monitor-area-chart"
>
- <alert-widget
- v-if="alertWidgetAvailable"
- :alerts-endpoint="alertsEndpoint"
- :relevant-queries="graphData.queries"
- :alerts-to-manage="getGraphAlerts(graphData.queries)"
- @setAlerts="setAlerts"
- />
+ <div class="d-flex align-items-center">
+ <alert-widget
+ v-if="alertWidgetAvailable && graphData"
+ :modal-id="`alert-modal-${index}`"
+ :alerts-endpoint="alertsEndpoint"
+ :relevant-queries="graphData.queries"
+ :alerts-to-manage="getGraphAlerts(graphData.queries)"
+ @setAlerts="setAlerts"
+ />
+ <gl-dropdown
+ v-gl-tooltip
+ class="mx-2"
+ toggle-class="btn btn-transparent border-0"
+ :right="true"
+ :no-caret="true"
+ :title="__('More actions')"
+ >
+ <template slot="button-content">
+ <icon name="ellipsis_v" class="text-secondary" />
+ </template>
+ <gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
+ {{ __('Download CSV') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item
+ class="js-chart-link"
+ :data-clipboard-text="clipboardText"
+ @click="showToast"
+ >
+ {{ __('Generate link to chart') }}
+ </gl-dropdown-item>
+ <gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
+ {{ __('Alerts') }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
</monitor-area-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 26f1bf3f68d..13aba3d9f44 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -1,11 +1,17 @@
import { __ } from '~/locale';
+export const sidebarAnimationDuration = 300; // milliseconds.
+
export const chartHeight = 300;
export const graphTypes = {
deploymentData: 'scatter',
};
+export const symbolSizes = {
+ default: 14,
+};
+
export const lineTypes = {
default: 'solid',
};
@@ -19,11 +25,24 @@ export const timeWindows = {
oneWeek: __('1 week'),
};
-export const timeWindowsKeyNames = {
- thirtyMinutes: 'thirtyMinutes',
- threeHours: 'threeHours',
- eightHours: 'eightHours',
- oneDay: 'oneDay',
- threeDays: 'threeDays',
- oneWeek: 'oneWeek',
+export const dateFormats = {
+ timeOfDay: 'h:MM TT',
+ default: 'dd mmm yyyy, h:MMTT',
};
+
+export const secondsIn = {
+ thirtyMinutes: 60 * 30,
+ threeHours: 60 * 60 * 3,
+ eightHours: 60 * 60 * 8,
+ oneDay: 60 * 60 * 24 * 1,
+ threeDays: 60 * 60 * 24 * 3,
+ oneWeek: 60 * 60 * 24 * 7 * 1,
+};
+
+export const timeWindowsKeyNames = Object.keys(secondsIn).reduce(
+ (otherTimeWindows, timeWindow) => ({
+ ...otherTimeWindows,
+ [timeWindow]: timeWindow,
+ }),
+ {},
+);
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index c0fee1ebb99..51cef20455c 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,9 +1,12 @@
import Vue from 'vue';
+import { GlToast } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { getParameterValues } from '~/lib/utils/url_utility';
import Dashboard from 'ee_else_ce/monitoring/components/dashboard.vue';
import store from './stores';
+Vue.use(GlToast);
+
export default (props = {}) => {
const el = document.getElementById('prometheus-graphs');
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 5b3da51e9a6..0cbad179f17 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -44,6 +44,10 @@ export const setFeatureFlags = (
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
};
+export const setShowErrorBanner = ({ commit }, enabled) => {
+ commit(types.SET_SHOW_ERROR_BANNER, enabled);
+};
+
export const requestMetricsDashboard = ({ commit }) => {
commit(types.REQUEST_METRICS_DATA);
};
@@ -99,7 +103,9 @@ export const fetchMetricsData = ({ state, dispatch }, params) => {
})
.catch(error => {
dispatch('receiveMetricsDataFailure', error);
- createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ if (state.setShowErrorBanner) {
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ }
});
};
@@ -119,7 +125,9 @@ export const fetchDashboard = ({ state, dispatch }, params) => {
})
.catch(error => {
dispatch('receiveMetricsDashboardFailure', error);
- createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ if (state.setShowErrorBanner) {
+ createFlash(s__('Metrics|There was an error while retrieving metrics'));
+ }
});
};
@@ -143,7 +151,7 @@ function fetchPrometheusResult(prometheusEndpoint, params) {
*/
export const fetchPrometheusMetric = ({ commit }, { metric, params }) => {
const { start, end } = params;
- const timeDiff = end - start;
+ const timeDiff = (new Date(end) - new Date(start)) / 1000;
const minStep = 60;
const queryDataPoints = 600;
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index c89daba3df7..4b1aadbcf05 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -16,3 +16,4 @@ export const SET_ALL_DASHBOARDS = 'SET_ALL_DASHBOARDS';
export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
+export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 0104dcb867d..b19520d6638 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -96,4 +96,7 @@ export default {
[types.SET_ADDITIONAL_PANEL_TYPES_ENABLED](state, enabled) {
state.additionalPanelTypesEnabled = enabled;
},
+ [types.SET_SHOW_ERROR_BANNER](state, enabled) {
+ state.showErrorBanner = enabled;
+ },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index e54bb712695..440bdc951e0 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -12,6 +12,7 @@ export default () => ({
additionalPanelTypesEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
+ showErrorBanner: true,
groups: [],
deploymentData: [],
environments: [],
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 478e2b3d06c..46b01f753f8 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,35 +1,24 @@
-import { timeWindows } from './constants';
+import { secondsIn, timeWindowsKeyNames } from './constants';
-/**
- * method that converts a predetermined time window to minutes
- * defaults to 8 hours as the default option
- * @param {String} timeWindow - The time window to convert to minutes
- * @returns {number} The time window in minutes
- */
-const getTimeDifferenceSeconds = timeWindow => {
- switch (timeWindow) {
- case timeWindows.thirtyMinutes:
- return 60 * 30;
- case timeWindows.threeHours:
- return 60 * 60 * 3;
- case timeWindows.oneDay:
- return 60 * 60 * 24 * 1;
- case timeWindows.threeDays:
- return 60 * 60 * 24 * 3;
- case timeWindows.oneWeek:
- return 60 * 60 * 24 * 7 * 1;
- default:
- return 60 * 60 * 8;
- }
-};
+export const getTimeDiff = timeWindow => {
+ const end = Math.floor(Date.now() / 1000); // convert milliseconds to seconds
+ const difference = secondsIn[timeWindow] || secondsIn.eightHours;
+ const start = end - difference;
-export const getTimeDiff = selectedTimeWindow => {
- const end = Date.now() / 1000; // convert milliseconds to seconds
- const start = end - getTimeDifferenceSeconds(selectedTimeWindow);
-
- return { start, end };
+ return {
+ start: new Date(start * 1000).toISOString(),
+ end: new Date(end * 1000).toISOString(),
+ };
};
+export const getTimeWindow = ({ start, end }) =>
+ Object.entries(secondsIn).reduce((acc, [timeRange, value]) => {
+ if (end - start === value) {
+ return timeRange;
+ }
+ return acc;
+ }, timeWindowsKeyNames.eightHours);
+
/**
* This method is used to validate if the graph data format for a chart component
* that needs a time series as a response from a prometheus query (query_range) is
diff --git a/app/assets/javascripts/mr_notes/init_notes.js b/app/assets/javascripts/mr_notes/init_notes.js
index 842a209a545..622db360d1f 100644
--- a/app/assets/javascripts/mr_notes/init_notes.js
+++ b/app/assets/javascripts/mr_notes/init_notes.js
@@ -3,6 +3,7 @@ import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex';
import store from 'ee_else_ce/mr_notes/stores';
import notesApp from '../notes/components/notes_app.vue';
+import discussionKeyboardNavigator from '../notes/components/discussion_keyboard_navigator.vue';
export default () => {
// eslint-disable-next-line no-new
@@ -56,15 +57,23 @@ export default () => {
},
},
render(createElement) {
- return createElement('notes-app', {
- props: {
- noteableData: this.noteableData,
- notesData: this.notesData,
- userData: this.currentUserData,
- shouldShow: this.activeTab === 'show',
- helpPagePath: this.helpPagePath,
- },
- });
+ const isDiffView = this.activeTab === 'diffs';
+
+ // NOTE: Even though `discussionKeyboardNavigator` is added to the `notes-app`,
+ // it adds a global key listener so it works on the diffs tab as well.
+ // If we create a single Vue app for all of the MR tabs, we should move this
+ // up the tree, to the root.
+ return createElement(discussionKeyboardNavigator, { props: { isDiffView } }, [
+ createElement('notes-app', {
+ props: {
+ noteableData: this.noteableData,
+ notesData: this.notesData,
+ userData: this.currentUserData,
+ shouldShow: this.activeTab === 'show',
+ helpPagePath: this.helpPagePath,
+ },
+ }),
+ ]);
},
});
};
diff --git a/app/assets/javascripts/mr_notes/stores/index.js b/app/assets/javascripts/mr_notes/stores/index.js
index c4225c8ec08..8fbd0291a7d 100644
--- a/app/assets/javascripts/mr_notes/stores/index.js
+++ b/app/assets/javascripts/mr_notes/stores/index.js
@@ -9,7 +9,7 @@ Vue.use(Vuex);
export const createStore = () =>
new Vuex.Store({
modules: {
- page: mrPageModule,
+ page: mrPageModule(),
notes: notesModule(),
diffs: diffsModule(),
},
diff --git a/app/assets/javascripts/mr_notes/stores/modules/index.js b/app/assets/javascripts/mr_notes/stores/modules/index.js
index 660081f76c8..c28e666943b 100644
--- a/app/assets/javascripts/mr_notes/stores/modules/index.js
+++ b/app/assets/javascripts/mr_notes/stores/modules/index.js
@@ -2,11 +2,11 @@ import actions from '../actions';
import getters from '../getters';
import mutations from '../mutations';
-export default {
+export default () => ({
state: {
activeTab: null,
},
actions,
getters,
mutations,
-};
+});
diff --git a/app/assets/javascripts/mr_popover/components/mr_popover.vue b/app/assets/javascripts/mr_popover/components/mr_popover.vue
index c203cb0667c..b81600660f6 100644
--- a/app/assets/javascripts/mr_popover/components/mr_popover.vue
+++ b/app/assets/javascripts/mr_popover/components/mr_popover.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlPopover, GlSkeletonLoading } from '@gitlab/ui';
import Icon from '../../vue_shared/components/icon.vue';
import CiIcon from '../../vue_shared/components/ci_icon.vue';
@@ -7,7 +8,8 @@ import query from '../queries/merge_request.query.graphql';
import { mrStates, humanMRStates } from '../constants';
export default {
- name: 'MRPopover',
+ // name: 'MRPopover' is a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
+ name: 'MRPopover', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
components: {
GlPopover,
GlSkeletonLoading,
@@ -102,9 +104,11 @@ export default {
<ci-icon v-if="detailedStatus" :status="detailedStatus" />
</div>
<h5 class="my-2">{{ mergeRequestTitle }}</h5>
+ <!-- eslint-disable @gitlab/vue-i18n/no-bare-strings -->
<div class="text-secondary">
{{ `${projectPath}!${mergeRequestIID}` }}
</div>
+ <!-- eslint-enable @gitlab/vue-i18n/no-bare-strings -->
</div>
</gl-popover>
</template>
diff --git a/app/assets/javascripts/notebook/cells/markdown.vue b/app/assets/javascripts/notebook/cells/markdown.vue
index 61eabbcb8b2..9e4a92426ee 100644
--- a/app/assets/javascripts/notebook/cells/markdown.vue
+++ b/app/assets/javascripts/notebook/cells/markdown.vue
@@ -43,7 +43,7 @@ renderer.paragraph = t => {
if (typeof katex !== 'undefined') {
const katexString = text
.replace(/&amp;/g, '&')
- .replace(/&=&/g, '\\space=\\space')
+ .replace(/&=&/g, '\\space=\\space') // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
.replace(/<(\/?)em>/g, '_');
const regex = new RegExp(katexRegexString, 'gi');
const matchLocation = katexString.search(regex);
diff --git a/app/assets/javascripts/notebook/cells/output/image.vue b/app/assets/javascripts/notebook/cells/output/image.vue
index f1130275525..842d9e8da0d 100644
--- a/app/assets/javascripts/notebook/cells/output/image.vue
+++ b/app/assets/javascripts/notebook/cells/output/image.vue
@@ -25,7 +25,7 @@ export default {
},
computed: {
imgSrc() {
- return `data:${this.outputType};base64,${this.rawCode}`;
+ return `data:${this.outputType};base64,${this.rawCode}`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
},
showOutput() {
return this.index === 0;
diff --git a/app/assets/javascripts/notebook/index.vue b/app/assets/javascripts/notebook/index.vue
index e7056c03e4a..4a3c1a28279 100644
--- a/app/assets/javascripts/notebook/index.vue
+++ b/app/assets/javascripts/notebook/index.vue
@@ -39,7 +39,7 @@ export default {
},
methods: {
cellType(type) {
- return `${type}-cell`;
+ return `${type}-cell`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
},
},
};
diff --git a/app/assets/javascripts/notes/components/diff_with_note.vue b/app/assets/javascripts/notes/components/diff_with_note.vue
index 164e79c6294..df537ba1ed2 100644
--- a/app/assets/javascripts/notes/components/diff_with_note.vue
+++ b/app/assets/javascripts/notes/components/diff_with_note.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { mapState, mapActions } from 'vuex';
import DiffFileHeader from '~/diffs/components/diff_file_header.vue';
import DiffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
diff --git a/app/assets/javascripts/notes/components/discussion_actions.vue b/app/assets/javascripts/notes/components/discussion_actions.vue
index f4570c1292c..edab750b572 100644
--- a/app/assets/javascripts/notes/components/discussion_actions.vue
+++ b/app/assets/javascripts/notes/components/discussion_actions.vue
@@ -39,30 +39,27 @@ export default {
</script>
<template>
- <div class="discussion-with-resolve-btn">
+ <div class="discussion-with-resolve-btn clearfix">
<reply-placeholder
+ data-qa-selector="discussion_reply_tab"
:button-text="s__('MergeRequests|Reply...')"
- class="qa-discussion-reply"
@onClick="$emit('showReplyForm')"
/>
- <resolve-discussion-button
- v-if="discussion.resolvable"
- :is-resolving="isResolving"
- :button-title="resolveButtonTitle"
- @onClick="$emit('resolve')"
- />
- <div v-if="discussion.resolvable" class="btn-group discussion-actions ml-sm-2" role="group">
- <resolve-with-issue-button v-if="resolveWithIssuePath" :url="resolveWithIssuePath" />
- <jump-to-next-discussion-button
- v-if="shouldShowJumpToNextDiscussion"
- @onClick="$emit('jumpToNextDiscussion')"
- />
+
+ <div class="btn-group discussion-actions" role="group">
+ <div class="btn-group">
+ <resolve-discussion-button
+ v-if="discussion.resolvable"
+ :is-resolving="isResolving"
+ :button-title="resolveButtonTitle"
+ @onClick="$emit('resolve')"
+ />
+ </div>
<resolve-with-issue-button
v-if="discussion.resolvable && resolveWithIssuePath"
:url="resolveWithIssuePath"
/>
</div>
-
<div
v-if="discussion.resolvable && shouldShowJumpToNextDiscussion"
class="btn-group discussion-actions ml-sm-2"
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index eb3fbbe1385..743684e7046 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -61,7 +61,7 @@ export default {
},
methods: {
...mapActions(['filterDiscussion', 'setCommentsDisabled', 'setTargetNoteHash']),
- selectFilter(value) {
+ selectFilter(value, persistFilter = true) {
const filter = parseInt(value, 10);
// close dropdown
@@ -69,7 +69,11 @@ export default {
if (filter === this.currentValue) return;
this.currentValue = filter;
- this.filterDiscussion({ path: this.getNotesDataByProp('discussionsPath'), filter });
+ this.filterDiscussion({
+ path: this.getNotesDataByProp('discussionsPath'),
+ filter,
+ persistFilter,
+ });
this.toggleCommentsForm();
},
toggleDropdown() {
@@ -85,7 +89,7 @@ export default {
const hash = getLocationHash();
if (/^note_/.test(hash) && this.currentValue !== DISCUSSION_FILTERS_DEFAULT_VALUE) {
- this.selectFilter(this.defaultValue);
+ this.selectFilter(this.defaultValue, false);
this.toggleDropdown(); // close dropdown
this.setTargetNoteHash(hash);
}
diff --git a/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue b/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue
new file mode 100644
index 00000000000..7fbfe8eebb2
--- /dev/null
+++ b/app/assets/javascripts/notes/components/discussion_keyboard_navigator.vue
@@ -0,0 +1,51 @@
+<script>
+/* global Mousetrap */
+import 'mousetrap';
+import { mapGetters, mapActions } from 'vuex';
+import discussionNavigation from '~/notes/mixins/discussion_navigation';
+
+export default {
+ mixins: [discussionNavigation],
+ props: {
+ isDiffView: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ },
+ data() {
+ return {
+ currentDiscussionId: null,
+ };
+ },
+ computed: {
+ ...mapGetters(['nextUnresolvedDiscussionId', 'previousUnresolvedDiscussionId']),
+ },
+ mounted() {
+ Mousetrap.bind('n', () => this.jumpToNextDiscussion());
+ Mousetrap.bind('p', () => this.jumpToPreviousDiscussion());
+ },
+ beforeDestroy() {
+ Mousetrap.unbind('n');
+ Mousetrap.unbind('p');
+ },
+ methods: {
+ ...mapActions(['expandDiscussion']),
+ jumpToNextDiscussion() {
+ const nextId = this.nextUnresolvedDiscussionId(this.currentDiscussionId, this.isDiffView);
+
+ this.jumpToDiscussion(nextId);
+ this.currentDiscussionId = nextId;
+ },
+ jumpToPreviousDiscussion() {
+ const prevId = this.previousUnresolvedDiscussionId(this.currentDiscussionId, this.isDiffView);
+
+ this.jumpToDiscussion(prevId);
+ this.currentDiscussionId = prevId;
+ },
+ },
+ render() {
+ return this.$slots.default;
+ },
+};
+</script>
diff --git a/app/assets/javascripts/notes/components/note_edited_text.vue b/app/assets/javascripts/notes/components/note_edited_text.vue
index 15ce49d7c31..1af5af5c470 100644
--- a/app/assets/javascripts/notes/components/note_edited_text.vue
+++ b/app/assets/javascripts/notes/components/note_edited_text.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import timeAgoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
export default {
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 3823861c0b9..222badf70d1 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -293,12 +293,16 @@ export default {
<input
v-model="isUnresolving"
type="checkbox"
- class="qa-unresolve-review-discussion"
+ data-qa-selector="unresolve_review_discussion_checkbox"
/>
{{ __('Unresolve thread') }}
</template>
<template v-else>
- <input v-model="isResolving" type="checkbox" class="qa-resolve-review-discussion" />
+ <input
+ v-model="isResolving"
+ type="checkbox"
+ data-qa-selector="resolve_review_discussion_checkbox"
+ />
{{ __('Resolve thread') }}
</template>
</label>
diff --git a/app/assets/javascripts/notes/services/notes_service.js b/app/assets/javascripts/notes/services/notes_service.js
index 9e0392110b6..3d239d8cb6b 100644
--- a/app/assets/javascripts/notes/services/notes_service.js
+++ b/app/assets/javascripts/notes/services/notes_service.js
@@ -5,8 +5,11 @@ import * as constants from '../constants';
Vue.use(VueResource);
export default {
- fetchDiscussions(endpoint, filter) {
- const config = filter !== undefined ? { params: { notes_filter: filter } } : null;
+ fetchDiscussions(endpoint, filter, persistFilter = true) {
+ const config =
+ filter !== undefined
+ ? { params: { notes_filter: filter, persist_filter: persistFilter } }
+ : null;
return Vue.http.get(endpoint, config);
},
replyToDiscussion(endpoint, data) {
diff --git a/app/assets/javascripts/notes/stores/actions.js b/app/assets/javascripts/notes/stores/actions.js
index 762a87ce0ff..b7857997d42 100644
--- a/app/assets/javascripts/notes/stores/actions.js
+++ b/app/assets/javascripts/notes/stores/actions.js
@@ -46,9 +46,9 @@ export const setNotesFetchedState = ({ commit }, state) =>
export const toggleDiscussion = ({ commit }, data) => commit(types.TOGGLE_DISCUSSION, data);
-export const fetchDiscussions = ({ commit, dispatch }, { path, filter }) =>
+export const fetchDiscussions = ({ commit, dispatch }, { path, filter, persistFilter }) =>
service
- .fetchDiscussions(path, filter)
+ .fetchDiscussions(path, filter, persistFilter)
.then(res => res.json())
.then(discussions => {
commit(types.SET_INITIAL_DISCUSSIONS, discussions);
@@ -411,9 +411,9 @@ export const setLoadingState = ({ commit }, data) => {
commit(types.SET_NOTES_LOADING_STATE, data);
};
-export const filterDiscussion = ({ dispatch }, { path, filter }) => {
+export const filterDiscussion = ({ dispatch }, { path, filter, persistFilter }) => {
dispatch('setLoadingState', true);
- dispatch('fetchDiscussions', { path, filter })
+ dispatch('fetchDiscussions', { path, filter, persistFilter })
.then(() => {
dispatch('setLoadingState', false);
dispatch('setNotesFetchedState', true);
diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js
index 8aa8f5037b3..52410f18d4a 100644
--- a/app/assets/javascripts/notes/stores/getters.js
+++ b/app/assets/javascripts/notes/stores/getters.js
@@ -183,6 +183,15 @@ export const nextUnresolvedDiscussionId = (state, getters) => (discussionId, dif
return slicedIds.length ? idsOrdered.slice(currentIndex + 1, currentIndex + 2)[0] : idsOrdered[0];
};
+export const previousUnresolvedDiscussionId = (state, getters) => (discussionId, diffOrder) => {
+ const idsOrdered = getters.unresolvedDiscussionsIdsOrdered(diffOrder);
+ const currentIndex = idsOrdered.indexOf(discussionId);
+ const slicedIds = idsOrdered.slice(currentIndex - 1, currentIndex);
+
+ // Get the last ID if there is none after the currentIndex
+ return slicedIds.length ? slicedIds[0] : idsOrdered[idsOrdered.length - 1];
+};
+
// @param {Boolean} diffOrder - is ordered by diff?
export const firstUnresolvedDiscussionId = (state, getters) => diffOrder => {
if (diffOrder) {
diff --git a/app/assets/javascripts/notes/stores/utils.js b/app/assets/javascripts/notes/stores/utils.js
index ed4cef4a917..97dcd54fe88 100644
--- a/app/assets/javascripts/notes/stores/utils.js
+++ b/app/assets/javascripts/notes/stores/utils.js
@@ -21,7 +21,7 @@ export const getQuickActionText = note => {
text = __('Applying multiple commands');
} else {
const commandDescription = executedCommands[0].description.toLowerCase();
- text = sprintf(__('Applying command to %{commandDescription}', { commandDescription }));
+ text = sprintf(__('Applying command to %{commandDescription}'), { commandDescription });
}
}
diff --git a/app/assets/javascripts/operation_settings/components/external_dashboard.vue b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
index ed518611d0b..e90e27a402a 100644
--- a/app/assets/javascripts/operation_settings/components/external_dashboard.vue
+++ b/app/assets/javascripts/operation_settings/components/external_dashboard.vue
@@ -50,13 +50,18 @@ export default {
<form>
<gl-form-group
:label="s__('ExternalMetrics|Full dashboard URL')"
+ label-for="full-dashboard-url"
:description="s__('ExternalMetrics|Enter the URL of the dashboard you want to link to')"
>
+ <!-- placeholder with a url is a false positive -->
+ <!-- eslint-disable @gitlab/vue-i18n/no-bare-attribute-strings -->
<gl-form-input
+ id="full-dashboard-url"
v-model="userDashboardUrl"
placeholder="https://my-org.gitlab.io/my-dashboards"
@keydown.enter.native.prevent="updateExternalDashboardUrl"
/>
+ <!-- eslint-enable @gitlab/vue-i18n/no-bare-attribute-strings -->
</gl-form-group>
<gl-button variant="success" @click="updateExternalDashboardUrl">
{{ __('Save Changes') }}
diff --git a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
index 6e00e31b828..7a6a486f551 100644
--- a/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
+++ b/app/assets/javascripts/pages/admin/broadcast_messages/broadcast_message.js
@@ -3,26 +3,31 @@ import _ from 'underscore';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import { __ } from '~/locale';
+import { textColorForBackground } from '~/lib/utils/color_utils';
export default () => {
- $('input#broadcast_message_color').on('input', function onMessageColorInput() {
+ const $broadcastMessageColor = $('input#broadcast_message_color');
+ const $broadcastMessagePreview = $('div.broadcast-message-preview');
+ $broadcastMessageColor.on('input', function onMessageColorInput() {
const previewColor = $(this).val();
- $('div.broadcast-message-preview').css('background-color', previewColor);
+ $broadcastMessagePreview.css('background-color', previewColor);
});
$('input#broadcast_message_font').on('input', function onMessageFontInput() {
const previewColor = $(this).val();
- $('div.broadcast-message-preview').css('color', previewColor);
+ $broadcastMessagePreview.css('color', previewColor);
});
- const previewPath = $('textarea#broadcast_message_message').data('previewPath');
+ const $broadcastMessage = $('textarea#broadcast_message_message');
+ const previewPath = $broadcastMessage.data('previewPath');
+ const $jsBroadcastMessagePreview = $('.js-broadcast-message-preview');
- $('textarea#broadcast_message_message').on(
+ $broadcastMessage.on(
'input',
_.debounce(function onMessageInput() {
const message = $(this).val();
if (message === '') {
- $('.js-broadcast-message-preview').text(__('Your message here'));
+ $jsBroadcastMessagePreview.text(__('Your message here'));
} else {
axios
.post(previewPath, {
@@ -31,10 +36,40 @@ export default () => {
},
})
.then(({ data }) => {
- $('.js-broadcast-message-preview').html(data.message);
+ $jsBroadcastMessagePreview.html(data.message);
})
.catch(() => flash(__('An error occurred while rendering preview broadcast message')));
}
}, 250),
);
+
+ const updateColorPreview = () => {
+ const selectedBackgroundColor = $broadcastMessageColor.val();
+ const contrastTextColor = textColorForBackground(selectedBackgroundColor);
+
+ // save contrastTextColor to hidden input field
+ $('input.text-font-color').val(contrastTextColor);
+
+ // Updates the preview color with the hex-color input
+ const selectedColorStyle = {
+ backgroundColor: selectedBackgroundColor,
+ color: contrastTextColor,
+ };
+
+ $('.label-color-preview').css(selectedColorStyle);
+
+ return $broadcastMessagePreview.css(selectedColorStyle);
+ };
+
+ const setSuggestedColor = e => {
+ const color = $(e.currentTarget).data('color');
+ $broadcastMessageColor
+ .val(color)
+ // Notify the form, that color has changed
+ .trigger('input');
+ updateColorPreview();
+ return e.preventDefault();
+ };
+
+ $(document).on('click', '.suggest-colors a', setSuggestedColor);
};
diff --git a/app/assets/javascripts/pages/groups/merge_requests/index.js b/app/assets/javascripts/pages/groups/merge_requests/index.js
index 12a26fd88fa..7520cfb6da0 100644
--- a/app/assets/javascripts/pages/groups/merge_requests/index.js
+++ b/app/assets/javascripts/pages/groups/merge_requests/index.js
@@ -1,11 +1,15 @@
import projectSelect from '~/project_select';
import initFilteredSearch from '~/pages/search/init_filtered_search';
+import issuableInitBulkUpdateSidebar from '~/issuable_init_bulk_update_sidebar';
import IssuableFilteredSearchTokenKeys from '~/filtered_search/issuable_filtered_search_token_keys';
import addExtraTokensForMergeRequests from 'ee_else_ce/filtered_search/add_extra_tokens_for_merge_requests';
import { FILTERED_SEARCH } from '~/pages/constants';
+const ISSUABLE_BULK_UPDATE_PREFIX = 'merge_request_';
+
document.addEventListener('DOMContentLoaded', () => {
addExtraTokensForMergeRequests(IssuableFilteredSearchTokenKeys);
+ issuableInitBulkUpdateSidebar.init(ISSUABLE_BULK_UPDATE_PREFIX);
initFilteredSearch({
page: FILTERED_SEARCH.MERGE_REQUESTS,
diff --git a/app/assets/javascripts/pages/projects/clusters/show/index.js b/app/assets/javascripts/pages/projects/clusters/show/index.js
index 8001d2dd1da..f091c01fc98 100644
--- a/app/assets/javascripts/pages/projects/clusters/show/index.js
+++ b/app/assets/javascripts/pages/projects/clusters/show/index.js
@@ -1,5 +1,7 @@
import ClustersBundle from '~/clusters/clusters_bundle';
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
document.addEventListener('DOMContentLoaded', () => {
new ClustersBundle(); // eslint-disable-line no-new
+ initGkeNamespace();
});
diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js
index d4bd02c14e9..55c377ebec0 100644
--- a/app/assets/javascripts/pages/projects/index.js
+++ b/app/assets/javascripts/pages/projects/index.js
@@ -1,4 +1,5 @@
import initGkeDropdowns from '~/projects/gke_cluster_dropdowns';
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
import PersistentUserCallout from '../../persistent_user_callout';
import Project from './project';
import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation';
@@ -16,6 +17,7 @@ document.addEventListener('DOMContentLoaded', () => {
PersistentUserCallout.factory(callout);
initGkeDropdowns();
+ initGkeNamespace();
}
new Project(); // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js
index f0d529758d5..332b6811af6 100644
--- a/app/assets/javascripts/pages/projects/project.js
+++ b/app/assets/javascripts/pages/projects/project.js
@@ -1,9 +1,10 @@
-/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */
+/* eslint-disable func-names, no-var, no-return-assign, object-shorthand, vars-on-top */
import $ from 'jquery';
import Cookies from 'js-cookie';
import { __ } from '~/locale';
-import { visitUrl } from '~/lib/utils/url_utility';
+import { visitUrl, mergeUrlParams } from '~/lib/utils/url_utility';
+import { serializeForm } from '~/lib/utils/forms';
import axios from '~/lib/utils/axios_utils';
import flash from '~/flash';
import projectSelect from '../../project_select';
@@ -107,9 +108,10 @@ export default class Project {
refLink.href = '#';
return $('.js-project-refs-dropdown').each(function() {
- var $dropdown, selected;
- $dropdown = $(this);
- selected = $dropdown.data('selected');
+ var $dropdown = $(this);
+ var selected = $dropdown.data('selected');
+ var fieldName = $dropdown.data('fieldName');
+ var shouldVisit = Boolean($dropdown.data('visit'));
return $dropdown.glDropdown({
data(term, callback) {
axios
@@ -127,7 +129,7 @@ export default class Project {
filterRemote: true,
filterByText: true,
inputFieldName: $dropdown.data('inputFieldName'),
- fieldName: $dropdown.data('fieldName'),
+ fieldName,
renderRow: function(ref) {
var li = refListItem.cloneNode(false);
@@ -158,15 +160,12 @@ export default class Project {
clicked: function(options) {
const { e } = options;
e.preventDefault();
- if ($('input[name="ref"]').length) {
+ if ($(`input[name="${fieldName}"]`).length) {
var $form = $dropdown.closest('form');
-
- var $visit = $dropdown.data('visit');
- var shouldVisit = $visit ? true : $visit;
var action = $form.attr('action');
- var divider = action.indexOf('?') === -1 ? '?' : '&';
+
if (shouldVisit) {
- visitUrl(`${action}${divider}${$form.serialize()}`);
+ visitUrl(mergeUrlParams(serializeForm($form[0]), action));
}
}
},
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index b4d24f3aa36..a223a8f5b08 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
import { __ } from '~/locale';
import projectFeatureSetting from './project_feature_setting.vue';
@@ -27,6 +28,11 @@ export default {
type: Object,
required: true,
},
+ canDisableEmails: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
canChangeVisibilityLevel: {
type: Boolean,
required: false,
@@ -103,6 +109,7 @@ export default {
lfsEnabled: true,
requestAccessEnabled: true,
highlightChangesClass: false,
+ emailsDisabled: false,
};
return { ...defaults, ...this.currentSettings };
@@ -340,5 +347,14 @@ export default {
/>
</project-setting-row>
</div>
+ <project-setting-row v-if="canDisableEmails" class="mb-3">
+ <label class="js-emails-disabled">
+ <input :value="emailsDisabled" type="hidden" name="project[emails_disabled]" />
+ <input v-model="emailsDisabled" type="checkbox" /> {{ __('Disable email notifications') }}
+ </label>
+ <span class="form-text text-muted">{{
+ __('This setting will override user notification preferences for all project members.')
+ }}</span>
+ </project-setting-row>
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/wikis/wikis.js b/app/assets/javascripts/pages/projects/wikis/wikis.js
index 9b58d42b47d..d41199f6374 100644
--- a/app/assets/javascripts/pages/projects/wikis/wikis.js
+++ b/app/assets/javascripts/pages/projects/wikis/wikis.js
@@ -1,6 +1,5 @@
import bp from '../../../breakpoints';
-import { parseQueryStringIntoObject } from '../../../lib/utils/common_utils';
-import { mergeUrlParams, redirectTo } from '../../../lib/utils/url_utility';
+import { s__, sprintf } from '~/locale';
export default class Wikis {
constructor() {
@@ -12,32 +11,37 @@ export default class Wikis {
sidebarToggles[i].addEventListener('click', e => this.handleToggleSidebar(e));
}
- this.newWikiForm = document.querySelector('form.new-wiki-page');
- if (this.newWikiForm) {
- this.newWikiForm.addEventListener('submit', e => this.handleNewWikiSubmit(e));
+ this.isNewWikiPage = Boolean(document.querySelector('.js-new-wiki-page'));
+ this.editTitleInput = document.querySelector('form.wiki-form #wiki_title');
+ this.commitMessageInput = document.querySelector('form.wiki-form #wiki_message');
+ this.commitMessageI18n = this.isNewWikiPage
+ ? s__('WikiPageCreate|Create %{pageTitle}')
+ : s__('WikiPageEdit|Update %{pageTitle}');
+
+ if (this.editTitleInput) {
+ // Initialize the commit message on load
+ if (this.editTitleInput.value) this.setWikiCommitMessage(this.editTitleInput.value);
+
+ // Set the commit message as the page title is changed
+ this.editTitleInput.addEventListener('keyup', e => this.handleWikiTitleChange(e));
}
window.addEventListener('resize', () => this.renderSidebar());
this.renderSidebar();
}
- handleNewWikiSubmit(e) {
- if (!this.newWikiForm) return;
-
- const slugInput = this.newWikiForm.querySelector('#new_wiki_path');
-
- const slug = slugInput.value;
+ handleWikiTitleChange(e) {
+ this.setWikiCommitMessage(e.target.value);
+ }
- if (slug.length > 0) {
- const wikisPath = slugInput.getAttribute('data-wikis-path');
+ setWikiCommitMessage(rawTitle) {
+ let title = rawTitle;
- // If the wiki is empty, we need to merge the current URL params to keep the "create" view.
- const params = parseQueryStringIntoObject(window.location.search.substr(1));
- const url = mergeUrlParams(params, `${wikisPath}/${slug}`);
- redirectTo(url);
+ // Replace hyphens with spaces
+ if (title) title = title.replace(/-+/g, ' ');
- e.preventDefault();
- }
+ const newCommitMessage = sprintf(this.commitMessageI18n, { pageTitle: title });
+ this.commitMessageInput.value = newCommitMessage;
}
handleToggleSidebar(e) {
diff --git a/app/assets/javascripts/pages/search/show/refresh_counts.js b/app/assets/javascripts/pages/search/show/refresh_counts.js
new file mode 100644
index 00000000000..fa75ee6075d
--- /dev/null
+++ b/app/assets/javascripts/pages/search/show/refresh_counts.js
@@ -0,0 +1,24 @@
+import axios from '~/lib/utils/axios_utils';
+
+function showCount(el, count) {
+ el.textContent = count;
+ el.classList.remove('hidden');
+}
+
+function refreshCount(el) {
+ const { url } = el.dataset;
+
+ return axios
+ .get(url)
+ .then(({ data }) => showCount(el, data.count))
+ .catch(e => {
+ // eslint-disable-next-line no-console
+ console.error(`Failed to fetch search count from '${url}'.`, e);
+ });
+}
+
+export default function refreshCounts() {
+ const elements = Array.from(document.querySelectorAll('.js-search-count'));
+
+ return Promise.all(elements.map(refreshCount));
+}
diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js
index d5a8e712d6b..86ec78e1df8 100644
--- a/app/assets/javascripts/pages/search/show/search.js
+++ b/app/assets/javascripts/pages/search/show/search.js
@@ -2,6 +2,8 @@ import $ from 'jquery';
import Flash from '~/flash';
import Api from '~/api';
import { __ } from '~/locale';
+import Project from '~/pages/projects/project';
+import refreshCounts from './refresh_counts';
export default class Search {
constructor() {
@@ -13,6 +15,7 @@ export default class Search {
this.groupId = $groupDropdown.data('groupId');
this.eventListeners();
+ refreshCounts();
$groupDropdown.glDropdown({
selectable: true,
@@ -37,9 +40,6 @@ export default class Search {
text(obj) {
return obj.full_name;
},
- toggleLabel(obj) {
- return `${$groupDropdown.data('defaultLabel')} ${obj.full_name}`;
- },
clicked: () => Search.submitSearch(),
});
@@ -70,11 +70,10 @@ export default class Search {
text(obj) {
return obj.name_with_namespace;
},
- toggleLabel(obj) {
- return `${$projectDropdown.data('defaultLabel')} ${obj.name_with_namespace}`;
- },
clicked: () => Search.submitSearch(),
});
+
+ Project.initRefSwitcher();
}
eventListeners() {
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1d8b388e935..4ac4efec45d 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -143,7 +143,7 @@ export default class UserTabs {
this.loadOverviewTab();
}
- const loadableActions = ['groups', 'contributed', 'projects', 'snippets'];
+ const loadableActions = ['groups', 'contributed', 'projects', 'starred', 'snippets'];
if (loadableActions.indexOf(action) > -1) {
this.loadTab(action, endpoint);
}
diff --git a/app/assets/javascripts/pdf/index.vue b/app/assets/javascripts/pdf/index.vue
index 6d39abd4a1f..bbbd9789dc9 100644
--- a/app/assets/javascripts/pdf/index.vue
+++ b/app/assets/javascripts/pdf/index.vue
@@ -14,7 +14,6 @@ export default {
},
data() {
return {
- loading: false,
pages: [],
};
},
@@ -35,19 +34,24 @@ export default {
load() {
this.pages = [];
return pdfjsLib
- .getDocument(this.document)
- .then(this.renderPages)
- .then(() => this.$emit('pdflabload'))
- .catch(error => this.$emit('pdflaberror', error))
- .then(() => {
- this.loading = false;
+ .getDocument({
+ url: this.document,
+ cMapUrl: '/assets/webpack/cmaps/',
+ cMapPacked: true,
+ })
+ .promise.then(this.renderPages)
+ .then(pages => {
+ this.pages = pages;
+ this.$emit('pdflabload');
+ })
+ .catch(error => {
+ this.$emit('pdflaberror', error);
});
},
renderPages(pdf) {
const pagePromises = [];
- this.loading = true;
for (let num = 1; num <= pdf.numPages; num += 1) {
- pagePromises.push(pdf.getPage(num).then(p => this.pages.push(p)));
+ pagePromises.push(pdf.getPage(num));
}
return Promise.all(pagePromises);
},
@@ -59,8 +63,8 @@ export default {
<div v-if="hasPDF" class="pdf-viewer">
<page
v-for="(page, index) in pages"
+ v-if="page"
:key="index"
- :v-if="!loading"
:page="page"
:number="index + 1"
/>
diff --git a/app/assets/javascripts/pdf/page/index.vue b/app/assets/javascripts/pdf/page/index.vue
index f16aaca6cd7..65f84e75e86 100644
--- a/app/assets/javascripts/pdf/page/index.vue
+++ b/app/assets/javascripts/pdf/page/index.vue
@@ -18,7 +18,7 @@ export default {
},
computed: {
viewport() {
- return this.page.getViewport(this.scale);
+ return this.page.getViewport({ scale: this.scale });
},
context() {
return this.$refs.canvas.getContext('2d');
@@ -36,10 +36,12 @@ export default {
this.rendering = true;
this.page
.render(this.renderContext)
- .then(() => {
+ .promise.then(() => {
this.rendering = false;
})
- .catch(error => this.$emit('pdflaberror', error));
+ .catch(error => {
+ this.$emit('pdflaberror', error);
+ });
},
};
</script>
diff --git a/app/assets/javascripts/performance_bar/components/detailed_metric.vue b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
index d5f1cea8356..a271284dd89 100644
--- a/app/assets/javascripts/performance_bar/components/detailed_metric.vue
+++ b/app/assets/javascripts/performance_bar/components/detailed_metric.vue
@@ -16,11 +16,14 @@ export default {
type: String,
required: true,
},
- header: {
+ title: {
type: String,
- required: true,
+ required: false,
+ default() {
+ return this.metric;
+ },
},
- details: {
+ header: {
type: String,
required: true,
},
@@ -34,14 +37,14 @@ export default {
return this.currentRequest.details[this.metric];
},
detailsList() {
- return this.metricDetails[this.details];
+ return this.metricDetails.details;
},
},
};
</script>
<template>
<div
- v-if="currentRequest.details"
+ v-if="currentRequest.details && metricDetails"
:id="`peek-view-${metric}`"
class="view qa-performance-bar-detailed-metric"
>
@@ -63,7 +66,7 @@ export default {
<template v-if="detailsList.length">
<tr v-for="(item, index) in detailsList" :key="index">
<td>
- <span>{{ item.duration }}ms</span>
+ <span>{{ sprintf(__('%{duration}ms'), { duration: item.duration }) }}</span>
</td>
<td>
<div class="js-toggle-container">
@@ -101,6 +104,6 @@ export default {
<div slot="footer"></div>
</gl-modal>
- {{ metric }}
+ {{ title }}
</div>
</template>
diff --git a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
index 015c1527500..9ad6e75b86b 100644
--- a/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
+++ b/app/assets/javascripts/performance_bar/components/performance_bar_app.vue
@@ -1,5 +1,4 @@
<script>
-import $ from 'jquery';
import { glEmojiTag } from '~/emoji';
import detailedMetric from './detailed_metric.vue';
@@ -28,23 +27,27 @@ export default {
type: String,
required: true,
},
- profileUrl: {
- type: String,
- required: true,
- },
},
detailedMetrics: [
- { metric: 'pg', header: s__('PerformanceBar|SQL queries'), details: 'queries', keys: ['sql'] },
+ {
+ metric: 'active-record',
+ title: 'pg',
+ header: s__('PerformanceBar|SQL queries'),
+ keys: ['sql'],
+ },
{
metric: 'gitaly',
header: s__('PerformanceBar|Gitaly calls'),
- details: 'details',
keys: ['feature', 'request'],
},
{
+ metric: 'rugged',
+ header: s__('PerformanceBar|Rugged calls'),
+ keys: ['feature', 'args'],
+ },
+ {
metric: 'redis',
- header: 'Redis calls',
- details: 'details',
+ header: s__('PerformanceBar|Redis calls'),
keys: ['cmd'],
},
],
@@ -66,9 +69,6 @@ export default {
initialRequest() {
return this.currentRequestId === this.requestId;
},
- lineProfileModal() {
- return $('#modal-peek-line-profile');
- },
hasHost() {
return this.currentRequest && this.currentRequest.details && this.currentRequest.details.host;
},
@@ -76,16 +76,11 @@ export default {
if (this.hasHost && this.currentRequest.details.host.canary) {
return glEmojiTag('baby_chick');
}
-
return '';
},
},
mounted() {
this.currentRequest = this.requestId;
-
- if (this.lineProfileModal.length) {
- this.lineProfileModal.modal('toggle');
- }
},
methods: {
changeCurrentRequest(newRequestId) {
@@ -112,27 +107,10 @@ export default {
:key="metric.metric"
:current-request="currentRequest"
:metric="metric.metric"
+ :title="metric.title"
:header="metric.header"
- :details="metric.details"
:keys="metric.keys"
/>
- <div v-if="initialRequest" id="peek-view-rblineprof" class="view">
- <button
- v-if="lineProfileModal.length"
- class="btn-link btn-blank"
- data-toggle="modal"
- data-target="#modal-peek-line-profile"
- >
- {{ s__('PerformanceBar|profile') }}
- </button>
- <a v-else :href="profileUrl">{{ s__('PerformanceBar|profile') }}</a>
- </div>
- <div id="peek-view-gc" class="view">
- <span v-if="currentRequest.details" class="bold">
- <span title="Invoke Time">{{ currentRequest.details.gc.gc_time }}</span
- >ms / <span title="Invoke Count">{{ currentRequest.details.gc.invokes }}</span> gc
- </span>
- </div>
<div
v-if="currentRequest.details && currentRequest.details.tracing"
id="peek-view-trace"
diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js
index 4a08e158f6b..8d6a3781048 100644
--- a/app/assets/javascripts/persistent_user_callout.js
+++ b/app/assets/javascripts/persistent_user_callout.js
@@ -1,13 +1,17 @@
+import { parseBoolean } from './lib/utils/common_utils';
import axios from './lib/utils/axios_utils';
import { __ } from './locale';
import Flash from './flash';
+const DEFERRED_LINK_CLASS = 'deferred-link';
+
export default class PersistentUserCallout {
constructor(container) {
- const { dismissEndpoint, featureId } = container.dataset;
+ const { dismissEndpoint, featureId, deferLinks } = container.dataset;
this.container = container;
this.dismissEndpoint = dismissEndpoint;
this.featureId = featureId;
+ this.deferLinks = parseBoolean(deferLinks);
this.init();
}
@@ -15,9 +19,21 @@ export default class PersistentUserCallout {
init() {
const closeButton = this.container.querySelector('.js-close');
closeButton.addEventListener('click', event => this.dismiss(event));
+
+ if (this.deferLinks) {
+ this.container.addEventListener('click', event => {
+ const isDeferredLink = event.target.classList.contains(DEFERRED_LINK_CLASS);
+
+ if (isDeferredLink) {
+ const { href, target } = event.target;
+
+ this.dismiss(event, { href, target });
+ }
+ });
+ }
}
- dismiss(event) {
+ dismiss(event, deferredLinkOptions = null) {
event.preventDefault();
axios
@@ -26,6 +42,11 @@ export default class PersistentUserCallout {
})
.then(() => {
this.container.remove();
+
+ if (deferredLinkOptions) {
+ const { href, target } = deferredLinkOptions;
+ window.open(href, target);
+ }
})
.catch(() => {
Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.'));
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 3f021a26ec5..4efc1d2408a 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -102,7 +102,11 @@ export default {
<span
v-if="pipeline.flags.detached_merge_request_pipeline"
v-gl-tooltip
- :title="__('This pipeline is run on the source branch')"
+ :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.',
+ )
+ "
class="js-pipeline-url-detached badge badge-info"
>
{{ __('detached') }}
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 244d332f38f..4b2d816c6a0 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -1,9 +1,11 @@
<script>
import { GlButton, GlTooltipDirective, GlLoadingIcon } from '@gitlab/ui';
-import { s__, sprintf } from '~/locale';
+import axios from '~/lib/utils/axios_utils';
+import flash from '~/flash';
+import { s__, __, sprintf } from '~/locale';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
+import Icon from '~/vue_shared/components/icon.vue';
import eventHub from '../event_hub';
-import Icon from '../../vue_shared/components/icon.vue';
export default {
directives: {
@@ -44,7 +46,24 @@ export default {
this.isLoading = true;
- eventHub.$emit('postAction', action.path);
+ /**
+ * Ideally, the component would not make an api call directly.
+ * However, in order to use the eventhub and know when to
+ * toggle back the `isLoading` property we'd need an ID
+ * to track the request with a wacther - since this component
+ * is rendered at least 20 times in the same page, moving the
+ * api call directly here is the most performant solution
+ */
+ axios
+ .post(`${action.path}.json`)
+ .then(() => {
+ this.isLoading = false;
+ eventHub.$emit('updateTable');
+ })
+ .catch(() => {
+ this.isLoading = false;
+ flash(__('An error occurred while making the request.'));
+ });
},
isActionDisabled(action) {
diff --git a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
index 2ab0ad4d013..3f07b77ed32 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_artifacts.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlLink, GlTooltipDirective } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/pipelines/mixins/pipelines.js b/app/assets/javascripts/pipelines/mixins/pipelines.js
index a6243366375..126a9a47a2b 100644
--- a/app/assets/javascripts/pipelines/mixins/pipelines.js
+++ b/app/assets/javascripts/pipelines/mixins/pipelines.js
@@ -60,12 +60,14 @@ export default {
eventHub.$on('postAction', this.postAction);
eventHub.$on('retryPipeline', this.postAction);
eventHub.$on('clickedDropdown', this.updateTable);
+ eventHub.$on('updateTable', this.updateTable);
eventHub.$on('refreshPipelinesTable', this.fetchPipelines);
},
beforeDestroy() {
eventHub.$off('postAction', this.postAction);
eventHub.$off('retryPipeline', this.postAction);
eventHub.$off('clickedDropdown', this.updateTable);
+ eventHub.$off('updateTable', this.updateTable);
eventHub.$off('refreshPipelinesTable', this.fetchPipelines);
},
destroyed() {
diff --git a/app/assets/javascripts/privacy_policy_update_callout.js b/app/assets/javascripts/privacy_policy_update_callout.js
new file mode 100644
index 00000000000..126b1ee1132
--- /dev/null
+++ b/app/assets/javascripts/privacy_policy_update_callout.js
@@ -0,0 +1,8 @@
+import PersistentUserCallout from '~/persistent_user_callout';
+
+function initPrivacyPolicyUpdateCallout() {
+ const callout = document.querySelector('.privacy-policy-update-64341');
+ PersistentUserCallout.factory(callout);
+}
+
+export default initPrivacyPolicyUpdateCallout;
diff --git a/app/assets/javascripts/projects/gke_cluster_namespace/index.js b/app/assets/javascripts/projects/gke_cluster_namespace/index.js
new file mode 100644
index 00000000000..0ec4d8807b0
--- /dev/null
+++ b/app/assets/javascripts/projects/gke_cluster_namespace/index.js
@@ -0,0 +1,37 @@
+/**
+ * Disables & hides the namespace inputs when the gitlab-managed checkbox is checked/unchecked.
+ */
+
+const setDisabled = (el, isDisabled) => {
+ if (isDisabled) {
+ el.classList.add('hidden');
+ el.querySelector('input').setAttribute('disabled', true);
+ } else {
+ el.classList.remove('hidden');
+ el.querySelector('input').removeAttribute('disabled');
+ }
+};
+
+const setState = glManagedCheckbox => {
+ const glManaged = document.querySelector('.js-namespace-prefixed');
+ const selfManaged = document.querySelector('.js-namespace');
+
+ if (glManagedCheckbox.checked) {
+ setDisabled(glManaged, false);
+ setDisabled(selfManaged, true);
+ } else {
+ setDisabled(glManaged, true);
+ setDisabled(selfManaged, false);
+ }
+};
+
+const initGkeNamespace = () => {
+ const glManagedCheckbox = document.querySelector('.js-gl-managed');
+
+ if (glManagedCheckbox) {
+ setState(glManagedCheckbox); // this is needed in order to set the initial state
+ glManagedCheckbox.addEventListener('change', () => setState(glManagedCheckbox));
+ }
+};
+
+export default initGkeNamespace;
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 38519c220c5..efbf0a4e3cf 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -81,9 +81,11 @@ export default {
);
},
dockerBuildCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker build -t ${this.repositoryUrl} .`;
},
dockerPushCommand() {
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `docker push ${this.repositoryUrl}`;
},
},
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index e157036871b..bfb2305c48c 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -84,7 +84,7 @@ export default {
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove repository')"
:aria-label="s__('ContainerRegistry|Remove repository')"
- class="js-remove-repo"
+ class="js-remove-repo btn-inverted"
variant="danger"
>
<icon name="remove" />
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index a498a553908..e9067bc2b56 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -1,7 +1,13 @@
<script>
import { mapActions } from 'vuex';
-import { GlButton, GlTooltipDirective, GlModal, GlModalDirective } from '@gitlab/ui';
-import { n__ } from '../../locale';
+import {
+ GlButton,
+ GlFormCheckbox,
+ GlTooltipDirective,
+ GlModal,
+ GlModalDirective,
+} from '@gitlab/ui';
+import { n__, s__, sprintf } from '../../locale';
import createFlash from '../../flash';
import ClipboardButton from '../../vue_shared/components/clipboard_button.vue';
import TablePagination from '../../vue_shared/components/pagination/table_pagination.vue';
@@ -14,6 +20,7 @@ export default {
components: {
ClipboardButton,
TablePagination,
+ GlFormCheckbox,
GlButton,
Icon,
GlModal,
@@ -31,33 +38,98 @@ export default {
},
data() {
return {
- itemToBeDeleted: null,
+ itemsToBeDeleted: [],
modalId: `confirm-image-deletion-modal-${this.repo.id}`,
+ selectAllChecked: false,
+ modalDescription: '',
};
},
computed: {
+ bulkDeletePath() {
+ return this.repo.tagsPath ? this.repo.tagsPath.replace('?format=json', '/bulk_destroy') : '';
+ },
shouldRenderPagination() {
return this.repo.pagination.total > this.repo.pagination.perPage;
},
+ modalTitle() {
+ return n__(
+ 'ContainerRegistry|Remove image',
+ 'ContainerRegistry|Remove images',
+ this.itemsToBeDeleted.length === 0 ? 1 : this.itemsToBeDeleted.length,
+ );
+ },
+ },
+ mounted() {
+ this.$refs.deleteModal.$refs.modal.$on('hide', this.removeModalEvents);
},
methods: {
- ...mapActions(['fetchList', 'deleteItem']),
+ ...mapActions(['fetchList', 'deleteItem', 'multiDeleteItems']),
+ setModalDescription(itemIndex = -1) {
+ if (itemIndex === -1) {
+ this.modalDescription = sprintf(
+ s__(`ContainerRegistry|You are about to delete <b>%{count}</b> images. This will
+ delete the images and all tags pointing to them.`),
+ { count: this.itemsToBeDeleted.length },
+ );
+ } else {
+ const { tag } = this.repo.list[itemIndex];
+
+ this.modalDescription = sprintf(
+ s__(`ContainerRegistry|You are about to delete the image <b>%{title}</b>. This will
+ delete the image and all tags pointing to this image.`),
+ { title: `${this.repo.name}:${tag}` },
+ );
+ }
+ },
layers(item) {
return item.layers ? n__('%d layer', '%d layers', item.layers) : '';
},
formatSize(size) {
return numberToHumanSize(size);
},
- setItemToBeDeleted(item) {
- this.itemToBeDeleted = item;
+ removeModalEvents() {
+ this.$refs.deleteModal.$refs.modal.$off('ok');
+ },
+ deleteSingleItem(index) {
+ this.setModalDescription(index);
+
+ this.$refs.deleteModal.$refs.modal.$once('ok', () => {
+ this.removeModalEvents();
+ this.handleSingleDelete(this.repo.list[index]);
+ });
+ },
+ deleteMultipleItems() {
+ if (this.itemsToBeDeleted.length === 1) {
+ this.setModalDescription(this.itemsToBeDeleted[0]);
+ } else if (this.itemsToBeDeleted.length > 1) {
+ this.setModalDescription();
+ }
+
+ this.$refs.deleteModal.$refs.modal.$once('ok', () => {
+ this.removeModalEvents();
+ this.handleMultipleDelete();
+ });
},
- handleDeleteRegistry() {
- const { itemToBeDeleted } = this;
- this.itemToBeDeleted = null;
- this.deleteItem(itemToBeDeleted)
+ handleSingleDelete(itemToDelete) {
+ this.deleteItem(itemToDelete)
.then(() => this.fetchList({ repo: this.repo }))
.catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
},
+ handleMultipleDelete() {
+ const { itemsToBeDeleted } = this;
+ this.itemsToBeDeleted = [];
+
+ if (this.bulkDeletePath) {
+ this.multiDeleteItems({
+ path: this.bulkDeletePath,
+ items: itemsToBeDeleted.map(x => this.repo.list[x].tag),
+ })
+ .then(() => this.fetchList({ repo: this.repo }))
+ .catch(() => this.showError(errorMessagesTypes.DELETE_REGISTRY));
+ } else {
+ this.showError(errorMessagesTypes.DELETE_REGISTRY);
+ }
+ },
onPageChange(pageNumber) {
this.fetchList({ repo: this.repo, page: pageNumber }).catch(() =>
this.showError(errorMessagesTypes.FETCH_REGISTRY),
@@ -66,6 +138,35 @@ export default {
showError(message) {
createFlash(errorMessages[message]);
},
+ onSelectAllChange() {
+ if (this.selectAllChecked) {
+ this.deselectAll();
+ } else {
+ this.selectAll();
+ }
+ },
+ selectAll() {
+ this.itemsToBeDeleted = this.repo.list.map((x, index) => index);
+ this.selectAllChecked = true;
+ },
+ deselectAll() {
+ this.itemsToBeDeleted = [];
+ this.selectAllChecked = false;
+ },
+ updateItemsToBeDeleted(index) {
+ const delIndex = this.itemsToBeDeleted.findIndex(x => x === index);
+
+ if (delIndex > -1) {
+ this.itemsToBeDeleted.splice(delIndex, 1);
+ this.selectAllChecked = false;
+ } else {
+ this.itemsToBeDeleted.push(index);
+
+ if (this.itemsToBeDeleted.length === this.repo.list.length) {
+ this.selectAllChecked = true;
+ }
+ }
+ },
},
};
</script>
@@ -74,15 +175,44 @@ export default {
<table class="table tags">
<thead>
<tr>
+ <th>
+ <gl-form-checkbox
+ v-if="repo.canDelete"
+ class="js-select-all-checkbox"
+ :checked="selectAllChecked"
+ @change="onSelectAllChange"
+ />
+ </th>
<th>{{ s__('ContainerRegistry|Tag') }}</th>
<th>{{ s__('ContainerRegistry|Tag ID') }}</th>
<th>{{ s__('ContainerRegistry|Size') }}</th>
<th>{{ s__('ContainerRegistry|Last Updated') }}</th>
- <th></th>
+ <th>
+ <gl-button
+ v-if="repo.canDelete"
+ v-gl-tooltip
+ v-gl-modal="modalId"
+ :disabled="!itemsToBeDeleted || itemsToBeDeleted.length === 0"
+ class="js-delete-registry float-right"
+ variant="danger"
+ :title="s__('ContainerRegistry|Remove selected images')"
+ :aria-label="s__('ContainerRegistry|Remove selected images')"
+ @click="deleteMultipleItems()"
+ ><icon name="remove"
+ /></gl-button>
+ </th>
</tr>
</thead>
<tbody>
- <tr v-for="item in repo.list" :key="item.tag">
+ <tr v-for="(item, index) in repo.list" :key="item.tag" class="registry-image-row">
+ <td class="check">
+ <gl-form-checkbox
+ v-if="item.canDelete"
+ class="js-select-checkbox"
+ :checked="itemsToBeDeleted && itemsToBeDeleted.includes(index)"
+ @change="updateItemsToBeDeleted(index)"
+ />
+ </td>
<td class="monospace">
{{ item.tag }}
<clipboard-button
@@ -111,16 +241,15 @@ export default {
</span>
</td>
- <td class="content">
+ <td class="content action-buttons">
<gl-button
v-if="item.canDelete"
- v-gl-tooltip
v-gl-modal="modalId"
:title="s__('ContainerRegistry|Remove image')"
:aria-label="s__('ContainerRegistry|Remove image')"
variant="danger"
- class="js-delete-registry d-none d-sm-block float-right"
- @click="setItemToBeDeleted(item)"
+ class="js-delete-registry-row float-right btn-inverted btn-border-color btn-icon"
+ @click="deleteSingleItem(index)"
>
<icon name="remove" />
</gl-button>
@@ -135,19 +264,10 @@ export default {
:page-info="repo.pagination"
/>
- <gl-modal :modal-id="modalId" ok-variant="danger" @ok="handleDeleteRegistry">
- <template v-slot:modal-title>{{ s__('ContainerRegistry|Remove image') }}</template>
- <template v-slot:modal-ok>{{ s__('ContainerRegistry|Remove image and tags') }}</template>
- <p
- v-html="
- sprintf(
- s__(
- 'ContainerRegistry|You are about to delete the image <b>%{title}</b>. This will delete the image and all tags pointing to this image.',
- ),
- { title: repo.name },
- )
- "
- ></p>
+ <gl-modal ref="deleteModal" :modal-id="modalId" ok-variant="danger">
+ <template v-slot:modal-title>{{ modalTitle }}</template>
+ <template v-slot:modal-ok>{{ s__('ContainerRegistry|Remove image(s) and tags') }}</template>
+ <p v-html="modalDescription"></p>
</gl-modal>
</div>
</template>
diff --git a/app/assets/javascripts/registry/stores/actions.js b/app/assets/javascripts/registry/stores/actions.js
index 0f5e9cc73a0..a2e0130e79e 100644
--- a/app/assets/javascripts/registry/stores/actions.js
+++ b/app/assets/javascripts/registry/stores/actions.js
@@ -36,6 +36,8 @@ export const fetchList = ({ commit }, { repo, page }) => {
};
export const deleteItem = (_, item) => axios.delete(item.destroyPath);
+export const multiDeleteItems = (_, { path, items }) =>
+ axios.delete(path, { params: { ids: items } });
export const setMainEndpoint = ({ commit }, data) => commit(types.SET_MAIN_ENDPOINT, data);
export const toggleLoading = ({ commit }) => commit(types.TOGGLE_MAIN_LOADING);
diff --git a/app/assets/javascripts/releases/components/release_block.vue b/app/assets/javascripts/releases/components/release_block.vue
index 0031ba04d78..7580c2d0ad0 100644
--- a/app/assets/javascripts/releases/components/release_block.vue
+++ b/app/assets/javascripts/releases/components/release_block.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import _ from 'underscore';
import { GlTooltipDirective, GlLink, GlBadge } from '@gitlab/ui';
import Icon from '~/vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/reports/components/modal.vue b/app/assets/javascripts/reports/components/modal.vue
index 162421b037f..cb9c1642608 100644
--- a/app/assets/javascripts/reports/components/modal.vue
+++ b/app/assets/javascripts/reports/components/modal.vue
@@ -1,4 +1,5 @@
<script>
+// import { sprintf, __ } from '~/locale';
import Modal from '~/vue_shared/components/gl_modal.vue';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import CodeBlock from '~/vue_shared/components/code_block.vue';
@@ -47,9 +48,9 @@ export default {
</a>
</template>
- <template v-else-if="field.type === $options.fieldTypes.miliseconds">
- {{ field.value }} ms
- </template>
+ <template v-else-if="field.type === $options.fieldTypes.miliseconds">{{
+ sprintf(__('%{value} ms'), { value: field.value })
+ }}</template>
<template v-else-if="field.type === $options.fieldTypes.text">
{{ field.value }}
diff --git a/app/assets/javascripts/reports/components/report_item.vue b/app/assets/javascripts/reports/components/report_item.vue
index d477fafd3f5..f3f7d2648a8 100644
--- a/app/assets/javascripts/reports/components/report_item.vue
+++ b/app/assets/javascripts/reports/components/report_item.vue
@@ -45,7 +45,7 @@ export default {
<template>
<li
:class="{ 'is-dismissed': issue.isDismissed }"
- class="report-block-list-issue justify-content-center align-items-center"
+ class="report-block-list-issue align-items-center"
>
<issue-status-icon
v-if="showReportSectionStatusIcon"
diff --git a/app/assets/javascripts/reports/components/report_link.vue b/app/assets/javascripts/reports/components/report_link.vue
index 052bc53d610..e32e1ac49ca 100644
--- a/app/assets/javascripts/reports/components/report_link.vue
+++ b/app/assets/javascripts/reports/components/report_link.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
export default {
name: 'ReportIssueLink',
props: {
diff --git a/app/assets/javascripts/reports/components/report_section.vue b/app/assets/javascripts/reports/components/report_section.vue
index 9bc3e6388e3..24612c8681a 100644
--- a/app/assets/javascripts/reports/components/report_section.vue
+++ b/app/assets/javascripts/reports/components/report_section.vue
@@ -166,7 +166,7 @@ export default {
<section class="media-section">
<div class="media">
<status-icon :status="statusIconName" :size="24" />
- <div class="media-body d-flex flex-align-self-center prepend-left-default">
+ <div class="media-body d-flex flex-align-self-center">
<span class="js-code-text code-text">
{{ headerText }}
<slot :name="slotName"></slot>
diff --git a/app/assets/javascripts/reports/components/summary_row.vue b/app/assets/javascripts/reports/components/summary_row.vue
index 1caf52431e2..aba798e63d0 100644
--- a/app/assets/javascripts/reports/components/summary_row.vue
+++ b/app/assets/javascripts/reports/components/summary_row.vue
@@ -44,9 +44,7 @@ export default {
};
</script>
<template>
- <div
- class="report-block-list-issue report-block-list-issue-parent justify-content-center align-items-center"
- >
+ <div class="report-block-list-issue report-block-list-issue-parent align-items-center">
<div class="report-block-list-icon append-right-default">
<gl-loading-icon
v-if="statusIcon === 'loading'"
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 26493556063..e2060d4aeec 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlTooltipDirective, GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale';
import Icon from '../../vue_shared/components/icon.vue';
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 6029460d975..171841178a3 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -115,6 +115,7 @@ export default {
<component :is="linkComponent" :to="routerLinkTo" :href="url" class="str-truncated">
{{ fullPath }}
</component>
+ <!-- eslint-disable-next-line @gitlab/vue-i18n/no-bare-strings -->
<gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">LFS</gl-badge>
<template v-if="isSubmodule">
@ <gl-link :href="submoduleTreeUrl" class="commit-sha">{{ shortSha }}</gl-link>
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index b3cc0878cad..c4814f8e63a 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -1,3 +1,5 @@
+#import "~/graphql_shared/fragments/pageInfo.fragment.graphql"
+
fragment TreeEntry on Entry {
id
name
@@ -5,11 +7,6 @@ fragment TreeEntry on Entry {
type
}
-fragment PageInfo on PageInfo {
- hasNextPage
- endCursor
-}
-
query getFiles(
$projectPath: ID!
$path: String
diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js
index 930c0d5e958..40a2158de78 100644
--- a/app/assets/javascripts/right_sidebar.js
+++ b/app/assets/javascripts/right_sidebar.js
@@ -101,10 +101,12 @@ Sidebar.prototype.toggleTodo = function(e) {
this.todoUpdateDone(data);
})
.catch(() =>
- flash(sprintf(__('There was an error %{message} todo.')), {
- message:
- ajaxType === 'post' ? s__('RightSidebar|adding a') : s__('RightSidebar|deleting the'),
- }),
+ flash(
+ sprintf(__('There was an error %{message} todo.'), {
+ message:
+ ajaxType === 'post' ? s__('RightSidebar|adding a') : s__('RightSidebar|deleting the'),
+ }),
+ ),
);
};
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/assignees.vue b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
index 805c21d0965..d9739e8d197 100644
--- a/app/assets/javascripts/sidebar/components/assignees/assignees.vue
+++ b/app/assets/javascripts/sidebar/components/assignees/assignees.vue
@@ -1,11 +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: {
@@ -22,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') }}
@@ -198,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/subscriptions/subscriptions.vue b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
index a75daca156c..0d1faceef11 100644
--- a/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
+++ b/app/assets/javascripts/sidebar/components/subscriptions/subscriptions.vue
@@ -73,22 +73,22 @@ export default {
<template>
<div>
- <div class="sidebar-collapsed-icon" @click="onClickCollapsedIcon">
- <span
- v-tooltip
- :title="notificationTooltip"
- data-container="body"
- data-placement="left"
- data-boundary="viewport"
- >
- <icon
- :name="notificationIcon"
- :size="16"
- aria-hidden="true"
- class="sidebar-item-icon is-active"
- />
- </span>
- </div>
+ <span
+ v-tooltip
+ class="sidebar-collapsed-icon"
+ :title="notificationTooltip"
+ data-container="body"
+ data-placement="left"
+ data-boundary="viewport"
+ @click="onClickCollapsedIcon"
+ >
+ <icon
+ :name="notificationIcon"
+ :size="16"
+ aria-hidden="true"
+ class="sidebar-item-icon is-active"
+ />
+ </span>
<span class="issuable-header-text hide-collapsed float-left"> {{ __('Notifications') }} </span>
<toggle-button
ref="toggleButton"
diff --git a/app/assets/javascripts/star.js b/app/assets/javascripts/star.js
index 70f89152f70..97afeecd8ac 100644
--- a/app/assets/javascripts/star.js
+++ b/app/assets/javascripts/star.js
@@ -18,7 +18,7 @@ export default class Star {
const isStarred = $starSpan.hasClass('starred');
$this
.parent()
- .find('.star-count')
+ .find('.count')
.text(data.star_count);
if (isStarred) {
diff --git a/app/assets/javascripts/tracking.js b/app/assets/javascripts/tracking.js
new file mode 100644
index 00000000000..2d0b099cf0b
--- /dev/null
+++ b/app/assets/javascripts/tracking.js
@@ -0,0 +1,67 @@
+import $ from 'jquery';
+
+const extractData = (el, opts = {}) => {
+ const { trackEvent, trackLabel = '', trackProperty = '' } = el.dataset;
+ let trackValue = el.dataset.trackValue || el.value || '';
+ if (el.type === 'checkbox' && !el.checked) trackValue = false;
+ return [
+ trackEvent + (opts.suffix || ''),
+ {
+ label: trackLabel,
+ property: trackProperty,
+ value: trackValue,
+ },
+ ];
+};
+
+export default class Tracking {
+ static enabled() {
+ return typeof window.snowplow === 'function';
+ }
+
+ static event(category = document.body.dataset.page, event = 'generic', data = {}) {
+ if (!this.enabled()) return false;
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
+ if (!category) throw new Error('Tracking: no category provided for tracking.');
+
+ return window.snowplow(
+ 'trackStructEvent',
+ category,
+ event,
+ Object.assign({}, { label: '', property: '', value: '' }, data),
+ );
+ }
+
+ constructor(category = document.body.dataset.page) {
+ this.category = category;
+ }
+
+ bind(container = document) {
+ if (!this.constructor.enabled()) return;
+ container.querySelectorAll(`[data-track-event]`).forEach(el => {
+ if (this.customHandlingFor(el)) return;
+ // jquery is required for select2, so we use it always
+ // see: https://github.com/select2/select2/issues/4686
+ $(el).on('click', this.eventHandler(this.category));
+ });
+ }
+
+ customHandlingFor(el) {
+ const classes = el.classList;
+
+ // bootstrap dropdowns
+ if (classes.contains('dropdown')) {
+ $(el).on('show.bs.dropdown', this.eventHandler(this.category, { suffix: '_show' }));
+ $(el).on('hide.bs.dropdown', this.eventHandler(this.category, { suffix: '_hide' }));
+ return true;
+ }
+
+ return false;
+ }
+
+ eventHandler(category = null, opts = {}) {
+ return e => {
+ this.constructor.event(category || this.category, ...extractData(e.currentTarget, opts));
+ };
+ }
+}
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
index 04bfb5e9532..a03dc14b319 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/comment.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/comment.js
@@ -1,148 +1,39 @@
-import { BLACK, COMMENT_BOX, MUTED, LOGOUT } from './constants';
-import { clearNote, postError } from './note';
-import {
- buttonClearStyles,
- selectCommentBox,
- selectCommentButton,
- selectNote,
- selectNoteContainer,
-} from './utils';
-
-const comment = `
- <div>
- <textarea id="${COMMENT_BOX}" name="${COMMENT_BOX}" rows="3" placeholder="Enter your feedback or idea" class="gitlab-input" aria-required="true"></textarea>
- <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-secondary" style="${buttonClearStyles}" type="button" id="${LOGOUT}"> Log out </button>
- <button class="gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="gitlab-comment-button"> Send feedback </button>
- </div>
-`;
-
-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 = '';
-};
-
-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 postComment = ({
- href,
- platform,
- browser,
- userAgent,
- innerWidth,
- innerHeight,
- projectId,
- projectPath,
- mergeRequestId,
- mrUrl,
- token,
-}) => {
- // Clear any old errors
- clearNote(COMMENT_BOX);
-
- setInProgressState();
-
- const commentText = selectCommentBox().value.trim();
-
- 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>
+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>
`;
+};
- 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 new Error(`${response.status}: ${response.statusText}`);
- })
- .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(
- `Your comment could not be sent. Please try again. Error: ${err.message}`,
- COMMENT_BOX,
- );
- resetCommentBox();
- resetCommentButton();
- });
+// 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 { comment, postComment };
+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
new file mode 100644
index 00000000000..da67763261c
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/comment_mr_note.js
@@ -0,0 +1,31 @@
+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
new file mode 100644
index 00000000000..ee5f2b62425
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/comment_post.js
@@ -0,0 +1,145 @@
+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
new file mode 100644
index 00000000000..49c9400437e
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/comment_storage.js
@@ -0,0 +1,20 @@
+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/constants.js b/app/assets/javascripts/visual_review_toolbar/components/constants.js
deleted file mode 100644
index 07fcb179d15..00000000000
--- a/app/assets/javascripts/visual_review_toolbar/components/constants.js
+++ /dev/null
@@ -1,41 +0,0 @@
-// component selectors
-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';
-const LOGOUT = 'gitlab-logout-button';
-const NOTE = 'gitlab-validation-note';
-const NOTE_CONTAINER = 'gitlab-note-wrapper';
-const REMEMBER_TOKEN = 'gitlab-remember_token';
-const REVIEW_CONTAINER = 'gitlab-review-container';
-const TOKEN_BOX = 'gitlab-token';
-
-// 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 {
- COLLAPSE_BUTTON,
- COMMENT_BOX,
- COMMENT_BUTTON,
- FORM,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- NOTE,
- NOTE_CONTAINER,
- REMEMBER_TOKEN,
- REVIEW_CONTAINER,
- TOKEN_BOX,
- BLACK,
- CLEAR,
- MUTED,
- RED,
- WHITE,
-};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/form_elements.js b/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
new file mode 100644
index 00000000000..608488a6fea
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/form_elements.js
@@ -0,0 +1,17 @@
+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
index 50b52d7d3a2..e88b3637ad8 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/index.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/index.js
@@ -1,33 +1,23 @@
-import { comment, postComment } from './comment';
-import {
- COLLAPSE_BUTTON,
- COMMENT_BUTTON,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- REVIEW_CONTAINER,
-} from './constants';
+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 } from './utils';
-import { buttonAndForm, logoutUser, toggleForm } from './wrapper';
-import { collapseButton } from './wrapper_icons';
+import { selectContainer, selectForm } from './utils';
+import { buttonAndForm, toggleForm } from './wrapper';
export {
+ addMr,
authorizeUser,
buttonAndForm,
- collapseButton,
+ changeSelectedMr,
comment,
login,
logoutUser,
+ mrForm,
note,
postComment,
+ saveComment,
selectContainer,
+ selectForm,
toggleForm,
- COLLAPSE_BUTTON,
- COMMENT_BUTTON,
- FORM_CONTAINER,
- LOGIN,
- LOGOUT,
- REVIEW_CONTAINER,
};
diff --git a/app/assets/javascripts/visual_review_toolbar/components/login.js b/app/assets/javascripts/visual_review_toolbar/components/login.js
index 0a71299f041..20ab01bc690 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/login.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/login.js
@@ -1,35 +1,31 @@
-import { LOGIN, REMEMBER_TOKEN, TOKEN_BOX } from './constants';
+import { nextView } from '../store';
+import { localStorage, LOGIN, TOKEN_BOX, STORAGE_TOKEN } from '../shared';
import { clearNote, postError } from './note';
-import { buttonClearStyles, selectRemember, selectToken } from './utils';
-import { addCommentForm } from './wrapper';
+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">Enter your <a class="gitlab-link" href="https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html">personal access token</a></label>
- <input class="gitlab-input" type="password" id="${TOKEN_BOX}" name="${TOKEN_BOX}" aria-required="true" autocomplete="current-password">
- </div>
- <div class="gitlab-checkbox-wrapper">
- <input type="checkbox" id="${REMEMBER_TOKEN}" name="${REMEMBER_TOKEN}" value="remember">
- <label for="${REMEMBER_TOKEN}" class="gitlab-checkbox-label">Remember me</label>
- </div>
- <div class="gitlab-button-wrapper">
- <button class="gitlab-button-wide gitlab-button gitlab-button-success" style="${buttonClearStyles}" type="button" id="${LOGIN}"> Submit </button>
- </div>
+ <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 { localStorage } = window;
const rememberMe = selectRemember().checked;
- // All the browsers we support have localStorage, so let's silently fail
- // and go on with the rest of the functionality.
- try {
- if (rememberMe) {
- localStorage.setItem('token', token);
- }
- } finally {
- state.token = token;
+ if (rememberMe) {
+ localStorage.setItem(STORAGE_TOKEN, token);
}
+
+ state.token = token;
};
const authorizeUser = state => {
@@ -45,7 +41,7 @@ const authorizeUser = state => {
}
storeToken(token, state);
- addCommentForm();
+ addForm(nextView(state, LOGIN));
};
-export { authorizeUser, 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
new file mode 100644
index 00000000000..695b3af8aa0
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/components/mr_id.js
@@ -0,0 +1,63 @@
+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
index 0150f640aae..9cddcb710f2 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/note.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/note.js
@@ -1,4 +1,4 @@
-import { NOTE, NOTE_CONTAINER, RED } from './constants';
+import { NOTE, NOTE_CONTAINER, RED } from '../shared';
import { selectById, selectNote, selectNoteContainer } from './utils';
const note = `
diff --git a/app/assets/javascripts/visual_review_toolbar/components/utils.js b/app/assets/javascripts/visual_review_toolbar/components/utils.js
index 00f4460925d..4ec9bd4a32a 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/utils.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/utils.js
@@ -6,12 +6,13 @@ import {
COMMENT_BUTTON,
FORM,
FORM_CONTAINER,
+ MR_ID,
NOTE,
NOTE_CONTAINER,
- REMEMBER_TOKEN,
+ REMEMBER_ITEM,
REVIEW_CONTAINER,
TOKEN_BOX,
-} from './constants';
+} from '../shared';
// this style must be applied inline in a handful of components
/* eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings */
@@ -27,9 +28,10 @@ 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_TOKEN);
+const selectRemember = () => document.getElementById(REMEMBER_ITEM);
const selectToken = () => document.getElementById(TOKEN_BOX);
export {
@@ -41,6 +43,7 @@ export {
selectCommentButton,
selectForm,
selectFormContainer,
+ selectMrBox,
selectNote,
selectNoteContainer,
selectRemember,
diff --git a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
index f2eaf1d7916..fdf8ad7c41f 100644
--- a/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
+++ b/app/assets/javascripts/visual_review_toolbar/components/wrapper.js
@@ -1,55 +1,32 @@
-import { comment } from './comment';
-import { CLEAR, FORM, FORM_CONTAINER, WHITE } from './constants';
-import { login } from './login';
-import { clearNote } from './note';
+import { CLEAR, FORM, FORM_CONTAINER, WHITE } from '../shared';
import {
selectCollapseButton,
selectForm,
selectFormContainer,
selectNoteContainer,
} from './utils';
-import { commentIcon, compressIcon } from './wrapper_icons';
+import { collapseButton, commentIcon, compressIcon } from './wrapper_icons';
const form = content => `
- <form id="${FORM}">
+ <form id="${FORM}" novalidate>
${content}
</form>
`;
-const buttonAndForm = ({ content, toggleButton }) => `
+const buttonAndForm = content => `
<div id="${FORM_CONTAINER}" class="gitlab-form-open">
- ${toggleButton}
+ ${collapseButton}
${form(content)}
</div>
`;
-const addCommentForm = () => {
+const addForm = nextForm => {
const formWrapper = selectForm();
- formWrapper.innerHTML = comment;
+ formWrapper.innerHTML = nextForm;
};
-const addLoginForm = () => {
- const formWrapper = selectForm();
- formWrapper.innerHTML = login;
-};
-
-function logoutUser() {
- const { localStorage } = window;
-
- // All the browsers we support have localStorage, so let's silently fail
- // and go on with the rest of the functionality.
- try {
- localStorage.removeItem('token');
- } catch (err) {
- return;
- }
-
- clearNote();
- addLoginForm();
-}
-
function toggleForm() {
- const collapseButton = selectCollapseButton();
+ const toggleButton = selectCollapseButton();
const currentForm = selectForm();
const formContainer = selectFormContainer();
const noteContainer = selectNoteContainer();
@@ -84,19 +61,19 @@ function toggleForm() {
},
};
- const nextState = collapseButton.classList.contains('gitlab-collapse-open') ? CLOSED : OPEN;
+ 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;
- collapseButton.classList.replace(...currentVals.buttonClasses);
- collapseButton.innerHTML = currentVals.icon;
+ toggleButton.classList.replace(...currentVals.buttonClasses);
+ toggleButton.innerHTML = currentVals.icon;
if (noteContainer && noteContainer.innerText.length > 0) {
noteContainer.style.display = currentVals.display;
}
}
-export { addCommentForm, addLoginForm, buttonAndForm, logoutUser, toggleForm };
+export { addForm, buttonAndForm, toggleForm };
diff --git a/app/assets/javascripts/visual_review_toolbar/index.js b/app/assets/javascripts/visual_review_toolbar/index.js
index f94eb88835a..67b3fadd772 100644
--- a/app/assets/javascripts/visual_review_toolbar/index.js
+++ b/app/assets/javascripts/visual_review_toolbar/index.js
@@ -1,7 +1,8 @@
import './styles/toolbar.css';
-import { buttonAndForm, note, selectContainer, REVIEW_CONTAINER } from './components';
-import { debounce, eventLookup, getInitialView, initializeState, updateWindowSize } from './store';
+import { buttonAndForm, note, selectForm, selectContainer } from './components';
+import { REVIEW_CONTAINER } from './shared';
+import { eventLookup, getInitialView, initializeGlobalListeners, initializeState } from './store';
/*
@@ -20,7 +21,7 @@ import { debounce, eventLookup, getInitialView, initializeState, updateWindowSiz
window.addEventListener('load', () => {
initializeState(window, document);
- const mainContent = buttonAndForm(getInitialView(window));
+ const mainContent = buttonAndForm(getInitialView());
const container = document.createElement('div');
container.setAttribute('id', REVIEW_CONTAINER);
container.insertAdjacentHTML('beforeend', note);
@@ -29,8 +30,22 @@ window.addEventListener('load', () => {
document.body.insertBefore(container, document.body.firstChild);
selectContainer().addEventListener('click', event => {
- eventLookup(event)();
+ eventLookup(event.target.id)();
});
- window.addEventListener('resize', debounce(updateWindowSize.bind(null, window), 200));
+ 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
new file mode 100644
index 00000000000..0d5b666ab0a
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/shared/constants.js
@@ -0,0 +1,56 @@
+// 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
new file mode 100644
index 00000000000..d8ccb170592
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/shared/index.js
@@ -0,0 +1,55 @@
+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
new file mode 100644
index 00000000000..00456d3536e
--- /dev/null
+++ b/app/assets/javascripts/visual_review_toolbar/shared/storage_utils.js
@@ -0,0 +1,42 @@
+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
index 93996be8473..c9095c77ef1 100644
--- a/app/assets/javascripts/visual_review_toolbar/store/events.js
+++ b/app/assets/javascripts/visual_review_toolbar/store/events.js
@@ -1,20 +1,37 @@
import {
+ addMr,
authorizeUser,
+ changeSelectedMr,
logoutUser,
postComment,
+ saveComment,
toggleForm,
+} from '../components';
+
+import {
+ CHANGE_MR_ID_BUTTON,
COLLAPSE_BUTTON,
COMMENT_BUTTON,
LOGIN,
LOGOUT,
-} from '../components';
+ MR_ID_BUTTON,
+} from '../shared';
import { state } from './state';
+import debounce from './utils';
const noop = () => {};
-const eventLookup = ({ target: { id } }) => {
+// 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:
@@ -22,7 +39,12 @@ const eventLookup = ({ target: { id } }) => {
case LOGIN:
return authorizeUser.bind(null, state);
case LOGOUT:
- return logoutUser;
+ return () => {
+ saveComment();
+ logoutUser(state);
+ };
+ case MR_ID_BUTTON:
+ return addMr.bind(null, state);
default:
return noop;
}
@@ -33,4 +55,19 @@ const updateWindowSize = wind => {
state.innerHeight = wind.innerHeight;
};
-export { eventLookup, updateWindowSize };
+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
index 7143588c0bf..07c8dd6f1d2 100644
--- a/app/assets/javascripts/visual_review_toolbar/store/index.js
+++ b/app/assets/javascripts/visual_review_toolbar/store/index.js
@@ -1,5 +1,11 @@
-import { eventLookup, updateWindowSize } from './events';
-import { getInitialView, initializeState } from './state';
-import debounce from './utils';
+import { eventLookup, initializeGlobalListeners } from './events';
+import { nextView, getInitialView, initializeState, setUsingGracefulStorageFlag } from './state';
-export { debounce, eventLookup, getInitialView, initializeState, updateWindowSize };
+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
index 22702d524b8..b7853bb0723 100644
--- a/app/assets/javascripts/visual_review_toolbar/store/state.js
+++ b/app/assets/javascripts/visual_review_toolbar/store/state.js
@@ -1,8 +1,9 @@
-import { comment, login, collapseButton } from '../components';
+import { comment, login, mrForm } from '../components';
+import { localStorage, COMMENT_BOX, LOGIN, MR_ID, STORAGE_MR_ID, STORAGE_TOKEN } from '../shared';
const state = {
browser: '',
- href: '',
+ usingGracefulStorage: '',
innerWidth: '',
innerHeight: '',
mergeRequestId: '',
@@ -23,11 +24,31 @@ const getBrowserId = sUsrAg => {
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,
- location: { href },
navigator: { platform, userAgent },
} = wind;
@@ -39,7 +60,6 @@ const initializeState = (wind, doc) => {
// This mutates our default state object above. It's weird but it makes the linter happy.
Object.assign(state, {
browser,
- href,
innerWidth,
innerHeight,
mergeRequestId,
@@ -49,30 +69,27 @@ const initializeState = (wind, doc) => {
projectPath,
userAgent,
});
-};
-function getInitialView({ localStorage }) {
- const loginView = {
- content: login,
- toggleButton: collapseButton,
- };
+ return state;
+};
- const commentView = {
- content: comment,
- toggleButton: collapseButton,
- };
+const getInitialView = () => {
+ const token = localStorage.getItem(STORAGE_TOKEN);
+ const mrId = localStorage.getItem(STORAGE_MR_ID);
- try {
- const token = localStorage.getItem('token');
+ if (token) {
+ state.token = token;
+ }
- if (token) {
- state.token = token;
- return commentView;
- }
- return loginView;
- } catch (err) {
- return loginView;
+ if (mrId) {
+ state.mergeRequestId = mrId;
}
-}
-export { initializeState, getInitialView, state };
+ return nextView(state);
+};
+
+const setUsingGracefulStorageFlag = flag => {
+ state.usingGracefulStorage = !flag;
+};
+
+export { initializeState, getInitialView, nextView, setUsingGracefulStorageFlag, state };
diff --git a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
index 6a7b2f52549..d1a8d66ef40 100644
--- a/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
+++ b/app/assets/javascripts/visual_review_toolbar/styles/toolbar.css
@@ -42,7 +42,7 @@
position: fixed;
bottom: 1rem;
right: 1rem;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell,
+ 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;
@@ -107,10 +107,14 @@
}
.gitlab-button-wrapper {
- margin-top: 1rem;
+ margin-top: 0.5rem;
display: flex;
align-items: baseline;
- justify-content: flex-end;
+ /*
+ this makes sure the hit enter to submit picks the correct button
+ on the comment view
+ */
+ flex-direction: row-reverse;
}
.gitlab-collapse {
@@ -155,6 +159,12 @@
text-decoration: underline;
}
+.gitlab-link-button {
+ border: none;
+ cursor: pointer;
+ padding: 0 .15rem;
+}
+
.gitlab-message {
padding: .25rem 0;
margin: 0;
@@ -165,7 +175,7 @@
font-size: .7rem;
line-height: 1rem;
color: #666;
- margin-bottom: 0;
+ margin-bottom: .5rem;
}
.gitlab-input {
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
index 5c7859828d8..bb6921225c2 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue
@@ -14,6 +14,8 @@ import ReviewAppLink from './review_app_link.vue';
import MRWidgetService from '../services/mr_widget_service';
export default {
+ // name: 'Deployment' 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: 'Deployment',
components: {
LoadingButton,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index a347269c916..53bf9d5ab6f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -23,7 +23,7 @@ export default {
};
</script>
<template>
- <section class="mr-widget-help">
+ <section class="mr-widget-help font-italic">
<template v-if="missingBranch">
{{ missingBranchInfo }}
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 17ac8ada32d..8fdf61a6b8d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -18,8 +18,8 @@ export default {
Deployment,
MrWidgetContainer,
MrWidgetPipeline,
- MergeTrainInfo: () =>
- import('ee_component/vue_merge_request_widget/components/merge_train_info.vue'),
+ MergeTrainPositionIndicator: () =>
+ import('ee_component/vue_merge_request_widget/components/merge_train_position_indicator.vue'),
},
props: {
mr: {
@@ -60,9 +60,9 @@ export default {
return this.isPostMerge ? this.mr.mergePipeline : this.mr.pipeline;
},
showVisualReviewAppLink() {
- return Boolean(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable);
+ return this.mr.visualReviewAppAvailable;
},
- showMergeTrainInfo() {
+ showMergeTrainPositionIndicator() {
return _.isNumber(this.mr.mergeTrainIndex);
},
},
@@ -90,8 +90,8 @@ export default {
:visual-review-app-meta="visualReviewAppMeta"
/>
</div>
- <merge-train-info
- v-if="showMergeTrainInfo"
+ <merge-train-position-indicator
+ v-if="showMergeTrainPositionIndicator"
class="mr-widget-extension"
:merge-train-index="mr.mergeTrainIndex"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
index 8dbd9e52cfe..d0df8309dc7 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_status_icon.vue
@@ -32,10 +32,13 @@ export default {
};
</script>
<template>
- <div class="d-flex widget-status-icon">
- <div v-if="isLoading" class="mr-widget-icon"><gl-loading-icon size="sm" /></div>
-
- <ci-icon v-else :status="statusObj" :size="24" />
+ <div class="d-flex align-self-start">
+ <div class="square s24 h-auto d-flex-center append-right-default">
+ <div v-if="isLoading" class="mr-widget-icon d-inline-flex">
+ <gl-loading-icon size="md" class="mr-loading-icon d-inline-flex" />
+ </div>
+ <ci-icon v-else :status="statusObj" :size="24" />
+ </div>
<button
v-if="showDisabledButton"
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
index b9562fbc260..fb07c03e34d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_merged.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import Flash from '~/flash';
import tooltip from '~/vue_shared/directives/tooltip';
import { s__, __ } from '~/locale';
@@ -84,6 +85,8 @@ export default {
.removeSourceBranch()
.then(res => res.data)
.then(data => {
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
if (data.message === 'Branch was deleted') {
eventHub.$emit('MRWidgetUpdateRequested', () => {
this.isMakingRequest = false;
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index d4514767912..e294e1de976 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -94,9 +94,6 @@ export default {
return __('Merge');
},
- shouldShowMergeOptionsDropdown() {
- return this.isAutoMergeAvailable && !this.mr.onlyAllowMergeIfPipelineSucceeds;
- },
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
},
@@ -246,7 +243,7 @@ export default {
{{ mergeButtonText }}
</button>
<button
- v-if="isAutoMergeAvailable"
+ v-if="shouldShowMergeImmediatelyDropdown"
:disabled="isMergeButtonDisabled"
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
@@ -256,7 +253,7 @@ export default {
<i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
</button>
<ul
- v-if="shouldShowMergeOptionsDropdown"
+ v-if="shouldShowMergeImmediatelyDropdown"
class="dropdown-menu dropdown-menu-right"
role="menu"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index accb9d9fef1..98f682c2e8a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -36,7 +36,7 @@ export default {
:disabled="isDisabled"
type="checkbox"
name="squash"
- class="qa-squash-checkbox"
+ class="qa-squash-checkbox js-squash-checkbox"
@change="$emit('input', $event.target.checked)"
/>
{{ __('Squash commits') }}
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
index 116d537c463..eef49e20159 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
@@ -15,5 +15,8 @@ export default {
// MWPS is currently the only auto merge strategy available in CE
return __('Merge when pipeline succeeds');
},
+ shouldShowMergeImmediatelyDropdown() {
+ return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
+ },
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 8d415c1bbea..edd21a81f8b 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -40,6 +40,8 @@ import { setFaviconOverlay } from '../lib/utils/common_utils';
export default {
el: '#js-vue-mr-widget',
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'MRWidget',
components: {
'mr-widget-header': WidgetHeader,
@@ -164,6 +166,7 @@ export default {
ciEnvironmentsStatusPath: store.ciEnvironmentsStatusPath,
mergeRequestBasicPath: store.mergeRequestBasicPath,
mergeRequestWidgetPath: store.mergeRequestWidgetPath,
+ mergeRequestCachedWidgetPath: store.mergeRequestCachedWidgetPath,
mergeActionsContentPath: store.mergeActionsContentPath,
rebasePath: store.rebasePath,
};
@@ -174,8 +177,7 @@ export default {
checkStatus(cb, isRebased) {
return this.service
.checkStatus()
- .then(res => res.data)
- .then(data => {
+ .then(({ data }) => {
this.handleNotification(data);
this.mr.setData(data, isRebased);
this.setFaviconHelper();
diff --git a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
index 1dae53039d5..f637a44bf2d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
+++ b/app/assets/javascripts/vue_merge_request_widget/services/mr_widget_service.js
@@ -34,7 +34,16 @@ export default class MRWidgetService {
}
checkStatus() {
- return axios.get(this.endpoints.mergeRequestWidgetPath);
+ // two endpoints are requested in order to get MR info:
+ // one which is etag-cached and invalidated and another one which is not cached
+ // the idea is to move all the fields to etag-cached endpoint and then perform only one request
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/61559#note_188801390
+ const getData = axios.get(this.endpoints.mergeRequestWidgetPath);
+ const getCachedData = axios.get(this.endpoints.mergeRequestCachedWidgetPath);
+
+ return axios
+ .all([getData, getCachedData])
+ .then(axios.spread((res, cachedRes) => ({ data: Object.assign(res.data, cachedRes.data) })));
}
fetchMergeActionsContent() {
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 581fee7477f..0f55bebd3fc 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
@@ -10,6 +10,8 @@ export default class MergeRequestStore {
this.sha = data.diff_head_sha;
this.gitlabLogo = data.gitlabLogo;
+ this.setPaths(data);
+
this.setData(data);
}
@@ -18,13 +20,9 @@ export default class MergeRequestStore {
this.sha = data.diff_head_sha;
}
- const currentUser = data.current_user;
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
this.squash = data.squash;
- this.squashBeforeMergeHelpPath =
- this.squashBeforeMergeHelpPath || data.squash_before_merge_help_path;
- this.troubleshootingDocsPath = this.troubleshootingDocsPath || data.troubleshooting_docs_path;
this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
this.iid = data.iid;
@@ -35,6 +33,7 @@ export default class MergeRequestStore {
this.sourceBranchProtected = data.source_branch_protected;
this.conflictsDocsPath = data.conflicts_docs_path;
this.mergeRequestPipelinesHelpPath = data.merge_request_pipelines_docs_path;
+ this.mergeTrainWhenPipelineSucceedsDocsPath = data.merge_train_when_pipeline_succeeds_docs_path;
this.mergeStatus = data.merge_status;
this.commitMessage = data.default_merge_commit_message;
this.shortMergeCommitSha = data.short_merge_commit_sha;
@@ -48,7 +47,7 @@ export default class MergeRequestStore {
this.postMergeDeployments = this.postMergeDeployments || [];
this.commits = data.commits_without_merge_commits || [];
this.squashCommitMessage = data.default_squash_commit_message;
- this.initRebase(data);
+ this.rebaseInProgress = data.rebase_in_progress;
if (data.issues_links) {
const links = data.issues_links;
@@ -66,14 +65,7 @@ export default class MergeRequestStore {
this.setToAutoMergeBy = MergeRequestStore.formatUserObject(data.merge_user || {});
this.mergeUserId = data.merge_user_id;
this.currentUserId = gon.current_user_id;
- this.sourceBranchPath = data.source_branch_path;
- this.sourceBranchLink = data.source_branch_with_namespace_link;
this.mergeError = data.merge_error;
- this.targetBranchPath = data.target_branch_commits_path;
- this.targetBranchTreePath = data.target_branch_tree_path;
- this.conflictResolutionPath = data.conflict_resolution_path;
- this.cancelAutoMergePath = data.cancel_auto_merge_path;
- this.removeWIPPath = data.remove_wip_path;
this.sourceBranchRemoved = !data.source_branch_exists;
this.shouldRemoveSourceBranch = data.remove_source_branch || false;
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
@@ -83,46 +75,23 @@ export default class MergeRequestStore {
this.preferredAutoMergeStrategy = MergeRequestStore.getPreferredAutoMergeStrategy(
this.availableAutoMergeStrategies,
);
- this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
- this.mergeRequestBasicPath = data.merge_request_basic_path;
- this.mergeRequestWidgetPath = data.merge_request_widget_path;
- this.emailPatchesPath = data.email_patches_path;
- this.plainDiffPath = data.plain_diff_path;
- this.newBlobPath = data.new_blob_path;
- this.createIssueToResolveDiscussionsPath = data.create_issue_to_resolve_discussions_path;
- this.mergeCheckPath = data.merge_check_path;
- this.mergeActionsContentPath = data.commit_change_content_path;
- this.mergeCommitPath = data.merge_commit_path;
this.isRemovingSourceBranch = this.isRemovingSourceBranch || false;
this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
- this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
- this.canMerge = Boolean(data.merge_path);
- this.canCreateIssue = currentUser.can_create_issue || false;
- this.canCancelAutomaticMerge = Boolean(data.cancel_auto_merge_path);
this.isSHAMismatch = this.sha !== data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
this.allowCollaboration = data.allow_collaboration;
- this.targetProjectFullPath = data.target_project_full_path;
- this.sourceProjectFullPath = data.source_project_full_path;
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;
- // Cherry-pick and Revert actions related
- this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
- this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
- this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path;
- this.revertInForkPath = currentUser.revert_in_fork_path;
-
// CI related
- this.ciEnvironmentsStatusPath = data.ci_environments_status_path;
this.hasCI = data.has_ci;
this.ciStatus = data.ci_status;
this.isPipelineFailed = this.ciStatus === 'failed' || this.ciStatus === 'canceled';
@@ -133,8 +102,33 @@ export default class MergeRequestStore {
this.isPipelineActive = data.pipeline ? data.pipeline.active : false;
this.isPipelineBlocked = pipelineStatus ? pipelineStatus.group === 'manual' : false;
this.ciStatusFaviconPath = pipelineStatus ? pipelineStatus.favicon : null;
-
this.testResultsPath = data.test_reports_path;
+ this.cancelAutoMergePath = data.cancel_auto_merge_path;
+ this.canCancelAutomaticMerge = Boolean(data.cancel_auto_merge_path);
+
+ this.newBlobPath = data.new_blob_path;
+ this.sourceBranchPath = data.source_branch_path;
+ this.sourceBranchLink = data.source_branch_with_namespace_link;
+ this.rebasePath = data.rebase_path;
+ this.targetBranchPath = data.target_branch_commits_path;
+ this.targetBranchTreePath = data.target_branch_tree_path;
+ this.conflictResolutionPath = data.conflict_resolution_path;
+ this.removeWIPPath = data.remove_wip_path;
+ this.createIssueToResolveDiscussionsPath = data.create_issue_to_resolve_discussions_path;
+ this.mergePath = data.merge_path;
+ this.canMerge = Boolean(data.merge_path);
+ this.mergeCommitPath = data.merge_commit_path;
+ this.canPushToSourceBranch = data.can_push_to_source_branch;
+
+ const currentUser = data.current_user;
+
+ this.cherryPickInForkPath = currentUser.cherry_pick_in_fork_path;
+ this.revertInForkPath = currentUser.revert_in_fork_path;
+
+ this.canRemoveSourceBranch = currentUser.can_remove_source_branch || false;
+ this.canCreateIssue = currentUser.can_create_issue || false;
+ this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
+ this.canRevertInCurrentMR = currentUser.can_revert_on_current_merge_request || false;
this.setState(data);
}
@@ -161,6 +155,24 @@ export default class MergeRequestStore {
}
}
+ setPaths(data) {
+ // Paths are set on the first load of the page and not auto-refreshed
+ this.squashBeforeMergeHelpPath = data.squash_before_merge_help_path;
+ this.troubleshootingDocsPath = data.troubleshooting_docs_path;
+ this.mergeRequestBasicPath = data.merge_request_basic_path;
+ this.mergeRequestWidgetPath = data.merge_request_widget_path;
+ this.mergeRequestCachedWidgetPath = data.merge_request_cached_widget_path;
+ this.emailPatchesPath = data.email_patches_path;
+ this.plainDiffPath = data.plain_diff_path;
+ this.mergeCheckPath = data.merge_check_path;
+ this.mergeActionsContentPath = data.commit_change_content_path;
+ this.targetProjectFullPath = data.target_project_full_path;
+ this.sourceProjectFullPath = data.source_project_full_path;
+ this.mergeRequestPipelinesHelpPath = data.merge_request_pipelines_docs_path;
+ this.conflictsDocsPath = data.conflicts_docs_path;
+ this.ciEnvironmentsStatusPath = data.ci_environments_status_path;
+ }
+
get isNothingToMergeState() {
return this.state === stateKey.nothingToMerge;
}
@@ -169,13 +181,6 @@ export default class MergeRequestStore {
return this.state === stateKey.merged;
}
- initRebase(data) {
- this.canPushToSourceBranch = data.can_push_to_source_branch;
- this.rebaseInProgress = data.rebase_in_progress;
- this.approvalsLeft = !data.approved;
- this.rebasePath = data.rebase_path;
- }
-
static buildMetrics(metrics) {
if (!metrics) {
return {};
diff --git a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
index 15cb0bd9792..beb2ac09992 100644
--- a/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
+++ b/app/assets/javascripts/vue_shared/components/changed_file_icon.vue
@@ -26,11 +26,6 @@ export default {
required: false,
default: false,
},
- forceModifiedIcon: {
- type: Boolean,
- required: false,
- default: false,
- },
size: {
type: Number,
required: false,
@@ -44,10 +39,10 @@ export default {
},
computed: {
changedIcon() {
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
const suffix = !this.file.changed && this.file.staged && !this.showStagedIcon ? '-solid' : '';
- if (this.forceModifiedIcon) return `file-modified${suffix}`;
-
return `${getCommitIconMap(this.file).icon}${suffix}`;
},
changedIconClass() {
@@ -86,7 +81,7 @@ export default {
v-gl-tooltip.right
:title="tooltipTitle"
:class="{ 'ml-auto': isCentered }"
- class="file-changed-icon"
+ class="file-changed-icon d-inline-block"
>
<icon v-if="showIcon" :name="changedIcon" :size="size" :css-classes="changedIconClass" />
</span>
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ae9b013d980..f7c508c4e23 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -152,37 +152,35 @@ export default {
:href="mergeRequestRef.path"
:title="mergeRequestRef.title"
class="ref-name"
+ >{{ mergeRequestRef.iid }}</gl-link
>
- {{ mergeRequestRef.iid }}
- </gl-link>
<gl-link
v-else
v-gl-tooltip
:href="commitRef.ref_url"
:title="commitRef.name"
class="ref-name"
+ >{{ commitRef.name }}</gl-link
>
- {{ commitRef.name }}
- </gl-link>
</template>
<icon name="commit" class="commit-icon js-commit-icon" />
- <gl-link :href="commitUrl" class="commit-sha mr-0"> {{ shortSha }} </gl-link>
+ <gl-link :href="commitUrl" class="commit-sha mr-0">{{ shortSha }}</gl-link>
- <div class="commit-title flex-truncate-parent">
- <tooltip-on-truncate v-if="title" class="flex-truncate-child" :title="title">
+ <div class="commit-title">
+ <span v-if="title" class="flex-truncate-parent">
<user-avatar-link
v-if="hasAuthor"
:link-href="author.path"
:img-src="author.avatar_url"
:img-alt="userImageAltDescription"
:tooltip-text="author.username"
- class="avatar-image-container"
+ class="avatar-image-container text-decoration-none"
/>
- <gl-link :href="commitUrl" class="commit-row-message cgray">
- {{ title }}
- </gl-link>
- </tooltip-on-truncate>
+ <tooltip-on-truncate :title="title" class="flex-truncate-child">
+ <gl-link :href="commitUrl" class="commit-row-message cgray">{{ title }}</gl-link>
+ </tooltip-on-truncate>
+ </span>
<span v-else>{{ __("Can't find HEAD commit for this branch") }}</span>
</div>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
index fc6a45b957e..6a4a834337a 100644
--- a/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/content_viewer/viewers/image_viewer.vue
@@ -27,7 +27,6 @@ export default {
return {
width: 0,
height: 0,
- isLoaded: false,
};
},
computed: {
@@ -63,8 +62,6 @@ export default {
this.height = contentImg.naturalHeight;
this.$nextTick(() => {
- this.isLoaded = true;
-
this.$emit('imgLoaded', {
width: this.width,
height: this.height,
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/icon.vue b/app/assets/javascripts/vue_shared/components/icon.vue
index 41c4c861566..fa89473da62 100644
--- a/app/assets/javascripts/vue_shared/components/icon.vue
+++ b/app/assets/javascripts/vue_shared/components/icon.vue
@@ -1,4 +1,6 @@
<script>
+import iconsPath from '@gitlab/svgs/dist/icons.svg';
+
// only allow classes in images.scss e.g. s12
const validSizes = [8, 10, 12, 14, 16, 18, 24, 32, 48, 72];
let iconValidator = () => true;
@@ -84,7 +86,7 @@ export default {
computed: {
spriteHref() {
- return `${gon.sprite_icons}#${this.name}`;
+ return `${iconsPath}#${this.name}`;
},
iconTestClass() {
return `ic-${this.name}`;
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index 56a16c9e4d6..4d27d1c9179 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -38,10 +38,11 @@ export default {
computed: {
mdTable() {
return [
- '| header | header |',
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ '| header | header |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
'| ------ | ------ |',
- '| cell | cell |',
- '| cell | cell |',
+ '| cell | cell |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+ '| cell | cell |', // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
].join('\n');
},
mdSuggestion() {
@@ -123,7 +124,7 @@ export default {
:cursor-offset="4"
:tag-content="lineContent"
icon="doc-code"
- class="qa-suggestion-btn"
+ class="qa-suggestion-btn js-suggestion-btn"
@click="handleSuggestDismissed"
/>
<gl-popover
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
index 2eb4ec12a4a..a7cd292e01d 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff.vue
@@ -39,7 +39,7 @@ export default {
<template>
<div class="md-suggestion">
<suggestion-diff-header
- class="qa-suggestion-diff-header"
+ class="qa-suggestion-diff-header js-suggestion-diff-header"
:can-apply="suggestion.appliable && suggestion.current_user.can_apply && !disabled"
:is-applied="suggestion.applied"
:help-page-path="helpPagePath"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 32783b85df4..12de3671477 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -41,7 +41,7 @@ export default {
<template>
<div class="md-suggestion-header border-bottom-0 mt-2">
- <div class="qa-suggestion-diff-header font-weight-bold">
+ <div class="qa-suggestion-diff-header js-suggestion-diff-header font-weight-bold">
{{ __('Suggested change') }}
<a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')" class="js-help-btn">
<icon name="question-o" css-classes="link-highlight" />
@@ -55,7 +55,7 @@ export default {
<gl-button
v-else-if="canApply"
v-gl-tooltip.viewport="__('This also resolves the discussion')"
- class="btn-inverted qa-apply-btn"
+ class="btn-inverted qa-apply-btn js-apply-btn"
:disabled="isApplying"
variant="success"
@click="applySuggestion"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
index 8ce5b615795..5140184eb8e 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/toolbar.vue
@@ -1,4 +1,5 @@
<script>
+/* eslint-disable @gitlab/vue-i18n/no-bare-strings */
import { GlLink } from '@gitlab/ui';
export default {
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/components/select2_select.vue b/app/assets/javascripts/vue_shared/components/select2_select.vue
index 6d2612556ff..eb741d238b5 100644
--- a/app/assets/javascripts/vue_shared/components/select2_select.vue
+++ b/app/assets/javascripts/vue_shared/components/select2_select.vue
@@ -3,6 +3,8 @@ import $ from 'jquery';
import 'select2';
export default {
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
name: 'Select2Select',
props: {
options: {
diff --git a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
index 8ba6b73f928..af4eb2de7f8 100644
--- a/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
+++ b/app/assets/javascripts/vue_shared/components/stacked_progress_bar.vue
@@ -78,6 +78,8 @@ export default {
return percent;
},
barStyle(percent) {
+ // False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/26
+ // eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `width: ${percent}%;`;
},
getTooltip(label, count) {
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/_ee/application_ee.scss b/app/assets/stylesheets/_ee/application_ee.scss
new file mode 100644
index 00000000000..0fb2c9b68a9
--- /dev/null
+++ b/app/assets/stylesheets/_ee/application_ee.scss
@@ -0,0 +1,5 @@
+/*
+ This is a noop-file. In EE:
+ ee/app/assets/stylesheets/_ee/application_ee.scss
+ will take precedence over it and import more styles
+ */
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d5ef66af31a..e98030f1511 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -12,7 +12,6 @@
// If you need to add unique style that should affect only one page - use pages/
// directory.
@import "at.js/dist/css/jquery.atwho";
-@import "pikaday/scss/pikaday";
@import "dropzone/dist/basic";
@import "select2/select2";
@@ -34,4 +33,8 @@
// Styles for JS behaviors.
@import "behaviors";
+// EE-only stylesheets
+@import "application_ee";
+
+// CSS util classes
@import "utilities";
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/callout.scss b/app/assets/stylesheets/framework/callout.scss
index 643b20c56bc..c5bb2a1256a 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -9,7 +9,9 @@
.bs-callout {
margin: $gl-padding 0;
padding: $gl-padding;
- border-left: 3px solid $border-color;
+ border-color: $border-color;
+ border-style: solid;
+ border-width: 0 0 0 3px;
color: $text-color;
background: $gray-light;
@@ -48,6 +50,10 @@
background-color: $blue-100;
border-color: $blue-200;
color: $blue-700;
+
+ h4 {
+ color: $blue-700;
+ }
}
.bs-callout-success {
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index 1bd5043ed10..e9218dcec67 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -434,12 +434,16 @@ img.emoji {
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
+.w-8em { width: 8em; }
+.w-3rem { width: 3rem; }
.h-12em { height: 12em; }
+.h-32-px { height: 32px;}
.mw-460 { max-width: 460px; }
.mw-6em { max-width: 6em; }
.mw-70p { max-width: 70%; }
+.mw-90p { max-width: 90%; }
.min-height-0 { min-height: 0; }
diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss
index e3dd127366d..96f6d02a68f 100644
--- a/app/assets/stylesheets/framework/flash.scss
+++ b/app/assets/stylesheets/framework/flash.scss
@@ -43,6 +43,7 @@
@extend .alert;
background-color: $orange-100;
color: $orange-900;
+ cursor: default;
margin: 0;
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 1bc597bd4ae..ca737c53318 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -131,7 +131,6 @@
> li:not(.d-none) a {
@include media-breakpoint-down(xs) {
margin-left: 0;
- min-width: 100%;
}
}
}
@@ -233,7 +232,6 @@
.impersonation-btn,
.impersonation-btn:hover {
background-color: $white-light;
- margin-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 460d9ea9526..ecd32dcd0ce 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -285,3 +285,19 @@ ul.indent-list {
max-width: 350px;
}
}
+
+.horizontal-list {
+ padding-left: 0;
+ list-style: none;
+
+ > li {
+ float: left;
+ }
+
+ &.list-items-separated {
+ > li:not(:last-child)::after {
+ content: '\00b7';
+ margin: 0 $gl-padding-4;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/responsive_tables.scss b/app/assets/stylesheets/framework/responsive_tables.scss
index 6bd44ee19bd..fd6f80e26cb 100644
--- a/app/assets/stylesheets/framework/responsive_tables.scss
+++ b/app/assets/stylesheets/framework/responsive_tables.scss
@@ -155,7 +155,7 @@
text-overflow: ellipsis;
@include media-breakpoint-up(md) {
- flex: 0 0 90%;
+ flex: 0 0 85%;
}
.avatar {
diff --git a/app/assets/stylesheets/framework/tooltips.scss b/app/assets/stylesheets/framework/tooltips.scss
index 98f28987a82..edc2fb532c8 100644
--- a/app/assets/stylesheets/framework/tooltips.scss
+++ b/app/assets/stylesheets/framework/tooltips.scss
@@ -1,7 +1,6 @@
.tooltip-inner {
- font-size: $tooltip-font-size;
+ font-size: $gl-font-size-small;
border-radius: $border-radius-default;
- line-height: 16px;
+ line-height: $gl-line-height;
font-weight: $gl-font-weight-normal;
- padding: 8px;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index c108f45622f..92190f8979e 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -603,6 +603,7 @@ $blame-blue: #254e77;
* Builds
*/
$builds-trace-bg: #111;
+$job-log-highlight-height: 18px;
/*
* Commit Page
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index ea96381a098..604b48e11ab 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -48,3 +48,7 @@ $spacers: (
9: ($spacer * 8)
);
$pagination-color: $gl-text-color;
+$tooltip-padding-y: 0.5rem;
+$tooltip-padding-x: 0.75rem;
+$tooltip-arrow-height: 0.5rem;
+$tooltip-arrow-width: 1rem;
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 ee0ec94c636..973f94c63aa 100644
--- a/app/assets/stylesheets/highlight/white_base.scss
+++ b/app/assets/stylesheets/highlight/white_base.scss
@@ -101,6 +101,10 @@ pre.code,
color: $white-code-color;
}
+.line_expansion {
+ @include diff-expansion($gray-light, $border-color, $blue-600);
+}
+
// Diff line
.line_holder {
&.match .line_content,
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index cbcd8a474f1..ba126d59eef 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -1216,11 +1216,10 @@ $ide-commit-header-height: 48px;
}
.ide-search-list-empty {
- height: 230px;
+ height: 69px;
}
.ide-merge-requests-dropdown-content {
- min-height: 230px;
max-height: 470px;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 6e98908eeed..73166940146 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -125,8 +125,24 @@
padding-left: $gl-padding-8;
}
- .section-header ~ .section.line {
- margin-left: $gl-padding;
+ .section-start {
+ display: inline;
+ }
+
+ .section-start,
+ .section-header {
+ &:hover {
+ cursor: pointer;
+
+ &::after {
+ content: '';
+ background-color: rgba($white-light, 0.2);
+ left: 0;
+ right: 0;
+ position: absolute;
+ height: $job-log-highlight-height;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/pages/container_registry.scss b/app/assets/stylesheets/pages/container_registry.scss
index a21fa29f34a..0f4bdb219a3 100644
--- a/app/assets/stylesheets/pages/container_registry.scss
+++ b/app/assets/stylesheets/pages/container_registry.scss
@@ -31,4 +31,21 @@
.table.tags {
margin-bottom: 0;
+
+ .registry-image-row {
+ .check {
+ padding-right: $gl-padding;
+ width: 5%;
+ }
+
+ .action-buttons {
+ opacity: 0;
+ }
+
+ &:hover {
+ .action-buttons {
+ opacity: 1;
+ }
+ }
+ }
}
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 95ea49ad465..77a2fd6b876 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -408,6 +408,14 @@ table.code {
table-layout: fixed;
border-radius: 0 0 $border-radius-default $border-radius-default;
+ tr:first-of-type.line_expansion > td {
+ border-top: 0;
+ }
+
+ tr:nth-last-of-type(2).line_expansion > td {
+ border-bottom: 0;
+ }
+
tr.line_holder td {
line-height: $code-line-height;
font-size: $code-font-size;
@@ -1024,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 & {
@@ -1035,7 +1042,7 @@ table.code {
.drag-handle {
bottom: 16px;
- transform: translateX(-6px);
+ transform: translateX(10px);
}
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index cff2e274390..1502cf18440 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -412,10 +412,6 @@ table.pipeline-project-metrics tr td {
font-size: $gl-font-size-large;
}
- .item-visibility {
- color: $gl-text-color-secondary;
- }
-
@include media-breakpoint-down(md) {
.title {
font-size: $gl-font-size;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6a0127eb51c..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;
}
@@ -390,7 +386,7 @@
.block {
width: $gutter-collapsed-width - 2px;
- padding: 15px 0 0;
+ padding: 0;
border-bottom: 0;
overflow: hidden;
@@ -427,10 +423,13 @@
}
.sidebar-collapsed-icon {
- display: block;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
width: 100%;
+ height: $sidebar-toggle-height;
text-align: center;
- margin-bottom: 10px;
color: $gl-text-color-secondary;
svg {
@@ -470,6 +469,16 @@
}
.btn-clipboard {
+ /*
+ This change should be temporary, because the DOM currently gets
+ generated from a ruby definition in `app/helpers/button_helper.rb`.
+ As soon as the `copy to clipboard` button will be transfered to
+ Vue this should be adjusted as well.
+ */
+ flex: 1;
+ align-self: stretch;
+ padding: 0;
+
border: 0;
background: transparent;
color: $gl-text-color-secondary;
@@ -493,7 +502,6 @@
.sidebar-collapsed-user {
padding-bottom: 0;
- margin-bottom: 10px;
.author-link {
padding-left: 0;
@@ -509,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/members.scss b/app/assets/stylesheets/pages/members.scss
index 68af01f9ccc..ae92a2fbd7b 100644
--- a/app/assets/stylesheets/pages/members.scss
+++ b/app/assets/stylesheets/pages/members.scss
@@ -9,10 +9,6 @@
}
}
-.member-sort-dropdown {
- margin-left: $gl-padding-8;
-}
-
.member {
&.is-overridden {
.btn-ldap-override {
@@ -62,43 +58,11 @@
}
}
-.member-search-form {
- position: relative;
-
- @include media-breakpoint-up(sm) {
- float: right;
- }
-
- .dropdown {
- width: 100%;
- margin-top: 5px;
-
- .dropdown-menu-toggle {
- vertical-align: middle;
- width: 100%;
- }
-
- @include media-breakpoint-up(sm) {
- margin-top: 0;
- width: 155px;
- }
- }
-
- .form-control {
- width: 100%;
- padding-right: 35px;
-
- @include media-breakpoint-up(sm) {
- width: 250px;
- }
- }
-}
-
.member-search-btn {
position: absolute;
right: 4px;
top: 0;
- height: 35px;
+ height: $input-height;
padding-left: 10px;
padding-right: 10px;
color: $gray-darkest;
@@ -177,7 +141,7 @@
padding-bottom: 1px;
}
- .flex-project-members-form {
+ .flex-users-form {
flex-wrap: nowrap;
white-space: nowrap;
margin-left: auto;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 2780afa11fa..c8d155706a9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -88,7 +88,7 @@
}
.mr-widget-info {
- padding-left: $gl-padding-50 - $gl-padding-32;
+ padding-left: $gl-padding;
padding-right: $gl-padding;
}
@@ -208,6 +208,10 @@
font-size: 22px;
}
+ .mr-loading-icon {
+ margin: 3px 0;
+ }
+
.ci-status-icon svg {
margin: 3px 0;
position: relative;
@@ -262,23 +266,11 @@
}
}
- .widget-status-icon {
- align-self: flex-start;
-
- button {
- margin-left: $gl-padding;
- }
- }
-
.mr-widget-body {
line-height: 28px;
@include clearfix;
- button {
- margin-left: $gl-padding;
- }
-
.approve-btn {
margin-right: 5px;
}
@@ -405,7 +397,6 @@
.mr-widget-help {
padding: 10px 16px 10px ($gl-padding-8 * 7);
- font-style: italic;
}
.ci-coverage {
@@ -527,7 +518,7 @@
}
.mr-links {
- padding-left: $status-icon-size + $gl-btn-padding;
+ padding-left: $gl-padding-8 + $status-icon-size + $gl-btn-padding;
&:last-child {
padding-bottom: $gl-padding;
@@ -914,7 +905,7 @@
}
.deploy-heading,
-.merge-train-info {
+.merge-train-position-indicator {
@include media-breakpoint-up(md) {
padding: $gl-padding-8 $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index b9b8eabf909..6c03dbb56a7 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -806,7 +806,7 @@ $note-form-margin-left: 72px;
border-radius: $border-radius-base;
border: 1px solid $border-gray-normal;
color: $note-disabled-comment-color;
- padding: 90px 0;
+ padding: $gl-padding-8 0;
&.discussion-locked {
border: 0;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index ff4fa8aacdc..5f4db37c317 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -695,6 +695,10 @@
top: -1px;
}
+ .spinner {
+ top: 2px;
+ }
+
&.play {
svg {
left: 2px;
@@ -861,6 +865,7 @@ button.mini-pipeline-graph-dropdown-toggle {
}
}
+ .spinner,
svg {
width: $ci-action-dropdown-svg-size;
height: $ci-action-dropdown-svg-size;
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
index 2d600e3aef6..72f1b5307ec 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -1,17 +1,17 @@
.prometheus-graphs {
- .dropdowns {
- .dropdown-menu-toggle {
- svg {
- position: absolute;
- right: 5%;
- top: 25%;
- }
+ .dropdown-buttons {
+ > div {
+ margin-left: auto;
}
+ }
- .dropdown-menu-toggle,
- .dropdown-menu {
- width: 240px;
- }
+ .col-form-label {
+ line-height: 1;
+ padding-top: 0;
+ }
+
+ .form-group {
+ margin-bottom: map-get($spacing-scale, 3);
}
}
@@ -29,6 +29,11 @@
padding: $gl-padding / 2;
}
+.prometheus-graph-embed {
+ border: 1px solid $border-color;
+ border-radius: $border-radius-default;
+}
+
.prometheus-graph-header {
display: flex;
align-items: center;
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/search.scss b/app/assets/stylesheets/pages/search.scss
index dbf600df9d6..58e46cfb70f 100644
--- a/app/assets/stylesheets/pages/search.scss
+++ b/app/assets/stylesheets/pages/search.scss
@@ -186,15 +186,12 @@ input[type='checkbox']:hover {
}
}
-.search-holder {
- @include media-breakpoint-up(sm) {
- display: flex;
- }
+.search-field-holder,
+.project-filter-form {
+ flex: 1 0 auto;
+ position: relative;
- .search-field-holder,
- .project-filter-form {
- flex: 1 0 auto;
- position: relative;
+ .search-holder & {
margin-right: 0;
@include media-breakpoint-up(sm) {
@@ -202,6 +199,7 @@ input[type='checkbox']:hover {
}
}
+
.search-icon {
position: absolute;
left: 10px;
@@ -215,9 +213,16 @@ input[type='checkbox']:hover {
padding-left: $gl-padding + 15px;
padding-right: $gl-padding + 15px;
}
+}
+
+.search-holder {
+ @include media-breakpoint-up(sm) {
+ display: flex;
+ }
.btn-search,
- .btn-success {
+ .btn-success,
+ .dropdown-menu-toggle {
width: 100%;
margin-top: 5px;
@@ -236,9 +241,6 @@ input[type='checkbox']:hover {
}
.dropdown-menu-toggle {
- width: 100%;
- margin-top: 5px;
-
@include media-breakpoint-up(sm) {
width: 180px;
margin-top: 0;
@@ -262,6 +264,25 @@ input[type='checkbox']:hover {
}
}
+.search-page-form {
+ .dropdown-menu-toggle,
+ .btn-search {
+ width: 100%;
+ }
+
+ .dropdown-menu-toggle {
+ @include media-breakpoint-up(lg) {
+ width: 240px;
+ }
+ }
+
+ .btn-search {
+ @include media-breakpoint-up(lg) {
+ width: auto;
+ }
+ }
+}
+
// Disable webkit input icons, link to solution: https://stackoverflow.com/questions/9421551/how-do-i-remove-all-default-webkit-search-field-styling
/* stylelint-disable property-no-vendor-prefix */
input[type='search']::-webkit-search-decoration,
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 3b62121eb0d..79de1d78a6e 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -272,7 +272,7 @@
}
.custom-monitored-metrics {
- .card-title {
+ .card-header {
display: flex;
align-items: center;
@@ -292,17 +292,6 @@
}
}
- .loading-metrics,
- .empty-metrics {
- padding: 30px 10px;
-
- p,
- .btn {
- margin-top: 10px;
- margin-bottom: 0;
- }
- }
-
.loading-metrics .metrics-load-spinner {
color: $gl-gray-700;
}
diff --git a/app/assets/stylesheets/pages/todos.scss b/app/assets/stylesheets/pages/todos.scss
index 586365eb1ce..7b64c67ae34 100644
--- a/app/assets/stylesheets/pages/todos.scss
+++ b/app/assets/stylesheets/pages/todos.scss
@@ -4,7 +4,7 @@
*/
.todos-list > .todo {
- // workaround because we cannot use border-colapse
+ // workaround because we cannot use border-collapse
border-top: 1px solid transparent;
display: flex;
flex-direction: row;
diff --git a/app/assets/stylesheets/pages/users.scss b/app/assets/stylesheets/pages/users.scss
new file mode 100644
index 00000000000..3b018c1e087
--- /dev/null
+++ b/app/assets/stylesheets/pages/users.scss
@@ -0,0 +1,105 @@
+.user-sort-dropdown {
+ margin-left: $gl-padding-8;
+}
+
+.user-search-form {
+ position: relative;
+
+ @include media-breakpoint-up(sm) {
+ float: right;
+ }
+
+ .dropdown {
+ width: 100%;
+ margin-top: 5px;
+
+ .dropdown-menu-toggle {
+ vertical-align: middle;
+ width: 100%;
+ }
+
+ @include media-breakpoint-up(sm) {
+ margin-top: 0;
+ width: 155px;
+ }
+ }
+
+ .form-control {
+ width: 100%;
+ padding-right: 35px;
+
+ @include media-breakpoint-up(sm) {
+ width: 250px;
+ }
+ }
+}
+
+.user-search-btn {
+ position: absolute;
+ right: 4px;
+ top: 0;
+ height: 35px;
+ padding-left: 10px;
+ padding-right: 10px;
+ color: $gray-darkest;
+ background: transparent;
+ border: 0;
+ outline: 0;
+}
+
+.flex-users-panel {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+
+ @include media-breakpoint-down(sm) {
+ display: block;
+
+ .flex-project-title {
+ vertical-align: top;
+ display: inline-block;
+ max-width: 90%;
+ }
+ }
+
+ .flex-project-title {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+
+ .badge.badge-pill {
+ height: 17px;
+ line-height: 16px;
+ margin-right: 5px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ }
+
+ .flex-users-form {
+ flex-wrap: nowrap;
+ white-space: nowrap;
+ margin-left: auto;
+ }
+}
+
+.content-list.members-list li {
+ display: flex;
+ justify-content: space-between;
+
+ .list-item-name {
+ float: none;
+ display: flex;
+ flex: 1;
+ }
+}
+
+.card-body .user-info {
+ float: left;
+
+ .user {
+ color: $gl-text-color;
+ font-weight: $gl-font-weight-bold;
+ }
+}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 60400f10ca5..379df1c4db1 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -1,19 +1,3 @@
-.new-wiki-page {
- .new-wiki-page-slug-tip {
- display: inline-block;
- max-width: 100%;
- margin-top: 5px;
- }
-}
-
-.wiki-form {
- .edit-wiki-page-slug-tip {
- display: inline-block;
- max-width: 100%;
- margin-top: 5px;
- }
-}
-
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
@@ -22,7 +6,6 @@
}
.wiki-page-header {
- @extend .top-area;
position: relative;
.wiki-breadcrumb {
diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss
index 5a8940ffd6d..ad7d87f0bf6 100644
--- a/app/assets/stylesheets/performance_bar.scss
+++ b/app/assets/stylesheets/performance_bar.scss
@@ -1,6 +1,5 @@
@import 'framework/variables';
@import 'framework/variables_overrides';
-@import 'peek/views/rblineprof';
#js-peek {
position: fixed;
@@ -128,13 +127,3 @@
#modal-peek-pg-queries-content {
color: $black;
}
-
-.peek-rblineprof-file {
- pre.duration {
- width: 280px;
- }
-
- .data {
- overflow: visible;
- }
-}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index e9ec8876688..99411641874 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -106,6 +106,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:lets_encrypt_notification_email,
:lets_encrypt_terms_of_service_accepted,
:domain_blacklist_file,
+ :raw_blob_request_limit,
disabled_oauth_sign_in_sources: [],
import_sources: [],
repository_storages: [],
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 15f7ef881c8..6317fa7c8d1 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -90,7 +90,8 @@ class Admin::GroupsController < Admin::ApplicationController
:visibility_level,
:require_two_factor_authentication,
:two_factor_grace_period,
- :project_creation_level
+ :project_creation_level,
+ :subgroup_creation_level
]
end
end
diff --git a/app/controllers/admin/requests_profiles_controller.rb b/app/controllers/admin/requests_profiles_controller.rb
index 89d4c4f18d9..24383455064 100644
--- a/app/controllers/admin/requests_profiles_controller.rb
+++ b/app/controllers/admin/requests_profiles_controller.rb
@@ -3,17 +3,17 @@
class Admin::RequestsProfilesController < Admin::ApplicationController
def index
@profile_token = Gitlab::RequestProfiler.profile_token
- @profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
+ @profiles = Gitlab::RequestProfiler.all.group_by(&:request_path)
end
def show
clean_name = Rack::Utils.clean_path_info(params[:name])
- profile = Gitlab::RequestProfiler::Profile.find(clean_name)
+ profile = Gitlab::RequestProfiler.find(clean_name)
- if profile
- render html: profile.content.html_safe
- else
- redirect_to admin_requests_profiles_path, alert: 'Profile not found'
+ unless profile && profile.content_type
+ return redirect_to admin_requests_profiles_path, alert: 'Profile not found'
end
+
+ send_file profile.file_path, type: "#{profile.content_type}; charset=utf-8", disposition: 'inline'
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index a02d0843615..98883af6286 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -39,7 +39,7 @@ class Admin::UsersController < Admin::ApplicationController
warden.set_user(user, scope: :user)
- Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
+ log_impersonation_event
flash[:alert] = _("You are now impersonating %{username}") % { username: user.username }
@@ -236,4 +236,8 @@ class Admin::UsersController < Admin::ApplicationController
def check_impersonation_availability
access_denied! unless Gitlab.config.gitlab.impersonation_enabled
end
+
+ def log_impersonation_event
+ Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
+ end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 75108bf2646..af6644b8fcc 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -12,6 +12,7 @@ class ApplicationController < ActionController::Base
include EnforcesTwoFactorAuthentication
include WithPerformanceBar
include SessionlessAuthentication
+ include ConfirmEmailWarning
before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms?
@@ -116,7 +117,7 @@ class ApplicationController < ActionController::Base
def render(*args)
super.tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
- if response.content_type == 'text/html' && (400..599).cover?(response.status)
+ if (400..599).cover?(response.status) && workhorse_excluded_content_types.include?(response.content_type)
response.headers['X-GitLab-Custom-Error'] = '1'
end
end
@@ -124,6 +125,10 @@ class ApplicationController < ActionController::Base
protected
+ def workhorse_excluded_content_types
+ @workhorse_excluded_content_types ||= %w(text/html application/json)
+ end
+
def append_info_to_payload(payload)
super
@@ -421,7 +426,7 @@ class ApplicationController < ActionController::Base
end
def manifest_import_enabled?
- Group.supports_nested_objects? && Gitlab::CurrentSettings.import_sources.include?('manifest')
+ Gitlab::CurrentSettings.import_sources.include?('manifest')
end
def phabricator_import_enabled?
@@ -499,9 +504,7 @@ class ApplicationController < ActionController::Base
end
def stop_impersonation
- impersonated_user = current_user
-
- Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}")
+ log_impersonation_event
warden.set_user(impersonator, scope: :user)
session[:impersonator_id] = nil
@@ -509,6 +512,14 @@ class ApplicationController < ActionController::Base
impersonated_user
end
+ def impersonated_user
+ current_user
+ end
+
+ def log_impersonation_event
+ Gitlab::AppLogger.info("User #{impersonator.username} has stopped impersonating #{impersonated_user.username}")
+ end
+
def impersonator
@impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 091327931c2..30a567c3bef 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -16,7 +16,7 @@ class AutocompleteController < ApplicationController
.new(params: params, current_user: current_user, project: project, group: group)
.execute
- render json: UserSerializer.new.represent(users)
+ render json: UserSerializer.new(params).represent(users, project: project)
end
def user
@@ -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/boards/issues_controller.rb b/app/controllers/boards/issues_controller.rb
index 90528f75ffd..1d1a72d21f1 100644
--- a/app/controllers/boards/issues_controller.rb
+++ b/app/controllers/boards/issues_controller.rb
@@ -26,7 +26,7 @@ module Boards
list_service = Boards::Issues::ListService.new(board_parent, current_user, filter_params)
issues = list_service.execute
issues = issues.page(params[:page]).per(params[:per] || 20).without_count
- Issue.move_to_end(issues) if Gitlab::Database.read_write?
+ Issue.move_nulls_to_end(issues) if Gitlab::Database.read_write?
issues = issues.preload(:milestone,
:assignees,
project: [
diff --git a/app/controllers/chaos_controller.rb b/app/controllers/chaos_controller.rb
index 2985da35d83..ac008165c16 100644
--- a/app/controllers/chaos_controller.rb
+++ b/app/controllers/chaos_controller.rb
@@ -1,57 +1,38 @@
# frozen_string_literal: true
class ChaosController < ActionController::Base
- before_action :validate_chaos_secret, unless: :development?
- before_action :request_start_time
+ before_action :validate_chaos_secret, unless: :development_or_test?
def leakmem
- retainer = []
- # Add `n` 1mb chunks of memory to the retainer array
- memory_mb.times { retainer << "x" * 1.megabyte }
-
- Kernel.sleep(duration_left)
-
- render plain: "OK"
+ do_chaos :leak_mem, Chaos::LeakMemWorker, memory_mb, duration_s
end
def cpu_spin
- rand while Time.now < expected_end_time
-
- render plain: "OK"
+ do_chaos :cpu_spin, Chaos::CpuSpinWorker, duration_s
end
def db_spin
- while Time.now < expected_end_time
- ActiveRecord::Base.connection.execute("SELECT 1")
-
- end_interval_time = Time.now + [duration_s, interval_s].min
- rand while Time.now < end_interval_time
- end
+ do_chaos :db_spin, Chaos::DbSpinWorker, duration_s, interval_s
end
def sleep
- Kernel.sleep(duration_left)
-
- render plain: "OK"
+ do_chaos :sleep, Chaos::SleepWorker, duration_s
end
def kill
- Process.kill("KILL", Process.pid)
+ do_chaos :kill, Chaos::KillWorker
end
private
- def request_start_time
- @start_time ||= Time.now
- end
-
- def expected_end_time
- request_start_time + duration_s
- end
+ def do_chaos(method, worker, *args)
+ if async
+ worker.perform_async(*args)
+ else
+ Gitlab::Chaos.public_send(method, *args) # rubocop: disable GitlabSecurity/PublicSend
+ end
- def duration_left
- # returns 0 if over time
- [expected_end_time - Time.now, 0].max
+ render plain: "OK"
end
def validate_chaos_secret
@@ -91,7 +72,12 @@ class ChaosController < ActionController::Base
memory_mb.to_i
end
- def development?
- Rails.env.development?
+ def async
+ async = params[:async] || false
+ Gitlab::Utils.to_boolean(async)
+ end
+
+ def development_or_test?
+ Rails.env.development? || Rails.env.test?
end
end
diff --git a/app/controllers/concerns/authenticates_with_two_factor.rb b/app/controllers/concerns/authenticates_with_two_factor.rb
index 4926062f9ca..8c8f0b3a22e 100644
--- a/app/controllers/concerns/authenticates_with_two_factor.rb
+++ b/app/controllers/concerns/authenticates_with_two_factor.rb
@@ -55,7 +55,7 @@ module AuthenticatesWithTwoFactor
remember_me(user) if user_params[:remember_me] == '1'
user.save!
- sign_in(user, message: :two_factor_authenticated)
+ sign_in(user, message: :two_factor_authenticated, event: :authentication)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=OTP")
@@ -72,7 +72,7 @@ module AuthenticatesWithTwoFactor
session.delete(:challenge)
remember_me(user) if user_params[:remember_me] == '1'
- sign_in(user, message: :two_factor_authenticated)
+ sign_in(user, message: :two_factor_authenticated, event: :authentication)
else
user.increment_failed_attempts!
Gitlab::AppLogger.info("Failed Login: user=#{user.username} ip=#{request.remote_ip} method=U2F")
diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb
new file mode 100644
index 00000000000..5a4b5897a4f
--- /dev/null
+++ b/app/controllers/concerns/confirm_email_warning.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module ConfirmEmailWarning
+ extend ActiveSupport::Concern
+
+ included do
+ before_action :set_confirm_warning, if: -> { Feature.enabled?(:soft_email_confirmation) }
+ end
+
+ protected
+
+ def set_confirm_warning
+ return unless current_user
+ return if current_user.confirmed?
+ return if peek_request? || json_request? || !request.get?
+
+ email = current_user.unconfirmed_email || current_user.email
+
+ flash.now[:warning] = _("Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}.").html_safe % {
+ email: email,
+ resend_link: view_context.link_to(_('Resend it'), user_confirmation_path(user: { email: email }), method: :post),
+ update_link: view_context.link_to(_('Update it'), profile_path)
+ }
+ end
+end
diff --git a/app/controllers/concerns/group_tree.rb b/app/controllers/concerns/group_tree.rb
index e9a7d6a3152..d076c62c707 100644
--- a/app/controllers/concerns/group_tree.rb
+++ b/app/controllers/concerns/group_tree.rb
@@ -32,18 +32,14 @@ module GroupTree
def filtered_groups_with_ancestors(groups)
filtered_groups = groups.search(params[:filter]).page(params[:page])
- if Group.supports_nested_objects?
- # We find the ancestors by ID of the search results here.
- # Otherwise the ancestors would also have filters applied,
- # which would cause them not to be preloaded.
- #
- # Pagination needs to be applied before loading the ancestors to
- # make sure ancestors are not cut off by pagination.
- Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
- .base_and_ancestors
- else
- filtered_groups
- end
+ # We find the ancestors by ID of the search results here.
+ # Otherwise the ancestors would also have filters applied,
+ # which would cause them not to be preloaded.
+ #
+ # Pagination needs to be applied before loading the ancestors to
+ # make sure ancestors are not cut off by pagination.
+ Gitlab::ObjectHierarchy.new(Group.where(id: filtered_groups.select(:id)))
+ .base_and_ancestors
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/controllers/concerns/invisible_captcha.rb b/app/controllers/concerns/invisible_captcha.rb
new file mode 100644
index 00000000000..45c0a5c58ef
--- /dev/null
+++ b/app/controllers/concerns/invisible_captcha.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module InvisibleCaptcha
+ extend ActiveSupport::Concern
+
+ included do
+ invisible_captcha only: :create, on_spam: :on_honeypot_spam_callback, on_timestamp_spam: :on_timestamp_spam_callback
+ end
+
+ def on_honeypot_spam_callback
+ return unless Feature.enabled?(:invisible_captcha)
+
+ invisible_captcha_honeypot_counter.increment
+ log_request('Invisible_Captcha_Honeypot_Request')
+
+ head(200)
+ end
+
+ def on_timestamp_spam_callback
+ return unless Feature.enabled?(:invisible_captcha)
+
+ invisible_captcha_timestamp_counter.increment
+ log_request('Invisible_Captcha_Timestamp_Request')
+
+ redirect_to new_user_session_path, alert: InvisibleCaptcha.timestamp_error_message
+ end
+
+ def invisible_captcha_honeypot_counter
+ @invisible_captcha_honeypot_counter ||=
+ Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_honeypot,
+ 'Counter of blocked sign up attempts with filled honeypot')
+ end
+
+ def invisible_captcha_timestamp_counter
+ @invisible_captcha_timestamp_counter ||=
+ Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_timestamp,
+ 'Counter of blocked sign up attempts with invalid timestamp')
+ end
+
+ def log_request(message)
+ request_information = {
+ message: message,
+ env: :invisible_captcha_signup_bot_detected,
+ remote_ip: request.ip,
+ request_method: request.request_method,
+ path: request.fullpath
+ }
+
+ Gitlab::AuthLogger.error(request_information)
+ end
+end
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index 6fa2f75be33..b86e4451a7e 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -98,13 +98,12 @@ module IssuableActions
render json: { notice: "#{quantity} #{resource_name.pluralize(quantity)} updated" }
end
- # rubocop: disable CodeReuse/ActiveRecord
+ # rubocop:disable CodeReuse/ActiveRecord
def discussions
- notes = issuable.discussion_notes
- .inc_relations_for_view
- .with_notes_filter(notes_filter)
- .includes(:noteable)
- .fresh
+ notes = NotesFinder.new(current_user, finder_params_for_issuable).execute
+ .inc_relations_for_view
+ .includes(:noteable)
+ .fresh
if notes_filter != UserPreference::NOTES_FILTERS[:only_comments]
notes = ResourceEvents::MergeIntoNotesService.new(issuable, current_user).execute(notes)
@@ -117,7 +116,7 @@ module IssuableActions
render json: discussion_serializer.represent(discussions, context: self)
end
- # rubocop: enable CodeReuse/ActiveRecord
+ # rubocop:enable CodeReuse/ActiveRecord
private
@@ -128,7 +127,8 @@ module IssuableActions
# GitLab Geo does not expect database UPDATE or INSERT statements to happen
# on GET requests.
# This is just a fail-safe in case notes_filter is sent via GET request in GitLab Geo.
- if Gitlab::Database.read_only?
+ # In some cases, we also force the filter to not be persisted with the `persist_filter` param
+ if Gitlab::Database.read_only? || params[:persist_filter] == 'false'
notes_filter_param || current_user&.notes_filter_for(issuable)
else
notes_filter = current_user&.set_notes_filter(notes_filter_param, issuable) || notes_filter_param
@@ -222,4 +222,13 @@ module IssuableActions
def parent
@project || @group # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
+
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ def finder_params_for_issuable
+ {
+ target: @issuable,
+ notes_filter: notes_filter
+ }.tap { |new_params| new_params[:project] = project if respond_to?(:project, true) }
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
end
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 21b3949e361..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,26 +139,14 @@ 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
def collection_type
- @collection_type ||= case finder_type.name
- when 'IssuesFinder'
+ @collection_type ||= if finder_type <= IssuesFinder
'Issue'
- when 'MergeRequestsFinder'
+ elsif finder_type <= MergeRequestsFinder
'MergeRequest'
end
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/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index 0098c4cdf4c..d2a961efff7 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -243,7 +243,7 @@ module NotesActions
end
def notes_finder
- @notes_finder ||= NotesFinder.new(project, current_user, finder_params)
+ @notes_finder ||= NotesFinder.new(current_user, finder_params)
end
def note_serializer
diff --git a/app/controllers/concerns/sessionless_authentication.rb b/app/controllers/concerns/sessionless_authentication.rb
index 590eefc6dab..4304b8565ce 100644
--- a/app/controllers/concerns/sessionless_authentication.rb
+++ b/app/controllers/concerns/sessionless_authentication.rb
@@ -13,7 +13,7 @@ module SessionlessAuthentication
end
def sessionless_user?
- current_user && !session.keys.include?('warden.user.user.key')
+ current_user && !session.key?('warden.user.user.key')
end
def sessionless_sign_in(user)
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/concerns/uploads_actions.rb b/app/controllers/concerns/uploads_actions.rb
index 59f6d3452a3..f5d35379e10 100644
--- a/app/controllers/concerns/uploads_actions.rb
+++ b/app/controllers/concerns/uploads_actions.rb
@@ -90,7 +90,7 @@ module UploadsActions
return unless uploader = build_uploader
upload_paths = uploader.upload_paths(params[:filename])
- upload = Upload.find_by(uploader: uploader_class.to_s, path: upload_paths)
+ upload = Upload.find_by(model: model, uploader: uploader_class.to_s, path: upload_paths)
upload&.build_uploader
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/controllers/concerns/with_performance_bar.rb b/app/controllers/concerns/with_performance_bar.rb
index 77c3d476ac6..4e0ae3c59eb 100644
--- a/app/controllers/concerns/with_performance_bar.rb
+++ b/app/controllers/concerns/with_performance_bar.rb
@@ -3,10 +3,6 @@
module WithPerformanceBar
extend ActiveSupport::Concern
- included do
- include Peek::Rblineprof::CustomControllerHelpers
- end
-
def peek_enabled?
return false unless Gitlab::PerformanceBar.enabled?(current_user)
diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb
index 2ae500a2fdf..b192189ba3c 100644
--- a/app/controllers/confirmations_controller.rb
+++ b/app/controllers/confirmations_controller.rb
@@ -11,7 +11,7 @@ class ConfirmationsController < Devise::ConfirmationsController
protected
def after_resending_confirmation_instructions_path_for(resource)
- users_almost_there_path
+ Feature.enabled?(:soft_email_confirmation) ? stored_location_for(resource) || dashboard_projects_path : users_almost_there_path
end
def after_confirmation_path_for(resource_name, resource)
diff --git a/app/controllers/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/graphql_controller.rb b/app/controllers/graphql_controller.rb
index 9fbbe373b0d..72d40f709e6 100644
--- a/app/controllers/graphql_controller.rb
+++ b/app/controllers/graphql_controller.rb
@@ -30,6 +30,10 @@ class GraphqlController < ApplicationController
render_error(exception.message, status: :unprocessable_entity)
end
+ rescue_from Gitlab::Graphql::Errors::ArgumentError do |exception|
+ render_error(exception.message, status: :unprocessable_entity)
+ end
+
private
def execute_multiplex
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index f1d6fb00cfc..557888711ec 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -5,6 +5,8 @@ class Groups::GroupMembersController < Groups::ApplicationController
include MembersPresentation
include SortingHelper
+ MEMBER_PER_PAGE_LIMIT = 50
+
def self.admin_not_required_endpoints
%i[index leave request_access]
end
@@ -24,7 +26,14 @@ class Groups::GroupMembersController < Groups::ApplicationController
@project = @group.projects.find(params[:project_id]) if params[:project_id]
@members = GroupMembersFinder.new(@group).execute
- @members = @members.non_invite unless can_manage_members
+
+ if can_manage_members
+ @invited_members = @members.invite
+ @invited_members = @invited_members.search_invite_email(params[:search_invited]) if params[:search_invited].present?
+ @invited_members = present_members(@invited_members.page(params[:invited_members_page]).per(MEMBER_PER_PAGE_LIMIT))
+ end
+
+ @members = @members.non_invite
@members = @members.search(params[:search]) if params[:search].present?
@members = @members.sort_by_attribute(@sort)
@@ -32,7 +41,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
@members = @members.filter_by_2fa(params[:two_factor])
end
- @members = @members.page(params[:page]).per(50)
+ @members = @members.page(params[:page]).per(MEMBER_PER_PAGE_LIMIT)
@members = present_members(@members)
@requesters = present_members(
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index dbddee47997..886d1f99d69 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -7,10 +7,6 @@ class GroupsController < Groups::ApplicationController
include PreviewMarkdown
include RecordUserLastActivity
- before_action do
- push_frontend_feature_flag(:manual_sorting)
- end
-
respond_to :html
prepend_before_action(only: [:show, :issues]) { authenticate_sessionless_user!(:rss) }
@@ -180,6 +176,7 @@ class GroupsController < Groups::ApplicationController
[
:avatar,
:description,
+ :emails_disabled,
:lfs_enabled,
:name,
:path,
@@ -192,7 +189,8 @@ class GroupsController < Groups::ApplicationController
:chat_team_name,
:require_two_factor_authentication,
:two_factor_grace_period,
- :project_creation_level
+ :project_creation_level,
+ :subgroup_creation_level
]
end
diff --git a/app/controllers/ide_controller.rb b/app/controllers/ide_controller.rb
index eeeebe430a7..af1e6cc703b 100644
--- a/app/controllers/ide_controller.rb
+++ b/app/controllers/ide_controller.rb
@@ -4,5 +4,6 @@ class IdeController < ApplicationController
layout 'fullscreen'
def index
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_views_count
end
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index f71ea8642cd..dc72a4e4fd9 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class Import::BitbucketServerController < Import::BaseController
+ include ActionView::Helpers::SanitizeHelper
+
before_action :verify_bitbucket_server_import_enabled
before_action :bitbucket_auth, except: [:new, :configure]
before_action :validate_import_params, only: [:create]
@@ -57,7 +59,7 @@ class Import::BitbucketServerController < Import::BaseController
# rubocop: disable CodeReuse/ActiveRecord
def status
- @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page)
+ @collection = bitbucket_client.repos(page_offset: page_offset, limit: limit_per_page, filter: sanitized_filter_param)
@repos, @incompatible_repos = @collection.partition { |repo| repo.valid? }
# Use the import URL to filter beyond what BaseService#find_already_added_projects
@@ -147,4 +149,8 @@ class Import::BitbucketServerController < Import::BaseController
def limit_per_page
BitbucketServer::Paginator::PAGE_LENGTH
end
+
+ def sanitized_filter_param
+ sanitize(params[:filter])
+ end
end
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index aa4aa0fbdac..ebb50fc8b10 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -10,7 +10,7 @@ class Import::GithubController < Import::BaseController
rescue_from Octokit::Unauthorized, with: :provider_unauthorized
def new
- if github_import_configured? && logged_in_with_provider?
+ if !ci_cd_only? && github_import_configured? && logged_in_with_provider?
go_to_provider_for_permissions
elsif session[access_token_key]
redirect_to status_import_url
@@ -169,11 +169,15 @@ class Import::GithubController < Import::BaseController
# rubocop: enable CodeReuse/ActiveRecord
def provider_auth
- if session[access_token_key].blank?
+ if !ci_cd_only? && session[access_token_key].blank?
go_to_provider_for_permissions
end
end
+ def ci_cd_only?
+ %w[1 true].include?(params[:ci_cd_only])
+ end
+
def client_options
{}
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 2a8dd997d04..b1efa767154 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -139,7 +139,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if user.two_factor_enabled? && !auth_user.bypass_two_factor?
prompt_for_two_factor(user)
else
- sign_in_and_redirect(user)
+ sign_in_and_redirect(user, event: :authentication)
end
else
fail_login(user)
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index 2ef18d900f2..da8a371acaa 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -92,7 +92,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
def build_from_ref
return unless @ref_name
- project.latest_successful_build_for(params[:job], @ref_name)
+ commit = project.commit(@ref_name)
+ return unless commit
+
+ project.latest_successful_build_for_sha(params[:job], commit.id)
end
def artifacts_file
diff --git a/app/controllers/projects/badges_controller.rb b/app/controllers/projects/badges_controller.rb
index 09a384e89ab..66b51b17790 100644
--- a/app/controllers/projects/badges_controller.rb
+++ b/app/controllers/projects/badges_controller.rb
@@ -3,7 +3,8 @@
class Projects::BadgesController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_admin_project!, only: [:index]
- before_action :no_cache_headers, except: [:index]
+ before_action :no_cache_headers, only: [:pipeline, :coverage]
+ before_action :authorize_read_build!, only: [:pipeline, :coverage]
def pipeline
pipeline_status = Gitlab::Badge::Pipeline::Status
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/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 141a7dfb923..e7bdb4b2042 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -49,7 +49,7 @@ class Projects::BranchesController < Projects::ApplicationController
branches = BranchesFinder.new(repository, params.permit(names: [])).execute
Gitlab::GitalyClient.allow_n_plus_1_calls do
- render json: branches.to_h { |branch| [branch.name, service.call(branch)] }
+ render json: branches.map { |branch| [branch.name, service.call(branch)] }.to_h
end
end
end
diff --git a/app/controllers/projects/build_artifacts_controller.rb b/app/controllers/projects/build_artifacts_controller.rb
index 4274c356227..99f4524eec5 100644
--- a/app/controllers/projects/build_artifacts_controller.rb
+++ b/app/controllers/projects/build_artifacts_controller.rb
@@ -51,6 +51,6 @@ class Projects::BuildArtifactsController < Projects::ApplicationController
def job_from_ref
return unless @ref_name
- project.latest_successful_build_for(params[:job], @ref_name)
+ project.latest_successful_build_for_ref(params[:job], @ref_name)
end
end
diff --git a/app/controllers/projects/cycle_analytics/events_controller.rb b/app/controllers/projects/cycle_analytics/events_controller.rb
index 6314d9f2a9f..926592b9681 100644
--- a/app/controllers/projects/cycle_analytics/events_controller.rb
+++ b/app/controllers/projects/cycle_analytics/events_controller.rb
@@ -23,7 +23,7 @@ module Projects
end
def test
- options(events_params)[:branch] = events_params[:branch_name]
+ options(cycle_analytics_params)[:branch] = cycle_analytics_params[:branch_name]
render_events(cycle_analytics[:test].events)
end
@@ -50,13 +50,13 @@ module Projects
end
def cycle_analytics
- @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(events_params))
+ @cycle_analytics ||= ::CycleAnalytics::ProjectLevel.new(project, options: options(cycle_analytics_params))
end
- def events_params
- return {} unless params[:events].present?
+ def cycle_analytics_params
+ return {} unless params[:cycle_analytics].present?
- params[:events].permit(:start_date, :branch_name)
+ params[:cycle_analytics].permit(:start_date, :branch_name)
end
end
end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 2d46a71bf99..3b0abecf2c9 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -14,8 +14,14 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
@cycle_analytics_no_data = @cycle_analytics.no_stats?
respond_to do |format|
- format.html
- format.json { render json: cycle_analytics_json }
+ format.html do
+ Gitlab::UsageDataCounters::CycleAnalyticsCounter.count(:views)
+
+ render :show
+ end
+ format.json do
+ render json: cycle_analytics_json
+ end
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index ccd54b369fa..5a1f93dc609 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,7 +11,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
- push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint)
+ push_frontend_feature_flag(:environment_metrics_use_prometheus_endpoint, default_enabled: true)
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
@@ -160,12 +160,13 @@ class Projects::EnvironmentsController < Projects::ApplicationController
end
def metrics_dashboard
- if Feature.enabled?(:gfm_embedded_metrics, project) && params[:embedded]
+ if params[:embedded]
result = dashboard_finder.find(
project,
current_user,
environment,
- embedded: params[:embedded]
+ dashboard_path: params[:dashboard],
+ **dashboard_params.to_h.symbolize_keys
)
elsif Feature.enabled?(:environment_metrics_show_multiple_dashboards, project)
result = dashboard_finder.find(
@@ -233,6 +234,10 @@ class Projects::EnvironmentsController < Projects::ApplicationController
params.require([:start, :end])
end
+ def dashboard_params
+ params.permit(:embedded, :group, :title, :y_label)
+ end
+
def dashboard_finder
Gitlab::Metrics::Dashboard::Finder
end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 956093b972b..abf8407a51c 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -49,7 +49,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
send_final_spnego_response
return # Allow access
end
- elsif project && download_request? && Guest.can?(:download_code, project)
+ elsif project && download_request? && http_allowed? && Guest.can?(:download_code, project)
+
@authentication_result = Gitlab::Auth::Result.new(nil, project, :none, [:download_code])
return # Allow access
@@ -113,4 +114,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
def ci?
authentication_result.ci?(project)
end
+
+ def http_allowed?
+ Gitlab::ProtocolAccess.allowed?('http')
+ end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 228de8bc6f3..b7fd286bfe0 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -10,10 +10,6 @@ class Projects::IssuesController < Projects::ApplicationController
include SpammableActions
include RecordUserLastActivity
- before_action do
- push_frontend_feature_flag(:manual_sorting)
- end
-
def issue_except_actions
%i[index calendar new create bulk_update import_csv]
end
@@ -194,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/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index 02ff6e872c9..adbc0159358 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -94,7 +94,7 @@ class Projects::JobsController < Projects::ApplicationController
def play
return respond_422 unless @build.playable?
- build = @build.play(current_user)
+ build = @build.play(current_user, play_params[:job_variables_attributes])
redirect_to build_path(build)
end
@@ -190,6 +190,10 @@ class Projects::JobsController < Projects::ApplicationController
{ query: { 'response-content-type' => 'text/plain; charset=utf-8', 'response-content-disposition' => 'inline' } }
end
+ def play_params
+ params.permit(job_variables_attributes: %i[key secret_value])
+ end
+
def trace_artifact_file
@trace_artifact_file ||= build.job_artifacts_trace&.file
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index dcc272aecff..006731c0e66 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -45,7 +45,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
def set_pipeline_variables
@pipelines =
- if can?(current_user, :read_pipeline, @project)
+ if can?(current_user, :read_pipeline, @merge_request.source_project)
@merge_request.all_pipelines
else
Ci::Pipeline.none
diff --git a/app/controllers/projects/merge_requests/content_controller.rb b/app/controllers/projects/merge_requests/content_controller.rb
index 6e026b83ee3..eec5c1a4355 100644
--- a/app/controllers/projects/merge_requests/content_controller.rb
+++ b/app/controllers/projects/merge_requests/content_controller.rb
@@ -7,16 +7,33 @@ class Projects::MergeRequests::ContentController < Projects::MergeRequests::Appl
# for other types of serialization
before_action :close_merge_request_if_no_source_project
+ before_action :set_polling_header
around_action :allow_gitaly_ref_name_caching
def widget
respond_to do |format|
format.json do
- Gitlab::PollingInterval.set_header(response, interval: 10_000)
+ render json: serializer(MergeRequestPollWidgetEntity)
+ end
+ end
+ end
- serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
- render json: serializer.represent(merge_request, serializer: 'widget')
+ def cached_widget
+ respond_to do |format|
+ format.json do
+ render json: serializer(MergeRequestPollCachedWidgetEntity)
end
end
end
+
+ private
+
+ def set_polling_header
+ Gitlab::PollingInterval.set_header(response, interval: 10_000)
+ end
+
+ def serializer(entity)
+ serializer = MergeRequestSerializer.new(current_user: current_user, project: merge_request.project)
+ serializer.represent(merge_request, {}, entity)
+ end
end
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index 32cefe54613..6ac5bb90706 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -23,6 +23,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@merge_request = ::MergeRequests::CreateService.new(project, current_user, merge_request_params).execute
if @merge_request.valid?
+ incr_count_webide_merge_request
+
redirect_to(merge_request_path(@merge_request))
else
@source_project = @merge_request.source_project
@@ -135,4 +137,10 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
end
+
+ def incr_count_webide_merge_request
+ return if params[:nav_source] != 'webide'
+
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_merge_requests_count
+ end
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2aa2508be16..f4cc0a5851b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -82,7 +82,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def pipelines
- @pipelines = @merge_request.all_pipelines.page(params[:page]).per(30)
+ set_pipeline_variables
+ @pipelines = @pipelines.page(params[:page]).per(30)
Gitlab::PollingInterval.set_header(response, interval: 10_000)
@@ -218,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/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 3152a38fd8e..65d9b074eee 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -68,7 +68,7 @@ class Projects::NotesController < Projects::ApplicationController
alias_method :awardable, :note
def finder_params
- params.merge(last_fetched_at: last_fetched_at, notes_filter: notes_filter)
+ params.merge(project: project, last_fetched_at: last_fetched_at, notes_filter: notes_filter)
end
def authorize_admin_note!
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 42ae5b0ef3c..c94fdd9483d 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -8,10 +8,30 @@ class Projects::RawController < Projects::ApplicationController
before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code!
+ before_action :show_rate_limit, only: [:show]
def show
@blob = @repository.blob_at(@commit.id, @path)
send_blob(@repository, @blob, inline: (params[:inline] != 'false'))
end
+
+ private
+
+ def show_rate_limit
+ limiter = ::Gitlab::ActionRateLimiter.new(action: :show_raw_controller)
+
+ return unless limiter.throttled?([@project, @commit, @path], raw_blob_request_limit)
+
+ limiter.log_request(request, :raw_blob_request_limit, current_user)
+
+ flash[:alert] = _('You cannot access the raw file. Please wait a minute.')
+ redirect_to project_blob_path(@project, File.join(@ref, @path)), status: :too_many_requests
+ end
+
+ def raw_blob_request_limit
+ Gitlab::CurrentSettings
+ .current_application_settings
+ .raw_blob_request_limit
+ end
end
diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb
index bf1d8d8b5fc..54e2faa2dd7 100644
--- a/app/controllers/projects/registry/tags_controller.rb
+++ b/app/controllers/projects/registry/tags_controller.rb
@@ -5,6 +5,8 @@ module Projects
class TagsController < ::Projects::Registry::ApplicationController
before_action :authorize_destroy_container_image!, only: [:destroy]
+ LIMIT = 15
+
def index
respond_to do |format|
format.json do
@@ -28,10 +30,40 @@ module Projects
end
end
+ def bulk_destroy
+ unless params[:ids].present?
+ head :bad_request
+ return
+ end
+
+ tag_names = params[:ids] || []
+ if tag_names.size > LIMIT
+ head :bad_request
+ return
+ end
+
+ @tags = tag_names.map { |tag_name| image.tag(tag_name) }
+ unless @tags.all? { |tag| tag.valid_name? }
+ head :bad_request
+ return
+ end
+
+ success_count = 0
+ @tags.each do |tag|
+ if tag.delete
+ success_count += 1
+ end
+ end
+
+ respond_to do |format|
+ format.json { head(success_count == @tags.size ? :no_content : :bad_request) }
+ end
+ end
+
private
def tags
- Kaminari::PaginatableArray.new(image.tags, limit: 15)
+ Kaminari::PaginatableArray.new(image.tags, limit: LIMIT)
end
def image
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 3b4215b766e..a51759641e4 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -6,6 +6,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
before_action :require_non_empty_project, except: :create
before_action :assign_archive_vars, only: :archive
+ before_action :assign_append_sha, only: :archive
before_action :authorize_download_code!
before_action :authorize_admin_project!, only: :create
@@ -16,19 +17,64 @@ class Projects::RepositoriesController < Projects::ApplicationController
end
def archive
- append_sha = params[:append_sha]
+ set_cache_headers
+ return if archive_not_modified?
- if @ref
- shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
- append_sha = false if @filename == shortname
- end
-
- send_git_archive @repository, ref: @ref, path: params[:path], format: params[:format], append_sha: append_sha
+ send_git_archive @repository, **repo_params
rescue => ex
logger.error("#{self.class.name}: #{ex}")
git_not_found!
end
+ private
+
+ def repo_params
+ @repo_params ||= { ref: @ref, path: params[:path], format: params[:format], append_sha: @append_sha }
+ end
+
+ def set_cache_headers
+ expires_in cache_max_age(archive_metadata['CommitId']), public: project.public?
+ fresh_when(etag: archive_metadata['ArchivePath'])
+ end
+
+ def archive_not_modified?
+ # Check response freshness (Last-Modified and ETag)
+ # against request If-Modified-Since and If-None-Match conditions.
+ request.fresh?(response)
+ end
+
+ def archive_metadata
+ @archive_metadata ||= @repository.archive_metadata(
+ @ref,
+ '', # Where archives are stored isn't really important for ETag purposes
+ repo_params[:format],
+ path: repo_params[:path],
+ append_sha: @append_sha
+ )
+ end
+
+ def cache_max_age(commit_id)
+ if @ref == commit_id
+ # This is a link to an archive by a commit SHA. That means that the archive
+ # is immutable. The only reason to invalidate the cache is if the commit
+ # was deleted or if the user lost access to the repository.
+ Repository::ARCHIVE_CACHE_TIME_IMMUTABLE
+ else
+ # A branch or tag points at this archive. That means that the expected archive
+ # content may change over time.
+ Repository::ARCHIVE_CACHE_TIME
+ end
+ end
+
+ def assign_append_sha
+ @append_sha = params[:append_sha]
+
+ if @ref
+ shortname = "#{@project.path}-#{@ref.tr('/', '-')}"
+ @append_sha = false if @filename == shortname
+ end
+ end
+
def assign_archive_vars
if params[:id]
@ref, @filename = extract_ref(params[:id])
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 255f1f3569a..59f948959d6 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -7,7 +7,8 @@ class Projects::SnippetsController < Projects::ApplicationController
include SnippetsActions
include RendersBlob
- skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+ skip_before_action :verify_authenticity_token,
+ if: -> { action_name == 'show' && js_request? }
before_action :check_snippets_available!
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :toggle_award_emoji, :mark_as_spam]
diff --git a/app/controllers/projects/starrers_controller.rb b/app/controllers/projects/starrers_controller.rb
new file mode 100644
index 00000000000..4efe956e973
--- /dev/null
+++ b/app/controllers/projects/starrers_controller.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class Projects::StarrersController < Projects::ApplicationController
+ include SortingHelper
+
+ 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
+ end
+
+ private
+
+ def has_starred_project?(starrers)
+ starrers.first { |starrer| starrer.user_id == current_user.id }
+ end
+end
diff --git a/app/controllers/projects/triggers_controller.rb b/app/controllers/projects/triggers_controller.rb
index 284e119ca06..7159d0243a3 100644
--- a/app/controllers/projects/triggers_controller.rb
+++ b/app/controllers/projects/triggers_controller.rb
@@ -4,7 +4,7 @@ class Projects::TriggersController < Projects::ApplicationController
before_action :authorize_admin_build!
before_action :authorize_manage_trigger!, except: [:index, :create]
before_action :authorize_admin_trigger!, only: [:edit, :update]
- before_action :trigger, only: [:take_ownership, :edit, :update, :destroy]
+ before_action :trigger, only: [:edit, :update, :destroy]
layout 'project_settings'
@@ -24,16 +24,6 @@ class Projects::TriggersController < Projects::ApplicationController
redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
end
- def take_ownership
- if trigger.update(owner: current_user)
- flash[:notice] = _('Trigger was re-assigned.')
- else
- flash[:alert] = _('You could not take ownership of trigger.')
- end
-
- redirect_to project_settings_ci_cd_path(@project, anchor: 'js-pipeline-triggers')
- end
-
def edit
end
diff --git a/app/controllers/projects/variables_controller.rb b/app/controllers/projects/variables_controller.rb
index 646728e8167..1dffc57fcf0 100644
--- a/app/controllers/projects/variables_controller.rb
+++ b/app/controllers/projects/variables_controller.rb
@@ -38,6 +38,6 @@ class Projects::VariablesController < Projects::ApplicationController
end
def variable_params_attributes
- %i[id variable_type key secret_value protected masked _destroy]
+ %i[id variable_type key secret_value protected masked environment_scope _destroy]
end
end
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index fa5bdbc7d49..b187fdb2723 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -6,15 +6,20 @@ class Projects::WikisController < Projects::ApplicationController
include Gitlab::Utils::StrongMemoize
before_action :authorize_read_wiki!
- before_action :authorize_create_wiki!, only: [:edit, :create, :history]
+ before_action :authorize_create_wiki!, only: [:edit, :create]
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki
before_action :load_page, only: [:show, :edit, :update, :history, :destroy]
- before_action :valid_encoding?, only: [:show, :edit, :update], if: :load_page
+ before_action :valid_encoding?,
+ if: -> { %w[show edit update].include?(action_name) && load_page }
before_action only: [:edit, :update], unless: :valid_encoding? do
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])
@@ -23,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
@@ -109,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
@@ -134,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 feefc7f8137..e04cbf10470 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -18,9 +18,11 @@ class ProjectsController < Projects::ApplicationController
before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create, :resolve]
before_action :repository, except: [:index, :new, :create, :resolve]
- before_action :assign_ref_vars, only: [:show], if: :repo_exists?
- before_action :tree, only: [:show], if: [:repo_exists?, :project_view_files?]
- before_action :lfs_blob_ids, only: [:show], if: [:repo_exists?, :project_view_files?]
+ before_action :assign_ref_vars, if: -> { action_name == 'show' && repo_exists? }
+ before_action :tree,
+ if: -> { action_name == 'show' && repo_exists? && project_view_files? }
+ before_action :lfs_blob_ids,
+ if: -> { action_name == 'show' && repo_exists? && project_view_files? }
before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export]
before_action :present_project, only: [:edit]
before_action :authorize_download_code!, only: [:refs]
@@ -282,6 +284,18 @@ class ProjectsController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ def resolve
+ @project = Project.find(params[:id])
+
+ if can?(current_user, :read_project, @project)
+ redirect_to @project
+ else
+ render_404
+ end
+ end
+
+ private
+
# Render project landing depending of which features are available
# So if page is not available in the list it renders the next page
#
@@ -347,6 +361,7 @@ class ProjectsController < Projects::ApplicationController
:container_registry_enabled,
:default_branch,
:description,
+ :emails_disabled,
:external_authorization_classification_label,
:import_url,
:issues_tracker,
@@ -451,14 +466,4 @@ class ProjectsController < Projects::ApplicationController
def present_project
@project = @project.present(current_user: current_user)
end
-
- def resolve
- @project = Project.find(params[:id])
-
- if can?(current_user, :read_project, @project)
- redirect_to @project
- else
- render_404
- end
- end
end
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index b2b151bbcf0..e773ec09924 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -4,12 +4,12 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
include AcceptsPendingInvitations
include RecaptchaExperimentHelper
+ include InvisibleCaptcha
prepend_before_action :check_captcha, only: :create
before_action :whitelist_query_limiting, only: [:destroy]
before_action :ensure_terms_accepted,
- if: -> { Gitlab::CurrentSettings.current_application_settings.enforce_terms? },
- only: [:create]
+ if: -> { action_name == 'create' && Gitlab::CurrentSettings.current_application_settings.enforce_terms? }
def new
redirect_to(new_user_session_path)
@@ -69,12 +69,12 @@ class RegistrationsController < Devise::RegistrationsController
def after_sign_up_path_for(user)
Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?))
- user.confirmed? ? stored_location_for(user) || dashboard_projects_path : users_almost_there_path
+ confirmed_or_unconfirmed_access_allowed(user) ? stored_location_or_dashboard(user) : users_almost_there_path
end
def after_inactive_sign_up_path_for(resource)
Gitlab::AppLogger.info(user_created_message)
- users_almost_there_path
+ Feature.enabled?(:soft_email_confirmation) ? dashboard_projects_path : users_almost_there_path
end
private
@@ -135,4 +135,12 @@ class RegistrationsController < Devise::RegistrationsController
def terms_accepted?
Gitlab::Utils.to_boolean(params[:terms_opt_in])
end
+
+ def confirmed_or_unconfirmed_access_allowed(user)
+ user.confirmed? || Feature.enabled?(:soft_email_confirmation)
+ end
+
+ def stored_location_or_dashboard(user)
+ stored_location_for(user) || dashboard_projects_path
+ end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 8c674be58c5..e30935be4b6 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -31,9 +31,20 @@ class SearchController < ApplicationController
render_commits if @scope == 'commits'
eager_load_user_status if @scope == 'users'
+ increment_navbar_searches_counter
+
check_single_commit_result
end
+ def count
+ params.require([:search, :scope])
+
+ scope = search_service.scope
+ count = search_service.search_results.formatted_count(scope)
+
+ render json: { count: count }
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def autocomplete
term = params[:term]
@@ -70,4 +81,10 @@ class SearchController < ApplicationController
redirect_to project_commit_path(@project, only_commit) if found_by_commit_sha
end
end
+
+ def increment_navbar_searches_counter
+ return if params[:nav_source] != 'navbar'
+
+ Gitlab::UsageDataCounters::SearchCounter.increment_navbar_searches_count
+ end
end
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index a841859621e..1880bead3ee 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -13,20 +13,30 @@ class SessionsController < Devise::SessionsController
prepend_before_action :check_initial_setup, only: [:new]
prepend_before_action :authenticate_with_two_factor,
- if: :two_factor_enabled?, only: [:create]
+ if: -> { action_name == 'create' && two_factor_enabled? }
prepend_before_action :check_captcha, only: [:create]
prepend_before_action :store_redirect_uri, only: [:new]
prepend_before_action :ldap_servers, only: [:new, :create]
prepend_before_action :require_no_authentication_without_flash, only: [:new, :create]
- prepend_before_action :ensure_password_authentication_enabled!, if: :password_based_login?, only: [:create]
+ prepend_before_action :ensure_password_authentication_enabled!, if: -> { action_name == 'create' && password_based_login? }
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
- after_action :log_failed_login, only: [:new], if: :failed_login?
-
+ after_action :log_failed_login, if: -> { action_name == 'new' && failed_login? }
helper_method :captcha_enabled?
+ # protect_from_forgery is already prepended in ApplicationController but
+ # authenticate_with_two_factor which signs in the user is prepended before
+ # that here.
+ # We need to make sure CSRF token is verified before authenticating the user
+ # because Devise.clean_up_csrf_token_on_authentication is set to true by
+ # default to avoid CSRF token fixation attacks. Authenticating the user first
+ # would cause the CSRF token to be cleared and then
+ # RequestForgeryProtection#verify_authenticity_token would fail because of
+ # token mismatch.
+ protect_from_forgery with: :exception, prepend: true
+
CAPTCHA_HEADER = 'X-GitLab-Show-Login-Captcha'.freeze
def new
diff --git a/app/controllers/snippets/notes_controller.rb b/app/controllers/snippets/notes_controller.rb
index 612897f27e6..551b37cb3d3 100644
--- a/app/controllers/snippets/notes_controller.rb
+++ b/app/controllers/snippets/notes_controller.rb
@@ -27,7 +27,9 @@ class Snippets::NotesController < ApplicationController
alias_method :noteable, :snippet
def finder_params
- params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet')
+ params.merge(last_fetched_at: last_fetched_at, target_id: snippet.id, target_type: 'personal_snippet').tap do |merged_params|
+ merged_params[:project] = project if respond_to?(:project)
+ end
end
def authorize_read_snippet!
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index fad036b8df8..869655e9550 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -8,7 +8,8 @@ class SnippetsController < ApplicationController
include RendersBlob
include PreviewMarkdown
- skip_before_action :verify_authenticity_token, only: [:show], if: :js_request?
+ skip_before_action :verify_authenticity_token,
+ if: -> { action_name == 'show' && js_request? }
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 072d62ddf38..91e0efcf45f 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -17,7 +17,7 @@ class UsersController < ApplicationController
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
before_action :user, except: [:exists]
before_action :authorize_read_user_profile!,
- only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :snippets]
+ only: [:calendar, :calendar_activities, :groups, :projects, :contributed_projects, :starred_projects, :snippets]
def show
respond_to do |format|
@@ -57,27 +57,30 @@ class UsersController < ApplicationController
def projects
load_projects
- skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
- skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
- compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
-
- respond_to do |format|
- format.html { render 'show' }
- format.json do
- pager_json("shared/projects/_list", @projects.count, projects: @projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
- end
- end
+ present_projects(@projects)
end
def contributed
load_contributed_projects
+ present_projects(@contributed_projects)
+ end
+
+ def starred
+ load_starred_projects
+
+ present_projects(@starred_projects)
+ end
+
+ def present_projects(projects)
+ skip_pagination = Gitlab::Utils.to_boolean(params[:skip_pagination])
+ skip_namespace = Gitlab::Utils.to_boolean(params[:skip_namespace])
+ compact_mode = Gitlab::Utils.to_boolean(params[:compact_mode])
+
respond_to do |format|
format.html { render 'show' }
format.json do
- render json: {
- html: view_to_html_string("shared/projects/_list", projects: @contributed_projects)
- }
+ pager_json("shared/projects/_list", projects.count, projects: projects, skip_pagination: skip_pagination, skip_namespace: skip_namespace, compact_mode: compact_mode)
end
end
end
@@ -120,6 +123,10 @@ class UsersController < ApplicationController
ContributedProjectsFinder.new(user).execute(current_user)
end
+ def starred_projects
+ StarredProjectsFinder.new(user, current_user: current_user).execute
+ end
+
def contributions_calendar
@contributions_calendar ||= Gitlab::ContributionsCalendar.new(user, current_user)
end
@@ -145,6 +152,12 @@ class UsersController < ApplicationController
prepare_projects_for_rendering(@contributed_projects)
end
+ def load_starred_projects
+ @starred_projects = starred_projects
+
+ prepare_projects_for_rendering(@starred_projects)
+ end
+
def load_groups
@groups = JoinedGroupsFinder.new(user).execute(current_user)
diff --git a/app/finders/autocomplete/move_to_project_finder.rb b/app/finders/autocomplete/move_to_project_finder.rb
index edaf74c5f92..491cce2232e 100644
--- a/app/finders/autocomplete/move_to_project_finder.rb
+++ b/app/finders/autocomplete/move_to_project_finder.rb
@@ -3,7 +3,9 @@
module Autocomplete
# Finder that retrieves a list of projects that an issue can be moved to.
class MoveToProjectFinder
- attr_reader :current_user, :search, :project_id, :offset_id
+ attr_reader :current_user, :search, :project_id
+
+ LIMIT = 20
# current_user - The User object of the user that wants to view the list of
# projects.
@@ -14,13 +16,10 @@ module Autocomplete
#
# * search: An optional search query to apply to the list of projects.
# * project_id: The ID of a project to exclude from the returned relation.
- # * offset_id: The ID of a project to use for pagination. When given, only
- # projects with a lower ID are included in the list.
def initialize(current_user, params = {})
@current_user = current_user
@search = params[:search]
@project_id = params[:project_id]
- @offset_id = params[:offset_id]
end
def execute
@@ -28,8 +27,8 @@ module Autocomplete
.projects_where_can_admin_issues
.optionally_search(search)
.excluding_project(project_id)
- .paginate_in_descending_order_using_id(before: offset_id)
.eager_load_namespace_and_owner
+ .sorted_by_name_asc_limited(LIMIT)
end
end
end
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/finders/branches_finder.rb b/app/finders/branches_finder.rb
index b462c8053fa..291a24c1405 100644
--- a/app/finders/branches_finder.rb
+++ b/app/finders/branches_finder.rb
@@ -69,7 +69,7 @@ class BranchesFinder
return branches unless names
branch_names = names.to_set
- branches.filter do |branch|
+ branches.select do |branch|
branch_names.include?(branch.name)
end
end
diff --git a/app/finders/clusters/knative_services_finder.rb b/app/finders/clusters/knative_services_finder.rb
index 7d3b53ef663..71cebe4495e 100644
--- a/app/finders/clusters/knative_services_finder.rb
+++ b/app/finders/clusters/knative_services_finder.rb
@@ -13,11 +13,11 @@ module Clusters
self.reactive_cache_key = ->(finder) { finder.model_name }
self.reactive_cache_worker_finder = ->(_id, *cache_args) { from_cache(*cache_args) }
- attr_reader :cluster, :project
+ attr_reader :cluster, :environment
- def initialize(cluster, project)
+ def initialize(cluster, environment)
@cluster = cluster
- @project = project
+ @environment = environment
end
def with_reactive_cache_memoized(*cache_args, &block)
@@ -30,11 +30,11 @@ module Clusters
clear_reactive_cache!(*cache_args)
end
- def self.from_cache(cluster_id, project_id)
+ def self.from_cache(cluster_id, environment_id)
cluster = Clusters::Cluster.find(cluster_id)
- project = ::Project.find(project_id)
+ environment = Environment.find(environment_id)
- new(cluster, project)
+ new(cluster, environment)
end
def calculate_reactive_cache(*)
@@ -56,7 +56,7 @@ module Clusters
end
def cache_args
- [cluster.id, project.id]
+ [cluster.id, environment.id]
end
def service_pod_details(service)
@@ -84,7 +84,7 @@ module Clusters
private
def search_namespace
- @search_namespace ||= cluster.kubernetes_namespace_for(project)
+ @search_namespace ||= cluster.kubernetes_namespace_for(environment)
end
def knative_client
diff --git a/app/finders/clusters/kubernetes_namespace_finder.rb b/app/finders/clusters/kubernetes_namespace_finder.rb
new file mode 100644
index 00000000000..e947796c1e7
--- /dev/null
+++ b/app/finders/clusters/kubernetes_namespace_finder.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Clusters
+ class KubernetesNamespaceFinder
+ attr_reader :cluster, :project, :environment_slug
+
+ def initialize(cluster, project:, environment_slug:, allow_blank_token: false)
+ @cluster = cluster
+ @project = project
+ @environment_slug = environment_slug
+ @allow_blank_token = allow_blank_token
+ end
+
+ def execute
+ find_namespace(with_environment: cluster.namespace_per_environment?)
+ end
+
+ private
+
+ attr_reader :allow_blank_token
+
+ def find_namespace(with_environment:)
+ relation = with_environment ? namespaces.with_environment_slug(environment_slug) : namespaces
+
+ relation.find_by_project_id(project.id)
+ end
+
+ def namespaces
+ if allow_blank_token
+ cluster.kubernetes_namespaces
+ else
+ cluster.kubernetes_namespaces.has_service_account_token
+ end
+ end
+ end
+end
diff --git a/app/finders/container_repositories_finder.rb b/app/finders/container_repositories_finder.rb
new file mode 100644
index 00000000000..eb91d7f825b
--- /dev/null
+++ b/app/finders/container_repositories_finder.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+class ContainerRepositoriesFinder
+ # id: group or project id
+ # container_type: :group or :project
+ def initialize(id:, container_type:)
+ @id = id
+ @type = container_type.to_sym
+ end
+
+ def execute
+ if project_type?
+ project.container_repositories
+ else
+ group.container_repositories
+ end
+ end
+
+ private
+
+ attr_reader :id, :type
+
+ def project_type?
+ type == :project
+ end
+
+ def project
+ Project.find(id)
+ end
+
+ def group
+ Group.find(id)
+ end
+end
diff --git a/app/finders/group_descendants_finder.rb b/app/finders/group_descendants_finder.rb
index ec340f38450..4e489a9c930 100644
--- a/app/finders/group_descendants_finder.rb
+++ b/app/finders/group_descendants_finder.rb
@@ -132,8 +132,6 @@ class GroupDescendantsFinder
end
def subgroups
- return Group.none unless Group.supports_nested_objects?
-
# When filtering subgroups, we want to find all matches within the tree of
# descendants to show to the user
groups = if params[:filter]
diff --git a/app/finders/group_members_finder.rb b/app/finders/group_members_finder.rb
index eebc67cfa9e..33ec6a715f9 100644
--- a/app/finders/group_members_finder.rb
+++ b/app/finders/group_members_finder.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-class GroupMembersFinder
+class GroupMembersFinder < UnionFinder
def initialize(group)
@group = group
end
@@ -8,18 +8,18 @@ class GroupMembersFinder
# rubocop: disable CodeReuse/ActiveRecord
def execute(include_descendants: false)
group_members = @group.members
- wheres = []
+ relations = []
return group_members unless @group.parent || include_descendants
- wheres << "members.id IN (#{group_members.select(:id).to_sql})"
+ relations << group_members
if @group.parent
parents_members = GroupMember.non_request
.where(source_id: @group.ancestors.select(:id))
.where.not(user_id: @group.users.select(:id))
- wheres << "members.id IN (#{parents_members.select(:id).to_sql})"
+ relations << parents_members
end
if include_descendants
@@ -27,10 +27,10 @@ class GroupMembersFinder
.where(source_id: @group.descendants.select(:id))
.where.not(user_id: @group.users.select(:id))
- wheres << "members.id IN (#{descendant_members.select(:id).to_sql})"
+ relations << descendant_members
end
- GroupMember.where(wheres.join(' OR '))
+ find_union(relations, GroupMember)
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index f4fbeacfaba..1773ac2d508 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -320,7 +320,6 @@ class IssuableFinder
def use_cte_for_search?
strong_memoize(:use_cte_for_search) do
next false unless search
- next false unless Gitlab::Database.postgresql?
# Only simple unsorted & simple sorts can use CTE
next false if params[:sort].present? && !params[:sort].in?(klass.simple_sorts.keys)
diff --git a/app/finders/members_finder.rb b/app/finders/members_finder.rb
index 917de249104..f730b015c0a 100644
--- a/app/finders/members_finder.rb
+++ b/app/finders/members_finder.rb
@@ -59,35 +59,16 @@ class MembersFinder
def distinct_on(union)
# We're interested in a list of members without duplicates by user_id.
# We prefer project members over group members, project members should go first.
- if Gitlab::Database.postgresql?
- <<~SQL
- SELECT DISTINCT ON (user_id, invite_email) member_union.*
- FROM (#{union.to_sql}) AS member_union
- ORDER BY user_id,
- invite_email,
- CASE
- WHEN type = 'ProjectMember' THEN 1
- WHEN type = 'GroupMember' THEN 2
- ELSE 3
- END
- SQL
- else
- # Older versions of MySQL do not support window functions (and DISTINCT ON is postgres-specific).
- <<~SQL
- SELECT t1.*
- FROM (#{union.to_sql}) AS t1
- JOIN (
- SELECT
- COALESCE(user_id, -1) AS user_id,
- COALESCE(invite_email, 'NULL') AS invite_email,
- MIN(CASE WHEN type = 'ProjectMember' THEN 1 WHEN type = 'GroupMember' THEN 2 ELSE 3 END) AS type_number
- FROM
- (#{union.to_sql}) AS t3
- GROUP BY COALESCE(user_id, -1), COALESCE(invite_email, 'NULL')
- ) AS t2 ON COALESCE(t1.user_id, -1) = t2.user_id
- AND COALESCE(t1.invite_email, 'NULL') = t2.invite_email
- AND CASE WHEN t1.type = 'ProjectMember' THEN 1 WHEN t1.type = 'GroupMember' THEN 2 ELSE 3 END = t2.type_number
- SQL
- end
+ <<~SQL
+ SELECT DISTINCT ON (user_id, invite_email) member_union.*
+ FROM (#{union.to_sql}) AS member_union
+ ORDER BY user_id,
+ invite_email,
+ CASE
+ WHEN type = 'ProjectMember' THEN 1
+ WHEN type = 'GroupMember' THEN 2
+ ELSE 3
+ END
+ SQL
end
end
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index 8f610d7dddb..f7d9100bb78 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -3,6 +3,8 @@
class NotesFinder
FETCH_OVERLAP = 5.seconds
+ attr_reader :target_type
+
# Used to filter Notes
# When used with target_type and target_id this returns notes specifically for the controller
#
@@ -10,15 +12,17 @@ class NotesFinder
# current_user - which user check authorizations with
# project - which project to look for notes on
# params:
+ # target: noteable
# target_type: string
# target_id: integer
# last_fetched_at: time
# search: string
#
- def initialize(project, current_user, params = {})
- @project = project
+ def initialize(current_user, params = {})
+ @project = params[:project]
@current_user = current_user
- @params = params
+ @params = params.dup
+ @target_type = @params[:target_type]
end
def execute
@@ -32,7 +36,27 @@ class NotesFinder
def target
return @target if defined?(@target)
- target_type = @params[:target_type]
+ if target_given?
+ use_explicit_target
+ else
+ find_target_by_type_and_ids
+ end
+ end
+
+ private
+
+ def target_given?
+ @params.key?(:target)
+ end
+
+ def use_explicit_target
+ @target = @params[:target]
+ @target_type = @target.class.name.underscore
+
+ @target
+ end
+
+ def find_target_by_type_and_ids
target_id = @params[:target_id]
target_iid = @params[:target_iid]
@@ -45,13 +69,11 @@ class NotesFinder
@project.commit(target_id)
end
else
- noteables_for_type_by_id(target_type, target_id, target_iid)
+ noteable_for_type_by_id(target_type, target_id, target_iid)
end
end
- private
-
- def noteables_for_type_by_id(type, id, iid)
+ def noteable_for_type_by_id(type, id, iid)
query = if id
{ id: id }
else
@@ -77,10 +99,6 @@ class NotesFinder
search(notes)
end
- def target_type
- @params[:target_type]
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def notes_of_any_type
types = %w(commit issue merge_request snippet)
diff --git a/app/finders/projects/serverless/functions_finder.rb b/app/finders/projects/serverless/functions_finder.rb
index ebe50806ca1..e8c50ef1a88 100644
--- a/app/finders/projects/serverless/functions_finder.rb
+++ b/app/finders/projects/serverless/functions_finder.rb
@@ -3,10 +3,11 @@
module Projects
module Serverless
class FunctionsFinder
+ include Gitlab::Utils::StrongMemoize
+
attr_reader :project
def initialize(project)
- @clusters = project.clusters
@project = project
end
@@ -16,9 +17,8 @@ module Projects
# Possible return values: Clusters::KnativeServicesFinder::KNATIVE_STATE
def knative_installed
- states = @clusters.map do |cluster|
- cluster.application_knative
- cluster.knative_services_finder(project).knative_detected.tap do |state|
+ states = services_finders.map do |finder|
+ finder.knative_detected.tap do |state|
return state if state == ::Clusters::KnativeServicesFinder::KNATIVE_STATES['checking'] # rubocop:disable Cop/AvoidReturnFromBlocks
end
end
@@ -31,66 +31,70 @@ module Projects
end
def invocation_metrics(environment_scope, name)
- return unless prometheus_adapter&.can_query?
+ environment = finders_for_scope(environment_scope).first&.environment
- cluster = @clusters.find do |c|
- environment_scope == c.environment_scope
+ if environment.present? && environment.prometheus_adapter&.can_query?
+ func = ::Serverless::Function.new(project, name, environment.deployment_namespace)
+ environment.prometheus_adapter.query(:knative_invocation, func)
end
-
- func = ::Serverless::Function.new(project, name, cluster.kubernetes_namespace_for(project))
- prometheus_adapter.query(:knative_invocation, func)
end
def has_prometheus?(environment_scope)
- @clusters.any? do |cluster|
- environment_scope == cluster.environment_scope && cluster.application_prometheus_available?
+ finders_for_scope(environment_scope).any? do |finder|
+ finder.cluster.application_prometheus_available?
end
end
private
def knative_service(environment_scope, name)
- @clusters.map do |cluster|
- next if environment_scope != cluster.environment_scope
-
- services = cluster
- .knative_services_finder(project)
+ finders_for_scope(environment_scope).map do |finder|
+ services = finder
.services
.select { |svc| svc["metadata"]["name"] == name }
- add_metadata(cluster, services).first unless services.nil?
+ add_metadata(finder, services).first unless services.nil?
end
end
def knative_services
- @clusters.map do |cluster|
- services = cluster
- .knative_services_finder(project)
- .services
+ services_finders.map do |finder|
+ services = finder.services
- add_metadata(cluster, services) unless services.nil?
+ add_metadata(finder, services) unless services.nil?
end
end
- def add_metadata(cluster, services)
+ def add_metadata(finder, services)
+ add_pod_count = services.one?
+
services.each do |s|
- s["environment_scope"] = cluster.environment_scope
- s["cluster_id"] = cluster.id
+ s["environment_scope"] = finder.cluster.environment_scope
+ s["cluster_id"] = finder.cluster.id
- if services.length == 1
- s["podcount"] = cluster
- .knative_services_finder(project)
+ if add_pod_count
+ s["podcount"] = finder
.service_pod_details(s["metadata"]["name"])
.length
end
end
end
- # rubocop: disable CodeReuse/ServiceClass
- def prometheus_adapter
- @prometheus_adapter ||= ::Prometheus::AdapterService.new(project).prometheus_adapter
+ def services_finders
+ strong_memoize(:services_finders) do
+ available_environments.map(&:knative_services_finder).compact
+ end
+ end
+
+ def available_environments
+ @project.environments.available.preload_cluster
+ end
+
+ def finders_for_scope(environment_scope)
+ services_finders.select do |finder|
+ environment_scope == finder.cluster.environment_scope
+ end
end
- # rubocop: enable CodeReuse/ServiceClass
end
end
end
diff --git a/app/finders/remote_mirror_finder.rb b/app/finders/remote_mirror_finder.rb
deleted file mode 100644
index 420db0077aa..00000000000
--- a/app/finders/remote_mirror_finder.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-class RemoteMirrorFinder
- attr_accessor :params
-
- def initialize(params)
- @params = params
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def execute
- RemoteMirror.find_by(id: params[:id])
- end
- # rubocop: enable CodeReuse/ActiveRecord
-end
diff --git a/app/finders/starred_projects_finder.rb b/app/finders/starred_projects_finder.rb
new file mode 100644
index 00000000000..fcb469d1d17
--- /dev/null
+++ b/app/finders/starred_projects_finder.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class StarredProjectsFinder < ProjectsFinder
+ def initialize(user, params: {}, current_user: nil)
+ super(
+ params: params,
+ current_user: current_user,
+ project_ids_relation: user.starred_projects.select(:id)
+ )
+ end
+end
diff --git a/app/finders/users_star_projects_finder.rb b/app/finders/users_star_projects_finder.rb
new file mode 100644
index 00000000000..49c4e087b4b
--- /dev/null
+++ b/app/finders/users_star_projects_finder.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class UsersStarProjectsFinder
+ include CustomAttributesFilter
+
+ attr_accessor :params
+
+ def initialize(project, params = {}, current_user: nil)
+ @params = params
+ @project = project
+ @current_user = current_user
+ end
+
+ def execute
+ stars = UsersStarProject.all
+ stars = by_project(stars)
+ stars = by_search(stars)
+ stars = filter_visible_profiles(stars)
+
+ stars
+ end
+
+ private
+
+ def by_search(items)
+ params[:search].present? ? items.search(params[:search]) : items
+ end
+
+ def by_project(items)
+ items.by_project(@project)
+ end
+
+ def filter_visible_profiles(items)
+ items.with_visible_profile(@current_user)
+ 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/group_type.rb b/app/graphql/types/group_type.rb
index 530aecc2bf9..66b41919914 100644
--- a/app/graphql/types/group_type.rb
+++ b/app/graphql/types/group_type.rb
@@ -14,10 +14,8 @@ module Types
group.avatar_url(only_path: false)
end
- if ::Group.supports_nested_objects?
- field :parent, GroupType,
- null: true,
- resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
- end
+ field :parent, GroupType,
+ null: true,
+ resolve: -> (obj, _args, _ctx) { Gitlab::Graphql::Loaders::BatchModelLoader.new(Group, obj.parent_id).find }
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 4bf9b708401..0ab19f1d2d2 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -160,6 +160,8 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
+ :allow_local_requests_from_web_hooks_and_services,
+ :allow_local_requests_from_system_hooks,
:dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
@@ -177,6 +179,7 @@ module ApplicationSettingsHelper
:domain_blacklist_enabled,
:domain_blacklist_raw,
:domain_whitelist_raw,
+ :outbound_local_requests_whitelist_raw,
:dsa_key_restriction,
:ecdsa_key_restriction,
:ed25519_key_restriction,
@@ -267,7 +270,11 @@ module ApplicationSettingsHelper
:diff_max_patch_bytes,
:commit_email_hostname,
:protected_ci_variables,
- :local_markdown_version
+ :local_markdown_version,
+ :snowplow_collector_hostname,
+ :snowplow_cookie_domain,
+ :snowplow_enabled,
+ :snowplow_site_id
]
end
diff --git a/app/helpers/avatars_helper.rb b/app/helpers/avatars_helper.rb
index 5906ddabee4..81ff359556d 100644
--- a/app/helpers/avatars_helper.rb
+++ b/app/helpers/avatars_helper.rb
@@ -52,7 +52,7 @@ module AvatarsHelper
user: commit_or_event.author,
user_name: commit_or_event.author_name,
user_email: commit_or_event.author_email,
- css_class: 'd-none d-sm-inline'
+ css_class: 'd-none d-sm-inline-block'
}))
end
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 42732eb93dd..d71af08a656 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
module DashboardHelper
+ include IconsHelper
+
def assigned_issues_dashboard_path
issues_dashboard_path(assignee_username: current_user.username)
end
@@ -25,6 +27,19 @@ module DashboardHelper
false
end
+ def feature_entry(title, href: nil, enabled: true)
+ enabled_text = enabled ? 'on' : 'off'
+ label = "#{title}: status #{enabled_text}"
+ link_or_title = href && enabled ? tag.a(title, href: href) : title
+
+ tag.p(aria: { label: label }) do
+ concat(link_or_title)
+ concat(tag.span(class: ['light', 'float-right']) do
+ concat(boolean_to_icon(enabled))
+ end)
+ end
+ end
+
private
def get_dashboard_nav_links
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 5aed7e313e6..bd26bd01313 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -31,6 +31,11 @@ module GroupsHelper
can?(current_user, :change_share_with_group_lock, group)
end
+ def can_disable_group_emails?(group)
+ Feature.enabled?(:emails_disabled, group, default_enabled: true) &&
+ can?(current_user, :set_emails_disabled, group) && !group.parent&.emails_disabled?
+ end
+
def group_issues_count(state:)
IssuesFinder
.new(current_user, group_id: @group.id, state: state, non_archived: true, include_subgroups: true)
@@ -127,10 +132,6 @@ module GroupsHelper
groups.to_json
end
- def supports_nested_groups?
- Group.supports_nested_objects?
- end
-
private
def get_group_sidebar_links
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index db4f29cd996..2ed016beea4 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -4,11 +4,10 @@ module LabelsHelper
extend self
include ActionView::Helpers::TagHelper
- def show_label_issuables_link?(label, issuables_type, current_user: nil, project: nil)
+ def show_label_issuables_link?(label, issuables_type, current_user: nil)
return true unless label.project_label?
- return true unless project
- project.feature_available?(issuables_type, current_user)
+ label.project.feature_available?(issuables_type, current_user)
end
# Link to a Label
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index 11b9cf22142..5678304ffcf 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -5,7 +5,7 @@ module NotificationsHelper
def notification_icon_class(level)
case level.to_sym
- when :disabled
+ when :disabled, :owner_disabled
'microphone-slash'
when :participating
'volume-up'
@@ -18,6 +18,16 @@ module NotificationsHelper
end
end
+ def notification_icon_level(notification_setting, emails_disabled = false)
+ if emails_disabled
+ 'owner_disabled'
+ elsif notification_setting.global?
+ current_user.global_notification_setting.level
+ else
+ notification_setting.level
+ end
+ end
+
def notification_icon(level, text = nil)
icon("#{notification_icon_class(level)} fw", text: text)
end
@@ -53,6 +63,8 @@ module NotificationsHelper
_('Use your global notification setting')
when :custom
_('You will only receive notifications for the events you choose')
+ when :owner_disabled
+ _('Notifications have been disabled by the project or group owner')
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 8d0079a4dd3..33bf2d57fae 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -155,6 +155,12 @@ module ProjectsHelper
end
end
+ def can_disable_emails?(project, current_user)
+ return false if project.group&.emails_disabled?
+
+ can?(current_user, :set_emails_disabled, project) && Feature.enabled?(:emails_disabled, project, default_enabled: true)
+ end
+
def last_push_event
current_user&.recent_push(@project)
end
@@ -541,13 +547,15 @@ module ProjectsHelper
snippetsAccessLevel: feature.snippets_access_level,
pagesAccessLevel: feature.pages_access_level,
containerRegistryEnabled: !!project.container_registry_enabled,
- lfsEnabled: !!project.lfs_enabled
+ lfsEnabled: !!project.lfs_enabled,
+ emailsDisabled: project.emails_disabled?
}
end
def project_permissions_panel_data(project)
{
currentSettings: project_permissions_settings(project),
+ canDisableEmails: can_disable_emails?(project, current_user),
canChangeVisibilityLevel: can_change_visibility_level?(project, current_user),
allowedVisibilityOptions: project_allowed_visibility_levels(project),
visibilityHelpPath: help_page_path('public_access/public_access'),
@@ -601,6 +609,11 @@ module ProjectsHelper
end
end
+ def filter_starrer_path(options = {})
+ options = params.slice(:sort).merge(options).permit!
+ "#{request.path}?#{options.to_param}"
+ end
+
def sidebar_projects_paths
%w[
projects#show
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index f5c4686a3bf..91c83380b62 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -145,17 +145,27 @@ module SearchHelper
Sanitize.clean(str)
end
- def search_filter_path(options = {})
- exist_opts = {
- search: params[:search],
- project_id: params[:project_id],
- group_id: params[:group_id],
- scope: params[:scope],
- repository_ref: params[:repository_ref]
- }
+ def search_filter_link(scope, label, data: {}, search: {})
+ search_params = params
+ .merge(search)
+ .merge({ scope: scope })
+ .permit(:search, :scope, :project_id, :group_id, :repository_ref, :snippets)
+
+ if @scope == scope
+ li_class = 'active'
+ count = @search_results.formatted_count(scope)
+ else
+ badge_class = 'js-search-count hidden'
+ badge_data = { url: search_count_path(search_params) }
+ end
- options = exist_opts.merge(options)
- search_path(options)
+ content_tag :li, class: li_class, data: data do
+ link_to search_path(search_params) do
+ concat label
+ concat ' '
+ concat content_tag(:span, count, class: ['badge badge-pill', badge_class], data: badge_data)
+ end
+ end
end
def search_filter_input_options(type)
@@ -212,10 +222,6 @@ module SearchHelper
sanitize(html, tags: %w(a p ol ul li pre code))
end
- def limited_count(count, limit = 1000)
- count > limit ? "#{limit}+" : count
- end
-
def search_tabs?(tab)
return false if Feature.disabled?(:users_search, default_enabled: true)
diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb
index 01ccf163b45..d4b50b7ecfb 100644
--- a/app/helpers/services_helper.rb
+++ b/app/helpers/services_helper.rb
@@ -39,7 +39,7 @@ module ServicesHelper
end
def disable_fields_service?(service)
- service.is_a?(KubernetesService) || (!current_controller?("admin/services") && service.deprecated?)
+ !current_controller?("admin/services") && service.deprecated?
end
extend self
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
new file mode 100644
index 00000000000..af98a611b8b
--- /dev/null
+++ b/app/helpers/sessions_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module SessionsHelper
+ def unconfirmed_email?
+ flash[:alert] == t(:unconfirmed, scope: [:devise, :failure])
+ end
+end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index d5e5b472115..a4eb76a2359 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -167,6 +167,15 @@ module SortingHelper
}
end
+ def starrers_sort_options_hash
+ {
+ sort_value_name => sort_title_name,
+ sort_value_name_desc => sort_title_name_desc,
+ sort_value_recently_created => sort_title_recently_starred,
+ sort_value_oldest_created => sort_title_oldest_starred
+ }
+ end
+
def sortable_item(item, path, sorted_by)
link_to item, path, class: sorted_by == item ? 'is-active' : ''
end
@@ -210,7 +219,7 @@ module SortingHelper
end
def sort_direction_button(reverse_url, reverse_sort, sort_value)
- link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
icon = sort_direction_icon(sort_value)
url = reverse_url
@@ -327,6 +336,10 @@ module SortingHelper
s_('SortOptions|Oldest sign in')
end
+ def sort_title_oldest_starred
+ s_('SortOptions|Oldest starred')
+ end
+
def sort_title_oldest_updated
s_('SortOptions|Oldest updated')
end
@@ -347,6 +360,10 @@ module SortingHelper
s_('SortOptions|Recent sign in')
end
+ def sort_title_recently_starred
+ s_('SortOptions|Recently starred')
+ end
+
def sort_title_recently_updated
s_('SortOptions|Last updated')
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 35e04b0ced3..e683e2959d1 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -13,6 +13,8 @@ module SubmoduleHelper
end
def submodule_links_for_url(submodule_item_id, url, repository)
+ return [nil, nil] unless url
+
if url == '.' || url == './'
url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
end
@@ -34,8 +36,8 @@ module SubmoduleHelper
project.sub!(/\.git\z/, '')
if self_url?(url, namespace, project)
- [namespace_project_path(namespace, project),
- namespace_project_tree_path(namespace, project, submodule_item_id)]
+ [url_helpers.namespace_project_path(namespace, project),
+ url_helpers.namespace_project_tree_path(namespace, project, submodule_item_id)]
elsif relative_self_url?(url)
relative_self_links(url, submodule_item_id, repository.project)
elsif github_dot_com_url?(url)
@@ -99,8 +101,8 @@ module SubmoduleHelper
begin
[
- namespace_project_path(target_namespace_path, submodule_base),
- namespace_project_tree_path(target_namespace_path, submodule_base, commit)
+ url_helpers.namespace_project_path(target_namespace_path, submodule_base),
+ url_helpers.namespace_project_tree_path(target_namespace_path, submodule_base, commit)
]
rescue ActionController::UrlGenerationError
[nil, nil]
@@ -118,4 +120,8 @@ module SubmoduleHelper
rescue URI::InvalidURIError
nil
end
+
+ def url_helpers
+ Gitlab::Routing.url_helpers
+ end
end
diff --git a/app/helpers/system_note_helper.rb b/app/helpers/system_note_helper.rb
index ac4e8f54260..3efae0a653c 100644
--- a/app/helpers/system_note_helper.rb
+++ b/app/helpers/system_note_helper.rb
@@ -21,6 +21,7 @@ module SystemNoteHelper
'discussion' => 'comment',
'moved' => 'arrow-right',
'outdated' => 'pencil-square',
+ 'pinned_embed' => 'thumbtack',
'duplicate' => 'issue-duplicate',
'locked' => 'lock',
'unlocked' => 'lock-open',
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 645160077f5..38142bc68cb 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -26,7 +26,7 @@ module TodosHelper
end
def todo_target_link(todo)
- text = raw("#{todo.target_type.titleize.downcase} ") +
+ text = raw(todo_target_type_name(todo) + ' ') +
if todo.for_commit?
content_tag(:span, todo.target_reference, class: 'commit-sha')
else
@@ -36,23 +36,34 @@ module TodosHelper
link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end
+ def todo_target_type_name(todo)
+ todo.target_type.titleize.downcase
+ end
+
def todo_target_path(todo)
return unless todo.target.present?
- anchor = dom_id(todo.note) if todo.note.present?
+ path_options = todo_target_path_options(todo)
if todo.for_commit?
- project_commit_path(todo.project,
- todo.target, anchor: anchor)
+ project_commit_path(todo.project, todo.target, path_options)
else
path = [todo.parent, todo.target]
path.unshift(:pipelines) if todo.build_failed?
- polymorphic_path(path, anchor: anchor)
+ polymorphic_path(path, path_options)
end
end
+ def todo_target_path_options(todo)
+ { anchor: todo_target_path_anchor(todo) }
+ end
+
+ def todo_target_path_anchor(todo)
+ dom_id(todo.note) if todo.note.present?
+ end
+
def todo_target_state_pill(todo)
return unless show_todo_state?(todo)
diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb
index 51ea79d1ddd..221d9692661 100644
--- a/app/helpers/tracking_helper.rb
+++ b/app/helpers/tracking_helper.rb
@@ -2,6 +2,21 @@
module TrackingHelper
def tracking_attrs(label, event, property)
- {} # CE has no tracking features
+ return {} unless tracking_enabled?
+
+ {
+ data: {
+ track_label: label,
+ track_event: event,
+ track_property: property
+ }
+ }
+ end
+
+ private
+
+ def tracking_enabled?
+ Rails.env.production? &&
+ ::Gitlab::CurrentSettings.snowplow_enabled?
end
end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
index 73ca17c6605..e38e3378e07 100644
--- a/app/helpers/users_helper.rb
+++ b/app/helpers/users_helper.rb
@@ -89,7 +89,7 @@ module UsersHelper
tabs = []
if can?(current_user, :read_user_profile, @user)
- tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets]
+ tabs += [:overview, :activity, :groups, :contributed, :projects, :starred, :snippets]
end
tabs
diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb
index edd48f82729..dd8fde2a697 100644
--- a/app/helpers/wiki_helper.rb
+++ b/app/helpers/wiki_helper.rb
@@ -50,7 +50,7 @@ module WikiHelper
def wiki_sort_controls(project, sort, direction)
sort ||= ProjectWiki::TITLE_ORDER
- link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort'
+ link_class = 'btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort'
reversed_direction = direction == 'desc' ? 'asc' : 'desc'
icon_class = direction == 'desc' ? 'highest' : 'lowest'
diff --git a/app/mailers/emails/members.rb b/app/mailers/emails/members.rb
index 2bfa59774d7..76fa7236ab1 100644
--- a/app/mailers/emails/members.rb
+++ b/app/mailers/emails/members.rb
@@ -9,11 +9,11 @@ module Emails
helper_method :member_source, :member
end
- def member_access_requested_email(member_source_type, member_id, recipient_notification_email)
+ def member_access_requested_email(member_source_type, member_id, recipient_id)
@member_source_type = member_source_type
@member_id = member_id
- mail(to: recipient_notification_email,
+ mail(to: recipient(recipient_id, notification_group),
subject: subject("Request to join the #{member_source.human_name} #{member_source.model_name.singular}"))
end
@@ -21,16 +21,15 @@ module Emails
@member_source_type = member_source_type
@member_id = member_id
- mail(to: member.user.notification_email,
+ mail(to: recipient(member.user, notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was granted"))
end
def member_access_denied_email(member_source_type, source_id, user_id)
@member_source_type = member_source_type
@member_source = member_source_class.find(source_id)
- requester = User.find(user_id)
- mail(to: requester.notification_email,
+ mail(to: recipient(user_id, notification_group),
subject: subject("Access to the #{member_source.human_name} #{member_source.model_name.singular} was denied"))
end
@@ -48,7 +47,7 @@ module Emails
@member_id = member_id
return unless member.created_by
- mail(to: member.created_by.notification_email,
+ mail(to: recipient(member.created_by, notification_group),
subject: subject('Invitation accepted'))
end
@@ -59,7 +58,7 @@ module Emails
@member_source = member_source_class.find(source_id)
@invite_email = invite_email
- mail(to: recipient(created_by_id, member_source_type == 'Project' ? @member_source.group : @member_source),
+ mail(to: recipient(created_by_id, notification_group),
subject: subject('Invitation declined'))
end
@@ -71,6 +70,10 @@ module Emails
@member_source ||= member.source
end
+ def notification_group
+ @member_source_type.casecmp?('project') ? member_source.group : member_source
+ end
+
private
def member_source_class
diff --git a/app/mailers/emails/remote_mirrors.rb b/app/mailers/emails/remote_mirrors.rb
index 2d8137843ec..f3938a052b0 100644
--- a/app/mailers/emails/remote_mirrors.rb
+++ b/app/mailers/emails/remote_mirrors.rb
@@ -3,7 +3,7 @@
module Emails
module RemoteMirrors
def remote_mirror_update_failed_email(remote_mirror_id, recipient_id)
- @remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
+ @remote_mirror = RemoteMirror.find_by_id(remote_mirror_id)
@project = @remote_mirror.project
mail(to: recipient(recipient_id, @project.group), subject: subject('Remote mirror update failed'))
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index 576caea4c10..5d292094a05 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -71,24 +71,18 @@ class Notify < BaseMailer
address.format
end
- # Look up a User by their ID and return their email address
+ # Look up a User's notification email for a particular context.
+ # Can look up by their ID or can accept a User object.
#
- # recipient_id - User ID
+ # recipient - User object OR a User ID
# notification_group - The parent group of the notification
#
# Returns a String containing the User's email address.
- def recipient(recipient_id, notification_group = nil)
- @current_user = User.find(recipient_id)
- group_notification_email = nil
+ def recipient(recipient, notification_group = nil)
+ user = recipient if recipient.is_a?(User)
+ user ||= User.find(recipient)
- if notification_group
- notification_settings = notification_group.notification_settings_for(@current_user, hierarchy_order: :asc)
- group_notification_email = notification_settings.find { |n| n.notification_email.present? }&.notification_email
- end
-
- # Return group-specific email address if present, otherwise return global
- # email address
- group_notification_email || @current_user.notification_email
+ user.notification_email_for(notification_group)
end
# Formats arguments into a String suitable for use as an email subject
diff --git a/app/mailers/previews/notify_preview.rb b/app/mailers/previews/notify_preview.rb
index 80e0a17c312..b3fab930922 100644
--- a/app/mailers/previews/notify_preview.rb
+++ b/app/mailers/previews/notify_preview.rb
@@ -105,11 +105,11 @@ class NotifyPreview < ActionMailer::Preview
end
def member_access_granted_email
- Notify.member_access_granted_email('project', user.id).message
+ Notify.member_access_granted_email(member.source_type, member.id).message
end
def member_access_requested_email
- Notify.member_access_requested_email('group', user.id, 'some@example.com').message
+ Notify.member_access_requested_email('group', user.id, user.id).message
end
def member_invite_accepted_email
@@ -183,6 +183,10 @@ class NotifyPreview < ActionMailer::Preview
@user ||= User.last
end
+ def member
+ @member ||= Member.last
+ end
+
def create_note(params)
Notes::CreateService.new(project, user, params).execute
end
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index 345767179eb..00192b1da59 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -93,12 +93,12 @@ class ActiveSession
end
def self.list_sessions(user)
- sessions_from_ids(session_ids_for_user(user))
+ sessions_from_ids(session_ids_for_user(user.id))
end
- def self.session_ids_for_user(user)
+ def self.session_ids_for_user(user_id)
Gitlab::Redis::SharedState.with do |redis|
- redis.smembers(lookup_key_name(user.id))
+ redis.smembers(lookup_key_name(user_id))
end
end
@@ -129,15 +129,17 @@ class ActiveSession
end
def self.cleaned_up_lookup_entries(redis, user)
- session_ids = session_ids_for_user(user)
+ session_ids = session_ids_for_user(user.id)
entries = raw_active_session_entries(session_ids, user.id)
# remove expired keys.
# only the single key entries are automatically expired by redis, the
# lookup entries in the set need to be removed manually.
session_ids_and_entries = session_ids.zip(entries)
- session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
- redis.srem(lookup_key_name(user.id), session_id)
+ redis.pipelined do
+ session_ids_and_entries.reject { |_session_id, entry| entry }.each do |session_id, _entry|
+ redis.srem(lookup_key_name(user.id), session_id)
+ end
end
entries.compact
diff --git a/app/models/analytics/cycle_analytics.rb b/app/models/analytics/cycle_analytics.rb
new file mode 100644
index 00000000000..626fc91cc41
--- /dev/null
+++ b/app/models/analytics/cycle_analytics.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ def self.table_name_prefix
+ 'analytics_cycle_analytics_'
+ end
+ end
+end
diff --git a/app/models/analytics/cycle_analytics/project_stage.rb b/app/models/analytics/cycle_analytics/project_stage.rb
new file mode 100644
index 00000000000..88c8cb40ccb
--- /dev/null
+++ b/app/models/analytics/cycle_analytics/project_stage.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module Analytics
+ module CycleAnalytics
+ class ProjectStage < ApplicationRecord
+ belongs_to :project
+ end
+ end
+end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 8e558487c1c..2a99c6e5c59 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -10,6 +10,8 @@ class ApplicationSetting < ApplicationRecord
add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
add_authentication_token_field :health_check_access_token
+ belongs_to :instance_administration_project, class_name: "Project"
+
# Include here so it can override methods from
# `add_authentication_token_field`
# We don't prepend for now because otherwise we'll need to
@@ -41,6 +43,11 @@ class ApplicationSetting < ApplicationRecord
validates :uuid, presence: true
+ validates :outbound_local_requests_whitelist,
+ length: { maximum: 1_000, message: N_('is too long (maximum is 1000 entries)') },
+ allow_nil: false,
+ qualified_domain_array: true
+
validates :session_expire_delay,
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
@@ -92,6 +99,11 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: :plantuml_enabled
+ validates :snowplow_collector_hostname,
+ presence: true,
+ hostname: true,
+ if: :snowplow_enabled
+
validates :max_attachment_size,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index df4caed175d..55ac1e129cf 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -2,6 +2,7 @@
module ApplicationSettingImplementation
extend ActiveSupport::Concern
+ include Gitlab::Utils::StrongMemoize
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or
@@ -20,7 +21,8 @@ module ApplicationSettingImplementation
{
after_sign_up_text: nil,
akismet_enabled: false,
- allow_local_requests_from_hooks_and_services: false,
+ allow_local_requests_from_web_hooks_and_services: false,
+ allow_local_requests_from_system_hooks: true,
dns_rebinding_protection_enabled: true,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
@@ -95,8 +97,14 @@ module ApplicationSettingImplementation
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
+ snowplow_collector_hostname: nil,
+ snowplow_cookie_domain: nil,
+ snowplow_enabled: false,
+ snowplow_site_id: nil,
protected_ci_variables: false,
- local_markdown_version: 0
+ local_markdown_version: 0,
+ outbound_local_requests_whitelist: [],
+ raw_blob_request_limit: 300
}
end
@@ -131,31 +139,65 @@ module ApplicationSettingImplementation
end
def domain_whitelist_raw
- self.domain_whitelist&.join("\n")
+ array_to_string(self.domain_whitelist)
end
def domain_blacklist_raw
- self.domain_blacklist&.join("\n")
+ array_to_string(self.domain_blacklist)
end
def domain_whitelist_raw=(values)
- self.domain_whitelist = []
- self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_whitelist.reject! { |d| d.empty? }
- self.domain_whitelist
+ self.domain_whitelist = domain_strings_to_array(values)
end
def domain_blacklist_raw=(values)
- self.domain_blacklist = []
- self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_blacklist.reject! { |d| d.empty? }
- self.domain_blacklist
+ self.domain_blacklist = domain_strings_to_array(values)
end
def domain_blacklist_file=(file)
self.domain_blacklist_raw = file.read
end
+ def outbound_local_requests_whitelist_raw
+ array_to_string(self.outbound_local_requests_whitelist)
+ end
+
+ def outbound_local_requests_whitelist_raw=(values)
+ clear_memoization(:outbound_local_requests_whitelist_arrays)
+
+ self.outbound_local_requests_whitelist = domain_strings_to_array(values)
+ end
+
+ def add_to_outbound_local_requests_whitelist(values_array)
+ clear_memoization(:outbound_local_requests_whitelist_arrays)
+
+ self.outbound_local_requests_whitelist ||= []
+ self.outbound_local_requests_whitelist += values_array
+
+ self.outbound_local_requests_whitelist.uniq!
+ end
+
+ def outbound_local_requests_whitelist_arrays
+ strong_memoize(:outbound_local_requests_whitelist_arrays) do
+ next [[], []] unless self.outbound_local_requests_whitelist
+
+ ip_whitelist = []
+ domain_whitelist = []
+
+ self.outbound_local_requests_whitelist.each do |str|
+ ip_obj = Gitlab::Utils.string_to_ip_object(str)
+
+ if ip_obj
+ ip_whitelist << ip_obj
+ else
+ domain_whitelist << str
+ end
+ end
+
+ [ip_whitelist, domain_whitelist]
+ end
+ end
+
def repository_storages
Array(read_attribute(:repository_storages))
end
@@ -255,6 +297,19 @@ module ApplicationSettingImplementation
private
+ def array_to_string(arr)
+ arr&.join("\n")
+ end
+
+ def domain_strings_to_array(values)
+ return [] unless values
+
+ values
+ .split(DOMAIN_LIST_SEPARATOR)
+ .reject(&:empty?)
+ .uniq
+ end
+
def ensure_uuid!
return if uuid?
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 635fcc86166..3c0efca31db 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -38,8 +38,10 @@ module Ci
has_one :deployment, as: :deployable, class_name: 'Deployment'
has_many :trace_sections, class_name: 'Ci::BuildTraceSection'
has_many :trace_chunks, class_name: 'Ci::BuildTraceChunk', foreign_key: :build_id
+ has_many :needs, class_name: 'Ci::BuildNeed', foreign_key: :build_id, inverse_of: :build
has_many :job_artifacts, class_name: 'Ci::JobArtifact', foreign_key: :job_id, dependent: :destroy, inverse_of: :job # rubocop:disable Cop/ActiveRecordDependent
+ has_many :job_variables, class_name: 'Ci::JobVariable', foreign_key: :job_id
Ci::JobArtifact.file_types.each do |key, value|
has_one :"job_artifacts_#{key}", -> { where(file_type: value) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id
@@ -48,6 +50,8 @@ module Ci
has_one :runner_session, class_name: 'Ci::BuildRunnerSession', validate: true, inverse_of: :build
accepts_nested_attributes_for :runner_session
+ accepts_nested_attributes_for :job_variables
+ accepts_nested_attributes_for :needs
delegate :url, to: :runner_session, prefix: true, allow_nil: true
delegate :terminal_specification, to: :runner_session, allow_nil: true
@@ -331,10 +335,10 @@ module Ci
end
# rubocop: disable CodeReuse/ServiceClass
- def play(current_user)
+ def play(current_user, job_variables_attributes = nil)
Ci::PlayBuildService
.new(project, current_user)
- .execute(self)
+ .execute(self, job_variables_attributes)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -380,7 +384,7 @@ module Ci
return unless has_environment?
strong_memoize(:expanded_environment_name) do
- ExpandVariables.expand(environment, simple_variables)
+ ExpandVariables.expand(environment, -> { simple_variables })
end
end
@@ -432,6 +436,7 @@ module Ci
Gitlab::Ci::Variables::Collection.new
.concat(persisted_variables)
.concat(scoped_variables)
+ .concat(job_variables)
.concat(persisted_environment_variables)
.to_runner_variables
end
@@ -531,6 +536,14 @@ module Ci
trace.exist?
end
+ def has_live_trace?
+ trace.live_trace_exist?
+ end
+
+ def has_archived_trace?
+ trace.archived_trace_exist?
+ end
+
def artifacts_file
job_artifacts_archive&.file
end
@@ -702,11 +715,17 @@ module Ci
depended_jobs = depends_on_builds
- return depended_jobs unless options[:dependencies].present?
+ # find all jobs that are needed
+ if Feature.enabled?(:ci_dag_support, project, default_enabled: true) && needs.exists?
+ depended_jobs = depended_jobs.where(name: needs.select(:name))
+ end
- depended_jobs.select do |job|
- options[:dependencies].include?(job.name)
+ # find all jobs that are dependent on
+ if options[:dependencies].present?
+ depended_jobs = depended_jobs.where(name: options[:dependencies])
end
+
+ depended_jobs
end
def empty_dependencies?
diff --git a/app/models/ci/build_need.rb b/app/models/ci/build_need.rb
new file mode 100644
index 00000000000..6531dfd332f
--- /dev/null
+++ b/app/models/ci/build_need.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildNeed < ApplicationRecord
+ extend Gitlab::Ci::Model
+
+ belongs_to :build, class_name: "Ci::Build", foreign_key: :build_id, inverse_of: :needs
+
+ validates :build, presence: true
+ validates :name, presence: true, length: { maximum: 128 }
+
+ scope :scoped_build, -> { where('ci_builds.id=ci_build_needs.build_id') }
+ end
+end
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index f80e98e5bca..e132cb045e2 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -176,6 +176,10 @@ module Ci
end
end
+ def self.archived_trace_exists_for?(job_id)
+ where(job_id: job_id).trace.take&.file&.file&.exists?
+ end
+
private
def file_format_adapter_class
diff --git a/app/models/ci/job_variable.rb b/app/models/ci/job_variable.rb
new file mode 100644
index 00000000000..862a0bc1299
--- /dev/null
+++ b/app/models/ci/job_variable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Ci
+ class JobVariable < ApplicationRecord
+ extend Gitlab::Ci::Model
+ include NewHasVariable
+
+ belongs_to :job, class_name: "Ci::Build", foreign_key: :job_id
+
+ alias_attribute :secret_value, :value
+
+ validates :key, uniqueness: { scope: :job_id }
+ end
+end
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 2262282e647..0a943a33bbb 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -229,15 +229,15 @@ module Ci
#
# ref - The name (or names) of the branch(es)/tag(s) to limit the list of
# pipelines to.
+ # sha - The commit SHA (or mutliple SHAs) to limit the list of pipelines to.
# limit - This limits a backlog search, default to 100.
- def self.newest_first(ref: nil, limit: 100)
+ def self.newest_first(ref: nil, sha: nil, limit: 100)
relation = order(id: :desc)
relation = relation.where(ref: ref) if ref
+ relation = relation.where(sha: sha) if sha
if limit
ids = relation.limit(limit).select(:id)
- # MySQL does not support limit in subquery
- ids = ids.pluck(:id) if Gitlab::Database.mysql?
relation = relation.where(id: ids)
end
@@ -248,10 +248,14 @@ module Ci
newest_first(ref: ref).pluck(:status).first
end
- def self.latest_successful_for(ref)
+ def self.latest_successful_for_ref(ref)
newest_first(ref: ref).success.take
end
+ def self.latest_successful_for_sha(sha)
+ newest_first(sha: sha).success.take
+ end
+
def self.latest_successful_for_refs(refs)
relation = newest_first(ref: refs).success
@@ -324,6 +328,10 @@ module Ci
config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source)
end
+ def self.bridgeable_statuses
+ ::Ci::Pipeline::AVAILABLE_STATUSES - %w[created preparing pending]
+ end
+
def stages_count
statuses.select(:stage).distinct.count
end
@@ -500,8 +508,9 @@ module Ci
return [] unless config_processor
strong_memoize(:stage_seeds) do
- seeds = config_processor.stages_attributes.map do |attributes|
- Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes)
+ seeds = config_processor.stages_attributes.inject([]) do |previous_stages, attributes|
+ seed = Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes, previous_stages)
+ previous_stages + [seed]
end
seeds.select(&:included?)
@@ -607,8 +616,8 @@ module Ci
end
# rubocop: disable CodeReuse/ServiceClass
- def process!
- Ci::ProcessPipelineService.new(project, user).execute(self)
+ def process!(trigger_build_ids = nil)
+ Ci::ProcessPipelineService.new(project, user).execute(self, trigger_build_ids)
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 07d00503861..43ff874ac23 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -264,7 +264,7 @@ module Ci
private
def cleanup_runner_queue
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
redis.del(runner_queue_key)
end
end
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index a77bbef0fca..760872d3e6b 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -6,6 +6,7 @@ module Ci
include HasVariable
include Presentable
include Maskable
+ prepend HasEnvironmentScope
belongs_to :project
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index d6a7d1d2bdd..6bd7473c8ff 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -24,12 +24,6 @@ module Clusters
'stable/cert-manager'
end
- # We will implement this in future MRs.
- # Need to reverse postinstall step
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: 'certmanager',
@@ -41,10 +35,44 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: 'certmanager',
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ postdelete: post_delete_script
+ )
+ end
+
private
def post_install_script
- ["/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"]
+ ["kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml"]
+ end
+
+ def post_delete_script
+ [
+ delete_private_key,
+ delete_crd('certificates.certmanager.k8s.io'),
+ delete_crd('clusterissuers.certmanager.k8s.io'),
+ delete_crd('issuers.certmanager.k8s.io')
+ ].compact
+ end
+
+ def private_key_name
+ @private_key_name ||= cluster_issuer_content.dig('spec', 'acme', 'privateKeySecretRef', 'name')
+ end
+
+ def delete_private_key
+ return unless private_key_name.present?
+
+ args = %W(secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found)
+
+ Gitlab::Kubernetes::KubectlCmd.delete(*args)
+ end
+
+ def delete_crd(definition)
+ Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found")
end
def cluster_issuer_file
diff --git a/app/models/clusters/applications/helm.rb b/app/models/clusters/applications/helm.rb
index a83d06c4b00..455cf200fbc 100644
--- a/app/models/clusters/applications/helm.rb
+++ b/app/models/clusters/applications/helm.rb
@@ -14,6 +14,7 @@ module Clusters
include ::Clusters::Concerns::ApplicationCore
include ::Clusters::Concerns::ApplicationStatus
+ include ::Gitlab::Utils::StrongMemoize
default_value_for :version, Gitlab::Kubernetes::Helm::HELM_VERSION
@@ -29,11 +30,22 @@ module Clusters
self.status = 'installable' if cluster&.platform_kubernetes_active?
end
- # We will implement this in future MRs.
- # Basically we need to check all other applications are not installed
- # first.
+ # It can only be uninstalled if there are no other applications installed
+ # or with intermitent installation statuses in the database.
def allowed_to_uninstall?
- false
+ strong_memoize(:allowed_to_uninstall) do
+ applications = nil
+
+ Clusters::Cluster::APPLICATIONS.each do |application_name, klass|
+ next if application_name == 'helm'
+
+ extra_apps = Clusters::Applications::Helm.where('EXISTS (?)', klass.select(1).where(cluster_id: cluster_id))
+
+ applications = applications ? applications.or(extra_apps) : extra_apps
+ end
+
+ !applications.exists?
+ end
end
def install_command
@@ -44,6 +56,14 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::ResetCommand.new(
+ name: name,
+ files: files,
+ rbac: cluster.platform_kubernetes_rbac?
+ )
+ end
+
def has_ssl?
ca_key.present? && ca_cert.present?
end
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 5df4812bd25..244fe738396 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -7,6 +7,7 @@ module Clusters
REPOSITORY = 'https://storage.googleapis.com/triggermesh-charts'.freeze
METRICS_CONFIG = 'https://storage.googleapis.com/triggermesh-charts/istio-metrics.yaml'.freeze
FETCH_IP_ADDRESS_DELAY = 30.seconds
+ API_RESOURCES_PATH = 'config/knative/api_resources.yml'
self.table_name = 'clusters_applications_knative'
@@ -46,12 +47,6 @@ module Clusters
{ "domain" => hostname }.to_yaml
end
- # Handled in a new issue:
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/59369
- def allowed_to_uninstall?
- false
- end
-
def install_command
Gitlab::Kubernetes::Helm::InstallCommand.new(
name: name,
@@ -76,10 +71,61 @@ module Clusters
cluster.kubeclient.get_service('istio-ingressgateway', 'istio-system')
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ predelete: delete_knative_services_and_metrics,
+ postdelete: delete_knative_istio_leftovers
+ )
+ end
+
private
+ def delete_knative_services_and_metrics
+ delete_knative_services + delete_knative_istio_metrics
+ end
+
+ def delete_knative_services
+ cluster.kubernetes_namespaces.map do |kubernetes_namespace|
+ Gitlab::Kubernetes::KubectlCmd.delete("ksvc", "--all", "-n", kubernetes_namespace.namespace)
+ end
+ end
+
+ def delete_knative_istio_leftovers
+ delete_knative_namespaces + delete_knative_and_istio_crds
+ end
+
+ def delete_knative_namespaces
+ [
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-serving"),
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-build")
+ ]
+ end
+
+ def delete_knative_and_istio_crds
+ api_resources.map do |crd|
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "crd", "#{crd}")
+ end
+ end
+
+ # returns an array of CRDs to be postdelete since helm does not
+ # manage the CRDs it creates.
+ def api_resources
+ @api_resources ||= YAML.safe_load(File.read(Rails.root.join(API_RESOURCES_PATH)))
+ end
+
def install_knative_metrics
- ["kubectl apply -f #{METRICS_CONFIG}"] if cluster.application_prometheus_available?
+ return [] unless cluster.application_prometheus_available?
+
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(METRICS_CONFIG)]
+ end
+
+ def delete_knative_istio_metrics
+ return [] unless cluster.application_prometheus_available?
+
+ [Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "-f", METRICS_CONFIG)]
end
def verify_cluster?
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 805c8a73f8c..f31a6b8b50e 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -59,6 +59,15 @@ module Clusters
)
end
+ def uninstall_command
+ Gitlab::Kubernetes::Helm::DeleteCommand.new(
+ name: name,
+ rbac: cluster.platform_kubernetes_rbac?,
+ files: files,
+ predelete: delete_knative_istio_metrics
+ )
+ end
+
# Returns a copy of files where the values of 'values.yaml'
# are replaced by the argument.
#
@@ -74,7 +83,7 @@ module Clusters
# ensures headers containing auth data are appended to original k8s client options
options = kube_client.rest_client.options.merge(headers: kube_client.headers)
- RestClient::Resource.new(proxy_url, options)
+ Gitlab::PrometheusClient.new(proxy_url, options)
rescue Kubeclient::HttpError
# If users have mistakenly set parameters or removed the depended clusters,
# `proxy_url` could raise an exception because gitlab can not communicate with the cluster.
@@ -95,7 +104,15 @@ module Clusters
end
def install_knative_metrics
- ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"] if cluster.application_knative_available?
+ return [] unless cluster.application_knative_available?
+
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(Clusters::Applications::Knative::METRICS_CONFIG)]
+ end
+
+ def delete_knative_istio_metrics
+ return [] unless cluster.application_knative_available?
+
+ [Gitlab::Kubernetes::KubectlCmd.delete("-f", Clusters::Applications::Knative::METRICS_CONFIG)]
end
end
end
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 6ae8c3bd7f3..6533b7a186e 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ApplicationRecord
- VERSION = '0.6.0'.freeze
+ VERSION = '0.7.0'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 8c044c86c47..97d39491b73 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -53,6 +53,7 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true }
+ validates :namespace_per_environment, inclusion: { in: [true, false] }
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
@@ -100,22 +101,6 @@ module Clusters
scope :default_environment, -> { where(environment_scope: DEFAULT_ENVIRONMENT) }
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.cluster_id = clusters.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
- scope :with_knative_installed, -> { joins(:application_knative).merge(Clusters::Applications::Knative.available) }
-
- scope :preload_knative, -> {
- preload(
- :kubernetes_namespaces,
- :platform_kubernetes,
- :application_knative
- )
- }
-
def self.ancestor_clusters_for_clusterable(clusterable, hierarchy_order: :asc)
return [] if clusterable.is_a?(Instance)
@@ -161,16 +146,6 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def all_projects
- if project_type?
- projects
- elsif group_type?
- first_group.all_projects
- else
- Project.none
- end
- end
-
def first_project
strong_memoize(:first_project) do
projects.first
@@ -193,36 +168,15 @@ module Clusters
platform_kubernetes.kubeclient if kubernetes?
end
- ##
- # This is subtly different to #find_or_initialize_kubernetes_namespace_for_project
- # below because it will ignore any namespaces that have not got a service account
- # token. This provides a guarantee that any namespace selected here can be used
- # for cluster operations - a namespace needs to have a service account configured
- # before it it can be used.
- #
- # This is used for selecting a namespace to use when querying a cluster, or
- # generating variables to pass to CI.
- def kubernetes_namespace_for(project)
- find_or_initialize_kubernetes_namespace_for_project(
- project, scope: kubernetes_namespaces.has_service_account_token
- ).namespace
- end
+ def kubernetes_namespace_for(environment)
+ project = environment.project
+ persisted_namespace = Clusters::KubernetesNamespaceFinder.new(
+ self,
+ project: project,
+ environment_slug: environment.slug
+ ).execute
- ##
- # This is subtly different to #kubernetes_namespace_for because it will include
- # namespaces that have yet to receive a service account token. This allows
- # the namespace configuration process to be repeatable - if a namespace has
- # already been created without a token we don't need to create another
- # record entirely, just set the token on the pre-existing namespace.
- #
- # This is used for configuring cluster namespaces.
- def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces)
- attributes = { project: project }
- attributes[:cluster_project] = cluster_project if project_type?
-
- scope.find_or_initialize_by(attributes).tap do |namespace|
- namespace.set_defaults
- end
+ persisted_namespace&.namespace || Gitlab::Kubernetes::DefaultNamespace.new(self, project: project).from_environment_slug(environment.slug)
end
def allow_user_defined_namespace?
@@ -241,10 +195,6 @@ module Clusters
end
end
- def knative_services_finder(project)
- @knative_services_finder ||= KnativeServicesFinder.new(self, project)
- end
-
private
def instance_domain
diff --git a/app/models/clusters/clusters_hierarchy.rb b/app/models/clusters/clusters_hierarchy.rb
index dab034b7234..5556fc8d3f0 100644
--- a/app/models/clusters/clusters_hierarchy.rb
+++ b/app/models/clusters/clusters_hierarchy.rb
@@ -46,7 +46,7 @@ module Clusters
def group_clusters_base_query
group_parent_id_alias = alias_as_column(groups[:parent_id], 'group_parent_id')
- join_sources = ::Group.left_joins(:clusters).join_sources
+ join_sources = ::Group.left_joins(:clusters).arel.join_sources
model
.unscoped
@@ -59,7 +59,7 @@ module Clusters
def project_clusters_base_query
projects = ::Project.arel_table
project_parent_id_alias = alias_as_column(projects[:namespace_id], 'group_parent_id')
- join_sources = ::Project.left_joins(:clusters).join_sources
+ join_sources = ::Project.left_joins(:clusters).arel.join_sources
model
.unscoped
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index b0c4900546e..69a2b99fcb6 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -9,12 +9,12 @@ module Clusters
belongs_to :cluster_project, class_name: 'Clusters::Project'
belongs_to :cluster, class_name: 'Clusters::Cluster'
belongs_to :project, class_name: '::Project'
+ belongs_to :environment, optional: true
has_one :platform_kubernetes, through: :cluster
- before_validation :set_defaults
-
validates :namespace, presence: true
validates :namespace, uniqueness: { scope: :cluster_id }
+ validates :environment_id, uniqueness: { scope: [:cluster_id, :project_id] }, allow_nil: true
validates :service_account_name, presence: true
@@ -27,6 +27,7 @@ module Clusters
algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
+ scope :with_environment_slug, -> (slug) { joins(:environment).where(environments: { slug: slug }) }
def token_name
"#{namespace}-token"
@@ -42,34 +43,8 @@ module Clusters
end
end
- def set_defaults
- self.namespace ||= default_platform_kubernetes_namespace
- self.namespace ||= default_project_namespace
- self.service_account_name ||= default_service_account_name
- end
-
private
- def default_service_account_name
- return unless namespace
-
- "#{namespace}-service-account"
- end
-
- def default_platform_kubernetes_namespace
- platform_kubernetes&.namespace.presence
- end
-
- def default_project_namespace
- Gitlab::NamespaceSanitizer.sanitize(project_slug) if project_slug
- end
-
- def project_slug
- return unless project
-
- "#{project.path}-#{project.id}".downcase
- end
-
def kubeconfig
to_kubeconfig(
url: api_url,
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 9296c28776b..37614fbe3ca 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -51,11 +51,6 @@ module Clusters
delegate :provided_by_user?, to: :cluster, allow_nil: true
delegate :allow_user_defined_namespace?, to: :cluster, allow_nil: true
- # This is just to maintain compatibility with KubernetesService, which
- # will be removed in https://gitlab.com/gitlab-org/gitlab-ce/issues/39217.
- # It can be removed once KubernetesService is gone.
- delegate :kubernetes_namespace_for, to: :cluster, allow_nil: true
-
alias_method :active?, :enabled?
enum_with_nil authorization_type: {
@@ -66,7 +61,7 @@ module Clusters
default_value_for :authorization_type, :rbac
- def predefined_variables(project:)
+ def predefined_variables(project:, environment_name:)
Gitlab::Ci::Variables::Collection.new.tap do |variables|
variables.append(key: 'KUBE_URL', value: api_url)
@@ -77,15 +72,14 @@ module Clusters
end
if !cluster.managed?
- project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase
+ namespace = Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: project).from_environment_name(environment_name)
variables
- .append(key: 'KUBE_URL', value: api_url)
.append(key: 'KUBE_TOKEN', value: token, public: false, masked: true)
- .append(key: 'KUBE_NAMESPACE', value: project_namespace)
- .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true)
+ .append(key: 'KUBE_NAMESPACE', value: namespace)
+ .append(key: 'KUBECONFIG', value: kubeconfig(namespace), public: false, file: true)
- elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project)
+ elsif kubernetes_namespace = find_persisted_namespace(project, environment_name: environment_name)
variables.concat(kubernetes_namespace.predefined_variables)
end
@@ -111,6 +105,22 @@ module Clusters
private
+ ##
+ # Environment slug can be predicted given an environment
+ # name, so even if the environment isn't persisted yet we
+ # still know what to look for.
+ def environment_slug(name)
+ Gitlab::Slug::Environment.new(name).generate
+ end
+
+ def find_persisted_namespace(project, environment_name:)
+ Clusters::KubernetesNamespaceFinder.new(
+ cluster,
+ project: project,
+ environment_slug: environment_slug(environment_name)
+ ).execute
+ end
+
def kubeconfig(namespace)
to_kubeconfig(
url: api_url,
diff --git a/app/models/commit.rb b/app/models/commit.rb
index be37fa2e76f..0889ce7e287 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -346,7 +346,7 @@ class Commit
if commits_in_merge_request.present?
message_body << ""
- commits_in_merge_request.reverse.each do |commit_in_merge|
+ commits_in_merge_request.reverse_each do |commit_in_merge|
message_body << "#{commit_in_merge.short_id} #{commit_in_merge.title}"
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index be6f3e9c5b0..4be4d95b4a1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -40,8 +40,23 @@ class CommitStatus < ApplicationRecord
scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
+ scope :before_stage, -> (index) { where('stage_idx < ?', index) }
+ scope :for_stage, -> (index) { where(stage_idx: index) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
+ scope :for_ids, -> (ids) { where(id: ids) }
+
+ scope :with_needs, -> (names = nil) do
+ needs = Ci::BuildNeed.scoped_build.select(1)
+ needs = needs.where(name: names) if names
+ where('EXISTS (?)', needs).preload(:needs)
+ end
+
+ scope :without_needs, -> (names = nil) do
+ needs = Ci::BuildNeed.scoped_build.select(1)
+ needs = needs.where(name: names) if names
+ where('NOT EXISTS (?)', needs)
+ end
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
# extend this `Hash` with new values.
@@ -116,7 +131,7 @@ class CommitStatus < ApplicationRecord
commit_status.run_after_commit do
if pipeline_id
if complete? || manual?
- PipelineProcessWorker.perform_async(pipeline_id)
+ PipelineProcessWorker.perform_async(pipeline_id, [id])
else
PipelineUpdateWorker.perform_async(pipeline_id)
end
@@ -139,6 +154,18 @@ class CommitStatus < ApplicationRecord
end
end
+ def self.names
+ select(:name)
+ end
+
+ def self.status_for_prior_stages(index)
+ before_stage(index).latest.status || 'success'
+ end
+
+ def self.status_for_names(names)
+ where(name: names).latest.status || 'success'
+ end
+
def locking_enabled?
will_save_change_to_status?
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/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb
index 53dff2adfc3..0c800621a55 100644
--- a/app/models/concerns/cacheable_attributes.rb
+++ b/app/models/concerns/cacheable_attributes.rb
@@ -5,6 +5,8 @@ module CacheableAttributes
included do
after_commit { self.class.expire }
+
+ private_class_method :request_store_cache_key
end
class_methods do
@@ -32,7 +34,11 @@ module CacheableAttributes
end
def cached
- Gitlab::SafeRequestStore[:"#{name}_cached_attributes"] ||= retrieve_from_cache
+ Gitlab::SafeRequestStore[request_store_cache_key] ||= retrieve_from_cache
+ end
+
+ def request_store_cache_key
+ :"#{name}_cached_attributes"
end
def retrieve_from_cache
@@ -58,6 +64,7 @@ module CacheableAttributes
end
def expire
+ Gitlab::SafeRequestStore.delete(request_store_cache_key)
cache_backend.delete(cache_key)
rescue
# Gracefully handle when Redis is not available. For example,
diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
index c93b6589ee7..abddbf1c7e3 100644
--- a/app/models/concerns/case_sensitivity.rb
+++ b/app/models/concerns/case_sensitivity.rb
@@ -40,14 +40,10 @@ module CaseSensitivity
end
def lower_value(value)
- return value if Gitlab::Database.mysql?
-
Arel::Nodes::NamedFunction.new('LOWER', [Arel::Nodes.build_quoted(value)])
end
def lower_column(column)
- return column if Gitlab::Database.mysql?
-
column.lower
end
end
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 9eed9492b37..304cc71e9dc 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -29,6 +29,7 @@ module Ci
def degenerate!
self.class.transaction do
self.update!(options: nil, yaml_variables: nil)
+ self.needs.all.delete_all
self.metadata&.destroy
end
end
diff --git a/app/models/concerns/descendant.rb b/app/models/concerns/descendant.rb
deleted file mode 100644
index 4c436522122..00000000000
--- a/app/models/concerns/descendant.rb
+++ /dev/null
@@ -1,11 +0,0 @@
-# frozen_string_literal: true
-
-module Descendant
- extend ActiveSupport::Concern
-
- class_methods do
- def supports_nested_objects?
- Gitlab::Database.postgresql?
- end
- end
-end
diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb
index 2d09eff0111..195d9e107c5 100644
--- a/app/models/concerns/diff_positionable_note.rb
+++ b/app/models/concerns/diff_positionable_note.rb
@@ -10,6 +10,8 @@ module DiffPositionableNote
serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize
+
+ validate :diff_refs_match_commit, if: :for_commit?
end
%i(original_position position change_position).each do |meth|
@@ -71,4 +73,10 @@ module DiffPositionableNote
self.position = result[:position]
end
end
+
+ def diff_refs_match_commit
+ return if self.original_position.diff_refs == commit&.diff_refs
+
+ errors.add(:commit_id, 'does not match the diff refs')
+ end
end
diff --git a/app/models/concerns/group_descendant.rb b/app/models/concerns/group_descendant.rb
index cfffd845e43..ed14b73ac1b 100644
--- a/app/models/concerns/group_descendant.rb
+++ b/app/models/concerns/group_descendant.rb
@@ -42,7 +42,7 @@ module GroupDescendant
parent = child.parent
exception = ArgumentError.new <<~MSG
- parent: [GroupDescendant: #{parent.inspect}] was not preloaded for [#{child.inspect}]")
+ Parent was not preloaded for child when rendering group hierarchy.
This error is not user facing, but causes a +1 query.
MSG
extras = {
@@ -50,7 +50,7 @@ module GroupDescendant
child: child.inspect,
preloaded: preloaded.map(&:full_path)
}
- issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/40785'
+ issue_url = 'https://gitlab.com/gitlab-org/gitlab-ce/issues/49404'
Gitlab::Sentry.track_exception(exception, issue_url: issue_url, extra: extras)
end
diff --git a/app/models/concerns/has_environment_scope.rb b/app/models/concerns/has_environment_scope.rb
new file mode 100644
index 00000000000..9553abe4dd3
--- /dev/null
+++ b/app/models/concerns/has_environment_scope.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module HasEnvironmentScope
+ extend ActiveSupport::Concern
+
+ prepended do
+ validates(
+ :environment_scope,
+ presence: true,
+ format: { with: ::Gitlab::Regex.environment_scope_regex,
+ message: ::Gitlab::Regex.environment_scope_regex_message }
+ )
+
+ ##
+ # Select rows which have a scope that matches the given environment name.
+ # Rows are ordered by relevance, by default. The most relevant row is
+ # placed at the end of a list.
+ #
+ # options:
+ # - relevant_only: (boolean)
+ # You can get the most relevant row only. Other rows are not be
+ # selected even if its scope matches the environment name.
+ # This is equivalent to using `#last` from SQL standpoint.
+ #
+ scope :on_environment, -> (environment_name, relevant_only: false) do
+ order_direction = relevant_only ? 'DESC' : 'ASC'
+
+ where = <<~SQL
+ environment_scope IN (:wildcard, :environment_name) OR
+ :environment_name LIKE
+ #{::Gitlab::SQL::Glob.to_like('environment_scope')}
+ SQL
+
+ order = <<~SQL
+ CASE environment_scope
+ WHEN :wildcard THEN 0
+ WHEN :environment_name THEN 2
+ ELSE 1
+ END #{order_direction}
+ SQL
+
+ values = {
+ wildcard: '*',
+ environment_name: environment_name
+ }
+
+ sanitized_order_sql = sanitize_sql_array([order, values])
+
+ # The query is trying to find variables with scopes matching the
+ # current environment name. Suppose the environment name is
+ # 'review/app', and we have variables with environment scopes like:
+ # * variable A: review
+ # * variable B: review/app
+ # * variable C: review/*
+ # * variable D: *
+ # And the query should find variable B, C, and D, because it would
+ # try to convert the scope into a LIKE pattern for each variable:
+ # * A: review
+ # * B: review/app
+ # * C: review/%
+ # * D: %
+ # Note that we'll match % and _ literally therefore we'll escape them.
+ # In this case, B, C, and D would match. We also want to prioritize
+ # the exact matched name, and put * last, and everything else in the
+ # middle. So the order should be: D < C < B
+ relation = where(where, values)
+ .order(Arel.sql(sanitized_order_sql)) # `order` cannot escape for us!
+
+ relation = relation.limit(1) if relevant_only
+
+ relation
+ end
+ end
+
+ def environment_scope=(new_environment_scope)
+ super(new_environment_scope.to_s.strip)
+ end
+end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 27a5c3d5286..71ebb586c13 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -106,10 +106,15 @@ module HasStatus
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
+ scope :incomplete, -> { without_statuses(completed_statuses) }
scope :cancelable, -> do
where(status: [:running, :preparing, :pending, :created, :scheduled])
end
+
+ scope :without_statuses, -> (names) do
+ with_status(all_state_names - names.to_a)
+ end
end
def started?
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 952de92cae1..e60b6497cb7 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -427,4 +427,11 @@ module Issuable
def wipless_title_changed(old_title)
old_title != title
end
+
+ ##
+ # Overridden on EE module
+ #
+ def supports_milestone?
+ respond_to?(:milestone_id)
+ end
end
diff --git a/app/models/concerns/maskable.rb b/app/models/concerns/maskable.rb
index e0f2c41b836..d70e47bc4ff 100644
--- a/app/models/concerns/maskable.rb
+++ b/app/models/concerns/maskable.rb
@@ -7,9 +7,10 @@ module Maskable
# * No escape characters
# * No variables
# * No spaces
- # * Minimal length of 8 characters from the Base64 alphabets (RFC4648)
+ # * Minimal length of 8 characters
+ # * Characters must be from the Base64 alphabet (RFC4648) with the addition of @ and :
# * Absolutely no fun is allowed
- REGEX = /\A[a-zA-Z0-9_+=\/-]{8,}\z/.freeze
+ REGEX = /\A[a-zA-Z0-9_+=\/@:-]{8,}\z/.freeze
included do
validates :masked, inclusion: { in: [true, false] }
diff --git a/app/models/concerns/new_has_variable.rb b/app/models/concerns/new_has_variable.rb
new file mode 100644
index 00000000000..429bf496872
--- /dev/null
+++ b/app/models/concerns/new_has_variable.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module NewHasVariable
+ extend ActiveSupport::Concern
+ include HasVariable
+
+ included do
+ attr_encrypted :value,
+ mode: :per_attribute_iv,
+ algorithm: 'aes-256-gcm',
+ key: Settings.attr_encrypted_db_key_base_32,
+ insecure_mode: false
+ end
+end
diff --git a/app/models/concerns/project_api_compatibility.rb b/app/models/concerns/project_api_compatibility.rb
index cb00efb06df..631b2a11e9a 100644
--- a/app/models/concerns/project_api_compatibility.rb
+++ b/app/models/concerns/project_api_compatibility.rb
@@ -9,12 +9,10 @@ module ProjectAPICompatibility
end
def auto_devops_enabled=(value)
- self.build_auto_devops if self.auto_devops&.enabled.nil?
- self.auto_devops.update! enabled: value
+ (auto_devops || build_auto_devops).enabled = value
end
def auto_devops_deploy_strategy=(value)
- self.build_auto_devops if self.auto_devops&.enabled.nil?
- self.auto_devops.update! deploy_strategy: value
+ (auto_devops || build_auto_devops).deploy_strategy = value
end
end
diff --git a/app/models/concerns/prometheus_adapter.rb b/app/models/concerns/prometheus_adapter.rb
index c2542dbe743..9ac4722c6b1 100644
--- a/app/models/concerns/prometheus_adapter.rb
+++ b/app/models/concerns/prometheus_adapter.rb
@@ -14,10 +14,6 @@ module PrometheusAdapter
raise NotImplementedError
end
- def prometheus_client_wrapper
- Gitlab::PrometheusClient.new(prometheus_client)
- end
-
def can_query?
prometheus_client.present?
end
@@ -35,7 +31,7 @@ module PrometheusAdapter
def calculate_reactive_cache(query_class_name, *args)
return unless prometheus_client
- data = Object.const_get(query_class_name, false).new(prometheus_client_wrapper).query(*args)
+ data = Object.const_get(query_class_name, false).new(prometheus_client).query(*args)
{
success: true,
data: data,
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index af387c99f3d..0648b4a78e1 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -47,7 +47,7 @@ module ProtectedRef
def access_levels_for_ref(ref, action:, protected_refs: nil)
self.matching(ref, protected_refs: protected_refs)
- .map(&:"#{action}_access_levels").flatten
+ .flat_map(&:"#{action}_access_levels")
end
# Returns all protected refs that match the given ref name.
diff --git a/app/models/concerns/relative_positioning.rb b/app/models/concerns/relative_positioning.rb
index e4fe46d722a..6d3c7a7ed68 100644
--- a/app/models/concerns/relative_positioning.rb
+++ b/app/models/concerns/relative_positioning.rb
@@ -1,5 +1,26 @@
# frozen_string_literal: true
+# This module makes it possible to handle items as a list, where the order of items can be easily altered
+# Requirements:
+#
+# - Only works for ActiveRecord models
+# - relative_position integer field must present on the model
+# - This module uses GROUP BY: the model should have a parent relation, example: project -> issues, project is the parent relation (issues table has a parent_id column)
+#
+# Setup like this in the body of your class:
+#
+# include RelativePositioning
+#
+# # base query used for the position calculation
+# def self.relative_positioning_query_base(issue)
+# where(deleted: false)
+# end
+#
+# # column that should be used in GROUP BY
+# def self.relative_positioning_parent_column
+# :project_id
+# end
+#
module RelativePositioning
extend ActiveSupport::Concern
@@ -8,12 +29,8 @@ module RelativePositioning
MAX_POSITION = Gitlab::Database::MAX_INT_VALUE
IDEAL_DISTANCE = 500
- included do
- after_save :save_positionable_neighbours
- end
-
class_methods do
- def move_to_end(objects)
+ def move_nulls_to_end(objects)
objects = objects.reject(&:relative_position)
return if objects.empty?
@@ -22,7 +39,7 @@ module RelativePositioning
self.transaction do
objects.each do |object|
- relative_position = position_between(max_relative_position, MAX_POSITION)
+ relative_position = position_between(max_relative_position || START_POSITION, MAX_POSITION)
object.relative_position = relative_position
max_relative_position = relative_position
object.save(touch: false)
@@ -93,11 +110,12 @@ module RelativePositioning
return move_after(before) unless after
return move_before(after) unless before
- # If there is no place to insert an issue we need to create one by moving the before issue closer
- # to its predecessor. This process will recursively move all the predecessors until we have a place
+ # If there is no place to insert an item we need to create one by moving the item
+ # before this and all preceding items until there is a gap
+ before, after = after, before if after.relative_position < before.relative_position
if (after.relative_position - before.relative_position) < 2
- before.move_before
- @positionable_neighbours = [before] # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ after.move_sequence_before
+ before.reset
end
self.relative_position = self.class.position_between(before.relative_position, after.relative_position)
@@ -107,12 +125,8 @@ module RelativePositioning
pos_before = before.relative_position
pos_after = before.next_relative_position
- if before.shift_after?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_after)
- issue_to_move.move_after
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
-
- pos_after = issue_to_move.relative_position
+ if pos_after && (pos_after - pos_before) < 2
+ before.move_sequence_after
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -122,12 +136,8 @@ module RelativePositioning
pos_after = after.relative_position
pos_before = after.prev_relative_position
- if after.shift_before?
- issue_to_move = self.class.in_parents(parent_ids).find_by!(relative_position: pos_before)
- issue_to_move.move_before
- @positionable_neighbours = [issue_to_move] # rubocop:disable Gitlab/ModuleWithInstanceVariables
-
- pos_before = issue_to_move.relative_position
+ if pos_before && (pos_after - pos_before) < 2
+ after.move_sequence_before
end
self.relative_position = self.class.position_between(pos_before, pos_after)
@@ -141,46 +151,95 @@ module RelativePositioning
self.relative_position = self.class.position_between(min_relative_position || START_POSITION, MIN_POSITION)
end
- # Indicates if there is an issue that should be shifted to free the place
- def shift_after?
- next_pos = next_relative_position
- next_pos && (next_pos - relative_position) == 1
+ # Moves the sequence before the current item to the middle of the next gap
+ # For example, we have 5 11 12 13 14 15 and the current item is 15
+ # This moves the sequence 11 12 13 14 to 8 9 10 11
+ def move_sequence_before
+ next_gap = find_next_gap_before
+ delta = optimum_delta_for_gap(next_gap)
+
+ move_sequence(next_gap[:start], relative_position, -delta)
end
- # Indicates if there is an issue that should be shifted to free the place
- def shift_before?
- prev_pos = prev_relative_position
- prev_pos && (relative_position - prev_pos) == 1
+ # Moves the sequence after the current item to the middle of the next gap
+ # For example, we have 11 12 13 14 15 21 and the current item is 11
+ # This moves the sequence 12 13 14 15 to 15 16 17 18
+ def move_sequence_after
+ next_gap = find_next_gap_after
+ delta = optimum_delta_for_gap(next_gap)
+
+ move_sequence(relative_position, next_gap[:start], delta)
end
private
- # rubocop:disable Gitlab/ModuleWithInstanceVariables
- def save_positionable_neighbours
- return unless @positionable_neighbours
+ # Supposing that we have a sequence of items: 1 5 11 12 13 and the current item is 13
+ # This would return: `{ start: 11, end: 5 }`
+ def find_next_gap_before
+ items_with_next_pos = scoped_items
+ .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position DESC) AS next_pos')
+ .where('relative_position <= ?', relative_position)
+ .order(relative_position: :desc)
+
+ find_next_gap(items_with_next_pos).tap do |gap|
+ gap[:end] ||= MIN_POSITION
+ end
+ end
+
+ # Supposing that we have a sequence of items: 13 14 15 20 24 and the current item is 13
+ # This would return: `{ start: 15, end: 20 }`
+ def find_next_gap_after
+ items_with_next_pos = scoped_items
+ .select('relative_position AS pos, LEAD(relative_position) OVER (ORDER BY relative_position ASC) AS next_pos')
+ .where('relative_position >= ?', relative_position)
+ .order(:relative_position)
- status = @positionable_neighbours.all? { |issue| issue.save(touch: false) }
- @positionable_neighbours = nil
+ find_next_gap(items_with_next_pos).tap do |gap|
+ gap[:end] ||= MAX_POSITION
+ end
+ end
+
+ def find_next_gap(items_with_next_pos)
+ gap = self.class.from(items_with_next_pos, :items_with_next_pos)
+ .where('ABS(pos - next_pos) > 1 OR next_pos IS NULL')
+ .limit(1)
+ .pluck(:pos, :next_pos)
+ .first
+
+ { start: gap[0], end: gap[1] }
+ end
- status
+ def optimum_delta_for_gap(gap)
+ delta = ((gap[:start] - gap[:end]) / 2.0).abs.ceil
+
+ [delta, IDEAL_DISTANCE].min
+ end
+
+ def move_sequence(start_pos, end_pos, delta)
+ scoped_items
+ .where.not(id: self.id)
+ .where('relative_position BETWEEN ? AND ?', start_pos, end_pos)
+ .update_all("relative_position = relative_position + #{delta}")
end
- # rubocop:enable Gitlab/ModuleWithInstanceVariables
def calculate_relative_position(calculation)
# When calculating across projects, this is much more efficient than
# MAX(relative_position) without the GROUP BY, due to index usage:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/54276#note_119340977
- relation = self.class
- .in_parents(parent_ids)
+ relation = scoped_items
.order(Gitlab::Database.nulls_last_order('position', 'DESC'))
+ .group(self.class.relative_positioning_parent_column)
.limit(1)
- .group(self.class.parent_column)
relation = yield relation if block_given?
relation
- .pluck(self.class.parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
+ .pluck(self.class.relative_positioning_parent_column, Arel.sql("#{calculation}(relative_position) AS position"))
.first&.
last
end
+
+ def scoped_items
+ self.class.relative_positioning_query_base(self)
+ end
end
diff --git a/app/models/concerns/routable.rb b/app/models/concerns/routable.rb
index 9becab632f3..116e8967651 100644
--- a/app/models/concerns/routable.rb
+++ b/app/models/concerns/routable.rb
@@ -33,29 +33,12 @@ module Routable
#
# Returns a single object, or nil.
def find_by_full_path(path, follow_redirects: false)
- # On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
- # any literal matches come first, for this we have to use "BINARY".
- # Without this there's still no guarantee in what order MySQL will return
- # rows.
- #
- # Why do we do this?
- #
- # Even though we have Rails validation on Route for unique paths
- # (case-insensitive), there are old projects in our DB (and possibly
- # clients' DBs) that have the same path with different cases.
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that
- # our unique index is case-sensitive in Postgres.
- binary = Gitlab::Database.mysql? ? 'BINARY' : ''
- order_sql = Arel.sql("(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
+ order_sql = Arel.sql("(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)")
found = where_full_path_in([path]).reorder(order_sql).take
return found if found
if follow_redirects
- if Gitlab::Database.postgresql?
- joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
- else
- joins(:redirect_routes).find_by(redirect_routes: { path: path })
- end
+ joins(:redirect_routes).find_by("LOWER(redirect_routes.path) = LOWER(?)", path)
end
end
@@ -67,27 +50,13 @@ module Routable
#
# Returns an ActiveRecord::Relation.
def where_full_path_in(paths)
- wheres = []
- cast_lower = Gitlab::Database.postgresql?
+ return none if paths.empty?
- paths.each do |path|
- path = connection.quote(path)
-
- where =
- if cast_lower
- "(LOWER(routes.path) = LOWER(#{path}))"
- else
- "(routes.path = #{path})"
- end
-
- wheres << where
+ wheres = paths.map do |path|
+ "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
end
- if wheres.empty?
- none
- else
- joins(:route).where(wheres.join(' OR '))
- end
+ joins(:route).where(wheres.join(' OR '))
end
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index b42adad94ba..8b536a123fc 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -15,7 +15,8 @@ module Taskable
INCOMPLETE_PATTERN = /(\[[\s]\])/.freeze
ITEM_PATTERN = %r{
^
- \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
+ (?:(?:>\s{0,4})*) # optional blockquote characters
+ \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list
\s+ # whitespace prefix has to be always presented for a list item
(\[\s\]|\[[xX]\]) # checkbox
(\s.+) # followed by whitespace and some text.
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index 1293df571a3..4099039dd96 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -3,10 +3,8 @@
module TokenAuthenticatable
extend ActiveSupport::Concern
- private
-
class_methods do
- private # rubocop:disable Lint/UselessAccessModifier
+ private
def add_authentication_token_field(token_field, options = {})
if token_authenticatable_fields.include?(token_field)
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index 570a735973f..869b3490f3f 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -73,15 +73,10 @@ module UpdateProjectStatistics
def schedule_namespace_aggregation_worker
run_after_commit do
- next unless schedule_aggregation_worker?
+ next if project.nil?
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
end
end
-
- def schedule_aggregation_worker?
- !project.nil? &&
- Feature.enabled?(:update_statistics_namespace, project.root_ancestor)
- end
end
end
diff --git a/app/models/container_repository.rb b/app/models/container_repository.rb
index 39e12ac2b06..2a5ae7930e6 100644
--- a/app/models/container_repository.rb
+++ b/app/models/container_repository.rb
@@ -70,10 +70,14 @@ class ContainerRepository < ApplicationRecord
digests = tags.map { |tag| tag.digest }.to_set
digests.all? do |digest|
- client.delete_repository_tag(self.path, digest)
+ delete_tag_by_digest(digest)
end
end
+ def delete_tag_by_digest(digest)
+ client.delete_repository_tag(self.path, digest)
+ end
+
def self.build_from_path(path)
self.new(project: path.repository_project,
name: path.repository_name)
@@ -86,4 +90,9 @@ class ContainerRepository < ApplicationRecord
def self.build_root_repository(project)
self.new(project: project, name: '')
end
+
+ def self.find_by_path!(path)
+ self.find_by!(project: path.repository_project,
+ name: path.repository_name)
+ end
end
diff --git a/app/models/cycle_analytics/base.rb b/app/models/cycle_analytics/base.rb
deleted file mode 100644
index d7b28cd1b67..00000000000
--- a/app/models/cycle_analytics/base.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-# frozen_string_literal: true
-
-module CycleAnalytics
- class Base
- STAGES = %i[issue plan code test review staging production].freeze
-
- def all_medians_by_stage
- STAGES.each_with_object({}) do |stage_name, medians_per_stage|
- medians_per_stage[stage_name] = self[stage_name].median
- end
- end
-
- def stats
- @stats ||= STAGES.map do |stage_name|
- self[stage_name].as_json
- end
- end
-
- def no_stats?
- stats.all? { |hash| hash[:value].nil? }
- end
-
- def [](stage_name)
- Gitlab::CycleAnalytics::Stage[stage_name].new(project: @project, options: @options)
- end
- end
-end
diff --git a/app/models/cycle_analytics/group_level.rb b/app/models/cycle_analytics/group_level.rb
new file mode 100644
index 00000000000..a41e1375484
--- /dev/null
+++ b/app/models/cycle_analytics/group_level.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ class GroupLevel
+ include LevelBase
+ attr_reader :options, :group
+
+ def initialize(group:, options:)
+ @group = group
+ @options = options.merge(group: group)
+ end
+
+ def summary
+ @summary ||= ::Gitlab::CycleAnalytics::GroupStageSummary.new(group, options: options).data
+ end
+
+ def permissions(*)
+ STAGES.each_with_object({}) do |stage, obj|
+ obj[stage] = true
+ end
+ end
+
+ def stats
+ @stats ||= STAGES.map do |stage_name|
+ self[stage_name].as_json(serializer: GroupAnalyticsStageSerializer)
+ end
+ end
+ end
+end
diff --git a/app/models/cycle_analytics/level_base.rb b/app/models/cycle_analytics/level_base.rb
new file mode 100644
index 00000000000..543349ebf8f
--- /dev/null
+++ b/app/models/cycle_analytics/level_base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module CycleAnalytics
+ module LevelBase
+ STAGES = %i[issue plan code test review staging production].freeze
+
+ def all_medians_by_stage
+ STAGES.each_with_object({}) do |stage_name, medians_per_stage|
+ medians_per_stage[stage_name] = self[stage_name].project_median
+ end
+ end
+
+ def stats
+ @stats ||= STAGES.map do |stage_name|
+ self[stage_name].as_json
+ end
+ end
+
+ def no_stats?
+ stats.all? { |hash| hash[:value].nil? }
+ end
+
+ def [](stage_name)
+ Gitlab::CycleAnalytics::Stage[stage_name].new(options: options)
+ end
+ end
+end
diff --git a/app/models/cycle_analytics/project_level.rb b/app/models/cycle_analytics/project_level.rb
index 22631cc7d41..4aa426c58a1 100644
--- a/app/models/cycle_analytics/project_level.rb
+++ b/app/models/cycle_analytics/project_level.rb
@@ -1,12 +1,13 @@
# frozen_string_literal: true
module CycleAnalytics
- class ProjectLevel < Base
+ class ProjectLevel
+ include LevelBase
attr_reader :project, :options
def initialize(project, options:)
@project = project
- @options = options
+ @options = options.merge(project: project)
end
def summary
diff --git a/app/models/dashboard_group_milestone.rb b/app/models/dashboard_group_milestone.rb
index 74aa04ab7d0..ec52f1ed370 100644
--- a/app/models/dashboard_group_milestone.rb
+++ b/app/models/dashboard_group_milestone.rb
@@ -15,8 +15,7 @@ class DashboardGroupMilestone < GlobalMilestone
milestones = Milestone.of_groups(groups.select(:id))
.reorder_by_due_date_asc
.order_by_name_asc
- .active
milestones = milestones.search_title(params[:search_title]) if params[:search_title].present?
- milestones.map { |m| new(m) }
+ Milestone.filter_by_state(milestones, params[:state]).map { |m| new(m) }
end
end
diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb
index db501b4b506..0bd90bd28e3 100644
--- a/app/models/deploy_key.rb
+++ b/app/models/deploy_key.rb
@@ -2,12 +2,14 @@
class DeployKey < Key
include IgnorableColumn
+ include FromUnion
has_many :deploy_keys_projects, inverse_of: :deploy_key, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :projects, through: :deploy_keys_projects
scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) }
scope :are_public, -> { where(public: true) }
+ scope :with_projects, -> { includes(deploy_keys_projects: { project: [:route, :namespace] }) }
ignore_column :can_push
@@ -22,7 +24,7 @@ class DeployKey < Key
end
def almost_orphaned?
- self.deploy_keys_projects.length == 1
+ self.deploy_keys_projects.count == 1
end
def destroyed_when_orphaned?
@@ -46,6 +48,6 @@ class DeployKey < Key
end
def projects_with_write_access
- Project.preload(:route).where(id: deploy_keys_projects.with_write_access.select(:project_id))
+ Project.with_route.where(id: deploy_keys_projects.with_write_access.select(:project_id))
end
end
diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb
index 15906ed8e06..40c66d5bc4c 100644
--- a/app/models/deploy_keys_project.rb
+++ b/app/models/deploy_keys_project.rb
@@ -1,9 +1,8 @@
# frozen_string_literal: true
class DeployKeysProject < ApplicationRecord
- belongs_to :project
+ belongs_to :project, inverse_of: :deploy_keys_projects
belongs_to :deploy_key, inverse_of: :deploy_keys_projects
-
scope :without_project_deleted, -> { joins(:project).where(projects: { pending_delete: false }) }
scope :in_project, ->(project) { where(project: project) }
scope :with_write_access, -> { where(can_push: true) }
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index b69cda4f2f9..68586e7a1fd 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -128,17 +128,8 @@ class Deployment < ApplicationRecord
merge_requests = merge_requests.where("merge_request_metrics.merged_at >= ?", previous_deployment.finished_at)
end
- # Need to use `map` instead of `select` because MySQL doesn't allow `SELECT`ing from the same table
- # that we're updating.
- merge_request_ids =
- if Gitlab::Database.postgresql?
- merge_requests.select(:id)
- elsif Gitlab::Database.mysql?
- merge_requests.map(&:id)
- end
-
MergeRequest::Metrics
- .where(merge_request_id: merge_request_ids, first_deployed_to_production_at: nil)
+ .where(merge_request_id: merge_requests.select(:id), first_deployed_to_production_at: nil)
.update_all(first_deployed_to_production_at: finished_at)
end
diff --git a/app/models/deployment_metrics.rb b/app/models/deployment_metrics.rb
index cfe762ca25e..2056c8bc59c 100644
--- a/app/models/deployment_metrics.rb
+++ b/app/models/deployment_metrics.rb
@@ -44,18 +44,7 @@ class DeploymentMetrics
end
end
- # TODO remove fallback case to deployment_platform_cluster.
- # Otherwise we will continue to pay the performance penalty described in
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/63475
- #
- # Removal issue: https://gitlab.com/gitlab-org/gitlab-ce/issues/64105
def cluster_prometheus
- cluster_with_fallback = cluster || deployment_platform_cluster
-
- cluster_with_fallback.application_prometheus if cluster_with_fallback&.application_prometheus_available?
- end
-
- def deployment_platform_cluster
- deployment.environment.deployment_platform&.cluster
+ cluster.application_prometheus if cluster&.application_prometheus_available?
end
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index f75c32633b1..861185dc222 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -20,7 +20,6 @@ class DiffNote < Note
validates :noteable_type, inclusion: { in: -> (_note) { noteable_types } }
validate :positions_complete
validate :verify_supported
- validate :diff_refs_match_commit, if: :for_commit?
before_validation :set_line_code, if: :on_text?
after_save :keep_around_commits
@@ -154,12 +153,6 @@ class DiffNote < Note
errors.add(:position, "is invalid")
end
- def diff_refs_match_commit
- return if self.original_position.diff_refs == self.commit.diff_refs
-
- errors.add(:commit_id, 'does not match the diff refs')
- end
-
def keep_around_commits
shas = [
self.original_position.base_sha,
diff --git a/app/models/environment.rb b/app/models/environment.rb
index b8ee54c1696..1b53c4b45f9 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -4,11 +4,6 @@ class Environment < ApplicationRecord
include Gitlab::Utils::StrongMemoize
include ReactiveCaching
- # Used to generate random suffixes for the slug
- LETTERS = ('a'..'z').freeze
- NUMBERS = ('0'..'9').freeze
- SUFFIX_CHARS = LETTERS.to_a + NUMBERS.to_a
-
belongs_to :project, required: true
has_many :deployments, -> { success }, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
@@ -53,6 +48,7 @@ class Environment < ApplicationRecord
end
scope :in_review_folder, -> { where(environment_type: "review") }
scope :for_name, -> (name) { where(name: name) }
+ scope :preload_cluster, -> { preload(last_deployment: :cluster) }
##
# Search environments which have names like the given query.
@@ -175,7 +171,7 @@ class Environment < ApplicationRecord
def deployment_namespace
strong_memoize(:kubernetes_namespace) do
- deployment_platform&.kubernetes_namespace_for(project)
+ deployment_platform.cluster.kubernetes_namespace_for(self) if deployment_platform
end
end
@@ -203,47 +199,13 @@ class Environment < ApplicationRecord
super.presence || generate_slug
end
- # An environment name is not necessarily suitable for use in URLs, DNS
- # or other third-party contexts, so provide a slugified version. A slug has
- # the following properties:
- # * contains only lowercase letters (a-z), numbers (0-9), and '-'
- # * begins with a letter
- # * has a maximum length of 24 bytes (OpenShift limitation)
- # * cannot end with `-`
- def generate_slug
- # Lowercase letters and numbers only
- slugified = +name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
-
- # Must start with a letter
- slugified = 'env-' + slugified unless LETTERS.cover?(slugified[0])
-
- # Repeated dashes are invalid (OpenShift limitation)
- slugified.gsub!(/\-+/, '-')
-
- # Maximum length: 24 characters (OpenShift limitation)
- slugified = slugified[0..23]
-
- # Cannot end with a dash (Kubernetes label limitation)
- slugified.chop! if slugified.end_with?('-')
-
- # Add a random suffix, shortening the current string if necessary, if it
- # has been slugified. This ensures uniqueness.
- if slugified != name
- slugified = slugified[0..16]
- slugified << '-' unless slugified.end_with?('-')
- slugified << random_suffix
- end
-
- self.slug = slugified
- end
-
def external_url_for(path, commit_sha)
return unless self.external_url
public_path = project.public_path_for_source_path(path, commit_sha)
return unless public_path
- [external_url, public_path].join('/')
+ [external_url.delete_suffix('/'), public_path.delete_prefix('/')].join('/')
end
def expire_etag_cache
@@ -272,13 +234,15 @@ class Environment < ApplicationRecord
end
end
+ def knative_services_finder
+ if last_deployment&.cluster
+ Clusters::KnativeServicesFinder.new(last_deployment.cluster, self)
+ end
+ end
+
private
- # Slugifying a name may remove the uniqueness guarantee afforded by it being
- # based on name (which must be unique). To compensate, we add a random
- # 6-byte suffix in those circumstances. This is not *guaranteed* uniqueness,
- # but the chance of collisions is vanishingly small
- def random_suffix
- (0..5).map { SUFFIX_CHARS.sample }.join
+ def generate_slug
+ self.slug = Gitlab::Slug::Environment.new(name).generate
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 9520db1bc0a..6c868b1d1f0 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -10,7 +10,6 @@ class Group < Namespace
include Referable
include SelectForProjectAuthorization
include LoadedInGroupList
- include Descendant
include GroupDescendant
include TokenAuthenticatable
include WithUploads
@@ -45,6 +44,8 @@ class Group < Namespace
has_many :cluster_groups, class_name: 'Clusters::Group'
has_many :clusters, through: :cluster_groups, class_name: 'Clusters::Cluster'
+ has_many :container_repositories, through: :projects
+
has_many :todos
accepts_nested_attributes_for :variables, allow_destroy: true
@@ -144,6 +145,12 @@ class Group < Namespace
notification_settings(hierarchy_order: hierarchy_order).where(user: user)
end
+ def notification_email_for(user)
+ # Finds the closest notification_setting with a `notification_email`
+ notification_settings = notification_settings_for(user, hierarchy_order: :asc)
+ notification_settings.find { |n| n.notification_email.present? }&.notification_email
+ end
+
def to_reference(_from = nil, full: nil)
"#{self.class.reference_prefix}#{full_path}"
end
@@ -382,7 +389,7 @@ class Group < Namespace
variables = Ci::GroupVariable.where(group: list_of_ids)
variables = variables.unprotected unless project.protected_for?(ref)
variables = variables.group_by(&:group_id)
- list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
+ list_of_ids.reverse.flat_map { |group| variables[group.id] }.compact
end
def group_member(user)
@@ -416,6 +423,10 @@ class Group < Namespace
super || ::Gitlab::CurrentSettings.default_project_creation
end
+ def subgroup_creation_level
+ super || ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb
index 90b4588a325..3d54d17e787 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -14,8 +14,10 @@ class SystemHook < WebHook
default_value_for :repository_update_events, true
default_value_for :merge_requests_events, false
+ validates :url, system_hook_url: true
+
# Allow urls pointing localhost and the local network
def allow_local_requests?
- true
+ Gitlab::CurrentSettings.allow_local_requests_from_system_hooks?
end
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index daf7ff4b771..16fc7fdbd48 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -15,8 +15,8 @@ class WebHook < ApplicationRecord
has_many :web_hook_logs, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
- validates :url, presence: true, public_url: { allow_localhost: lambda(&:allow_local_requests?),
- allow_local_network: lambda(&:allow_local_requests?) }
+ validates :url, presence: true
+ validates :url, public_url: true, unless: ->(hook) { hook.is_a?(SystemHook) }
validates :token, format: { without: /\n/ }
validates :push_events_branch_filter, branch_filter: true
@@ -35,6 +35,6 @@ class WebHook < ApplicationRecord
# Allow urls pointing localhost and the local network
def allow_local_requests?
- false
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 12d30389910..c5a18f0af0f 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -43,6 +43,7 @@ class Issue < ApplicationRecord
validates :project, presence: true
alias_attribute :parent_ids, :project_id
+ alias_method :issuing_parent, :project
scope :in_projects, ->(project_ids) { where(project_id: project_ids) }
@@ -63,7 +64,7 @@ class Issue < ApplicationRecord
scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) }
- after_save :expire_etag_cache
+ after_commit :expire_etag_cache
after_save :ensure_metrics, unless: :imported?
attr_spammable :title, spam_title: true
@@ -91,11 +92,11 @@ class Issue < ApplicationRecord
end
end
- class << self
- alias_method :in_parents, :in_projects
+ def self.relative_positioning_query_base(issue)
+ in_projects(issue.parent_ids)
end
- def self.parent_column
+ def self.relative_positioning_parent_column
:project_id
end
@@ -131,7 +132,7 @@ class Issue < ApplicationRecord
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
+ when 'relative_position' then order_relative_position_asc.with_order_id_desc
else
super
end
diff --git a/app/models/label.rb b/app/models/label.rb
index b83e0862bab..d9455b36242 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -33,7 +33,7 @@ class Label < ApplicationRecord
default_scope { order(title: :asc) }
- scope :templates, -> { where(template: true) }
+ scope :templates, -> { where(template: true, type: [Label.name, nil]) }
scope :with_title, ->(title) { where(title: title) }
scope :with_lists_and_board, -> { joins(lists: :board).merge(List.movable) }
scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) }
@@ -137,6 +137,12 @@ class Label < ApplicationRecord
where(id: ids)
end
+ def self.on_project_board?(project_id, label_id)
+ return false if label_id.blank?
+
+ on_project_boards(project_id).where(id: label_id).exists?
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
diff --git a/app/models/list.rb b/app/models/list.rb
index d28a9bda82d..ccadd39bda2 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -3,10 +3,11 @@
class List < ApplicationRecord
belongs_to :board
belongs_to :label
+ include Importable
enum list_type: { backlog: 0, label: 1, closed: 2, assignee: 3, milestone: 4 }
- validates :board, :list_type, presence: true
+ validates :board, :list_type, presence: true, unless: :importing?
validates :label, :position, presence: true, if: :label?
validates :label_id, uniqueness: { scope: :board_id }, if: :label?
validates :position, numericality: { only_integer: true, greater_than_or_equal_to: 0 }, if: :movable?
diff --git a/app/models/member.rb b/app/models/member.rb
index c7583434148..dbae1076670 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -107,6 +107,10 @@ class Member < ApplicationRecord
joins(:user).merge(User.search(query))
end
+ def search_invite_email(query)
+ invite.where(['invite_email ILIKE ?', "%#{query}%"])
+ end
+
def filter_by_2fa(value)
case value
when 'enabled'
diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb
index 4cba69069bb..3d6f397e599 100644
--- a/app/models/members/group_member.rb
+++ b/app/models/members/group_member.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class GroupMember < Member
+ include FromUnion
+
SOURCE_TYPE = 'Namespace'.freeze
belongs_to :group, foreign_key: 'source_id'
@@ -13,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/merge_request.rb b/app/models/merge_request.rb
index 68e6e48fb7d..bfd636fa62a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -73,6 +73,7 @@ class MergeRequest < ApplicationRecord
after_update :clear_memoized_shas
after_update :reload_diff_if_branch_changed
after_save :ensure_metrics
+ after_commit :expire_etag_cache
# When this attribute is true some MR validation is ignored
# It allows us to close or modify broken merge requests
@@ -192,6 +193,7 @@ class MergeRequest < ApplicationRecord
alias_attribute :project, :target_project
alias_attribute :project_id, :target_project_id
alias_attribute :auto_merge_enabled, :merge_when_pipeline_succeeds
+ alias_method :issuing_parent, :target_project
def self.reference_prefix
'!'
@@ -218,18 +220,7 @@ class MergeRequest < ApplicationRecord
end
def rebase_in_progress?
- (rebase_jid.present? && Gitlab::SidekiqStatus.running?(rebase_jid)) ||
- gitaly_rebase_in_progress?
- end
-
- # TODO: remove the Gitaly lookup after v12.1, when rebase_jid will be reliable
- def gitaly_rebase_in_progress?
- strong_memoize(:gitaly_rebase_in_progress) do
- # The source project can be deleted
- next false unless source_project
-
- source_project.repository.rebase_in_progress?(id)
- end
+ rebase_jid.present? && Gitlab::SidekiqStatus.running?(rebase_jid)
end
# Use this method whenever you need to make sure the head_pipeline is synced with the
@@ -388,6 +379,10 @@ class MergeRequest < ApplicationRecord
def merge_async(user_id, params)
jid = MergeWorker.perform_async(id, user_id, params.to_h)
update_column(:merge_jid, jid)
+
+ # merge_ongoing? depends on merge_jid
+ # expire etag cache since the attribute is changed without triggering callbacks
+ expire_etag_cache
end
# Set off a rebase asynchronously, atomically updating the `rebase_jid` of
@@ -408,6 +403,10 @@ class MergeRequest < ApplicationRecord
update_column(:rebase_jid, jid)
end
+
+ # rebase_in_progress? depends on rebase_jid
+ # expire etag cache since the attribute is changed without triggering callbacks
+ expire_etag_cache
end
def merge_participants
@@ -752,7 +751,7 @@ class MergeRequest < ApplicationRecord
end
def check_mergeability
- MergeRequests::MergeabilityCheckService.new(self).execute
+ MergeRequests::MergeabilityCheckService.new(self).execute(retry_lease: false)
end
# rubocop: enable CodeReuse/ServiceClass
@@ -1249,15 +1248,8 @@ class MergeRequest < ApplicationRecord
end
def all_commits
- # MySQL doesn't support LIMIT in a subquery.
- diffs_relation = if Gitlab::Database.postgresql?
- merge_request_diffs.recent
- else
- merge_request_diffs
- end
-
MergeRequestDiffCommit
- .where(merge_request_diff: diffs_relation)
+ .where(merge_request_diff: merge_request_diffs.recent)
.limit(10_000)
end
@@ -1435,4 +1427,11 @@ class MergeRequest < ApplicationRecord
variables.append(key: 'CI_MERGE_REQUEST_SOURCE_BRANCH_NAME', value: source_branch.to_s)
end
end
+
+ def expire_etag_cache
+ return unless project.namespace
+
+ key = Gitlab::Routing.url_helpers.cached_widget_project_json_merge_request_path(project, self, format: :json)
+ Gitlab::EtagCaching::Store.new.touch(key)
+ end
end
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index f45bd0e03de..2c9dbf2585c 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -196,6 +196,12 @@ class MergeRequestDiff < ApplicationRecord
real_size.presence || raw_diffs.size
end
+ def lines_count
+ strong_memoize(:lines_count) do
+ diffs.diff_files.sum(&:line_count)
+ end
+ end
+
def raw_diffs(options = {})
if options[:ignore_whitespace_change]
@diffs_no_whitespace ||= compare.diffs(options)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 37c129e843a..2ad2838111e 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -149,29 +149,10 @@ class Milestone < ApplicationRecord
end
def self.upcoming_ids(projects, groups)
- rel = unscoped
- .for_projects_and_groups(projects, groups)
- .active.where('milestones.due_date > CURRENT_DATE')
-
- if Gitlab::Database.postgresql?
- rel.order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
- else
- # We need to use MySQL's NULL-safe comparison operator `<=>` here
- # because one of `project_id` or `group_id` is always NULL
- join_clause = <<~HEREDOC
- LEFT OUTER JOIN milestones earlier_milestones
- ON milestones.project_id <=> earlier_milestones.project_id
- AND milestones.group_id <=> earlier_milestones.group_id
- AND milestones.due_date > earlier_milestones.due_date
- AND earlier_milestones.due_date > CURRENT_DATE
- AND earlier_milestones.state = 'active'
- HEREDOC
-
- rel
- .joins(join_clause)
- .where('earlier_milestones.id IS NULL')
- .select(:id)
- end
+ unscoped
+ .for_projects_and_groups(projects, groups)
+ .active.where('milestones.due_date > CURRENT_DATE')
+ .order(:project_id, :group_id, :due_date).select('DISTINCT ON (project_id, group_id) id')
end
def participants
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b8d7348268a..9f9c4288667 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -172,6 +172,13 @@ class Namespace < ApplicationRecord
end
end
+ # any ancestor can disable emails for all descendants
+ def emails_disabled?
+ strong_memoize(:emails_disabled) do
+ Feature.enabled?(:emails_disabled, self, default_enabled: true) && self_and_ancestors.where(emails_disabled: true).exists?
+ end
+ end
+
def lfs_enabled?
# User namespace will always default to the global setting
Gitlab.config.lfs.enabled
@@ -332,8 +339,6 @@ class Namespace < ApplicationRecord
end
def force_share_with_group_lock_on_descendants
- return unless Group.supports_nested_objects?
-
# We can't use `descendants.update_all` since Rails will throw away the WITH
# RECURSIVE statement. We also can't use WHERE EXISTS since we can't use
# different table aliases, hence we're just using WHERE IN. Since we have a
diff --git a/app/models/namespace/aggregation_schedule.rb b/app/models/namespace/aggregation_schedule.rb
index 0bef352cf24..61a7eb4b576 100644
--- a/app/models/namespace/aggregation_schedule.rb
+++ b/app/models/namespace/aggregation_schedule.rb
@@ -6,21 +6,13 @@ class Namespace::AggregationSchedule < ApplicationRecord
self.primary_key = :namespace_id
- DEFAULT_LEASE_TIMEOUT = 3.hours
+ DEFAULT_LEASE_TIMEOUT = 1.5.hours.to_i
REDIS_SHARED_KEY = 'gitlab:update_namespace_statistics_delay'.freeze
belongs_to :namespace
after_create :schedule_root_storage_statistics
- def self.delay_timeout
- redis_timeout = Gitlab::Redis::SharedState.with do |redis|
- redis.get(REDIS_SHARED_KEY)
- end
-
- redis_timeout.nil? ? DEFAULT_LEASE_TIMEOUT : redis_timeout.to_i
- end
-
def schedule_root_storage_statistics
run_after_commit_or_now do
try_obtain_lease do
@@ -28,7 +20,7 @@ class Namespace::AggregationSchedule < ApplicationRecord
.perform_async(namespace_id)
Namespaces::RootStatisticsWorker
- .perform_in(self.class.delay_timeout, namespace_id)
+ .perform_in(DEFAULT_LEASE_TIMEOUT, namespace_id)
end
end
end
@@ -37,7 +29,7 @@ class Namespace::AggregationSchedule < ApplicationRecord
# Used by ExclusiveLeaseGuard
def lease_timeout
- self.class.delay_timeout
+ DEFAULT_LEASE_TIMEOUT
end
# Used by ExclusiveLeaseGuard
diff --git a/app/models/note.rb b/app/models/note.rb
index 5c31cff9816..a12d1eb7243 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -27,6 +27,10 @@ class Note < ApplicationRecord
def values
constants.map {|const| self.const_get(const)}
end
+
+ def value?(val)
+ values.include?(val)
+ end
end
end
@@ -292,7 +296,7 @@ class Note < ApplicationRecord
end
def special_role=(role)
- raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.values.include?(role)
+ raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.value?(role)
@special_role = role
end
diff --git a/app/models/notification_recipient.rb b/app/models/notification_recipient.rb
index a7f73c0f29c..8e44e3d8e17 100644
--- a/app/models/notification_recipient.rb
+++ b/app/models/notification_recipient.rb
@@ -4,6 +4,7 @@ class NotificationRecipient
include Gitlab::Utils::StrongMemoize
attr_reader :user, :type, :reason
+
def initialize(user, type, **opts)
unless NotificationSetting.levels.key?(type) || type == :subscription
raise ArgumentError, "invalid type: #{type.inspect}"
@@ -30,6 +31,7 @@ class NotificationRecipient
def notifiable?
return false unless has_access?
+ return false if emails_disabled?
return false if own_activity?
# even users with :disabled notifications receive manual subscriptions
@@ -109,6 +111,12 @@ class NotificationRecipient
private
+ # They are disabled if the project or group has disallowed it.
+ # No need to check the group if there is already a project
+ def emails_disabled?
+ @project ? @project.emails_disabled? : @group&.emails_disabled?
+ end
+
def read_ability
return if @skip_read_ability
return @read_ability if instance_variable_defined?(:@read_ability)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index e6e491634ab..27c122d3559 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -22,7 +22,7 @@ class PagesDomain < ApplicationRecord
validate :validate_pages_domain
validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
- validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }
+ validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? }
attr_encrypted :key,
mode: :per_attribute_iv_and_salt,
diff --git a/app/models/personal_access_token.rb b/app/models/personal_access_token.rb
index f69f0e2dccb..7ae431eaad7 100644
--- a/app/models/personal_access_token.rb
+++ b/app/models/personal_access_token.rb
@@ -7,6 +7,7 @@ class PersonalAccessToken < ApplicationRecord
add_authentication_token_field :token, digest: true
REDIS_EXPIRY_TIME = 3.minutes
+ TOKEN_LENGTH = 20
serialize :scopes, Array # rubocop:disable Cop/ActiveRecordSerialize
diff --git a/app/models/project.rb b/app/models/project.rb
index 2906aca75fc..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?,
@@ -162,7 +164,6 @@ class Project < ApplicationRecord
has_one :bugzilla_service
has_one :gitlab_issue_tracker_service, inverse_of: :project
has_one :external_wiki_service
- has_one :kubernetes_service, inverse_of: :project
has_one :prometheus_service, inverse_of: :project
has_one :mock_ci_service
has_one :mock_deployment_service
@@ -214,7 +215,7 @@ class Project < ApplicationRecord
as: :source, class_name: 'ProjectMember', dependent: :delete_all # rubocop:disable Cop/ActiveRecordDependent
has_many :members_and_requesters, as: :source, class_name: 'ProjectMember'
- has_many :deploy_keys_projects
+ has_many :deploy_keys_projects, inverse_of: :project
has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects
has_many :starrers, through: :users_star_projects, source: :user
@@ -277,13 +278,14 @@ class Project < ApplicationRecord
has_many :project_deploy_tokens
has_many :deploy_tokens, through: :project_deploy_tokens
- has_one :auto_devops, class_name: 'ProjectAutoDevops'
+ has_one :auto_devops, class_name: 'ProjectAutoDevops', inverse_of: :project, autosave: true
has_many :custom_attributes, class_name: 'ProjectCustomAttribute'
has_many :project_badges, class_name: 'ProjectBadge'
has_one :ci_cd_settings, class_name: 'ProjectCiCdSetting', inverse_of: :project, autosave: true, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :remote_mirrors, inverse_of: :project
+ has_many :cycle_analytics_stages, class_name: 'Analytics::CycleAnalytics::ProjectStage'
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature, update_only: true
@@ -357,6 +359,7 @@ class Project < ApplicationRecord
scope :sorted_by_activity, -> { reorder(Arel.sql("GREATEST(COALESCE(last_activity_at, '1970-01-01'), COALESCE(last_repository_updated_at, '1970-01-01')) DESC")) }
scope :sorted_by_stars_desc, -> { reorder(star_count: :desc) }
scope :sorted_by_stars_asc, -> { reorder(star_count: :asc) }
+ scope :sorted_by_name_asc_limited, ->(limit) { reorder(name: :asc).limit(limit) }
scope :in_namespace, ->(namespace_ids) { where(namespace_id: namespace_ids) }
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
@@ -414,12 +417,6 @@ class Project < ApplicationRecord
.where(project_ci_cd_settings: { group_runners_enabled: true })
end
- scope :missing_kubernetes_namespace, -> (kubernetes_namespaces) do
- subquery = kubernetes_namespaces.select('1').where('clusters_kubernetes_namespaces.project_id = projects.id')
-
- where('NOT EXISTS (?)', subquery)
- end
-
enum auto_cancel_pending_pipelines: { disabled: 0, enabled: 1 }
chronic_duration_attr :build_timeout_human_readable, :build_timeout,
@@ -441,22 +438,6 @@ class Project < ApplicationRecord
without_deleted.find_by_id(id)
end
- # Paginates a collection using a `WHERE id < ?` condition.
- #
- # before - A project ID to use for filtering out projects with an equal or
- # greater ID. If no ID is given, all projects are included.
- #
- # limit - The maximum number of rows to include.
- def self.paginate_in_descending_order_using_id(
- before: nil,
- limit: Kaminari.config.default_per_page
- )
- relation = order_id_desc.limit(limit)
- relation = relation.where('projects.id < ?', before) if before
-
- relation
- end
-
def self.eager_load_namespace_and_owner
includes(namespace: :owner)
end
@@ -653,6 +634,13 @@ class Project < ApplicationRecord
alias_method :ancestors, :ancestors_upto
+ def emails_disabled?
+ strong_memoize(:emails_disabled) do
+ # disabling in the namespace overrides the project setting
+ Feature.enabled?(:emails_disabled, self, default_enabled: true) && (super || namespace.emails_disabled?)
+ end
+ end
+
def lfs_enabled?
return namespace.lfs_enabled? if self[:lfs_enabled].nil?
@@ -734,16 +722,27 @@ class Project < ApplicationRecord
repository.commits_by(oids: oids)
end
- # ref can't be HEAD, can only be branch/tag name or SHA
- def latest_successful_build_for(job_name, ref = default_branch)
- latest_pipeline = ci_pipelines.latest_successful_for(ref)
+ # ref can't be HEAD, can only be branch/tag name
+ def latest_successful_build_for_ref(job_name, ref = default_branch)
+ return unless ref
+
+ latest_pipeline = ci_pipelines.latest_successful_for_ref(ref)
+ return unless latest_pipeline
+
+ latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
+ end
+
+ def latest_successful_build_for_sha(job_name, sha)
+ return unless sha
+
+ latest_pipeline = ci_pipelines.latest_successful_for_sha(sha)
return unless latest_pipeline
latest_pipeline.builds.latest.with_artifacts_archive.find_by(name: job_name)
end
- def latest_successful_build_for!(job_name, ref = default_branch)
- latest_successful_build_for(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
+ def latest_successful_build_for_ref!(job_name, ref = default_branch)
+ latest_successful_build_for_ref(job_name, ref) || raise(ActiveRecord::RecordNotFound.new("Couldn't find job #{job_name}"))
end
def merge_base_commit(first_commit_id, second_commit_id)
@@ -1241,6 +1240,14 @@ class Project < ApplicationRecord
end
end
+ def has_active_hooks?(hooks_scope = :push_hooks)
+ hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any?
+ end
+
+ def has_active_services?(hooks_scope = :push_hooks)
+ services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
+ end
+
def valid_repo?
repository.exists?
rescue
@@ -1496,12 +1503,19 @@ class Project < ApplicationRecord
!namespace.share_with_group_lock
end
- def pipeline_for(ref, sha = nil)
+ def pipeline_for(ref, sha = nil, id = nil)
sha ||= commit(ref).try(:sha)
-
return unless sha
- ci_pipelines.order(id: :desc).find_by(sha: sha, ref: ref)
+ if id.present?
+ pipelines_for(ref, sha).find_by(id: id)
+ else
+ pipelines_for(ref, sha).take
+ end
+ end
+
+ def pipelines_for(ref, sha)
+ ci_pipelines.order(id: :desc).where(sha: sha, ref: ref)
end
def latest_successful_pipeline_for_default_branch
@@ -1510,12 +1524,12 @@ class Project < ApplicationRecord
end
@latest_successful_pipeline_for_default_branch =
- ci_pipelines.latest_successful_for(default_branch)
+ ci_pipelines.latest_successful_for_ref(default_branch)
end
def latest_successful_pipeline_for(ref = nil)
if ref && ref != default_branch
- ci_pipelines.latest_successful_for(ref)
+ ci_pipelines.latest_successful_for_ref(ref)
else
latest_successful_pipeline_for_default_branch
end
@@ -1831,11 +1845,16 @@ class Project < ApplicationRecord
end
def ci_variables_for(ref:, environment: nil)
- # EE would use the environment
- if protected_for?(ref)
- variables
+ result = if protected_for?(ref)
+ variables
+ else
+ variables.unprotected
+ end
+
+ if environment
+ result.on_environment(environment)
else
- variables.unprotected
+ result.where(environment_scope: '*')
end
end
@@ -1858,8 +1877,12 @@ class Project < ApplicationRecord
end
end
- def deployment_variables(environment: nil)
- deployment_platform(environment: environment)&.predefined_variables(project: self) || []
+ def deployment_variables(environment:)
+ platform = deployment_platform(environment: environment)
+
+ return [] unless platform.present?
+
+ platform.predefined_variables(project: self, environment_name: environment)
end
def auto_devops_variables
@@ -1869,16 +1892,24 @@ class Project < ApplicationRecord
end
def append_or_update_attribute(name, value)
- old_values = public_send(name.to_s) # rubocop:disable GitlabSecurity/PublicSend
+ if Project.reflect_on_association(name).try(:macro) == :has_many
+ # if this is 1-to-N relation, update the parent object
+ value.each do |item|
+ item.update!(
+ Project.reflect_on_association(name).foreign_key => id)
+ end
+
+ # force to drop relation cache
+ public_send(name).reset # rubocop:disable GitlabSecurity/PublicSend
- if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any?
- update_attribute(name, old_values + value)
+ # succeeded
+ true
else
+ # if this is another relation or attribute, update just object
update_attribute(name, value)
end
-
- rescue ActiveRecord::RecordNotSaved => e
- handle_update_attribute_error(e, value)
+ rescue ActiveRecord::RecordInvalid => e
+ raise e, "Failed to set #{name}: #{e.message}"
end
# Tries to set repository as read_only, checking for existing Git transfers in progress beforehand
@@ -2267,18 +2298,6 @@ class Project < ApplicationRecord
ContainerRepository.build_root_repository(self).has_tags?
end
- def handle_update_attribute_error(ex, value)
- if ex.message.start_with?('Failed to replace')
- if value.respond_to?(:each)
- invalid = value.detect(&:invalid?)
-
- raise ex, ([ex.message] + invalid.errors.full_messages).join(' ') if invalid
- end
- end
-
- raise ex
- end
-
def fetch_branch_allows_collaboration(user, branch_name = nil)
return false unless user
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 67c12363a3c..e11d0c48b4b 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -1,11 +1,7 @@
# frozen_string_literal: true
class ProjectAutoDevops < ApplicationRecord
- include IgnorableColumn
-
- ignore_column :domain
-
- belongs_to :project
+ belongs_to :project, inverse_of: :auto_devops
enum deploy_strategy: {
continuous: 0,
diff --git a/app/models/project_services/chat_message/pipeline_message.rb b/app/models/project_services/chat_message/pipeline_message.rb
index 62aec4351db..a3793d9937b 100644
--- a/app/models/project_services/chat_message/pipeline_message.rb
+++ b/app/models/project_services/chat_message/pipeline_message.rb
@@ -1,24 +1,47 @@
# frozen_string_literal: true
+require 'slack-notifier'
module ChatMessage
class PipelineMessage < BaseMessage
+ MAX_VISIBLE_JOBS = 10
+
+ attr_reader :user
attr_reader :ref_type
attr_reader :ref
attr_reader :status
+ attr_reader :detailed_status
attr_reader :duration
+ attr_reader :finished_at
attr_reader :pipeline_id
+ attr_reader :failed_stages
+ attr_reader :failed_jobs
+
+ attr_reader :project
+ attr_reader :commit
+ attr_reader :committer
+ attr_reader :pipeline
def initialize(data)
super
+ @user = data[:user]
@user_name = data.dig(:user, :username) || 'API'
pipeline_attributes = data[:object_attributes]
@ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
@ref = pipeline_attributes[:ref]
@status = pipeline_attributes[:status]
+ @detailed_status = pipeline_attributes[:detailed_status]
@duration = pipeline_attributes[:duration].to_i
+ @finished_at = pipeline_attributes[:finished_at] ? Time.parse(pipeline_attributes[:finished_at]).to_i : nil
@pipeline_id = pipeline_attributes[:id]
+ @failed_jobs = Array(data[:builds]).select { |b| b[:status] == 'failed' }.reverse # Show failed jobs from oldest to newest
+ @failed_stages = @failed_jobs.map { |j| j[:stage] }.uniq
+
+ @project = Project.find(data[:project][:id])
+ @commit = project.commit_by(oid: data[:commit][:id])
+ @committer = commit.committer
+ @pipeline = Ci::Pipeline.find(pipeline_id)
end
def pretext
@@ -28,38 +51,145 @@ module ChatMessage
def attachments
return message if markdown
- [{ text: format(message), color: attachment_color }]
+ return [{ text: format(message), color: attachment_color }] unless fancy_notifications?
+
+ [{
+ fallback: format(message),
+ color: attachment_color,
+ author_name: user_combined_name,
+ author_icon: user_avatar,
+ author_link: author_url,
+ title: s_("ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}") %
+ {
+ pipeline_id: pipeline_id,
+ humanized_status: humanized_status,
+ duration: pretty_duration(duration)
+ },
+ title_link: pipeline_url,
+ fields: attachments_fields,
+ footer: project.name,
+ footer_icon: project.avatar_url(only_path: false),
+ ts: finished_at
+ }]
end
def activity
{
- title: "Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status}",
- subtitle: "in #{project_link}",
- text: "in #{pretty_duration(duration)}",
+ title: s_("ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}") %
+ {
+ pipeline_link: pipeline_link,
+ ref_type: ref_type,
+ branch_link: branch_link,
+ user_combined_name: user_combined_name,
+ humanized_status: humanized_status
+ },
+ subtitle: s_("ChatMessage|in %{project_link}") % { project_link: project_link },
+ text: s_("ChatMessage|in %{duration}") % { duration: pretty_duration(duration) },
image: user_avatar || ''
}
end
private
+ def fancy_notifications?
+ Feature.enabled?(:fancy_pipeline_slack_notifications, default_enabled: true)
+ end
+
+ def failed_stages_field
+ {
+ title: s_("ChatMessage|Failed stage").pluralize(failed_stages.length),
+ value: Slack::Notifier::LinkFormatter.format(failed_stages_links),
+ short: true
+ }
+ end
+
+ def failed_jobs_field
+ {
+ title: s_("ChatMessage|Failed job").pluralize(failed_jobs.length),
+ value: Slack::Notifier::LinkFormatter.format(failed_jobs_links),
+ short: true
+ }
+ end
+
+ def yaml_error_field
+ {
+ title: s_("ChatMessage|Invalid CI config YAML file"),
+ value: pipeline.yaml_errors,
+ short: false
+ }
+ end
+
+ def attachments_fields
+ fields = [
+ {
+ title: ref_type == "tag" ? s_("ChatMessage|Tag") : s_("ChatMessage|Branch"),
+ value: Slack::Notifier::LinkFormatter.format(ref_name_link),
+ short: true
+ },
+ {
+ title: s_("ChatMessage|Commit"),
+ value: Slack::Notifier::LinkFormatter.format(commit_link),
+ short: true
+ }
+ ]
+
+ fields << failed_stages_field if failed_stages.any?
+ fields << failed_jobs_field if failed_jobs.any?
+ fields << yaml_error_field if pipeline.has_yaml_errors?
+
+ fields
+ end
+
def message
- "#{project_link}: Pipeline #{pipeline_link} of #{ref_type} #{branch_link} by #{user_combined_name} #{humanized_status} in #{pretty_duration(duration)}"
+ s_("ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}") %
+ {
+ project_link: project_link,
+ pipeline_link: pipeline_link,
+ ref_type: ref_type,
+ branch_link: branch_link,
+ user_combined_name: user_combined_name,
+ humanized_status: humanized_status,
+ duration: pretty_duration(duration)
+ }
end
def humanized_status
- case status
- when 'success'
- 'passed'
+ if fancy_notifications?
+ case status
+ when 'success'
+ detailed_status == "passed with warnings" ? s_("ChatMessage|has passed with warnings") : s_("ChatMessage|has passed")
+ when 'failed'
+ s_("ChatMessage|has failed")
+ else
+ status
+ end
else
- status
+ case status
+ when 'success'
+ s_("ChatMessage|passed")
+ when 'failed'
+ s_("ChatMessage|failed")
+ else
+ status
+ end
end
end
def attachment_color
- if status == 'success'
- 'good'
+ if fancy_notifications?
+ case status
+ when 'success'
+ detailed_status == 'passed with warnings' ? 'warning' : 'good'
+ else
+ 'danger'
+ end
else
- 'danger'
+ case status
+ when 'success'
+ 'good'
+ else
+ 'danger'
+ end
end
end
@@ -71,16 +201,83 @@ module ChatMessage
"[#{ref}](#{branch_url})"
end
+ def project_url
+ project.web_url
+ end
+
def project_link
- "[#{project_name}](#{project_url})"
+ "[#{project.name}](#{project_url})"
+ end
+
+ def pipeline_failed_jobs_url
+ "#{project_url}/pipelines/#{pipeline_id}/failures"
end
def pipeline_url
- "#{project_url}/pipelines/#{pipeline_id}"
+ if fancy_notifications? && failed_jobs.any?
+ pipeline_failed_jobs_url
+ else
+ "#{project_url}/pipelines/#{pipeline_id}"
+ end
end
def pipeline_link
"[##{pipeline_id}](#{pipeline_url})"
end
+
+ def job_url(job)
+ "#{project_url}/-/jobs/#{job[:id]}"
+ end
+
+ def job_link(job)
+ "[#{job[:name]}](#{job_url(job)})"
+ end
+
+ def failed_jobs_links
+ failed = failed_jobs.slice(0, MAX_VISIBLE_JOBS)
+ truncated = failed_jobs.slice(MAX_VISIBLE_JOBS, failed_jobs.size)
+
+ failed_links = failed.map { |job| job_link(job) }
+
+ unless truncated.blank?
+ failed_links << s_("ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})") % {
+ count: truncated.size,
+ pipeline_failed_jobs_url: pipeline_failed_jobs_url
+ }
+ end
+
+ failed_links.join(I18n.translate(:'support.array.words_connector'))
+ end
+
+ def stage_link(stage)
+ # All stages link to the pipeline page
+ "[#{stage}](#{pipeline_url})"
+ end
+
+ def failed_stages_links
+ failed_stages.map { |s| stage_link(s) }.join(I18n.translate(:'support.array.words_connector'))
+ end
+
+ def commit_url
+ Gitlab::UrlBuilder.build(commit)
+ end
+
+ def commit_link
+ "[#{commit.title}](#{commit_url})"
+ end
+
+ def commits_page_url
+ "#{project_url}/commits/#{ref}"
+ end
+
+ def ref_name_link
+ "[#{ref}](#{commits_page_url})"
+ end
+
+ def author_url
+ return unless user && committer
+
+ Gitlab::UrlBuilder.build(committer)
+ end
end
end
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 45de64a9990..8ca40138a8f 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -24,6 +24,7 @@ class EmailsOnPushService < Service
def execute(push_data)
return unless supported_events.include?(push_data[:object_kind])
+ return if project.emails_disabled?
EmailsOnPushWorker.perform_async(
project_id,
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index e571700fd02..d08fcd8954d 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -31,7 +31,7 @@ class JiraService < IssueTrackerService
# {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1
def self.reference_pattern(only_long: true)
- @reference_pattern ||= /(?<issue>\b([A-Z][A-Z0-9_]+-)\d+)/
+ @reference_pattern ||= /(?<issue>\b#{Gitlab::Regex.jira_issue_key_regex})/
end
def initialize_properties
@@ -54,7 +54,7 @@ class JiraService < IssueTrackerService
username: self.username,
password: self.password,
site: URI.join(url, '/').to_s, # Intended to find the root
- context_path: url.path.chomp('/'),
+ context_path: url.path,
auth_type: :basic,
read_timeout: 120,
use_cookies: true,
@@ -103,6 +103,12 @@ class JiraService < IssueTrackerService
"#{url}/secure/CreateIssue.jspa"
end
+ alias_method :original_url, :url
+
+ def url
+ original_url&.chomp('/')
+ end
+
def execute(push)
# This method is a no-op, because currently JiraService does not
# support any events.
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
deleted file mode 100644
index 9f5c226f4c9..00000000000
--- a/app/models/project_services/kubernetes_service.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-# frozen_string_literal: true
-
-class KubernetesService < Service
- default_value_for :category, 'deployment'
-
- # Namespace defaults to the project path, but can be overridden in case that
- # is an invalid or inappropriate name
- prop_accessor :namespace
-
- # Access to kubernetes is directly through the API
- prop_accessor :api_url
-
- # Bearer authentication
- # TODO: user/password auth, client certificates
- prop_accessor :token
-
- # Provide a custom CA bundle for self-signed deployments
- prop_accessor :ca_pem
-
- with_options presence: true, if: :activated? do
- validates :api_url, public_url: true
- validates :token
- end
-
- before_validation :enforce_namespace_to_lower_case
-
- attr_accessor :skip_deprecation_validation
-
- validate :deprecation_validation, unless: :skip_deprecation_validation
-
- validates :namespace,
- allow_blank: true,
- length: 1..63,
- if: :activated?,
- format: {
- with: Gitlab::Regex.kubernetes_namespace_regex,
- message: Gitlab::Regex.kubernetes_namespace_regex_message
- }
-
- def self.supported_events
- %w()
- end
-
- def can_test?
- false
- end
-
- def initialize_properties
- self.properties = {} if properties.nil?
- end
-
- def title
- 'Kubernetes'
- end
-
- def description
- 'Kubernetes / OpenShift integration'
- end
-
- def self.to_param
- 'kubernetes'
- end
-
- def fields
- [
- { type: 'text',
- name: 'api_url',
- title: 'API URL',
- placeholder: 'Kubernetes API URL, like https://kube.example.com/' },
- { type: 'textarea',
- name: 'ca_pem',
- title: 'CA Certificate',
- placeholder: 'Certificate Authority bundle (PEM format)' },
- { type: 'text',
- name: 'namespace',
- title: 'Project namespace (optional/unique)',
- placeholder: namespace_placeholder },
- { type: 'text',
- name: 'token',
- title: 'Token',
- placeholder: 'Service token' }
- ]
- end
-
- def deprecated?
- true
- end
-
- def editable?
- false
- end
-
- def deprecation_message
- content = if project
- _("Kubernetes service integration has been disabled. Fields on this page are not used by GitLab, you can configure your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % {
- url: Gitlab::Routing.url_helpers.project_clusters_path(project)
- }
- else
- _("The instance-level Kubernetes service integration is disabled. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>.") % {
- url: Gitlab::Routing.url_helpers.admin_clusters_path
- }
- end
-
- content.html_safe
- end
-
- TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
-
- private
-
- def namespace_placeholder
- default_namespace || TEMPLATE_PLACEHOLDER
- end
-
- def default_namespace
- return unless project
-
- slug = "#{project.path}-#{project.id}".downcase
- slug.gsub(/[^-a-z0-9]/, '-').gsub(/^-+/, '')
- end
-
- def enforce_namespace_to_lower_case
- self.namespace = self.namespace&.downcase
- end
-
- def deprecation_validation
- return if active_changed?(from: true, to: false) || (new_record? && !active?)
-
- if deprecated?
- errors[:base] << deprecation_message
- end
- end
-end
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
index 1103cb11e73..6f2b0f7747f 100644
--- a/app/models/project_services/mock_deployment_service.rb
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -24,7 +24,7 @@ class MockDeploymentService < Service
%w()
end
- def predefined_variables(project:)
+ def predefined_variables(project:, environment_name:)
[]
end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index c68a9d923c8..6eff2ea2e3a 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -63,15 +63,16 @@ class PrometheusService < MonitoringService
# Check we can connect to the Prometheus API
def test(*args)
- Gitlab::PrometheusClient.new(prometheus_client).ping
-
+ prometheus_client.ping
{ success: true, result: 'Checked API endpoint' }
rescue Gitlab::PrometheusClient::Error => err
{ success: false, result: err }
end
def prometheus_client
- RestClient::Resource.new(api_url, max_redirects: 0) if should_return_client?
+ return unless should_return_client?
+
+ Gitlab::PrometheusClient.new(api_url)
end
def prometheus_available?
@@ -84,7 +85,7 @@ class PrometheusService < MonitoringService
private
def should_return_client?
- api_url && manual_configuration? && active? && valid?
+ api_url.present? && manual_configuration? && active? && valid?
end
def synchronize_service_state
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 5f5cff97808..cb16ad75d14 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -35,6 +35,8 @@ class SlashCommandsService < Service
chat_user = find_chat_user(params)
if chat_user&.user
+ return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands)
+
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
url = authorize_chat_name_url(params)
diff --git a/app/models/project_statistics.rb b/app/models/project_statistics.rb
index 3802d258664..47999a3694e 100644
--- a/app/models/project_statistics.rb
+++ b/app/models/project_statistics.rb
@@ -93,13 +93,7 @@ class ProjectStatistics < ApplicationRecord
def schedule_namespace_aggregation_worker
run_after_commit do
- next unless schedule_aggregation_worker?
-
Namespaces::ScheduleAggregationWorker.perform_async(project.namespace_id)
end
end
-
- def schedule_aggregation_worker?
- Feature.enabled?(:update_statistics_namespace, project&.root_ancestor)
- end
end
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/prometheus_metric.rb b/app/models/prometheus_metric.rb
index b8e7673dcf5..c7786500c5c 100644
--- a/app/models/prometheus_metric.rb
+++ b/app/models/prometheus_metric.rb
@@ -32,6 +32,10 @@ class PrometheusMetric < ApplicationRecord
Gitlab::Prometheus::Metric.new(id: id, title: title, required_metrics: required_metrics, weight: 0, y_label: y_label, queries: queries)
end
+ def to_metric_hash
+ queries.first.merge(metric_id: id)
+ end
+
def queries
[
{
diff --git a/app/models/prometheus_metric_enums.rb b/app/models/prometheus_metric_enums.rb
index 6cb22cc69cd..d58f825f222 100644
--- a/app/models/prometheus_metric_enums.rb
+++ b/app/models/prometheus_metric_enums.rb
@@ -9,13 +9,17 @@ module PrometheusMetricEnums
aws_elb: -3,
nginx: -4,
kubernetes: -5,
- nginx_ingress: -6,
+ nginx_ingress: -6
+ }.merge(custom_groups).freeze
+ end
- # custom/user groups
+ # custom/user groups
+ def self.custom_groups
+ {
business: 0,
response: 1,
system: 2
- }
+ }.freeze
end
def self.group_details
@@ -50,16 +54,20 @@ module PrometheusMetricEnums
group_title: _('System metrics (Kubernetes)'),
required_metrics: %w(container_memory_usage_bytes container_cpu_usage_seconds_total),
priority: 5
- }.freeze,
+ }.freeze
+ }.merge(custom_group_details).freeze
+ end
- # custom/user groups
+ # custom/user groups
+ def self.custom_group_details
+ {
business: {
group_title: _('Business metrics (Custom)'),
priority: 0
}.freeze,
response: {
group_title: _('Response metrics (Custom)'),
- priority: -5
+ priority: -5
}.freeze,
system: {
group_title: _('System metrics (Custom)'),
diff --git a/app/models/redirect_route.rb b/app/models/redirect_route.rb
index 2e4769364c6..22f60802257 100644
--- a/app/models/redirect_route.rb
+++ b/app/models/redirect_route.rb
@@ -11,11 +11,7 @@ class RedirectRoute < ApplicationRecord
uniqueness: { case_sensitive: false }
scope :matching_path_and_descendants, -> (path) do
- wheres = if Gitlab::Database.postgresql?
- 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
- else
- 'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
- end
+ wheres = 'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index af705b29f7a..c9ee0653d86 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -4,6 +4,8 @@ class RemoteMirror < ApplicationRecord
include AfterCommitQueue
include MirrorAuthentication
+ MAX_FIRST_RUNTIME = 3.hours
+ MAX_INCREMENTAL_RUNTIME = 1.hour
PROTECTED_BACKOFF_DELAY = 1.minute
UNPROTECTED_BACKOFF_DELAY = 5.minutes
@@ -31,11 +33,18 @@ class RemoteMirror < ApplicationRecord
scope :enabled, -> { where(enabled: true) }
scope :started, -> { with_update_status(:started) }
- scope :stuck, -> { started.where('last_update_at < ? OR (last_update_at IS NULL AND updated_at < ?)', 1.day.ago, 1.day.ago) }
+
+ scope :stuck, -> do
+ started
+ .where('(last_update_started_at < ? AND last_update_at IS NOT NULL)',
+ MAX_INCREMENTAL_RUNTIME.ago)
+ .or(where('(last_update_started_at < ? AND last_update_at IS NULL)',
+ MAX_FIRST_RUNTIME.ago))
+ end
state_machine :update_status, initial: :none do
event :update_start do
- transition [:none, :finished, :failed] => :started
+ transition any => :started
end
event :update_finish do
@@ -46,9 +55,14 @@ class RemoteMirror < ApplicationRecord
transition started: :failed
end
+ event :update_retry do
+ transition started: :to_retry
+ end
+
state :started
state :finished
state :failed
+ state :to_retry
after_transition any => :started do |remote_mirror, _|
Gitlab::Metrics.add_event(:remote_mirrors_running)
@@ -138,16 +152,27 @@ class RemoteMirror < ApplicationRecord
end
def updated_since?(timestamp)
- last_update_started_at && last_update_started_at > timestamp && !update_failed?
+ return false if failed?
+
+ last_update_started_at && last_update_started_at > timestamp
end
def mark_for_delete_if_blank_url
mark_for_destruction if url.blank?
end
- def mark_as_failed(error_message)
- update_column(:last_error, Gitlab::UrlSanitizer.sanitize(error_message))
- update_fail
+ def update_error_message(error_message)
+ self.last_error = Gitlab::UrlSanitizer.sanitize(error_message)
+ end
+
+ def mark_for_retry!(error_message)
+ update_error_message(error_message)
+ update_retry!
+ end
+
+ def mark_as_failed!(error_message)
+ update_error_message(error_message)
+ update_fail!
end
def url=(value)
@@ -173,7 +198,7 @@ class RemoteMirror < ApplicationRecord
result = URI.parse(url)
result.password = '*****' if result.password
- result.user = '*****' if result.user && result.user != "git" # tokens or other data may be saved as user
+ result.user = '*****' if result.user && result.user != 'git' # tokens or other data may be saved as user
result.to_s
end
@@ -190,6 +215,18 @@ class RemoteMirror < ApplicationRecord
update_column(:error_notification_sent, true)
end
+ def backoff_delay
+ if self.only_protected_branches
+ PROTECTED_BACKOFF_DELAY
+ else
+ UNPROTECTED_BACKOFF_DELAY
+ end
+ end
+
+ def max_runtime
+ last_update_at.present? ? MAX_INCREMENTAL_RUNTIME : MAX_FIRST_RUNTIME
+ end
+
private
def store_credentials
@@ -219,14 +256,6 @@ class RemoteMirror < ApplicationRecord
self.last_update_started_at >= Time.now - backoff_delay
end
- def backoff_delay
- if self.only_protected_branches
- PROTECTED_BACKOFF_DELAY
- else
- UNPROTECTED_BACKOFF_DELAY
- end
- end
-
def reset_fields
update_columns(
last_error: nil,
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 187382ad182..b957b9b0bdd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -7,6 +7,9 @@ class Repository
REF_KEEP_AROUND = 'keep-around'.freeze
REF_ENVIRONMENTS = 'environments'.freeze
+ ARCHIVE_CACHE_TIME = 60 # Cache archives referred to by a (mutable) ref for 1 minute
+ ARCHIVE_CACHE_TIME_IMMUTABLE = 3600 # Cache archives referred to by an immutable reference for 1 hour
+
RESERVED_REFS_NAMES = %W[
heads
tags
@@ -236,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)
@@ -386,11 +389,15 @@ class Repository
expire_statistics_caches
end
- # Runs code after a repository has been created.
- def after_create
+ def expire_status_cache
expire_exists_cache
expire_root_ref_cache
expire_emptiness_caches
+ end
+
+ # Runs code after a repository has been created.
+ def after_create
+ expire_status_cache
repository_event(:create_repository)
end
@@ -415,25 +422,29 @@ class Repository
end
# Runs code before pushing (= creating or removing) a tag.
+ #
+ # Note that this doesn't expire the tags. You may need to call
+ # expire_caches_for_tags or expire_tags_cache.
def before_push_tag
+ repository_event(:push_tag)
+ end
+
+ def expire_caches_for_tags
expire_statistics_caches
expire_emptiness_caches
expire_tags_cache
-
- repository_event(:push_tag)
end
# Runs code before removing a tag.
def before_remove_tag
- expire_tags_cache
- expire_statistics_caches
+ expire_caches_for_tags
repository_event(:remove_tag)
end
# Runs code after removing a tag.
def after_remove_tag
- expire_tags_cache
+ expire_caches_for_tags
end
# Runs code after the HEAD of a repository is changed.
@@ -457,8 +468,8 @@ class Repository
end
# Runs code after a new branch has been created.
- def after_create_branch
- expire_branches_cache
+ def after_create_branch(expire_cache: true)
+ expire_branches_cache if expire_cache
repository_event(:push_branch)
end
@@ -471,8 +482,8 @@ class Repository
end
# Runs code after an existing branch has been removed.
- def after_remove_branch
- expire_branches_cache
+ def after_remove_branch(expire_cache: true)
+ expire_branches_cache if expire_cache
end
def method_missing(msg, *args, &block)
@@ -554,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
@@ -1119,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/service.rb b/app/models/service.rb
index 752467622f2..f6d8fb1fb46 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -260,7 +260,6 @@ class Service < ApplicationRecord
hipchat
irker
jira
- kubernetes
mattermost_slash_commands
mattermost
packagist
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 55da37c9545..9a2640db9ca 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -16,7 +16,7 @@ class SystemNoteMetadata < ApplicationRecord
commit description merge confidential visible label assignee cross_reference
title time_tracking branch milestone discussion task moved
opened closed merged duplicate locked unlocked
- outdated tag due_date
+ outdated tag due_date pinned_embed
].freeze
validates :note, presence: true
diff --git a/app/models/user.rb b/app/models/user.rb
index 0fd3daa3383..6131a8dc710 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -282,6 +282,17 @@ class User < ApplicationRecord
scope :for_todos, -> (todos) { where(id: todos.select(:user_id)) }
scope :with_emails, -> { preload(:emails) }
scope :with_dashboard, -> (dashboard) { where(dashboard: dashboard) }
+ scope :with_public_profile, -> { where(private_profile: false) }
+
+ def self.with_visible_profile(user)
+ return with_public_profile if user.nil?
+
+ if user.admin?
+ all
+ else
+ with_public_profile.or(where(id: user.id))
+ end
+ end
# Limits the users to those that have TODOs, optionally in the given state.
#
@@ -427,18 +438,20 @@ class User < ApplicationRecord
order = <<~SQL
CASE
- WHEN users.name = %{query} THEN 0
- WHEN users.username = %{query} THEN 1
- WHEN users.email = %{query} THEN 2
+ WHEN users.name = :query THEN 0
+ WHEN users.username = :query THEN 1
+ WHEN users.email = :query THEN 2
ELSE 3
END
SQL
+ sanitized_order_sql = Arel.sql(sanitize_sql_array([order, query: query]))
+
where(
fuzzy_arel_match(:name, query, lower_exact_match: true)
.or(fuzzy_arel_match(:username, query, lower_exact_match: true))
.or(arel_table[:email].eq(query))
- ).reorder(order % { query: ApplicationRecord.connection.quote(query) }, :name)
+ ).reorder(sanitized_order_sql, :name)
end
# Limits the result set to users _not_ in the given query/list of IDs.
@@ -933,7 +946,7 @@ class User < ApplicationRecord
end
def project_deploy_keys
- DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
+ DeployKey.in_projects(authorized_projects.select(:id)).distinct(:id)
end
def highest_role
@@ -941,11 +954,10 @@ class User < ApplicationRecord
end
def accessible_deploy_keys
- @accessible_deploy_keys ||= begin
- key_ids = project_deploy_keys.pluck(:id)
- key_ids.push(*DeployKey.are_public.pluck(:id))
- DeployKey.where(id: key_ids)
- end
+ DeployKey.from_union([
+ DeployKey.where(id: project_deploy_keys.select(:deploy_key_id)),
+ DeployKey.are_public
+ ])
end
def created_by
@@ -1259,6 +1271,11 @@ class User < ApplicationRecord
end
end
+ def notification_email_for(notification_group)
+ # Return group-specific email address if present, otherwise return global notification email address
+ notification_group&.notification_email_for(self) || notification_email
+ end
+
def notification_settings_for(source)
if notification_settings.loaded?
notification_settings.find do |notification|
@@ -1490,6 +1507,13 @@ class User < ApplicationRecord
super
end
+ # override from Devise::Confirmable
+ def confirmation_period_valid?
+ return false if Feature.disabled?(:soft_email_confirmation)
+
+ super
+ end
+
private
def default_private_profile_to_false
diff --git a/app/models/user_preference.rb b/app/models/user_preference.rb
index f1326f4c8cb..b236250c24e 100644
--- a/app/models/user_preference.rb
+++ b/app/models/user_preference.rb
@@ -26,7 +26,7 @@ class UserPreference < ApplicationRecord
def set_notes_filter(filter_id, issuable)
# No need to update the column if the value is already set.
- if filter_id && NOTES_FILTERS.values.include?(filter_id)
+ if filter_id && NOTES_FILTERS.value?(filter_id)
field = notes_filter_field_for(issuable)
self[field] = filter_id
diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb
index 9be6bd2e6f3..c633e2d8b3d 100644
--- a/app/models/users_star_project.rb
+++ b/app/models/users_star_project.rb
@@ -1,10 +1,38 @@
# frozen_string_literal: true
class UsersStarProject < ApplicationRecord
+ include Sortable
+
belongs_to :project, counter_cache: :star_count, touch: true
belongs_to :user
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id] }
validates :project, presence: true
+
+ alias_attribute :starred_since, :created_at
+
+ scope :order_user_name_asc, -> { joins(:user).merge(User.order_name_asc) }
+ scope :order_user_name_desc, -> { joins(:user).merge(User.order_name_desc) }
+ 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)
+ order_method = method || 'id_desc'
+
+ case order_method.to_s
+ when 'name_asc' then order_user_name_asc
+ when 'name_desc' then order_user_name_desc
+ else
+ order_by(order_method)
+ end
+ end
+
+ def search(query)
+ joins(:user).merge(User.search(query))
+ end
+ end
end
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 134de1c9ace..311aab0dcd4 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -33,6 +33,7 @@ class GlobalPolicy < BasePolicy
enable :access_git
enable :receive_notifications
enable :use_quick_actions
+ enable :use_slash_commands
end
rule { blocked | internal }.policy do
@@ -40,6 +41,7 @@ class GlobalPolicy < BasePolicy
prevent :access_api
prevent :access_git
prevent :receive_notifications
+ prevent :use_slash_commands
end
rule { required_terms_not_accepted }.policy do
@@ -57,6 +59,7 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do
prevent :log_in
+ prevent :use_slash_commands
end
rule { ~(anonymous & restricted_public_level) }.policy do
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index 9219283992f..c686e7763bb 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -16,8 +16,6 @@ class GroupPolicy < BasePolicy
condition(:maintainer) { access_level >= GroupMember::MAINTAINER }
condition(:reporter) { access_level >= GroupMember::REPORTER }
- condition(:nested_groups_supported, scope: :global) { Group.supports_nested_objects? }
-
condition(:has_parent, scope: :subject) { @subject.has_parent? }
condition(:share_with_group_locked, scope: :subject) { @subject.share_with_group_lock? }
condition(:parent_share_with_group_locked, scope: :subject) { @subject.parent&.share_with_group_lock? }
@@ -38,6 +36,10 @@ class GroupPolicy < BasePolicy
@subject.project_creation_level == ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS
end
+ condition(:maintainer_can_create_group) do
+ @subject.subgroup_creation_level == ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS
+ end
+
rule { public_group }.policy do
enable :read_group
enable :read_list
@@ -66,6 +68,7 @@ class GroupPolicy < BasePolicy
rule { developer }.enable :admin_milestone
rule { reporter }.policy do
+ enable :read_container_image
enable :admin_label
enable :admin_list
enable :admin_issue
@@ -89,6 +92,7 @@ class GroupPolicy < BasePolicy
enable :change_visibility_level
enable :set_note_created_at
+ enable :set_emails_disabled
end
rule { can?(:read_nested_project_resources) }.policy do
@@ -104,7 +108,8 @@ class GroupPolicy < BasePolicy
enable :read_nested_project_resources
end
- rule { owner & nested_groups_supported }.enable :create_subgroup
+ rule { owner }.enable :create_subgroup
+ rule { maintainer & maintainer_can_create_group }.enable :create_subgroup
rule { public_group | logged_in_viewable }.enable :view_globally
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index e79bac6bee3..b8dee1b0789 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -162,6 +162,7 @@ class ProjectPolicy < BasePolicy
enable :set_issue_created_at
enable :set_issue_updated_at
enable :set_note_created_at
+ enable :set_emails_disabled
end
rule { can?(:guest_access) }.policy do
diff --git a/app/presenters/blob_presenter.rb b/app/presenters/blob_presenter.rb
index 91c9abe750b..2cf3278d240 100644
--- a/app/presenters/blob_presenter.rb
+++ b/app/presenters/blob_presenter.rb
@@ -4,7 +4,7 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
presents :blob
def highlight(plain: nil)
- blob.load_all_data! if blob.respond_to?(:load_all_data!)
+ load_all_blob_data
Gitlab::Highlight.highlight(
blob.path,
@@ -17,4 +17,10 @@ class BlobPresenter < Gitlab::View::Presenter::Delegated
def web_url
Gitlab::Routing.url_helpers.project_blob_url(blob.repository.project, File.join(blob.commit_id, blob.path))
end
+
+ private
+
+ def load_all_blob_data
+ blob.load_all_data! if blob.respond_to?(:load_all_data!)
+ end
end
diff --git a/app/presenters/blobs/unfold_presenter.rb b/app/presenters/blobs/unfold_presenter.rb
index 7b13db3bb74..a256dd05a4d 100644
--- a/app/presenters/blobs/unfold_presenter.rb
+++ b/app/presenters/blobs/unfold_presenter.rb
@@ -1,34 +1,42 @@
# 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, GtOneCoercion
- 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)
- @subject = blob
- @all_lines = highlight.lines
- super(params)
+ super(blob)
+ self.attributes = params
- if full?
- self.attributes = { since: 1, to: @all_lines.size, bottom: false, unfold: false, offset: 0, indent: 0 }
- end
+ # 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
+
+ @all_lines = blob.data.lines
+
+ handle_full_or_end!
end
- # Converts a String array to Gitlab::Diff::Line array, with match line added
+ # Returns an array of Gitlab::Diff::Line with match line added
def diff_lines
- diff_lines = lines.map do |line|
- Gitlab::Diff::Line.new(line, nil, nil, nil, nil, rich_text: line)
+ diff_lines = lines.map.with_index do |line, index|
+ full_line = limited_blob_lines[index].delete("\n")
+
+ Gitlab::Diff::Line.new(full_line, nil, nil, nil, nil, rich_text: line)
end
add_match_line(diff_lines)
@@ -38,9 +46,7 @@ module Blobs
def lines
strong_memoize(:lines) do
- lines = @all_lines
- lines = lines[since - 1..to - 1] unless full?
- lines.map(&:html_safe)
+ limit(highlight.lines).map(&:html_safe)
end
end
@@ -54,10 +60,31 @@ module Blobs
private
+ def handle_full_or_end!
+ return unless full? || to == -1
+
+ self.since = 1 if full?
+
+ self.attributes = {
+ to: all_lines_size,
+ bottom: false,
+ unfold: false,
+ offset: 0,
+ indent: 0
+ }
+ end
+
+ def all_lines_size
+ strong_memoize(:all_lines_size) do
+ @all_lines.size
+ end
+ end
+
def add_match_line(diff_lines)
return unless unfold?
+ return if bottom? && to >= all_lines_size
- if bottom? && to < @all_lines.size
+ if bottom? && to < all_lines_size
old_pos = to - offset
new_pos = to
elsif since != 1
@@ -71,5 +98,17 @@ module Blobs
bottom? ? diff_lines.push(match_line) : diff_lines.unshift(match_line)
end
+
+ def limited_blob_lines
+ strong_memoize(:limited_blob_lines) do
+ limit(@all_lines)
+ end
+ end
+
+ def limit(lines)
+ return lines if full?
+
+ lines[since - 1..to - 1]
+ end
end
end
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 9c44ed711a6..919d2653ec8 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -208,14 +208,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
merge_request.subscribed?(current_user, merge_request.target_project)
end
- def conflicts_docs_path
- help_page_path('user/project/merge_requests/resolve_conflicts.md')
- end
-
- def merge_request_pipelines_docs_path
- help_page_path('ci/merge_request_pipelines/index.md')
- end
-
def source_branch_link
if source_branch_exists?
link_to(source_branch, source_branch_commits_path, class: 'ref-name')
diff --git a/app/presenters/projects/settings/deploy_keys_presenter.rb b/app/presenters/projects/settings/deploy_keys_presenter.rb
index 85518c9a3a4..6f8c4e1f902 100644
--- a/app/presenters/projects/settings/deploy_keys_presenter.rb
+++ b/app/presenters/projects/settings/deploy_keys_presenter.rb
@@ -12,48 +12,38 @@ module Projects
@key ||= DeployKey.new.tap { |dk| dk.deploy_keys_projects.build }
end
- # rubocop: disable CodeReuse/ActiveRecord
def enabled_keys
- @enabled_keys ||= project.deploy_keys.includes(:projects)
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def any_keys_enabled?
- enabled_keys.any?
+ project.deploy_keys
end
def available_keys
- @available_keys ||= current_user.accessible_deploy_keys - enabled_keys
+ current_user
+ .accessible_deploy_keys
+ .id_not_in(enabled_keys.select(:id))
+ .with_projects
end
- # rubocop: disable CodeReuse/ActiveRecord
def available_project_keys
- @available_project_keys ||= current_user.project_deploy_keys.includes(:projects) - enabled_keys
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- def key_available?(deploy_key)
- available_keys.include?(deploy_key)
+ current_user
+ .project_deploy_keys
+ .id_not_in(enabled_keys.select(:id))
+ .with_projects
end
- # rubocop: disable CodeReuse/ActiveRecord
def available_public_keys
- return @available_public_keys if defined?(@available_public_keys)
-
- @available_public_keys ||= DeployKey.are_public.includes(:projects) - enabled_keys
-
- # Public keys that are already used by another accessible project are already
- # in @available_project_keys.
- @available_public_keys -= available_project_keys
+ DeployKey
+ .are_public
+ .id_not_in(enabled_keys.select(:id))
+ .id_not_in(available_project_keys.select(:id))
+ .with_projects
end
- # rubocop: enable CodeReuse/ActiveRecord
def as_json
serializer = DeployKeySerializer.new # rubocop: disable CodeReuse/Serializer
opts = { user: current_user }
{
- enabled_keys: serializer.represent(enabled_keys, opts),
+ enabled_keys: serializer.represent(enabled_keys.with_projects, opts),
available_project_keys: serializer.represent(available_project_keys, opts),
public_keys: serializer.represent(available_public_keys, opts)
}
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index ab15bd0ac7a..307ce14a921 100644
--- a/app/serializers/analytics_issue_entity.rb
+++ b/app/serializers/analytics_issue_entity.rb
@@ -20,12 +20,12 @@ class AnalyticsIssueEntity < Grape::Entity
end
expose :url do |object|
- url_to(:namespace_project_issue, id: object[:iid].to_s)
+ url_to(:namespace_project_issue, object)
end
private
- def url_to(route, id)
- public_send("#{route}_url", request.project.namespace, request.project, id) # rubocop:disable GitlabSecurity/PublicSend
+ def url_to(route, object)
+ public_send("#{route}_url", object[:namespace_path], object[:project_path], object[:iid].to_s) # rubocop:disable GitlabSecurity/PublicSend
end
end
diff --git a/app/serializers/analytics_merge_request_entity.rb b/app/serializers/analytics_merge_request_entity.rb
index b7134da9461..21d7eeb81b0 100644
--- a/app/serializers/analytics_merge_request_entity.rb
+++ b/app/serializers/analytics_merge_request_entity.rb
@@ -4,6 +4,6 @@ class AnalyticsMergeRequestEntity < AnalyticsIssueEntity
expose :state
expose :url do |object|
- url_to(:namespace_project_merge_request, id: object[:iid].to_s)
+ url_to(:namespace_project_merge_request, object)
end
end
diff --git a/app/serializers/analytics_stage_entity.rb b/app/serializers/analytics_stage_entity.rb
index 8bc6da5aeeb..eb38b90fb18 100644
--- a/app/serializers/analytics_stage_entity.rb
+++ b/app/serializers/analytics_stage_entity.rb
@@ -8,9 +8,9 @@ class AnalyticsStageEntity < Grape::Entity
expose :legend
expose :description
- expose :median, as: :value do |stage|
+ expose :project_median, as: :value do |stage|
# median returns a BatchLoader instance which we first have to unwrap by using to_f
# we use to_f to make sure results below 1 are presented to the end-user
- stage.median.to_f.nonzero? ? distance_of_time_in_words(stage.median) : nil
+ stage.project_median.to_f.nonzero? ? distance_of_time_in_words(stage.project_median) : nil
end
end
diff --git a/app/serializers/deploy_key_entity.rb b/app/serializers/deploy_key_entity.rb
index 54bf030aba1..e47d6454780 100644
--- a/app/serializers/deploy_key_entity.rb
+++ b/app/serializers/deploy_key_entity.rb
@@ -10,9 +10,10 @@ class DeployKeyEntity < Grape::Entity
expose :created_at
expose :updated_at
expose :deploy_keys_projects, using: DeployKeysProjectEntity do |deploy_key|
- deploy_key.deploy_keys_projects
- .without_project_deleted
- .select { |deploy_key_project| Ability.allowed?(options[:user], :read_project, deploy_key_project.project) }
+ deploy_key.deploy_keys_projects.select do |deploy_key_project|
+ !deploy_key_project.project&.pending_delete? &&
+ Ability.allowed?(options[:user], :read_project, deploy_key_project.project)
+ end
end
expose :can_edit
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index 943c707218d..d6466ad1cdd 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -18,6 +18,7 @@ class DeploymentEntity < Grape::Entity
end
expose :created_at
+ expose :deployed_at
expose :tag
expose :last?
expose :user, using: UserEntity
diff --git a/app/serializers/deployment_serializer.rb b/app/serializers/deployment_serializer.rb
index 04db6b88489..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, :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/group_analytics_stage_entity.rb b/app/serializers/group_analytics_stage_entity.rb
new file mode 100644
index 00000000000..81be20e7dd8
--- /dev/null
+++ b/app/serializers/group_analytics_stage_entity.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class GroupAnalyticsStageEntity < Grape::Entity
+ include EntityDateHelper
+
+ expose :title
+ expose :name
+ expose :legend
+ expose :description
+
+ expose :group_median, as: :value do |stage|
+ # group_median returns a BatchLoader instance which we first have to unwrap by using to_f
+ # we use to_f to make sure results below 1 are presented to the end-user
+ stage.group_median.to_f.nonzero? ? distance_of_time_in_words(stage.group_median) : nil
+ end
+end
diff --git a/app/serializers/group_analytics_stage_serializer.rb b/app/serializers/group_analytics_stage_serializer.rb
new file mode 100644
index 00000000000..ec448dea602
--- /dev/null
+++ b/app/serializers/group_analytics_stage_serializer.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class GroupAnalyticsStageSerializer < BaseSerializer
+ entity GroupAnalyticsStageEntity
+end
diff --git a/app/serializers/issuable_sidebar_basic_entity.rb b/app/serializers/issuable_sidebar_basic_entity.rb
index 61de3c93337..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
@@ -98,6 +99,10 @@ class IssuableSidebarBasicEntity < Grape::Entity
autocomplete_projects_path(project_id: issuable.project.id)
end
+ expose :project_emails_disabled do |issuable|
+ issuable.project.emails_disabled?
+ end
+
private
def current_user
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index 36e601f45c5..82139855760 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -16,9 +16,14 @@ class IssueEntity < IssuableEntity
expose :discussion_locked
expose :assignees, using: API::Entities::UserBasic
expose :due_date
- expose :moved_to_id
expose :project_id
+ expose :moved_to_id do |issue|
+ if issue.moved_to_id.present? && can?(request.current_user, :read_issue, issue.moved_to)
+ issue.moved_to_id
+ end
+ end
+
expose :web_url do |issue|
project_issue_path(issue.project, issue)
end
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
new file mode 100644
index 00000000000..005a3e47bbb
--- /dev/null
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+class MergeRequestPollCachedWidgetEntity < IssuableEntity
+ expose :auto_merge_enabled
+ expose :state
+ expose :merge_commit_sha
+ expose :short_merge_commit_sha
+ expose :merge_error
+ expose :merge_status
+ expose :merge_user_id
+ expose :source_branch
+ expose :source_project_id
+ expose :target_branch
+ expose :target_branch_sha
+ expose :target_project_id
+ expose :squash
+ expose :rebase_in_progress?, as: :rebase_in_progress
+ expose :default_squash_commit_message
+ expose :commits_count
+ expose :merge_ongoing?, as: :merge_ongoing
+ expose :work_in_progress?, as: :work_in_progress
+ expose :cannot_be_merged?, as: :has_conflicts
+ expose :can_be_merged?, as: :can_be_merged
+ expose :remove_source_branch?, as: :remove_source_branch
+ expose :source_branch_exists?, as: :source_branch_exists
+ expose :branch_missing?, as: :branch_missing
+
+ expose :commits_without_merge_commits, using: MergeRequestWidgetCommitEntity do |merge_request|
+ merge_request.commits.without_merge_commits
+ end
+ expose :diff_head_sha do |merge_request|
+ merge_request.diff_head_sha.presence
+ end
+ expose :metrics do |merge_request|
+ metrics = build_metrics(merge_request)
+
+ MergeRequestMetricsEntity.new(metrics).as_json
+ end
+
+ expose :diverged_commits_count do |merge_request|
+ if merge_request.open? && merge_request.diverged_from_target_branch?
+ merge_request.diverged_commits_count
+ else
+ 0
+ end
+ end
+
+ # Paths
+ #
+ expose :target_branch_commits_path do |merge_request|
+ presenter(merge_request).target_branch_commits_path
+ end
+
+ expose :target_branch_tree_path do |merge_request|
+ presenter(merge_request).target_branch_tree_path
+ end
+
+ expose :merge_commit_path do |merge_request|
+ if merge_request.merge_commit_sha
+ project_commit_path(merge_request.project, merge_request.merge_commit_sha)
+ end
+ end
+
+ expose :source_branch_path do |merge_request|
+ presenter(merge_request).source_branch_path
+ end
+
+ expose :source_branch_with_namespace_link do |merge_request|
+ presenter(merge_request).source_branch_with_namespace_link
+ end
+
+ private
+
+ delegate :current_user, to: :request
+
+ def presenter(merge_request)
+ @presenters ||= {}
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
+ end
+
+ # Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs,
+ # we can remove this method and just serialize MergeRequest#metrics
+ # instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587
+ def build_metrics(merge_request)
+ # There's no need to query and serialize metrics data for merge requests that are not
+ # merged or closed.
+ return unless merge_request.merged? || merge_request.closed?
+ return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id
+ return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id
+
+ build_metrics_from_events(merge_request)
+ end
+
+ def build_metrics_from_events(merge_request)
+ closed_event = merge_request.closed_event
+ merge_event = merge_request.merge_event
+
+ MergeRequest::Metrics.new(latest_closed_at: closed_event&.updated_at,
+ latest_closed_by: closed_event&.author,
+ merged_at: merge_event&.updated_at,
+ merged_by: merge_event&.author)
+ end
+end
diff --git a/app/serializers/merge_request_poll_widget_entity.rb b/app/serializers/merge_request_poll_widget_entity.rb
new file mode 100644
index 00000000000..65132b4b215
--- /dev/null
+++ b/app/serializers/merge_request_poll_widget_entity.rb
@@ -0,0 +1,142 @@
+# frozen_string_literal: true
+
+class MergeRequestPollWidgetEntity < IssuableEntity
+ expose :auto_merge_strategy
+ expose :available_auto_merge_strategies do |merge_request|
+ AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
+ end
+ expose :source_branch_protected do |merge_request|
+ merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
+ end
+ expose :allow_collaboration
+ expose :should_be_rebased?, as: :should_be_rebased
+ expose :ff_only_enabled do |merge_request|
+ merge_request.project.merge_requests_ff_only_enabled
+ end
+
+ # User entities
+ expose :merge_user, using: UserEntity
+
+ expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline, if: -> (mr, _) { presenter(mr).can_read_pipeline? }
+
+ expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)}
+
+ expose :default_merge_commit_message
+
+ expose :mergeable?, as: :mergeable
+
+ expose :default_merge_commit_message_with_description do |merge_request|
+ merge_request.default_merge_commit_message(include_description: true)
+ end
+
+ # Booleans
+ expose :mergeable_discussions_state?, as: :mergeable_discussions_state do |merge_request|
+ # This avoids calling MergeRequest#mergeable_discussions_state without
+ # considering the state of the MR first. If a MR isn't mergeable, we can
+ # safely short-circuit it.
+ if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
+ merge_request.mergeable_discussions_state?
+ else
+ false
+ end
+ end
+
+ expose :project_archived do |merge_request|
+ merge_request.project.archived?
+ end
+
+ expose :only_allow_merge_if_pipeline_succeeds do |merge_request|
+ merge_request.project.only_allow_merge_if_pipeline_succeeds?
+ end
+
+ # CI related
+ expose :has_ci?, as: :has_ci
+ expose :ci_status do |merge_request|
+ presenter(merge_request).ci_status
+ end
+
+ expose :cancel_auto_merge_path do |merge_request|
+ presenter(merge_request).cancel_auto_merge_path
+ end
+
+ expose :test_reports_path do |merge_request|
+ if merge_request.has_test_reports?
+ test_reports_project_merge_request_path(merge_request.project, merge_request, format: :json)
+ end
+ end
+
+ expose :supports_suggestion?, as: :can_receive_suggestion
+
+ expose :create_issue_to_resolve_discussions_path do |merge_request|
+ presenter(merge_request).create_issue_to_resolve_discussions_path
+ end
+
+ expose :current_user do
+ expose :can_remove_source_branch do |merge_request|
+ presenter(merge_request).can_remove_source_branch?
+ end
+
+ expose :can_revert_on_current_merge_request do |merge_request|
+ presenter(merge_request).can_revert_on_current_merge_request?
+ end
+
+ expose :can_cherry_pick_on_current_merge_request do |merge_request|
+ presenter(merge_request).can_cherry_pick_on_current_merge_request?
+ end
+
+ expose :can_create_note do |merge_request|
+ can?(current_user, :create_note, merge_request)
+ end
+
+ expose :can_create_issue do |merge_request|
+ can?(current_user, :create_issue, merge_request.project)
+ end
+
+ expose :can_update do |merge_request|
+ can?(current_user, :update_merge_request, merge_request)
+ end
+ end
+
+ expose :can_push_to_source_branch do |merge_request|
+ presenter(merge_request).can_push_to_source_branch?
+ end
+
+ expose :new_blob_path do |merge_request|
+ if presenter(merge_request).can_push_to_source_branch?
+ project_new_blob_path(merge_request.source_project, merge_request.source_branch)
+ end
+ end
+
+ expose :rebase_path do |merge_request|
+ presenter(merge_request).rebase_path
+ end
+
+ expose :conflict_resolution_path do |merge_request|
+ presenter(merge_request).conflict_resolution_path
+ end
+
+ expose :remove_wip_path do |merge_request|
+ presenter(merge_request).remove_wip_path
+ end
+
+ expose :merge_path do |merge_request|
+ presenter(merge_request).merge_path
+ end
+
+ expose :cherry_pick_in_fork_path do |merge_request|
+ presenter(merge_request).cherry_pick_in_fork_path
+ end
+
+ expose :revert_in_fork_path do |merge_request|
+ presenter(merge_request).revert_in_fork_path
+ end
+
+ private
+
+ delegate :current_user, to: :request
+
+ def presenter(merge_request)
+ @presenters ||= {}
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
+ end
+end
diff --git a/app/serializers/merge_request_serializer.rb b/app/serializers/merge_request_serializer.rb
index 6f589351670..bd2e682a122 100644
--- a/app/serializers/merge_request_serializer.rb
+++ b/app/serializers/merge_request_serializer.rb
@@ -4,11 +4,11 @@ class MergeRequestSerializer < BaseSerializer
# This overrided method takes care of which entity should be used
# to serialize the `merge_request` based on `serializer` key in `opts` param.
# Hence, `entity` doesn't need to be declared on the class scope.
- def represent(merge_request, opts = {})
- entity =
+ def represent(merge_request, opts = {}, entity = nil)
+ 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 fd2673fa0cc..554b307d4f8 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -1,117 +1,62 @@
# frozen_string_literal: true
-class MergeRequestWidgetEntity < IssuableEntity
- expose :state
- expose :in_progress_merge_commit_sha
- expose :merge_commit_sha
- expose :short_merge_commit_sha
- expose :merge_error
+class MergeRequestWidgetEntity < Grape::Entity
+ include RequestAwareEntity
+
+ # Currently this attr is exposed to be used in app/assets/javascripts/notes/stores/getters.js
+ # in order to determine whether a noteable is an issue or an MR
expose :merge_params
- expose :merge_status
- expose :merge_user_id
- expose :auto_merge_enabled
- expose :auto_merge_strategy
- expose :available_auto_merge_strategies do |merge_request|
- AutoMergeService.new(merge_request.project, current_user).available_strategies(merge_request) # rubocop: disable CodeReuse/ServiceClass
- end
- expose :source_branch
- expose :source_branch_protected do |merge_request|
- merge_request.source_project.present? && ProtectedBranch.protected?(merge_request.source_project, merge_request.source_branch)
- end
- expose :source_project_id
+
expose :source_project_full_path do |merge_request|
merge_request.source_project&.full_path
end
- expose :squash
- expose :target_branch
- expose :target_branch_sha
- expose :target_project_id
+
expose :target_project_full_path do |merge_request|
merge_request.project&.full_path
end
- expose :allow_collaboration
- expose :should_be_rebased?, as: :should_be_rebased
- expose :ff_only_enabled do |merge_request|
- merge_request.project.merge_requests_ff_only_enabled
+ expose :email_patches_path do |merge_request|
+ project_merge_request_path(merge_request.project, merge_request, format: :patch)
end
- expose :metrics do |merge_request|
- metrics = build_metrics(merge_request)
-
- MergeRequestMetricsEntity.new(metrics).as_json
+ expose :plain_diff_path do |merge_request|
+ project_merge_request_path(merge_request.project, merge_request, format: :diff)
end
- expose :rebase_commit_sha
- expose :rebase_in_progress?, as: :rebase_in_progress
-
- expose :can_push_to_source_branch do |merge_request|
- presenter(merge_request).can_push_to_source_branch?
+ expose :merge_request_basic_path do |merge_request|
+ project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
end
- expose :rebase_path do |merge_request|
- presenter(merge_request).rebase_path
+ expose :merge_request_widget_path do |merge_request|
+ widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
- # User entities
- expose :merge_user, using: UserEntity
-
- # Diff sha's
- expose :diff_head_sha do |merge_request|
- merge_request.diff_head_sha.presence
+ expose :merge_request_cached_widget_path do |merge_request|
+ cached_widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
end
- expose :actual_head_pipeline, with: PipelineDetailsEntity, as: :pipeline, if: -> (mr, _) { presenter(mr).can_read_pipeline? }
-
- expose :merge_pipeline, with: PipelineDetailsEntity, if: ->(mr, _) { mr.merged? && can?(request.current_user, :read_pipeline, mr.target_project)}
-
- expose :default_squash_commit_message
- expose :default_merge_commit_message
-
- expose :default_merge_commit_message_with_description do |merge_request|
- merge_request.default_merge_commit_message(include_description: true)
+ expose :create_note_path do |merge_request|
+ project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
end
- expose :commits_without_merge_commits, using: MergeRequestWidgetCommitEntity do |merge_request|
- merge_request.commits.without_merge_commits
+ expose :commit_change_content_path do |merge_request|
+ commit_change_content_project_merge_request_path(merge_request.project, merge_request)
end
- expose :commits_count
-
- # Booleans
- expose :merge_ongoing?, as: :merge_ongoing
- expose :work_in_progress?, as: :work_in_progress
- expose :source_branch_exists?, as: :source_branch_exists
-
- expose :mergeable_discussions_state?, as: :mergeable_discussions_state do |merge_request|
- # This avoids calling MergeRequest#mergeable_discussions_state without
- # considering the state of the MR first. If a MR isn't mergeable, we can
- # safely short-circuit it.
- if merge_request.mergeable_state?(skip_ci_check: true, skip_discussions_check: true)
- merge_request.mergeable_discussions_state?
- else
- false
- end
+ expose :preview_note_path do |merge_request|
+ preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
end
- expose :branch_missing?, as: :branch_missing
- expose :cannot_be_merged?, as: :has_conflicts
- expose :can_be_merged?, as: :can_be_merged
- expose :mergeable?, as: :mergeable
- expose :remove_source_branch?, as: :remove_source_branch
-
- expose :project_archived do |merge_request|
- merge_request.project.archived?
+ expose :conflicts_docs_path do |merge_request|
+ help_page_path('user/project/merge_requests/resolve_conflicts.md')
end
- expose :only_allow_merge_if_pipeline_succeeds do |merge_request|
- merge_request.project.only_allow_merge_if_pipeline_succeeds?
+ expose :merge_request_pipelines_docs_path do |merge_request|
+ help_page_path('ci/merge_request_pipelines/index.md')
end
- # CI related
- expose :has_ci?, as: :has_ci
- expose :ci_status do |merge_request|
- presenter(merge_request).ci_status
+ expose :ci_environments_status_path do |merge_request|
+ ci_environments_status_project_merge_request_path(merge_request.project, merge_request)
end
# Rendering and redacting Markdown can be expensive. These links are
@@ -131,175 +76,16 @@ class MergeRequestWidgetEntity < IssuableEntity
end
end
- expose :source_branch_with_namespace_link do |merge_request|
- presenter(merge_request).source_branch_with_namespace_link
- end
-
- expose :source_branch_path do |merge_request|
- presenter(merge_request).source_branch_path
- end
-
- expose :current_user do
- expose :can_remove_source_branch do |merge_request|
- presenter(merge_request).can_remove_source_branch?
- end
-
- expose :can_revert_on_current_merge_request do |merge_request|
- presenter(merge_request).can_revert_on_current_merge_request?
- end
-
- expose :can_cherry_pick_on_current_merge_request do |merge_request|
- presenter(merge_request).can_cherry_pick_on_current_merge_request?
- end
-
- expose :can_create_note do |merge_request|
- can?(request.current_user, :create_note, merge_request)
- end
-
- expose :can_create_issue do |merge_request|
- can?(current_user, :create_issue, merge_request.project)
- end
-
- expose :can_update do |merge_request|
- can?(request.current_user, :update_merge_request, merge_request)
- end
- end
-
- # Paths
- #
- expose :target_branch_commits_path do |merge_request|
- presenter(merge_request).target_branch_commits_path
- end
-
- expose :target_branch_tree_path do |merge_request|
- presenter(merge_request).target_branch_tree_path
- end
-
- expose :new_blob_path do |merge_request|
- if presenter(merge_request).can_push_to_source_branch?
- project_new_blob_path(merge_request.source_project, merge_request.source_branch)
- end
- end
-
- expose :conflict_resolution_path do |merge_request|
- presenter(merge_request).conflict_resolution_path
- end
-
- expose :remove_wip_path do |merge_request|
- presenter(merge_request).remove_wip_path
- end
-
- expose :cancel_auto_merge_path do |merge_request|
- presenter(merge_request).cancel_auto_merge_path
- end
-
- expose :create_issue_to_resolve_discussions_path do |merge_request|
- presenter(merge_request).create_issue_to_resolve_discussions_path
- end
-
- expose :merge_path do |merge_request|
- presenter(merge_request).merge_path
- end
-
- expose :cherry_pick_in_fork_path do |merge_request|
- presenter(merge_request).cherry_pick_in_fork_path
- end
-
- expose :revert_in_fork_path do |merge_request|
- presenter(merge_request).revert_in_fork_path
- end
-
- expose :email_patches_path do |merge_request|
- project_merge_request_path(merge_request.project, merge_request, format: :patch)
- end
-
- expose :plain_diff_path do |merge_request|
- project_merge_request_path(merge_request.project, merge_request, format: :diff)
- end
-
- expose :merge_request_basic_path do |merge_request|
- project_merge_request_path(merge_request.target_project, merge_request, serializer: :basic, format: :json)
- end
-
- expose :merge_request_widget_path do |merge_request|
- widget_project_json_merge_request_path(merge_request.target_project, merge_request, format: :json)
- end
-
- expose :ci_environments_status_path do |merge_request|
- ci_environments_status_project_merge_request_path(merge_request.project, merge_request)
- end
-
- expose :diverged_commits_count do |merge_request|
- if merge_request.open? && merge_request.diverged_from_target_branch?
- merge_request.diverged_commits_count
- else
- 0
- end
- end
-
- expose :create_note_path do |merge_request|
- project_notes_path(merge_request.project, target_type: 'merge_request', target_id: merge_request.id)
- end
-
- expose :commit_change_content_path do |merge_request|
- commit_change_content_project_merge_request_path(merge_request.project, merge_request)
- end
-
- expose :preview_note_path do |merge_request|
- preview_markdown_path(merge_request.project, target_type: 'MergeRequest', target_id: merge_request.iid)
- end
-
- expose :merge_commit_path do |merge_request|
- if merge_request.merge_commit_sha
- project_commit_path(merge_request.project, merge_request.merge_commit_sha)
- end
- end
-
- expose :test_reports_path do |merge_request|
- if merge_request.has_test_reports?
- test_reports_project_merge_request_path(merge_request.project, merge_request, format: :json)
- end
- end
-
- expose :supports_suggestion?, as: :can_receive_suggestion
-
- expose :conflicts_docs_path do |merge_request|
- presenter(merge_request).conflicts_docs_path
- end
-
- expose :merge_request_pipelines_docs_path do |merge_request|
- presenter(merge_request).merge_request_pipelines_docs_path
+ def as_json(options = {})
+ super(options)
+ .merge(MergeRequestPollCachedWidgetEntity.new(object, **@options.opts_hash).as_json(options))
+ .merge(MergeRequestPollWidgetEntity.new(object, **@options.opts_hash).as_json(options))
end
private
- delegate :current_user, to: :request
-
def presenter(merge_request)
@presenters ||= {}
- @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: current_user) # rubocop: disable CodeReuse/Presenter
- end
-
- # Once SchedulePopulateMergeRequestMetricsWithEventsData fully runs,
- # we can remove this method and just serialize MergeRequest#metrics
- # instead. See https://gitlab.com/gitlab-org/gitlab-ce/issues/41587
- def build_metrics(merge_request)
- # There's no need to query and serialize metrics data for merge requests that are not
- # merged or closed.
- return unless merge_request.merged? || merge_request.closed?
- return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id
- return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id
-
- build_metrics_from_events(merge_request)
- end
-
- def build_metrics_from_events(merge_request)
- closed_event = merge_request.closed_event
- merge_event = merge_request.merge_event
-
- MergeRequest::Metrics.new(latest_closed_at: closed_event&.updated_at,
- latest_closed_by: closed_event&.author,
- merged_at: merge_event&.updated_at,
- merged_by: merge_event&.author)
+ @presenters[merge_request] ||= MergeRequestPresenter.new(merge_request, current_user: request.current_user) # rubocop: disable CodeReuse/Presenter
end
end
diff --git a/app/serializers/stage_entity.rb b/app/serializers/stage_entity.rb
index 029dd3d0684..0b0454c5282 100644
--- a/app/serializers/stage_entity.rb
+++ b/app/serializers/stage_entity.rb
@@ -59,14 +59,14 @@ class StageEntity < Grape::Entity
end
def latest_statuses
- HasStatus::ORDERED_STATUSES.map do |ordered_status|
+ HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_statuses.fetch(ordered_status, [])
- end.flatten
+ end
end
def retried_statuses
- HasStatus::ORDERED_STATUSES.map do |ordered_status|
+ HasStatus::ORDERED_STATUSES.flat_map do |ordered_status|
grouped_retried_statuses.fetch(ordered_status, [])
- end.flatten
+ end
end
end
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index 2111e1b5667..d988caea92d 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -2,4 +2,21 @@
class UserSerializer < BaseSerializer
entity UserEntity
+
+ def represent(resource, opts = {}, entity = nil)
+ if params[:merge_request_iid]
+ merge_request = opts[:project].merge_requests.find_by_iid!(params[:merge_request_iid])
+ preload_max_member_access(merge_request.project, Array(resource))
+
+ super(resource, opts.merge(merge_request: merge_request), MergeRequestAssigneeEntity)
+ else
+ super
+ end
+ end
+
+ private
+
+ def preload_max_member_access(project, users)
+ project.team.max_member_access_for_user_ids(users.map(&:id))
+ end
end
diff --git a/app/serializers/variable_entity.rb b/app/serializers/variable_entity.rb
index 4d48e13cfca..8b19925f153 100644
--- a/app/serializers/variable_entity.rb
+++ b/app/serializers/variable_entity.rb
@@ -7,4 +7,5 @@ class VariableEntity < Grape::Entity
expose :protected?, as: :protected
expose :masked?, as: :masked
+ expose :environment_scope
end
diff --git a/app/services/application_settings/update_service.rb b/app/services/application_settings/update_service.rb
index 7eeaf8aade1..471df6e2d0c 100644
--- a/app/services/application_settings/update_service.rb
+++ b/app/services/application_settings/update_service.rb
@@ -15,6 +15,8 @@ module ApplicationSettings
update_terms(@params.delete(:terms))
+ add_to_outbound_local_requests_whitelist(@params.delete(:add_to_outbound_local_requests_whitelist))
+
if params.key?(:performance_bar_allowed_group_path)
params[:performance_bar_allowed_group_id] = performance_bar_allowed_group_id
end
@@ -32,6 +34,13 @@ module ApplicationSettings
params.key?(:usage_ping_enabled) || params.key?(:version_check_enabled)
end
+ def add_to_outbound_local_requests_whitelist(values)
+ values_array = Array(values).reject(&:empty?)
+ return if values_array.empty?
+
+ @application_setting.add_to_outbound_local_requests_whitelist(values_array)
+ end
+
def update_terms(terms)
return unless terms.present?
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 707caee482c..0a069320936 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -17,6 +17,14 @@ module Auth
end
def self.full_access_token(*names)
+ access_token(%w(*), names)
+ end
+
+ def self.pull_access_token(*names)
+ access_token(['pull'], names)
+ end
+
+ def self.access_token(actions, names)
names = names.flatten
registry = Gitlab.config.registry
token = JSONWebToken::RSAToken.new(registry.key)
@@ -25,7 +33,7 @@ module Auth
token.expire_time = token_expire_at
token[:access] = names.map do |name|
- { type: 'repository', name: name, actions: %w(*) }
+ { type: 'repository', name: name, actions: actions }
end
token.encoded
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/archive_trace_service.rb b/app/services/ci/archive_trace_service.rb
index b5cfa1d019c..700d78361a4 100644
--- a/app/services/ci/archive_trace_service.rb
+++ b/app/services/ci/archive_trace_service.rb
@@ -2,8 +2,25 @@
module Ci
class ArchiveTraceService
- def execute(job)
+ def execute(job, worker_name:)
+ # TODO: Remove this logging once we confirmed new live trace architecture is functional.
+ # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/4667.
+ unless job.has_live_trace?
+ Sidekiq.logger.warn(class: worker_name,
+ message: 'The job does not have live trace but going to be archived.',
+ job_id: job.id)
+ return
+ end
+
job.trace.archive!
+
+ # TODO: Remove this logging once we confirmed new live trace architecture is functional.
+ # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/4667.
+ unless job.has_archived_trace?
+ Sidekiq.logger.warn(class: worker_name,
+ message: 'The job does not have archived trace after archiving.',
+ job_id: job.id)
+ end
rescue ::Gitlab::Ci::Trace::AlreadyArchivedError
# It's already archived, thus we can safely ignore this exception.
rescue => e
@@ -11,7 +28,7 @@ module Ci
# If `archive!` keeps failing for over a week, that could incur data loss.
# (See more https://docs.gitlab.com/ee/administration/job_traces.html#new-live-trace-architecture)
# In order to avoid interrupting the system, we do not raise an exception here.
- archive_error(e, job)
+ archive_error(e, job, worker_name)
end
private
@@ -22,9 +39,12 @@ module Ci
"Counter of failed attempts of trace archiving")
end
- def archive_error(error, job)
+ def archive_error(error, job, worker_name)
failed_archive_counter.increment
- Rails.logger.error "Failed to archive trace. id: #{job.id} message: #{error.message}" # rubocop:disable Gitlab/RailsLogger
+
+ Sidekiq.logger.warn(class: worker_name,
+ message: "Failed to archive trace. message: #{error.message}.",
+ job_id: job.id)
Gitlab::Sentry
.track_exception(error,
diff --git a/app/services/ci/play_build_service.rb b/app/services/ci/play_build_service.rb
index eb0b070657d..9f922ffde81 100644
--- a/app/services/ci/play_build_service.rb
+++ b/app/services/ci/play_build_service.rb
@@ -2,7 +2,7 @@
module Ci
class PlayBuildService < ::BaseService
- def execute(build)
+ def execute(build, job_variables_attributes = nil)
unless can?(current_user, :update_build, build)
raise Gitlab::Access::AccessDeniedError
end
@@ -10,7 +10,7 @@ module Ci
# Try to enqueue the build, otherwise create a duplicate.
#
if build.enqueue
- build.tap { |action| action.update(user: current_user) }
+ build.tap { |action| action.update(user: current_user, job_variables_attributes: job_variables_attributes || []) }
else
Ci::Build.retry(build, current_user)
end
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index aaf56048b5c..3b145a65d79 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -4,56 +4,105 @@ module Ci
class ProcessPipelineService < BaseService
attr_reader :pipeline
- def execute(pipeline)
+ def execute(pipeline, trigger_build_ids = nil)
@pipeline = pipeline
update_retried
- new_builds =
- stage_indexes_of_created_processables.map do |index|
- process_stage(index)
- end
+ success = process_stages_without_needs
+
+ # we evaluate dependent needs,
+ # only when the another job has finished
+ success = process_builds_with_needs(trigger_build_ids) || success
@pipeline.update_status
- new_builds.flatten.any?
+ success
end
private
- def process_stage(index)
+ def process_stages_without_needs
+ stage_indexes_of_created_processables_without_needs.flat_map do |index|
+ process_stage_without_needs(index)
+ end.any?
+ end
+
+ def process_stage_without_needs(index)
current_status = status_for_prior_stages(index)
- return if HasStatus::BLOCKED_STATUS.include?(current_status)
+ return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
- if HasStatus::COMPLETED_STATUSES.include?(current_status)
- created_processables_in_stage(index).select do |build|
- Gitlab::OptimisticLocking.retry_lock(build) do |subject|
- Ci::ProcessBuildService.new(project, @user)
- .execute(build, current_status)
- end
- end
+ created_processables_in_stage_without_needs(index).select do |build|
+ process_build(build, current_status)
+ end
+ end
+
+ def process_builds_with_needs(trigger_build_ids)
+ return false unless trigger_build_ids.present?
+ return false unless Feature.enabled?(:ci_dag_support, project, default_enabled: true)
+
+ # we find processables that are dependent:
+ # 1. because of current dependency,
+ trigger_build_names = pipeline.processables.latest
+ .for_ids(trigger_build_ids).names
+
+ # 2. does not have builds that not yet complete
+ incomplete_build_names = pipeline.processables.latest
+ .incomplete.names
+
+ # Each found processable is guaranteed here to have completed status
+ created_processables
+ .with_needs(trigger_build_names)
+ .without_needs(incomplete_build_names)
+ .find_each
+ .map(&method(:process_build_with_needs))
+ .any?
+ end
+
+ def process_build_with_needs(build)
+ current_status = status_for_build_needs(build.needs.map(&:name))
+
+ return unless HasStatus::COMPLETED_STATUSES.include?(current_status)
+
+ process_build(build, current_status)
+ end
+
+ def process_build(build, current_status)
+ Gitlab::OptimisticLocking.retry_lock(build) do |subject|
+ Ci::ProcessBuildService.new(project, @user)
+ .execute(subject, current_status)
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def status_for_prior_stages(index)
- pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
+ pipeline.processables.status_for_prior_stages(index)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
- def stage_indexes_of_created_processables
- created_processables.order(:stage_idx).pluck(Arel.sql('DISTINCT stage_idx'))
+ def status_for_build_needs(needs)
+ pipeline.processables.status_for_names(needs)
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
- def created_processables_in_stage(index)
- created_processables.where(stage_idx: index)
+ def stage_indexes_of_created_processables_without_needs
+ created_processables_without_needs.order(:stage_idx)
+ .pluck(Arel.sql('DISTINCT stage_idx'))
end
# rubocop: enable CodeReuse/ActiveRecord
+ def created_processables_in_stage_without_needs(index)
+ created_processables_without_needs
+ .for_stage(index)
+ end
+
+ def created_processables_without_needs
+ if Feature.enabled?(:ci_dag_support, project, default_enabled: true)
+ pipeline.processables.created.without_needs
+ else
+ pipeline.processables.created
+ end
+ end
+
def created_processables
pipeline.processables.created
end
diff --git a/app/services/ci/retry_build_service.rb b/app/services/ci/retry_build_service.rb
index fab8a179843..338495ba030 100644
--- a/app/services/ci/retry_build_service.rb
+++ b/app/services/ci/retry_build_service.rb
@@ -5,7 +5,7 @@ module Ci
CLONE_ACCESSORS = %i[pipeline project ref tag options name
allow_failure stage stage_id stage_idx trigger_request
yaml_variables when environment coverage_regex
- description tag_list protected].freeze
+ description tag_list protected needs].freeze
def execute(build)
reprocess!(build).tap do |new_build|
diff --git a/app/services/clusters/build_kubernetes_namespace_service.rb b/app/services/clusters/build_kubernetes_namespace_service.rb
new file mode 100644
index 00000000000..2574f77bbf9
--- /dev/null
+++ b/app/services/clusters/build_kubernetes_namespace_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Clusters
+ class BuildKubernetesNamespaceService
+ attr_reader :cluster, :environment
+
+ def initialize(cluster, environment:)
+ @cluster = cluster
+ @environment = environment
+ end
+
+ def execute
+ cluster.kubernetes_namespaces.build(attributes)
+ end
+
+ private
+
+ def attributes
+ attributes = {
+ project: environment.project,
+ namespace: namespace,
+ service_account_name: "#{namespace}-service-account"
+ }
+
+ attributes[:cluster_project] = cluster.cluster_project if cluster.project_type?
+ attributes[:environment] = environment if cluster.namespace_per_environment?
+
+ attributes
+ end
+
+ def namespace
+ Gitlab::Kubernetes::DefaultNamespace.new(cluster, project: environment.project).from_environment_slug(environment.slug)
+ end
+ end
+end
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 5fb5e15c32d..e5a5b73321a 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -11,7 +11,8 @@ module Clusters
def execute(access_token: nil)
raise ArgumentError, 'Unknown clusterable provided' unless clusterable
- cluster_params = params.merge(user: current_user).merge(clusterable_params)
+ cluster_params = params.merge(global_params).merge(clusterable_params)
+
cluster_params[:provider_gcp_attributes].try do |provider|
provider[:access_token] = access_token
end
@@ -35,6 +36,10 @@ module Clusters
@clusterable ||= params.delete(:clusterable)
end
+ def global_params
+ { user: current_user, namespace_per_environment: Feature.enabled?(:kubernetes_namespace_per_environment, default_enabled: true) }
+ end
+
def clusterable_params
case clusterable
when ::Project
diff --git a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
index 806f320381d..c45dac7b273 100644
--- a/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
+++ b/app/services/clusters/gcp/kubernetes/create_or_update_namespace_service.rb
@@ -11,7 +11,6 @@ module Clusters
end
def execute
- configure_kubernetes_namespace
create_project_service_account
configure_kubernetes_token
@@ -22,10 +21,6 @@ module Clusters
attr_reader :cluster, :kubernetes_namespace, :platform
- def configure_kubernetes_namespace
- kubernetes_namespace.set_defaults
- end
-
def create_project_service_account
Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService.namespace_creator(
platform.kubeclient,
diff --git a/app/services/clusters/refresh_service.rb b/app/services/clusters/refresh_service.rb
deleted file mode 100644
index 3752a306793..00000000000
--- a/app/services/clusters/refresh_service.rb
+++ /dev/null
@@ -1,40 +0,0 @@
-# frozen_string_literal: true
-
-module Clusters
- class RefreshService
- def self.create_or_update_namespaces_for_cluster(cluster)
- projects_with_missing_kubernetes_namespaces_for_cluster(cluster).each do |project|
- create_or_update_namespace(cluster, project)
- end
- end
-
- def self.create_or_update_namespaces_for_project(project)
- clusters_with_missing_kubernetes_namespaces_for_project(project).each do |cluster|
- create_or_update_namespace(cluster, project)
- end
- end
-
- def self.projects_with_missing_kubernetes_namespaces_for_cluster(cluster)
- cluster.all_projects.missing_kubernetes_namespace(cluster.kubernetes_namespaces)
- end
-
- private_class_method :projects_with_missing_kubernetes_namespaces_for_cluster
-
- def self.clusters_with_missing_kubernetes_namespaces_for_project(project)
- project.clusters.managed.missing_kubernetes_namespace(project.kubernetes_namespaces)
- end
-
- private_class_method :clusters_with_missing_kubernetes_namespaces_for_project
-
- def self.create_or_update_namespace(cluster, project)
- kubernetes_namespace = cluster.find_or_initialize_kubernetes_namespace_for_project(project)
-
- ::Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
- cluster: cluster,
- kubernetes_namespace: kubernetes_namespace
- ).execute
- end
-
- private_class_method :create_or_update_namespace
- end
-end
diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb
index 6d466c2fc9c..97fbb70f350 100644
--- a/app/services/cohorts_service.rb
+++ b/app/services/cohorts_service.rb
@@ -95,10 +95,6 @@ class CohortsService
# rubocop: enable CodeReuse/ActiveRecord
def column_to_date(column)
- if Gitlab::Database.postgresql?
- "CAST(DATE_TRUNC('month', #{column}) AS date)"
- else
- "STR_TO_DATE(DATE_FORMAT(#{column}, '%Y-%m-01'), '%Y-%m-%d')"
- end
+ "CAST(DATE_TRUNC('month', #{column}) AS date)"
end
end
diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb
index bb34a3d3352..f3be68f9602 100644
--- a/app/services/commits/create_service.rb
+++ b/app/services/commits/create_service.rb
@@ -10,6 +10,7 @@ module Commits
@start_project = params[:start_project] || @project
@start_branch = params[:start_branch]
+ @start_sha = params[:start_sha]
@branch_name = params[:branch_name]
@force = params[:force] || false
end
@@ -40,7 +41,7 @@ module Commits
end
def different_branch?
- @start_branch != @branch_name || @start_project != @project
+ @start_project != @project || @start_branch != @branch_name || @start_sha.present?
end
def force?
@@ -49,6 +50,7 @@ module Commits
def validate!
validate_permissions!
+ validate_start_sha!
validate_on_branch!
validate_branch_existence!
@@ -63,7 +65,21 @@ module Commits
end
end
+ def validate_start_sha!
+ return unless @start_sha
+
+ if @start_branch
+ raise_error("You can't pass both start_branch and start_sha")
+ elsif !Gitlab::Git.commit_id?(@start_sha)
+ raise_error("Invalid start_sha '#{@start_sha}'")
+ elsif !@start_project.repository.commit(@start_sha)
+ raise_error("Cannot find start_sha '#{@start_sha}'")
+ end
+ end
+
def validate_on_branch!
+ return unless @start_branch
+
if !@start_project.empty_repo? && !@start_project.repository.branch_exists?(@start_branch)
raise_error('You can only create or edit files when you are on a branch')
end
diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb
index 6f1fce4989e..6e5bf823cc7 100644
--- a/app/services/create_snippet_service.rb
+++ b/app/services/create_snippet_service.rb
@@ -23,6 +23,7 @@ class CreateSnippetService < BaseService
if snippet.save
UserAgentDetailService.new(snippet, @request).create
+ Gitlab::UsageDataCounters::SnippetCounter.count(:create)
end
snippet
diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb
index d8c4e5bc5e8..65af4dd5a28 100644
--- a/app/services/files/multi_service.rb
+++ b/app/services/files/multi_service.rb
@@ -47,6 +47,7 @@ module Files
author_name: @author_name,
start_project: @start_project,
start_branch_name: @start_branch,
+ start_sha: @start_sha,
force: force?
)
rescue ArgumentError => e
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index d30df34e54b..47c308c8280 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -8,8 +8,6 @@ module Git
PROCESS_COMMIT_LIMIT = 100
def execute
- project.repository.after_create if project.empty_repo?
-
create_events
create_pipelines
execute_project_hooks
@@ -19,7 +17,7 @@ module Git
update_remote_mirrors
- push_data
+ success
end
private
@@ -33,7 +31,7 @@ module Git
end
def limited_commits
- commits.last(PROCESS_COMMIT_LIMIT)
+ @limited_commits ||= commits.last(PROCESS_COMMIT_LIMIT)
end
def commits_count
@@ -48,43 +46,69 @@ module Git
[]
end
+ # Push events in the activity feed only show information for the
+ # last commit.
def create_events
- EventCreateService.new.push(project, current_user, push_data)
+ EventCreateService.new.push(project, current_user, event_push_data)
end
def create_pipelines
return unless params.fetch(:create_pipelines, true)
Ci::CreatePipelineService
- .new(project, current_user, push_data)
+ .new(project, current_user, pipeline_params)
.execute(:push, pipeline_options)
end
def execute_project_hooks
- project.execute_hooks(push_data, hook_name)
- project.execute_services(push_data, hook_name)
+ # Creating push_data invokes one CommitDelta RPC per commit. Only
+ # build this data if we actually need it.
+ project.execute_hooks(push_data, hook_name) if project.has_active_hooks?(hook_name)
+ project.execute_services(push_data, hook_name) if project.has_active_services?(hook_name)
end
def enqueue_invalidate_cache
- ProjectCacheWorker.perform_async(
- project.id,
- invalidated_file_types,
- [:commit_count, :repository_size]
- )
+ file_types = invalidated_file_types
+
+ return unless file_types.present?
+
+ ProjectCacheWorker.perform_async(project.id, file_types, [], false)
end
- def push_data
- @push_data ||= Gitlab::DataBuilder::Push.build(
- project: project,
- user: current_user,
+ def pipeline_params
+ {
+ before: params[:oldrev],
+ after: params[:newrev],
+ ref: params[:ref],
+ 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)
+ {
oldrev: params[:oldrev],
newrev: params[:newrev],
ref: params[:ref],
- commits: limited_commits,
+ project: project,
+ user: current_user,
+ commits: commits,
message: event_message,
commits_count: commits_count,
- push_options: params[:push_options] || {}
- )
+ with_changed_files: with_changed_files
+ }
+ end
+
+ def event_push_data
+ # We only need the last commit for the event push, and we don't
+ # need the full deltas either.
+ @event_push_data ||= Gitlab::DataBuilder::Push.build(
+ push_data_params(commits: commits.last, with_changed_files: false))
+ end
+
+ def push_data
+ @push_data ||= Gitlab::DataBuilder::Push.build(push_data_params(commits: limited_commits))
# Dependent code may modify the push data, so return a duplicate each time
@push_data.dup
diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb
index c41f445c3c4..d2b037a680c 100644
--- a/app/services/git/branch_hooks_service.rb
+++ b/app/services/git/branch_hooks_service.rb
@@ -63,7 +63,7 @@ module Git
end
def branch_create_hooks
- project.repository.after_create_branch
+ project.repository.after_create_branch(expire_cache: false)
project.after_create_default_branch if default_branch?
end
@@ -78,13 +78,21 @@ module Git
end
def branch_remove_hooks
- project.repository.after_remove_branch
+ project.repository.after_remove_branch(expire_cache: false)
end
# Schedules processing of commit messages
def enqueue_process_commit_messages
- limited_commits.each do |commit|
- next unless commit.matches_cross_reference_regex?
+ referencing_commits = limited_commits.select(&:matches_cross_reference_regex?)
+
+ upstream_commit_ids = upstream_commit_ids(referencing_commits)
+
+ referencing_commits.each do |commit|
+ # Avoid reprocessing commits that already exist upstream if the project
+ # is a fork. This will prevent duplicated/superfluous system notes on
+ # mentionables referenced by a commit that is pushed to the upstream,
+ # that is then also pushed to forks when these get synced by users.
+ next if upstream_commit_ids.include?(commit.id)
ProcessCommitWorker.perform_async(
project.id,
@@ -142,5 +150,18 @@ module Git
def branch_name
strong_memoize(:branch_name) { Gitlab::Git.ref_name(params[:ref]) }
end
+
+ def upstream_commit_ids(commits)
+ set = Set.new
+
+ upstream_project = project.fork_source
+ if upstream_project
+ upstream_project
+ .commits_by(oids: commits.map(&:id))
+ .each { |commit| set << commit.id }
+ end
+
+ set
+ end
end
end
diff --git a/app/services/groups/nested_create_service.rb b/app/services/groups/nested_create_service.rb
index 01bd685712b..a51ac9aa593 100644
--- a/app/services/groups/nested_create_service.rb
+++ b/app/services/groups/nested_create_service.rb
@@ -18,10 +18,6 @@ module Groups
return namespace
end
- if group_path.include?('/') && !Group.supports_nested_objects?
- raise 'Nested groups are not supported on MySQL'
- end
-
create_group_path
end
diff --git a/app/services/groups/transfer_service.rb b/app/services/groups/transfer_service.rb
index 98e7c311572..fe7e07ef9f0 100644
--- a/app/services/groups/transfer_service.rb
+++ b/app/services/groups/transfer_service.rb
@@ -43,7 +43,6 @@ module Groups
def ensure_allowed_transfer
raise_transfer_error(:group_is_already_root) if group_is_already_root?
- raise_transfer_error(:database_not_supported) unless Group.supports_nested_objects?
raise_transfer_error(:same_parent_as_current) if same_parent?
raise_transfer_error(:invalid_policies) unless valid_policies?
raise_transfer_error(:namespace_with_same_path) if namespace_with_same_path?
diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb
index 73e1e00dc33..116756bacfe 100644
--- a/app/services/groups/update_service.rb
+++ b/app/services/groups/update_service.rb
@@ -46,6 +46,11 @@ module Groups
params.delete(:parent_id)
end
+ # overridden in EE
+ def remove_unallowed_params
+ params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, group)
+ end
+
def valid_share_with_group_lock_change?
return true unless changing_share_with_group_lock?
return true if can?(current_user, :change_share_with_group_lock, group)
diff --git a/app/services/issuable/bulk_update_service.rb b/app/services/issuable/bulk_update_service.rb
index 6d215d7a3b9..273a12f386a 100644
--- a/app/services/issuable/bulk_update_service.rb
+++ b/app/services/issuable/bulk_update_service.rb
@@ -29,7 +29,7 @@ module Issuable
items.each do |issuable|
next unless can?(current_user, :"update_#{type}", issuable)
- update_class.new(issuable.project, current_user, params).execute(issuable)
+ update_class.new(issuable.issuing_parent, current_user, params).execute(issuable)
end
{
diff --git a/app/services/issuable/clone/attributes_rewriter.rb b/app/services/issuable/clone/attributes_rewriter.rb
index 0300cc0d8d3..3c061d35558 100644
--- a/app/services/issuable/clone/attributes_rewriter.rb
+++ b/app/services/issuable/clone/attributes_rewriter.rb
@@ -17,6 +17,8 @@ module Issuable
private
def cloneable_milestone
+ return unless new_entity.supports_milestone?
+
title = original_entity.milestone&.title
return unless title
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index db673cace81..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)
@@ -358,6 +355,7 @@ class IssuableBaseService < BaseService
assignees: issuable.assignees.to_a
}
associations[:total_time_spent] = issuable.total_time_spent if issuable.respond_to?(:total_time_spent)
+ associations[:description] = issuable.description
associations
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 7cd825aa967..c8f4412c9f2 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -61,6 +61,8 @@ module Issues
if added_mentions.present?
notification_service.async.new_mentions_in_issue(issue, added_mentions, current_user)
end
+
+ ZoomNotesService.new(issue, project, current_user, old_description: old_associations[:description]).execute
end
def handle_task_changes(issuable)
diff --git a/app/services/members/base_service.rb b/app/services/members/base_service.rb
index e78affff797..5d69418fb7d 100644
--- a/app/services/members/base_service.rb
+++ b/app/services/members/base_service.rb
@@ -51,7 +51,9 @@ module Members
def enqueue_delete_todos(member)
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
# don't enqueue immediately to prevent todos removal in case of a mistake
- TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ member.run_after_commit_or_now do
+ TodosDestroyer::EntityLeaveWorker.perform_in(Todo::WAIT_FOR_DELETE, member.user_id, member.source_id, type)
+ end
end
end
end
diff --git a/app/services/members/destroy_service.rb b/app/services/members/destroy_service.rb
index c8d5e563cd8..0164760920f 100644
--- a/app/services/members/destroy_service.rb
+++ b/app/services/members/destroy_service.rb
@@ -31,7 +31,7 @@ module Members
return unless member.is_a?(GroupMember) && member.user && member.group
delete_project_members(member)
- delete_subgroup_members(member) if Group.supports_nested_objects?
+ delete_subgroup_members(member)
end
def delete_project_members(member)
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index 109c964e577..b28f80939ae 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -11,15 +11,18 @@ module MergeRequests
# https://gitlab.com/gitlab-org/gitlab-ce/issues/53658
merge_quick_actions_into_params!(merge_request, only: [:target_branch])
merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) if params.has_key?(:force_remove_source_branch)
- merge_request.assign_attributes(params)
+ # Assign the projects first so we can use policies for `filter_params`
merge_request.author = current_user
+ merge_request.source_project = find_source_project
+ merge_request.target_project = find_target_project
+
+ filter_params(merge_request)
+ merge_request.assign_attributes(params.to_h.compact)
+
merge_request.compare_commits = []
- merge_request.source_project = find_source_project
- merge_request.target_project = find_target_project
- merge_request.target_branch = find_target_branch
- merge_request.can_be_created = projects_and_branches_valid?
- ensure_milestone_available(merge_request)
+ merge_request.target_branch = find_target_branch
+ merge_request.can_be_created = projects_and_branches_valid?
# compare branches only if branches are valid, otherwise
# compare_branches may raise an error
@@ -50,12 +53,14 @@ module MergeRequests
to: :merge_request
def find_source_project
+ source_project = project_from_params(:source_project)
return source_project if source_project.present? && can?(current_user, :create_merge_request_from, source_project)
project
end
def find_target_project
+ target_project = project_from_params(:target_project)
return target_project if target_project.present? && can?(current_user, :create_merge_request_in, target_project)
target_project = project.default_merge_request_target
@@ -65,6 +70,17 @@ module MergeRequests
project
end
+ def project_from_params(param_name)
+ project_from_params = params.delete(param_name)
+
+ id_param_name = :"#{param_name}_id"
+ if project_from_params.nil? && params[id_param_name]
+ project_from_params = Project.find_by_id(params.delete(id_param_name))
+ end
+
+ project_from_params
+ end
+
def find_target_branch
target_branch || target_project.default_branch
end
diff --git a/app/services/merge_requests/mergeability_check_service.rb b/app/services/merge_requests/mergeability_check_service.rb
index 9fa50c9448f..962e2327b3e 100644
--- a/app/services/merge_requests/mergeability_check_service.rb
+++ b/app/services/merge_requests/mergeability_check_service.rb
@@ -3,6 +3,7 @@
module MergeRequests
class MergeabilityCheckService < ::BaseService
include Gitlab::Utils::StrongMemoize
+ include Gitlab::ExclusiveLeaseHelpers
delegate :project, to: :@merge_request
delegate :repository, to: :project
@@ -21,13 +22,35 @@ module MergeRequests
# where we need the current state of the merge ref in repository, the `recheck`
# argument is required.
#
+ # retry_lease - Concurrent calls wait for at least 10 seconds until the
+ # lease is granted (other process finishes running). Returns an error
+ # ServiceResponse if the lease is not granted during this time.
+ #
# Returns a ServiceResponse indicating merge_status is/became can_be_merged
# and the merge-ref is synced. Success in case of being/becoming mergeable,
# error otherwise.
- def execute(recheck: false)
+ def execute(recheck: false, retry_lease: true)
return ServiceResponse.error(message: 'Invalid argument') unless merge_request
return ServiceResponse.error(message: 'Unsupported operation') if Gitlab::Database.read_only?
+ return check_mergeability(recheck) unless merge_ref_auto_sync_lock_enabled?
+
+ in_write_lock(retry_lease: retry_lease) do |retried|
+ # When multiple calls are waiting for the same lock (retry_lease),
+ # it's possible that when granted, the MR status was already updated for
+ # that object, therefore we reset if there was a lease retry.
+ merge_request.reset if retried
+
+ check_mergeability(recheck)
+ end
+ rescue FailedToObtainLockError => error
+ ServiceResponse.error(message: error.message)
+ end
+
+ private
+
+ attr_reader :merge_request
+ def check_mergeability(recheck)
recheck! if recheck
update_merge_status
@@ -46,9 +69,21 @@ module MergeRequests
ServiceResponse.success(payload: payload)
end
- private
+ # It's possible for this service to send concurrent requests to Gitaly in order
+ # to "git update-ref" the same ref. Therefore we handle a light exclusive
+ # lease here.
+ #
+ def in_write_lock(retry_lease:, &block)
+ lease_key = "mergeability_check:#{merge_request.id}"
- attr_reader :merge_request
+ lease_opts = {
+ ttl: 1.minute,
+ retries: retry_lease ? 10 : 0,
+ sleep_sec: retry_lease ? 1.second : 0
+ }
+
+ in_lock(lease_key, lease_opts, &block)
+ end
def payload
strong_memoize(:payload) do
@@ -116,5 +151,9 @@ module MergeRequests
def merge_ref_auto_sync_enabled?
Feature.enabled?(:merge_ref_auto_sync, project, default_enabled: true)
end
+
+ def merge_ref_auto_sync_lock_enabled?
+ Feature.enabled?(:merge_ref_auto_sync_lock, project, default_enabled: true)
+ end
end
end
diff --git a/app/services/merge_requests/push_options_handler_service.rb b/app/services/merge_requests/push_options_handler_service.rb
index a24163331e8..b210004e6e1 100644
--- a/app/services/merge_requests/push_options_handler_service.rb
+++ b/app/services/merge_requests/push_options_handler_service.rb
@@ -117,15 +117,16 @@ module MergeRequests
collect_errors_from_merge_request(merge_request) unless merge_request.valid?
end
- def create_params(branch)
+ def base_params
params = {
- assignees: [current_user],
- source_branch: branch,
- source_project: project,
- target_branch: push_options[:target] || target_project.default_branch,
- target_project: target_project
+ title: push_options[:title],
+ description: push_options[:description],
+ target_branch: push_options[:target],
+ force_remove_source_branch: push_options[:remove_source_branch]
}
+ params.compact!
+
if push_options.key?(:merge_when_pipeline_succeeds)
params.merge!(
merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
@@ -136,23 +137,25 @@ module MergeRequests
params
end
- def update_params
- params = {}
+ def create_params(branch)
+ params = base_params
- if push_options.key?(:merge_when_pipeline_succeeds)
- params.merge!(
- merge_when_pipeline_succeeds: push_options[:merge_when_pipeline_succeeds],
- merge_user: current_user
- )
- end
+ params.merge!(
+ assignees: [current_user],
+ source_branch: branch,
+ source_project: project,
+ target_project: target_project
+ )
- if push_options.key?(:target)
- params[:target_branch] = push_options[:target]
- end
+ params[:target_branch] ||= target_project.default_branch
params
end
+ def update_params
+ base_params
+ end
+
def collect_errors_from_merge_request(merge_request)
merge_request.errors.full_messages.each do |error|
errors << error
diff --git a/app/services/merge_requests/rebase_service.rb b/app/services/merge_requests/rebase_service.rb
index 8d3b9b05819..27c16ba1777 100644
--- a/app/services/merge_requests/rebase_service.rb
+++ b/app/services/merge_requests/rebase_service.rb
@@ -15,7 +15,8 @@ module MergeRequests
end
def rebase
- if merge_request.gitaly_rebase_in_progress?
+ # Ensure Gitaly isn't already running a rebase
+ if source_project.repository.rebase_in_progress?(merge_request.id)
log_error('Rebase task canceled: Another rebase is already in progress', save_message_on_model: true)
return false
end
diff --git a/app/services/metrics/dashboard/base_embed_service.rb b/app/services/metrics/dashboard/base_embed_service.rb
new file mode 100644
index 00000000000..8bb5f4892cb
--- /dev/null
+++ b/app/services/metrics/dashboard/base_embed_service.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+# Base class for embed services. Contains a few basic helper
+# methods that the embed services share.
+module Metrics
+ module Dashboard
+ class BaseEmbedService < ::Metrics::Dashboard::BaseService
+ def cache_key
+ "dynamic_metrics_dashboard_#{identifiers}"
+ end
+
+ protected
+
+ def dashboard_path
+ params[:dashboard_path].presence ||
+ ::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH
+ end
+
+ def group
+ params[:group]
+ end
+
+ def title
+ params[:title]
+ end
+
+ def y_label
+ params[:y_label]
+ end
+
+ def identifiers
+ [dashboard_path, group, title, y_label].join('|')
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/base_service.rb b/app/services/metrics/dashboard/base_service.rb
new file mode 100644
index 00000000000..8a42675c66d
--- /dev/null
+++ b/app/services/metrics/dashboard/base_service.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+module Metrics
+ module Dashboard
+ class BaseService < ::BaseService
+ include Gitlab::Metrics::Dashboard::Errors
+
+ def get_dashboard
+ return error('Insufficient permissions.', :unauthorized) unless allowed?
+
+ success(dashboard: process_dashboard)
+ rescue StandardError => e
+ handle_errors(e)
+ end
+
+ # Summary of all known dashboards for the service.
+ # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
+ def self.all_dashboard_paths(_project)
+ raise NotImplementedError
+ end
+
+ # Returns an un-processed dashboard from the cache.
+ def raw_dashboard
+ Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard }
+ end
+
+ private
+
+ # Determines whether users should be able to view
+ # dashboards at all.
+ def allowed?
+ Ability.allowed?(current_user, :read_environment, project)
+ end
+
+ # Returns a new dashboard Hash, supplemented with DB info
+ def process_dashboard
+ Gitlab::Metrics::Dashboard::Processor
+ .new(project, params[:environment], raw_dashboard)
+ .process(insert_project_metrics: insert_project_metrics?)
+ end
+
+ # @return [String] Relative filepath of the dashboard yml
+ def dashboard_path
+ params[:dashboard_path]
+ end
+
+ # @return [Hash] an unmodified dashboard
+ def get_raw_dashboard
+ raise NotImplementedError
+ end
+
+ # @return [String]
+ def cache_key
+ raise NotImplementedError
+ end
+
+ # Determines whether custom metrics should be included
+ # in the processed output.
+ # @return [Boolean]
+ def insert_project_metrics?
+ false
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/custom_metric_embed_service.rb b/app/services/metrics/dashboard/custom_metric_embed_service.rb
new file mode 100644
index 00000000000..50f070989fc
--- /dev/null
+++ b/app/services/metrics/dashboard/custom_metric_embed_service.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+# Responsible for returning a dashboard containing specified
+# custom metrics. Creates panels based on the matching metrics
+# stored in the database.
+#
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class CustomMetricEmbedService < ::Metrics::Dashboard::BaseEmbedService
+ extend ::Gitlab::Utils::Override
+ include Gitlab::Utils::StrongMemoize
+ include Gitlab::Metrics::Dashboard::Defaults
+
+ class << self
+ # Determines whether the provided params are sufficient
+ # to uniquely identify a panel composed of user-defined
+ # custom metrics from the DB.
+ def valid_params?(params)
+ [
+ params[:embedded],
+ valid_dashboard?(params[:dashboard_path]),
+ valid_group_title?(params[:group]),
+ params[:title].present?,
+ params.has_key?(:y_label)
+ ].all?
+ end
+
+ private
+
+ # A group title is valid if it is one of the limited
+ # options the user can select in the UI.
+ def valid_group_title?(group)
+ PrometheusMetricEnums
+ .custom_group_details
+ .map { |_, details| details[:group_title] }
+ .include?(group)
+ end
+
+ # All custom metrics are displayed on the system dashboard.
+ # Nil is acceptable as we'll default to the system dashboard.
+ def valid_dashboard?(dashboard)
+ dashboard.nil? || SystemDashboardService.system_dashboard?(dashboard)
+ end
+ end
+
+ # Returns a new dashboard with only the matching
+ # metrics from the system dashboard, stripped of
+ # group info.
+ #
+ # Note: This overrides the method #raw_dashboard,
+ # which means the result will not be cached. This
+ # is because we are inserting DB info into the
+ # dashboard before post-processing. This ensures
+ # we aren't acting on deleted or out-of-date metrics.
+ #
+ # @return [Hash]
+ override :raw_dashboard
+ def raw_dashboard
+ panels_not_found!(identifiers) if panels.empty?
+
+ { 'panel_groups' => [{ 'panels' => panels }] }
+ end
+
+ private
+
+ # Generated dashboard panels for each metric which
+ # matches the provided input.
+ # @return [Array<Hash>]
+ def panels
+ strong_memoize(:panels) do
+ metrics.map { |metric| panel_for_metric(metric) }
+ end
+ end
+
+ # Metrics which match the provided inputs.
+ # There may be multiple metrics, but they should be
+ # displayed in a single panel/chart.
+ # @return [ActiveRecord::AssociationRelation<PromtheusMetric>]
+ # rubocop: disable CodeReuse/ActiveRecord
+ def metrics
+ project.prometheus_metrics.where(
+ group: group_key,
+ title: title,
+ y_label: y_label
+ )
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ # Returns a symbol representing the group that
+ # the dashboard's group title belongs to.
+ # It will be one of the keys found under
+ # PrometheusMetricEnums.custom_groups.
+ #
+ # @return [String]
+ def group_key
+ strong_memoize(:group_key) do
+ PrometheusMetricEnums
+ .group_details
+ .find { |_, details| details[:group_title] == group }
+ .first
+ .to_s
+ end
+ end
+
+ # Returns a representation of a PromtheusMetric
+ # as a dashboard panel. As the panel is generated
+ # on the fly, we're using default values for info
+ # not represented in the DB.
+ #
+ # @return [Hash]
+ def panel_for_metric(metric)
+ {
+ type: DEFAULT_PANEL_TYPE,
+ weight: DEFAULT_PANEL_WEIGHT,
+ title: metric.title,
+ y_label: metric.y_label,
+ metrics: [metric.to_metric_hash]
+ }
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/default_embed_service.rb b/app/services/metrics/dashboard/default_embed_service.rb
new file mode 100644
index 00000000000..e1bd98bd5c2
--- /dev/null
+++ b/app/services/metrics/dashboard/default_embed_service.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+# Responsible for returning a filtered system dashboard
+# containing only the default embedded metrics. This class
+# operates by selecting metrics directly from the system
+# dashboard.
+#
+# Why isn't this filtering in a processing stage? By filtering
+# here, we ensure the dynamically-determined dashboard is cached.
+#
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class DefaultEmbedService < ::Metrics::Dashboard::BaseEmbedService
+ # For the default filtering for embedded metrics,
+ # uses the 'id' key in dashboard-yml definition for
+ # identification.
+ DEFAULT_EMBEDDED_METRICS_IDENTIFIERS = %w(
+ system_metrics_kubernetes_container_memory_total
+ system_metrics_kubernetes_container_cores_total
+ ).freeze
+
+ # Returns a new dashboard with only the matching
+ # metrics from the system dashboard, stripped of groups.
+ # @return [Hash]
+ def get_raw_dashboard
+ panels = panel_groups.each_with_object([]) do |group, panels|
+ matched_panels = group['panels'].select { |panel| matching_panel?(panel) }
+
+ panels.concat(matched_panels)
+ end
+
+ { 'panel_groups' => [{ 'panels' => panels }] }
+ end
+
+ private
+
+ # Returns an array of the panels groups on the
+ # system dashboard
+ def panel_groups
+ ::Metrics::Dashboard::SystemDashboardService
+ .new(project, nil)
+ .raw_dashboard['panel_groups']
+ end
+
+ # Identifies a panel as "matching" if any metric ids in
+ # the panel is in the list of identifiers to collect.
+ def matching_panel?(panel)
+ panel['metrics'].any? do |metric|
+ metric_identifiers.include?(metric['id'])
+ end
+ end
+
+ def metric_identifiers
+ DEFAULT_EMBEDDED_METRICS_IDENTIFIERS
+ end
+
+ def identifiers
+ metric_identifiers.join('|')
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/dynamic_embed_service.rb b/app/services/metrics/dashboard/dynamic_embed_service.rb
new file mode 100644
index 00000000000..db5b7c9e32a
--- /dev/null
+++ b/app/services/metrics/dashboard/dynamic_embed_service.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+# Responsible for returning a filtered project dashboard
+# containing only the request-provided metrics. The result
+# is then cached for future requests. Metrics are identified
+# based on a combination of identifiers for now, but the ideal
+# would be similar to the approach in DefaultEmbedService, but
+# a single unique identifier is not currently available across
+# all metric types (custom, project-defined, cluster, or system).
+#
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class DynamicEmbedService < ::Metrics::Dashboard::BaseEmbedService
+ include Gitlab::Utils::StrongMemoize
+
+ class << self
+ # Determines whether the provided params are sufficient
+ # to uniquely identify a panel from a yml-defined dashboard.
+ #
+ # See https://docs.gitlab.com/ee/user/project/integrations/prometheus.html#defining-custom-dashboards-per-project
+ # for additional info on defining custom dashboards.
+ def valid_params?(params)
+ [
+ params[:embedded],
+ params[:group].present?,
+ params[:title].present?,
+ params[:y_label]
+ ].all?
+ end
+ end
+
+ # Returns a new dashboard with only the matching
+ # metrics from the system dashboard, stripped of groups.
+ # @return [Hash]
+ def get_raw_dashboard
+ not_found! if panels.empty?
+
+ { 'panel_groups' => [{ 'panels' => panels }] }
+ end
+
+ private
+
+ def panels
+ strong_memoize(:panels) do
+ not_found! unless base_dashboard
+ not_found! unless groups = base_dashboard['panel_groups']
+ not_found! unless matching_group = find_group(groups)
+ not_found! unless all_panels = matching_group['panels']
+
+ find_panels(all_panels)
+ end
+ end
+
+ def base_dashboard
+ strong_memoize(:base_dashboard) do
+ Gitlab::Metrics::Dashboard::Finder.find_raw(project, dashboard_path: dashboard_path)
+ end
+ end
+
+ def find_group(groups)
+ groups.find do |candidate_group|
+ candidate_group['group'] == group
+ end
+ end
+
+ def find_panels(all_panels)
+ all_panels.select do |panel|
+ panel['title'] == title && panel['y_label'] == y_label
+ end
+ end
+
+ def not_found!
+ panels_not_found!(identifiers)
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/project_dashboard_service.rb b/app/services/metrics/dashboard/project_dashboard_service.rb
new file mode 100644
index 00000000000..756d387c0e6
--- /dev/null
+++ b/app/services/metrics/dashboard/project_dashboard_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+# Searches a projects repository for a metrics dashboard and formats the output.
+# Expects any custom dashboards will be located in `.gitlab/dashboards`
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class ProjectDashboardService < ::Metrics::Dashboard::BaseService
+ DASHBOARD_ROOT = ".gitlab/dashboards"
+
+ class << self
+ def all_dashboard_paths(project)
+ file_finder(project)
+ .list_files_for(DASHBOARD_ROOT)
+ .map do |filepath|
+ {
+ path: filepath,
+ display_name: name_for_path(filepath),
+ default: false
+ }
+ end
+ end
+
+ def file_finder(project)
+ Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml')
+ end
+
+ # Grabs the filepath after the base directory.
+ def name_for_path(filepath)
+ filepath.delete_prefix("#{DASHBOARD_ROOT}/")
+ end
+ end
+
+ private
+
+ # Searches the project repo for a custom-defined dashboard.
+ def get_raw_dashboard
+ yml = self.class.file_finder(project).read(dashboard_path)
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ "project_#{project.id}_metrics_dashboard_#{dashboard_path}"
+ end
+ end
+ end
+end
diff --git a/app/services/metrics/dashboard/system_dashboard_service.rb b/app/services/metrics/dashboard/system_dashboard_service.rb
new file mode 100644
index 00000000000..fcd71aadb03
--- /dev/null
+++ b/app/services/metrics/dashboard/system_dashboard_service.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+# Fetches the system metrics dashboard and formats the output.
+# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
+module Metrics
+ module Dashboard
+ class SystemDashboardService < ::Metrics::Dashboard::BaseService
+ SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
+ SYSTEM_DASHBOARD_NAME = 'Default'
+
+ class << self
+ def all_dashboard_paths(_project)
+ [{
+ path: SYSTEM_DASHBOARD_PATH,
+ display_name: SYSTEM_DASHBOARD_NAME,
+ default: true
+ }]
+ end
+
+ def system_dashboard?(filepath)
+ filepath == SYSTEM_DASHBOARD_PATH
+ end
+ end
+
+ private
+
+ def dashboard_path
+ SYSTEM_DASHBOARD_PATH
+ end
+
+ # Returns the base metrics shipped with every GitLab service.
+ def get_raw_dashboard
+ yml = File.read(Rails.root.join(dashboard_path))
+
+ YAML.safe_load(yml)
+ end
+
+ def cache_key
+ "metrics_dashboard_#{dashboard_path}"
+ end
+
+ def insert_project_metrics?
+ true
+ end
+ end
+ end
+end
diff --git a/app/services/notes/base_service.rb b/app/services/notes/base_service.rb
index c1260837c12..b4d04c47cc0 100644
--- a/app/services/notes/base_service.rb
+++ b/app/services/notes/base_service.rb
@@ -9,5 +9,9 @@ module Notes
note.noteable.diffs.clear_cache
end
end
+
+ def increment_usage_counter(note)
+ Gitlab::UsageDataCounters::NoteCounter.count(:create, note.noteable_type)
+ end
end
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 1b46f6d8a72..248e81080cc 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -21,7 +21,7 @@ module Notes
if quick_actions_service.supported?(note)
options = { merge_request_diff_head_sha: merge_request_diff_head_sha }
- content, update_params = quick_actions_service.execute(note, options)
+ content, update_params, message = quick_actions_service.execute(note, options)
only_commands = content.empty?
@@ -41,6 +41,7 @@ module Notes
todo_service.new_note(note, current_user)
clear_noteable_diffs_cache(note)
Suggestions::CreateService.new(note).execute
+ increment_usage_counter(note)
end
if quick_actions_service.commands_executed_count.to_i > 0
@@ -52,7 +53,7 @@ module Notes
# We must add the error after we call #save because errors are reset
# when #save is called
if only_commands
- note.errors.add(:commands_only, 'Commands applied')
+ note.errors.add(:commands_only, message.presence || _('Failed to apply commands.'))
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 5aa804666f0..83710ffce2f 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -321,6 +321,9 @@ class NotificationService
end
def decline_project_invite(project_member)
+ # Must always send, regardless of project/namespace configuration since it's a
+ # response to the user's action.
+
mailer.member_invite_declined_email(
project_member.real_source_type,
project_member.project.id,
@@ -351,8 +354,8 @@ class NotificationService
end
def decline_group_invite(group_member)
- # always send this one, since it's a response to the user's own
- # action
+ # Must always send, regardless of project/namespace configuration since it's a
+ # response to the user's action.
mailer.member_invite_declined_email(
group_member.real_source_type,
@@ -410,6 +413,10 @@ class NotificationService
end
def pipeline_finished(pipeline, recipients = nil)
+ # Must always check project configuration since recipients could be a list of emails
+ # from the PipelinesEmailService integration.
+ return if pipeline.project.emails_disabled?
+
email_template = "pipeline_#{pipeline.status}_email"
return unless mailer.respond_to?(email_template)
@@ -418,7 +425,9 @@ class NotificationService
[pipeline.user], :watch,
custom_action: :"#{pipeline.status}_pipeline",
target: pipeline
- ).map(&:notification_email)
+ ).map do |user|
+ user.notification_email_for(pipeline.project.group)
+ end
if recipients.any?
mailer.public_send(email_template, pipeline, recipients).deliver_later
@@ -426,6 +435,8 @@ class NotificationService
end
def autodevops_disabled(pipeline, recipients)
+ return if pipeline.project.emails_disabled?
+
recipients.each do |recipient|
mailer.autodevops_disabled_email(pipeline, recipient).deliver_later
end
@@ -470,10 +481,14 @@ class NotificationService
end
def repository_cleanup_success(project, user)
+ return if project.emails_disabled?
+
mailer.send(:repository_cleanup_success_email, project, user).deliver_later
end
def repository_cleanup_failure(project, user, error)
+ return if project.emails_disabled?
+
mailer.send(:repository_cleanup_failure_email, project, user, error).deliver_later
end
@@ -593,7 +608,7 @@ class NotificationService
end
def deliver_access_request_email(recipient, member)
- mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.notification_email).deliver_later
+ mailer.member_access_requested_email(member.real_source_type, member.id, recipient.user.id).deliver_later
end
def fallback_to_group_owners_maintainers?(recipients, member)
diff --git a/app/services/projects/base_move_relations_service.rb b/app/services/projects/base_move_relations_service.rb
index 24dec1f3a45..3a159cef58b 100644
--- a/app/services/projects/base_move_relations_service.rb
+++ b/app/services/projects/base_move_relations_service.rb
@@ -10,17 +10,5 @@ module Projects
true
end
-
- private
-
- # rubocop: disable CodeReuse/ActiveRecord
- def prepare_relation(relation, id_param = :id)
- if Gitlab::Database.postgresql?
- relation
- else
- relation.model.where("#{id_param}": relation.pluck(id_param))
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index b805a7f1211..5893b8eedff 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -173,6 +173,7 @@ module Projects
end
def remove_registry_tags
+ return true unless Gitlab.config.registry.enabled
return false unless remove_legacy_registry_tags
project.container_repositories.find_each do |container_repository|
@@ -210,11 +211,20 @@ module Projects
end
def flush_caches(project)
- project.repository.before_delete
+ ignore_git_errors(repo_path) { project.repository.before_delete }
- Repository.new(wiki_path, project, disk_path: repo_path).before_delete
+ ignore_git_errors(wiki_path) { Repository.new(wiki_path, project, disk_path: repo_path).before_delete }
Projects::ForksCountService.new(project).delete_cache
end
+
+ # If we get a Gitaly error, the repository may be corrupted. We can
+ # ignore these errors since we're going to trash the repositories
+ # anyway.
+ def ignore_git_errors(disk_path, &block)
+ yield
+ rescue Gitlab::Git::CommandError => e
+ Gitlab::GitLogger.warn(class: self.class.name, project_id: project.id, disk_path: disk_path, message: e.to_s)
+ end
end
end
diff --git a/app/services/projects/fetch_statistics_increment_service.rb b/app/services/projects/fetch_statistics_increment_service.rb
index 8644e6bf313..b150fd2d9f1 100644
--- a/app/services/projects/fetch_statistics_increment_service.rb
+++ b/app/services/projects/fetch_statistics_increment_service.rb
@@ -12,14 +12,9 @@ module Projects
increment_fetch_count_sql = <<~SQL
INSERT INTO #{table_name} (project_id, date, fetch_count)
VALUES (#{project.id}, '#{Date.today}', 1)
+ ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1
SQL
- increment_fetch_count_sql += if Gitlab::Database.postgresql?
- "ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1"
- else
- "ON DUPLICATE KEY UPDATE fetch_count = #{table_name}.fetch_count + 1"
- end
-
ActiveRecord::Base.connection.execute(increment_fetch_count_sql)
end
diff --git a/app/services/projects/move_deploy_keys_projects_service.rb b/app/services/projects/move_deploy_keys_projects_service.rb
index b6a3af8c7b8..01419563538 100644
--- a/app/services/projects/move_deploy_keys_projects_service.rb
+++ b/app/services/projects/move_deploy_keys_projects_service.rb
@@ -16,8 +16,7 @@ module Projects
private
def move_deploy_keys_projects
- prepare_relation(non_existent_deploy_keys_projects)
- .update_all(project_id: @project.id)
+ non_existent_deploy_keys_projects.update_all(project_id: @project.id)
end
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/projects/move_lfs_objects_projects_service.rb b/app/services/projects/move_lfs_objects_projects_service.rb
index 308a54ad06e..10e19014db4 100644
--- a/app/services/projects/move_lfs_objects_projects_service.rb
+++ b/app/services/projects/move_lfs_objects_projects_service.rb
@@ -16,8 +16,7 @@ module Projects
private
def move_lfs_objects_projects
- prepare_relation(non_existent_lfs_objects_projects)
- .update_all(project_id: @project.lfs_storage_project.id)
+ non_existent_lfs_objects_projects.update_all(project_id: @project.lfs_storage_project.id)
end
def remove_remaining_lfs_objects_project
diff --git a/app/services/projects/move_notification_settings_service.rb b/app/services/projects/move_notification_settings_service.rb
index e740c44bd26..65a888fe26b 100644
--- a/app/services/projects/move_notification_settings_service.rb
+++ b/app/services/projects/move_notification_settings_service.rb
@@ -16,8 +16,7 @@ module Projects
private
def move_notification_settings
- prepare_relation(non_existent_notifications)
- .update_all(source_id: @project.id)
+ non_existent_notifications.update_all(source_id: @project.id)
end
# Remove remaining notification settings from source_project
diff --git a/app/services/projects/move_project_authorizations_service.rb b/app/services/projects/move_project_authorizations_service.rb
index 2985ba89014..c95ad60ab5e 100644
--- a/app/services/projects/move_project_authorizations_service.rb
+++ b/app/services/projects/move_project_authorizations_service.rb
@@ -21,8 +21,7 @@ module Projects
private
def move_project_authorizations
- prepare_relation(non_existent_authorization, :user_id)
- .update_all(project_id: @project.id)
+ non_existent_authorization.update_all(project_id: @project.id)
end
def remove_remaining_authorizations
diff --git a/app/services/projects/move_project_group_links_service.rb b/app/services/projects/move_project_group_links_service.rb
index cf4b291c761..d1aa9af2bcb 100644
--- a/app/services/projects/move_project_group_links_service.rb
+++ b/app/services/projects/move_project_group_links_service.rb
@@ -20,8 +20,7 @@ module Projects
private
def move_group_links
- prepare_relation(non_existent_group_links)
- .update_all(project_id: @project.id)
+ non_existent_group_links.update_all(project_id: @project.id)
end
# Remove remaining project group links from source_project
diff --git a/app/services/projects/move_project_members_service.rb b/app/services/projects/move_project_members_service.rb
index faf389241d2..de4e7e5a1e3 100644
--- a/app/services/projects/move_project_members_service.rb
+++ b/app/services/projects/move_project_members_service.rb
@@ -20,7 +20,7 @@ module Projects
private
def move_project_members
- prepare_relation(non_existent_members).update_all(source_id: @project.id)
+ non_existent_members.update_all(source_id: @project.id)
end
def remove_remaining_members
diff --git a/app/services/projects/update_remote_mirror_service.rb b/app/services/projects/update_remote_mirror_service.rb
index 1244a0f72a7..13a467a3ef9 100644
--- a/app/services/projects/update_remote_mirror_service.rb
+++ b/app/services/projects/update_remote_mirror_service.rb
@@ -2,31 +2,52 @@
module Projects
class UpdateRemoteMirrorService < BaseService
- attr_reader :errors
+ MAX_TRIES = 3
- def execute(remote_mirror)
+ def execute(remote_mirror, tries)
return success unless remote_mirror.enabled?
- errors = []
+ update_mirror(remote_mirror)
- begin
- remote_mirror.ensure_remote!
- repository.fetch_remote(remote_mirror.remote_name, ssh_auth: remote_mirror, no_tags: true)
+ success
+ rescue Gitlab::Git::CommandError => e
+ # This happens if one of the gitaly calls above fail, for example when
+ # branches have diverged, or the pre-receive hook fails.
+ retry_or_fail(remote_mirror, e.message, tries)
- opts = {}
- if remote_mirror.only_protected_branches?
- opts[:only_branches_matching] = project.protected_branches.select(:name).map(&:name)
- end
+ error(e.message)
+ rescue => e
+ remote_mirror.mark_as_failed!(e.message)
+ raise e
+ end
+
+ private
+
+ def update_mirror(remote_mirror)
+ remote_mirror.update_start!
+
+ remote_mirror.ensure_remote!
+ repository.fetch_remote(remote_mirror.remote_name, ssh_auth: remote_mirror, no_tags: true)
- remote_mirror.update_repository(opts)
- rescue => e
- errors << e.message.strip
+ opts = {}
+ if remote_mirror.only_protected_branches?
+ opts[:only_branches_matching] = project.protected_branches.select(:name).map(&:name)
end
- if errors.present?
- error(errors.join("\n\n"))
+ remote_mirror.update_repository(opts)
+
+ remote_mirror.update_finish!
+ end
+
+ def retry_or_fail(mirror, message, tries)
+ if tries < MAX_TRIES
+ mirror.mark_for_retry!(message)
else
- success
+ # It's not likely we'll be able to recover from this ourselves, so we'll
+ # notify the users of the problem, and don't trigger any sidekiq retries
+ # Instead, we'll wait for the next change to try the push again, or until
+ # a user manually retries.
+ mirror.mark_as_failed!(message)
end
end
end
diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb
index 2bc04470342..8acbdc7e02b 100644
--- a/app/services/projects/update_service.rb
+++ b/app/services/projects/update_service.rb
@@ -9,6 +9,7 @@ module Projects
# rubocop: disable CodeReuse/ActiveRecord
def execute
+ remove_unallowed_params
validate!
ensure_wiki_exists if enabling_wiki?
@@ -54,6 +55,10 @@ module Projects
end
end
+ def remove_unallowed_params
+ params.delete(:emails_disabled) unless can?(current_user, :set_emails_disabled, project)
+ end
+
def after_update
todos_features_changes = %w(
issues_access_level
@@ -122,7 +127,7 @@ module Projects
ProjectWiki.new(project, project.owner).wiki
rescue ProjectWiki::CouldNotCreateWikiError
log_error("Could not create wiki for #{project.full_name}")
- Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki')
+ Gitlab::Metrics.counter(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki').increment
end
def update_pages_config
diff --git a/app/services/prometheus/proxy_service.rb b/app/services/prometheus/proxy_service.rb
index c5d2b84878b..a62eb76b8ce 100644
--- a/app/services/prometheus/proxy_service.rb
+++ b/app/services/prometheus/proxy_service.rb
@@ -98,7 +98,7 @@ module Prometheus
end
def prometheus_client_wrapper
- prometheus_adapter&.prometheus_client_wrapper
+ prometheus_adapter&.prometheus_client
end
def can_query?
diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb
index 8ff73522e5f..7f944e25887 100644
--- a/app/services/quick_actions/interpret_service.rb
+++ b/app/services/quick_actions/interpret_service.rb
@@ -31,17 +31,19 @@ module QuickActions
end
# Takes a text and interprets the commands that are extracted from it.
- # Returns the content without commands, and hash of changes to be applied to a record.
+ # Returns the content without commands, a hash of changes to be applied to a record
+ # and a string containing the execution_message to show to the user.
def execute(content, quick_action_target, only: nil)
- return [content, {}] unless current_user.can?(:use_quick_actions)
+ return [content, {}, ''] unless current_user.can?(:use_quick_actions)
@quick_action_target = quick_action_target
@updates = {}
+ @execution_message = {}
content, commands = extractor.extract_commands(content, only: only)
extract_updates(commands)
- [content, @updates]
+ [content, @updates, execution_messages_for(commands)]
end
# Takes a text and interprets the commands that are extracted from it.
@@ -119,8 +121,12 @@ module QuickActions
labels_params.scan(/"([^"]+)"|([^ ]+)/).flatten.compact
end
- def find_label_references(labels_param)
- find_labels(labels_param).map(&:to_reference)
+ def find_label_references(labels_param, format = :id)
+ labels_to_reference(find_labels(labels_param), format)
+ end
+
+ def labels_to_reference(labels, format = :id)
+ labels.map { |l| l.to_reference(format: format) }
end
def find_label_ids(labels_param)
@@ -128,11 +134,24 @@ module QuickActions
end
def explain_commands(commands)
+ map_commands(commands, :explain)
+ end
+
+ def execution_messages_for(commands)
+ map_commands(commands, :execute_message).join(' ')
+ end
+
+ def map_commands(commands, method)
commands.map do |name, arg|
definition = self.class.definition_by_name(name)
next unless definition
- definition.explain(self, arg)
+ case method
+ when :explain
+ definition.explain(self, arg)
+ when :execute_message
+ @execution_message[name.to_sym] || definition.execute_message(self, arg)
+ end
end.compact
end
diff --git a/app/services/self_monitoring/project/create_service.rb b/app/services/self_monitoring/project/create_service.rb
index e5ef8c15456..c925c6a1610 100644
--- a/app/services/self_monitoring/project/create_service.rb
+++ b/app/services/self_monitoring/project/create_service.rb
@@ -4,16 +4,23 @@ module SelfMonitoring
module Project
class CreateService < ::BaseService
include Stepable
+ include Gitlab::Utils::StrongMemoize
- DEFAULT_VISIBILITY_LEVEL = Gitlab::VisibilityLevel::INTERNAL
- DEFAULT_NAME = 'GitLab Instance Administration'
- DEFAULT_DESCRIPTION = <<~HEREDOC
+ 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,
- :add_project_members,
+ :save_project_id,
+ :add_group_members,
+ :add_to_whitelist,
:add_prometheus_manual_configuration
def initialize
@@ -35,20 +42,60 @@ module SelfMonitoring
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
- admin_user = project_owner
+ 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 self-monitoring project. Errors: #{project.errors.full_messages}")
- error('Could not create project')
+ log_error(_("Could not create instance administration project. Errors: %{errors}") % { errors: project.errors.full_messages })
+ error(_('Could not create project'))
end
end
- def add_project_members
- members = project.add_users(project_maintainers, Gitlab::Access::MAINTAINER)
+ 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?
@@ -59,15 +106,30 @@ module SelfMonitoring
end
end
- def add_prometheus_manual_configuration
+ def add_to_whitelist
return success unless prometheus_enabled?
return success unless prometheus_listen_address.present?
- # TODO: Currently, adding the internal prometheus server as a manual configuration
- # is only possible if the setting to allow webhooks and services to connect
- # to local network is on.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/44496 will add
- # a whitelist that will allow connections to certain ips on the local network.
+ 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')
@@ -79,6 +141,22 @@ module SelfMonitoring
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
@@ -94,21 +172,30 @@ module SelfMonitoring
@instance_admins ||= User.admins.active
end
- def project_owner
+ def group_owner
instance_admins.first
end
- def project_maintainers
- # Exclude the first so that the project_owner is not added again as a member.
- instance_admins - [project_owner]
+ 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: DEFAULT_VISIBILITY_LEVEL,
- name: DEFAULT_NAME,
- description: DEFAULT_DESCRIPTION
+ visibility_level: VISIBILITY_LEVEL,
+ name: PROJECT_NAME,
+ description: PROJECT_DESCRIPTION,
+ namespace_id: @group.id
}
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index e4564bc9b00..ee7223d6349 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -597,6 +597,14 @@ module SystemNoteService
note_text =~ /\A#{cross_reference_note_prefix}/i
end
+ def zoom_link_added(issue, project, author)
+ 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, _('removed a Zoom call from this issue'), action: 'pinned_embed'))
+ end
+
private
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/services/update_deployment_service.rb b/app/services/update_deployment_service.rb
index 49a7d0178f4..dcafebae52d 100644
--- a/app/services/update_deployment_service.rb
+++ b/app/services/update_deployment_service.rb
@@ -42,7 +42,7 @@ class UpdateDeploymentService
return unless environment_url
@expanded_environment_url =
- ExpandVariables.expand(environment_url, variables)
+ ExpandVariables.expand(environment_url, -> { variables })
end
def environment_url
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index 15bc1046a4e..2969c360de5 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -25,6 +25,8 @@ class UpdateSnippetService < BaseService
snippet.assign_attributes(params)
spam_check(snippet, current_user)
- snippet.save
+ snippet.save.tap do |succeeded|
+ Gitlab::UsageDataCounters::SnippetCounter.count(:update) if succeeded
+ end
end
end
diff --git a/app/services/users/refresh_authorized_projects_service.rb b/app/services/users/refresh_authorized_projects_service.rb
index 4a26d2be2af..ae67b4f5256 100644
--- a/app/services/users/refresh_authorized_projects_service.rb
+++ b/app/services/users/refresh_authorized_projects_service.rb
@@ -102,13 +102,7 @@ module Users
end
def fresh_authorizations
- klass = if Group.supports_nested_objects?
- Gitlab::ProjectAuthorizations::WithNestedGroups
- else
- Gitlab::ProjectAuthorizations::WithoutNestedGroups
- end
-
- klass.new(user).calculate
+ Gitlab::ProjectAuthorizations.new(user).calculate
end
end
end
diff --git a/app/services/web_hook_service.rb b/app/services/web_hook_service.rb
index 6d675c026bb..8c294218708 100644
--- a/app/services/web_hook_service.rb
+++ b/app/services/web_hook_service.rb
@@ -17,8 +17,10 @@ class WebHookService
@hook = hook
@data = data
@hook_name = hook_name.to_s
- @request_options = { timeout: Gitlab.config.gitlab.webhook_timeout }
- @request_options.merge!(allow_local_requests: true) if @hook.is_a?(SystemHook)
+ @request_options = {
+ timeout: Gitlab.config.gitlab.webhook_timeout,
+ allow_local_requests: hook.allow_local_requests?
+ }
end
def execute
diff --git a/app/services/wiki_pages/base_service.rb b/app/services/wiki_pages/base_service.rb
index e259f5bd1bc..b9df690c2b7 100644
--- a/app/services/wiki_pages/base_service.rb
+++ b/app/services/wiki_pages/base_service.rb
@@ -8,6 +8,12 @@ module WikiPages
page_data = Gitlab::DataBuilder::WikiPage.build(page, current_user, action)
@project.execute_hooks(page_data, :wiki_page_hooks)
@project.execute_services(page_data, :wiki_page_hooks)
+ increment_usage(action)
+ end
+
+ # This method throws an error if the action is an unanticipated value.
+ def increment_usage(action)
+ Gitlab::UsageDataCounters::WikiPageCounter.count(action)
end
end
end
diff --git a/app/services/zoom_notes_service.rb b/app/services/zoom_notes_service.rb
new file mode 100644
index 00000000000..983a7fcacd1
--- /dev/null
+++ b/app/services/zoom_notes_service.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+class ZoomNotesService
+ def initialize(issue, project, current_user, old_description: nil)
+ @issue = issue
+ @project = project
+ @current_user = current_user
+ @old_description = old_description
+ end
+
+ def execute
+ return if @issue.description == @old_description
+
+ if zoom_link_added?
+ zoom_link_added_notification
+ elsif zoom_link_removed?
+ zoom_link_removed_notification
+ end
+ end
+
+ private
+
+ def zoom_link_added?
+ has_zoom_link?(@issue.description) && !has_zoom_link?(@old_description)
+ end
+
+ def zoom_link_removed?
+ !has_zoom_link?(@issue.description) && has_zoom_link?(@old_description)
+ end
+
+ def has_zoom_link?(text)
+ Gitlab::ZoomLinkExtractor.new(text).match?
+ end
+
+ def zoom_link_added_notification
+ SystemNoteService.zoom_link_added(@issue, @project, @current_user)
+ end
+
+ def zoom_link_removed_notification
+ SystemNoteService.zoom_link_removed(@issue, @project, @current_user)
+ end
+end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 00b51f92b12..967fcdc704e 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -23,19 +23,11 @@ module RecordsUploads
return unless model
return unless file && file.exists?
- # MySQL InnoDB may encounter a deadlock if a deletion and an
- # insert is in the same transaction due to its next-key locking
- # algorithm, so we need to skip the transaction.
- # https://gitlab.com/gitlab-org/gitlab-ce/issues/55161#note_131556351
- if Gitlab::Database.mysql?
- readd_upload
- else
- Upload.transaction { readd_upload }
- end
+ Upload.transaction { readd_upload }
end
def readd_upload
- uploads.where(path: upload_path).delete_all
+ uploads.where(model: model, path: upload_path).delete_all
upload.delete if upload
self.upload = build_upload.tap(&:save!)
diff --git a/app/validators/addressable_url_validator.rb b/app/validators/addressable_url_validator.rb
index 273e15ef925..bb445499cee 100644
--- a/app/validators/addressable_url_validator.rb
+++ b/app/validators/addressable_url_validator.rb
@@ -107,6 +107,6 @@ class AddressableUrlValidator < ActiveModel::EachValidator
# calls this validator.
#
# See https://gitlab.com/gitlab-org/gitlab-ee/issues/9833
- ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
+ ApplicationSetting.current&.allow_local_requests_from_web_hooks_and_services?
end
end
diff --git a/app/validators/qualified_domain_array_validator.rb b/app/validators/qualified_domain_array_validator.rb
new file mode 100644
index 00000000000..c3a79d21ac0
--- /dev/null
+++ b/app/validators/qualified_domain_array_validator.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+# QualifiedDomainArrayValidator
+#
+# Custom validator for URL hosts/'qualified domains' (FQDNs, ex: gitlab.com, sub.example.com).
+# This does not check if the domain actually exists. It only checks if it is a
+# valid domain string.
+#
+# Example:
+#
+# class ApplicationSetting < ApplicationRecord
+# validates :outbound_local_requests_whitelist, qualified_domain_array: true
+# end
+#
+class QualifiedDomainArrayValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ validate_value_present(record, attribute, value)
+ validate_host_length(record, attribute, value)
+ validate_idna_encoding(record, attribute, value)
+ validate_sanitization(record, attribute, value)
+ end
+
+ private
+
+ def validate_value_present(record, attribute, value)
+ return unless value.nil?
+
+ record.errors.add(attribute, _('entries cannot be nil'))
+ end
+
+ def validate_host_length(record, attribute, value)
+ return unless value&.any? { |entry| entry.size > 255 }
+
+ record.errors.add(attribute, _('entries cannot be larger than 255 characters'))
+ end
+
+ def validate_idna_encoding(record, attribute, value)
+ return if value&.all?(&:ascii_only?)
+
+ record.errors.add(attribute, _('unicode domains should use IDNA encoding'))
+ end
+
+ def validate_sanitization(record, attribute, value)
+ sanitizer = Rails::Html::FullSanitizer.new
+ return unless value&.any? { |str| sanitizer.sanitize(str) != str }
+
+ record.errors.add(attribute, _('entries cannot contain HTML tags'))
+ end
+end
diff --git a/app/validators/system_hook_url_validator.rb b/app/validators/system_hook_url_validator.rb
new file mode 100644
index 00000000000..f4253006dad
--- /dev/null
+++ b/app/validators/system_hook_url_validator.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# SystemHookUrlValidator
+#
+# Custom validator specific to SystemHook URLs. This validator works like AddressableUrlValidator but
+# it blocks urls pointing to localhost or the local network depending on
+# ApplicationSetting.allow_local_requests_from_system_hooks
+#
+# Example:
+# class SystemHook < WebHook
+# validates :url, system_hook_url: true
+# end
+#
+class SystemHookUrlValidator < PublicUrlValidator
+ def self.allow_setting_local_requests?
+ ApplicationSetting.current&.allow_local_requests_from_system_hooks?
+ end
+end
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
index a869f1bd4df..5e5ab1e4269 100644
--- a/app/views/admin/application_settings/_help_page.html.haml
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -16,6 +16,6 @@
.form-group
= f.label :help_page_support_url, _('Support page URL'), class: 'label-bold'
= f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
- %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page')
+ %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page and help dropdown')
= f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_outbound.html.haml b/app/views/admin/application_settings/_outbound.html.haml
index d16304ed338..ad26f52aea7 100644
--- a/app/views/admin/application_settings/_outbound.html.haml
+++ b/app/views/admin/application_settings/_outbound.html.haml
@@ -4,9 +4,20 @@
%fieldset
.form-group
.form-check
- = f.check_box :allow_local_requests_from_hooks_and_services, class: 'form-check-input'
- = f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
- Allow requests to the local network from hooks and services
+ = f.check_box :allow_local_requests_from_web_hooks_and_services, class: 'form-check-input'
+ = f.label :allow_local_requests_from_web_hooks_and_services, class: 'form-check-label' do
+ = _('Allow requests to the local network from web hooks and services')
+ .form-check
+ = f.check_box :allow_local_requests_from_system_hooks, class: 'form-check-input'
+ = f.label :allow_local_requests_from_system_hooks, class: 'form-check-label' do
+ = _('Allow requests to the local network from system hooks')
+
+ .form-group
+ = f.label :outbound_local_requests_whitelist_raw, class: 'label-bold' do
+ = _('Whitelist to allow requests to the local network from hooks and services')
+ = f.text_area :outbound_local_requests_whitelist_raw, placeholder: "example.com, 192.168.1.1", class: 'form-control', rows: 8
+ %span.form-text.text-muted
+ = _('Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com.')
.form-group
.form-check
diff --git a/app/views/admin/application_settings/_performance.html.haml b/app/views/admin/application_settings/_performance.html.haml
index 7821a83530f..b52171afc69 100644
--- a/app/views/admin/application_settings/_performance.html.haml
+++ b/app/views/admin/application_settings/_performance.html.haml
@@ -15,4 +15,10 @@
AuthorizedKeysCommand. Click on the help icon for more details.
= link_to icon('question-circle'), help_page_path('administration/operations/fast_ssh_key_lookup')
+ .form-group
+ = f.label :raw_blob_request_limit, _('Raw blob request rate limit per minute'), class: 'label-bold'
+ = f.number_field :raw_blob_request_limit, class: 'form-control'
+ .form-text.text-muted
+ = _('Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0.')
+
= f.submit 'Save changes', class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml
new file mode 100644
index 00000000000..b60b5d55a1b
--- /dev/null
+++ b/app/views/admin/application_settings/_snowplow.html.haml
@@ -0,0 +1,30 @@
+- expanded = true if !@application_setting.valid? && @application_setting.errors.any? { |k| k.to_s.start_with?('snowplow_') }
+%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Snowplow')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure the %{link} integration.').html_safe % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank') }
+ .settings-content
+
+ = form_for @application_setting, url: integrations_admin_application_settings_path, html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :snowplow_enabled, class: 'form-check-input'
+ = f.label :snowplow_enabled, _('Enable snowplow tracking'), class: 'form-check-label'
+ .form-group
+ = f.label :snowplow_collector_hostname, _('Collector hostname'), class: 'label-light'
+ = f.text_field :snowplow_collector_hostname, class: 'form-control', placeholder: 'snowplow.example.com'
+ .form-group
+ = f.label :snowplow_site_id, _('Site ID'), class: 'label-light'
+ = f.text_field :snowplow_site_id, class: 'form-control'
+ .form-group
+ = f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light'
+ = f.text_field :snowplow_cookie_domain, class: 'form-control'
+
+ = f.submit _('Save changes'), class: 'btn btn-success'
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/admin/broadcast_messages/_form.html.haml b/app/views/admin/broadcast_messages/_form.html.haml
index c8ee87c6212..962234d3aea 100644
--- a/app/views/admin/broadcast_messages/_form.html.haml
+++ b/app/views/admin/broadcast_messages/_form.html.haml
@@ -17,19 +17,27 @@
required: true,
dir: 'auto',
data: { preview_path: preview_admin_broadcast_messages_path }
- .form-group.row.js-toggle-colors-container
- .col-sm-10.offset-sm-2
- = link_to 'Customize colors', '#', class: 'js-toggle-colors-link'
- .form-group.row.js-toggle-colors-container.toggle-colors.hide
+ .form-group.row
.col-sm-2.col-form-label
- = f.label :color, "Background Color"
+ = f.label :color, _("Background color")
.col-sm-10
- = f.color_field :color, class: "form-control"
+ .input-group
+ .input-group-prepend
+ .input-group-text.label-color-preview{ :style => 'background-color: ' + @broadcast_message.color + '; color: ' + @broadcast_message.font }
+ = '&nbsp;'.html_safe
+ = f.text_field :color, class: "form-control"
+ .form-text.text-muted
+ = _('Choose any color.')
+ %br
+ = _("Or you can choose one of the suggested colors below")
+
+ = render_suggested_colors
+
.form-group.row.js-toggle-colors-container.toggle-colors.hide
.col-sm-2.col-form-label
= f.label :font, "Font Color"
.col-sm-10
- = f.color_field :font, class: "form-control"
+ = f.color_field :font, class: "form-control text-font-color"
.form-group.row
.col-sm-2.col-form-label
= f.label :starts_at, _("Starts at (UTC)")
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 581f6ae0714..c29ecb43fe6 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -76,51 +76,17 @@
.info-well
.well-segment.admin-well.admin-well-features
%h4 Features
- - sign_up = "Sign up"
- %p{ "aria-label" => "#{sign_up}: status " + (allow_signup? ? "on" : "off") }
- = sign_up
- %span.light.float-right
- = boolean_to_icon allow_signup?
- - ldap = "LDAP"
- %p{ "aria-label" => "#{ldap}: status " + (Gitlab.config.ldap.enabled ? "on" : "off") }
- = ldap
- %span.light.float-right
- = boolean_to_icon Gitlab.config.ldap.enabled
- - gravatar = "Gravatar"
- %p{ "aria-label" => "#{gravatar}: status " + (gravatar_enabled? ? "on" : "off") }
- = gravatar
- %span.light.float-right
- = boolean_to_icon gravatar_enabled?
- - omniauth = "OmniAuth"
- %p{ "aria-label" => "#{omniauth}: status " + (Gitlab::Auth.omniauth_enabled? ? "on" : "off") }
- = omniauth
- %span.light.float-right
- = boolean_to_icon Gitlab::Auth.omniauth_enabled?
- - reply_email = "Reply by email"
- %p{ "aria-label" => "#{reply_email}: status " + (Gitlab::IncomingEmail.enabled? ? "on" : "off") }
- = reply_email
- %span.light.float-right
- = boolean_to_icon Gitlab::IncomingEmail.enabled?
+ = feature_entry(_('Sign up'), href: admin_application_settings_path(anchor: 'js-signup-settings'))
+ = feature_entry(_('LDAP'), enabled: Gitlab.config.ldap.enabled)
+ = feature_entry(_('Gravatar'), href: admin_application_settings_path(anchor: 'js-account-settings'), enabled: gravatar_enabled?)
+ = feature_entry(_('OmniAuth'), href: admin_application_settings_path(anchor: 'js-signin-settings'), enabled: Gitlab::Auth.omniauth_enabled?)
+ = feature_entry(_('Reply by email'), enabled: Gitlab::IncomingEmail.enabled?)
= render_if_exists 'admin/dashboard/elastic_and_geo'
- - container_reg = "Container Registry"
- %p{ "aria-label" => "#{container_reg}: status " + (Gitlab.config.registry.enabled ? "on" : "off") }
- = container_reg
- %span.light.float-right
- = boolean_to_icon Gitlab.config.registry.enabled
- - gitlab_pages = 'GitLab Pages'
- - gitlab_pages_enabled = Gitlab.config.pages.enabled
- %p{ "aria-label" => "#{gitlab_pages}: status " + (gitlab_pages_enabled ? "on" : "off") }
- = gitlab_pages
- %span.light.float-right
- = boolean_to_icon gitlab_pages_enabled
- - gitlab_shared_runners = 'Shared Runners'
- - gitlab_shared_runners_enabled = Gitlab.config.gitlab_ci.shared_runners_enabled
- %p{ "aria-label" => "#{gitlab_shared_runners}: status " + (gitlab_shared_runners_enabled ? "on" : "off") }
- = gitlab_shared_runners
- %span.light.float-right
- = boolean_to_icon gitlab_shared_runners_enabled
+ = feature_entry(_('Container Registry'), href: ci_cd_admin_application_settings_path(anchor: 'js-registry-settings'), enabled: Gitlab.config.registry.enabled)
+ = feature_entry(_('Gitlab Pages'), href: help_instance_configuration_url, enabled: Gitlab.config.pages.enabled)
+ = feature_entry(_('Shared Runners'), href: admin_runners_path, enabled: Gitlab.config.gitlab_ci.shared_runners_enabled)
.col-md-4
.info-well
.well-segment.admin-well
@@ -130,7 +96,8 @@
.float-right
= version_status_badge
%p
- GitLab
+ %a{ href: admin_application_settings_path }
+ GitLab
%span.float-right
= Gitlab::VERSION
= "(#{Gitlab.revision})"
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 98230684d56..f9cc118a252 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -40,6 +40,11 @@
%strong
= @group.created_at.to_s(:medium)
+ %li
+ %span.light= _('ID:')
+ %strong
+ = @group.id
+
= render_if_exists 'admin/namespace_plan_info', namespace: @group
%li
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 0fae8060b32..3eff0a221d7 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -55,6 +55,11 @@
= @project.created_at.to_s(:medium)
%li
+ %span.light ID:
+ %strong
+ = @project.id
+
+ %li
%span.light http:
%strong
= link_to @project.http_url_to_repo, project_path(@project)
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index adfc67d66d0..86bfeef580c 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -19,7 +19,8 @@
%ul.content-list
- profiles.each do |profile|
%li
- = link_to profile.time.to_s(:long), admin_requests_profile_path(profile)
+ = link_to profile.time.to_s(:long) + ' ' + profile.profile_mode.capitalize,
+ admin_requests_profile_path(profile)
- else
%p
No profiles found
diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml
index ab08d5c4906..495ee6a04ea 100644
--- a/app/views/admin/services/_form.html.haml
+++ b/app/views/admin/services/_form.html.haml
@@ -6,6 +6,5 @@
= form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form|
= render 'shared/service_settings', form: form, subject: @service
- - unless @service.is_a?(KubernetesService)
- .footer-block.row-content-block
- = form.submit 'Save', class: 'btn btn-success'
+ .footer-block.row-content-block
+ = form.submit 'Save', class: 'btn btn-success'
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index 77729636f9d..bb1e22cc610 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -19,7 +19,7 @@
- editing_current_user = (current_user == @user)
= f.radio_button :access_level, :regular, disabled: editing_current_user
- = label_tag :regular, class: 'font-weight-bold' do
+ = f.label :access_level_regular, class: 'font-weight-bold' do
Regular
%p.light
Regular users have access to their groups and projects
@@ -27,7 +27,7 @@
= render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user
= f.radio_button :access_level, :admin, disabled: editing_current_user
- = label_tag :admin, class: 'font-weight-bold' do
+ = f.label :access_level_admin, class: 'font-weight-bold' do
Admin
%p.light
Administrators have access to all groups, projects and users and can manage all features in this installation
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index c3d5ce0fe70..6fc7ec1bb6f 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -52,7 +52,7 @@
= icon("search", class: "search-icon")
= button_tag s_('AdminUsers|Search users') if Rails.env.test?
.dropdown.user-sort-dropdown
- - toggle_text = if @sort.present? then users_sort_options_hash[@sort] else sort_title_name end
+ - toggle_text = @sort.present? ? users_sort_options_hash[@sort] : sort_title_name
= dropdown_toggle(toggle_text, { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right
%li.dropdown-header
diff --git a/app/views/ci/variables/_environment_scope.html.haml b/app/views/ci/variables/_environment_scope.html.haml
new file mode 100644
index 00000000000..15e61d85881
--- /dev/null
+++ b/app/views/ci/variables/_environment_scope.html.haml
@@ -0,0 +1,21 @@
+- form_field = local_assigns.fetch(:form_field, nil)
+- variable = local_assigns.fetch(:variable, nil)
+
+- if @project
+ - environment_scope = variable&.environment_scope || '*'
+ - environment_scope_label = environment_scope == '*' ? s_('CiVariable|All environments') : environment_scope
+
+ %input{ type: "hidden", name: "#{form_field}[variables_attributes][][environment_scope]", value: environment_scope }
+ = dropdown_tag(environment_scope_label,
+ options: { wrapper_class: 'ci-variable-body-item js-variable-environment-dropdown-wrapper',
+ toggle_class: 'js-variable-environment-toggle wide',
+ filter: true,
+ dropdown_class: "dropdown-menu-selectable",
+ placeholder: s_('CiVariable|Search environments'),
+ footer_content: true,
+ data: { selected: environment_scope } }) do
+ %ul.dropdown-footer-list
+ %li
+ %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item", title: s_('CiVariable|New environment') }
+ = s_('CiVariable|Create wildcard')
+ %code
diff --git a/app/views/ci/variables/_environment_scope_header.html.haml b/app/views/ci/variables/_environment_scope_header.html.haml
new file mode 100644
index 00000000000..4ba4ceec16c
--- /dev/null
+++ b/app/views/ci/variables/_environment_scope_header.html.haml
@@ -0,0 +1,2 @@
+.bold.table-section.section-15.append-right-10
+ = s_('CiVariables|Scope')
diff --git a/app/views/clusters/clusters/_banner.html.haml b/app/views/clusters/clusters/_banner.html.haml
index a5de67be96b..4b4278075a6 100644
--- a/app/views/clusters/clusters/_banner.html.haml
+++ b/app/views/clusters/clusters/_banner.html.haml
@@ -3,7 +3,8 @@
%p.js-error-reason
.hidden.js-cluster-creating.bs-callout.bs-callout-info{ role: 'alert' }
- = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
+ %span.spinner.spinner-dark.spinner-sm{ 'aria-label': 'Loading' }
+ %span.prepend-left-4= s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...')
.hidden.row.js-cluster-api-unreachable.bs-callout.bs-callout-warning{ role: 'alert' }
.col-11
@@ -18,4 +19,4 @@
%button.js-close-banner.close.cluster-application-banner-close.h-100.m-0= "×"
.hidden.js-cluster-success.bs-callout.bs-callout-success{ role: 'alert' }
- = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details")
+ = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine.")
diff --git a/app/views/clusters/clusters/_namespace.html.haml b/app/views/clusters/clusters/_namespace.html.haml
new file mode 100644
index 00000000000..0c64819ad62
--- /dev/null
+++ b/app/views/clusters/clusters/_namespace.html.haml
@@ -0,0 +1,14 @@
+- managed_namespace_help_text = s_('ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path.')
+- non_managed_namespace_help_text = s_('ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, pod logs, and Web terminals.')
+- managed_namespace_help_link = link_to _('More information'), help_page_path('user/project/clusters/index.md',
+ anchor: 'gitlab-managed-clusters'), target: '_blank'
+
+.js-namespace-prefixed
+ = platform_field.text_field :namespace,
+ label: s_('ClusterIntegration|Project namespace prefix (optional, unique)'), label_class: 'label-bold',
+ help: '%{help_text} %{help_link}'.html_safe % { help_text: managed_namespace_help_text, help_link: managed_namespace_help_link }
+.js-namespace.hidden
+ = platform_field.text_field :namespace,
+ label: s_('ClusterIntegration|Project namespace (optional, unique)'), label_class: 'label-bold',
+ help: '%{help_text}'.html_safe % { help_text: non_managed_namespace_help_text },
+ disabled: true
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 4dfbb310142..913d4caa0bc 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -33,26 +33,29 @@
%section#cluster-integration
%h4= @cluster.name
= render 'banner'
- = render 'form'
-
- = render_if_exists 'projects/clusters/prometheus_graphs'
-
- .cluster-applications-table#js-cluster-applications
-
- %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
- .settings-header
- %h4= s_('ClusterIntegration|Kubernetes cluster details')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
- .settings-content
- = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
-
- %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
- .settings-header
- %h4= _('Advanced settings')
- %button.btn.js-settings-toggle{ type: 'button' }
- = expanded ? _('Collapse') : _('Expand')
- %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
- .settings-content#advanced-settings-section
- = render 'advanced_settings'
+
+ - unless @cluster.status_name.in? %i/scheduled creating/
+ = render 'form'
+
+ - unless @cluster.status_name.in? %i/scheduled creating/
+ = render_if_exists 'projects/clusters/prometheus_graphs'
+
+ .cluster-applications-table#js-cluster-applications
+
+ %section.settings#js-cluster-details{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= s_('ClusterIntegration|Kubernetes cluster details')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p= s_('ClusterIntegration|See and edit the details for your Kubernetes cluster')
+ .settings-content
+ = render 'clusters/platforms/kubernetes/form', cluster: @cluster, platform: @cluster.platform_kubernetes, update_cluster_url_path: clusterable.cluster_path(@cluster)
+
+ %section.settings.no-animate#js-cluster-advanced-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4= _('Advanced settings')
+ %button.btn.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p= s_("ClusterIntegration|Advanced options on this Kubernetes cluster's integration")
+ .settings-content#advanced-settings-section
+ = render 'advanced_settings'
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index f2fc5ac93fb..1d212553c3b 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -34,10 +34,6 @@
autocomplete: 'off', label_class: 'label-bold',
help: '%{help_text} %{help_link}'.html_safe % { help_text: token_help_text, help_link: more_info_link }
- - if @user_cluster.allow_user_defined_namespace?
- = platform_kubernetes_field.text_field :namespace,
- label: s_('ClusterIntegration|Project namespace (optional, unique)'), label_class: 'label-bold'
-
= platform_kubernetes_field.form_group :authorization_type,
{ help: '%{help_text} %{help_link}'.html_safe % { help_text: rbac_help_text, help_link: rbac_help_link } } do
= platform_kubernetes_field.check_box :authorization_type,
@@ -46,10 +42,15 @@
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ class: 'js-gl-managed',
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
= link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+ = field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
+ - if @user_cluster.allow_user_defined_namespace?
+ = render('clusters/clusters/namespace', platform_field: platform_kubernetes_field)
+
.form-group
= field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success'
diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml
index f2e44462226..e50c573bd90 100644
--- a/app/views/clusters/platforms/kubernetes/_form.html.haml
+++ b/app/views/clusters/platforms/kubernetes/_form.html.haml
@@ -36,10 +36,6 @@
label: s_('ClusterIntegration|Service Token'), label_class: 'label-bold',
input_group_class: 'gl-field-error-anchor', append: show_token_btn + copy_token_btn
- - if cluster.allow_user_defined_namespace?
- = platform_field.text_field :namespace, label: s_('ClusterIntegration|Project namespace (optional, unique)'),
- label_class: 'label-bold'
-
= platform_field.form_group :authorization_type do
= platform_field.check_box :authorization_type, { disabled: true, label: s_('ClusterIntegration|RBAC-enabled cluster'),
label_class: 'label-bold', inline: true }, 'rbac', 'abac'
@@ -49,10 +45,14 @@
.form-group
= field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'),
+ class: 'js-gl-managed',
label_class: 'label-bold' }
.form-text.text-muted
= s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.')
= link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'gitlab-managed-clusters'), target: '_blank'
+ - if cluster.allow_user_defined_namespace?
+ = render('clusters/clusters/namespace', platform_field: platform_field)
+
.form-group
= field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success'
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 09ea7716a47..fee87c6324c 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -7,12 +7,12 @@
= f.hidden_field :reset_password_token
.form-group
= f.label 'New password', for: "user_password"
- = f.password_field :password, class: "form-control top qa-password-field", required: true, title: 'This field is required'
+ = f.password_field :password, class: "form-control top", required: true, title: 'This field is required', data: { qa_selector: 'password_field'}
.form-group
= f.label 'Confirm new password', for: "user_password_confirmation"
- = f.password_field :password_confirmation, class: "form-control bottom qa-password-confirmation", title: 'This field is required', required: true
+ = f.password_field :password_confirmation, class: "form-control bottom", title: 'This field is required', data: { qa_selector: 'password_confirmation_field' }, required: true
.clearfix
- = f.submit "Change your password", class: "btn btn-primary qa-change-password-button"
+ = f.submit "Change your password", class: "btn btn-primary", data: { qa_selector: 'change_password_button' }
.clearfix.prepend-top-20
%p
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 2f10f08c839..0b1d3d1ddb3 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,20 +1,23 @@
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
.form-group
- = f.label "Username or email", for: "user_login", class: 'label-bold'
- = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required.", data: { qa_selector: 'login_field' }
+ = f.label _('Username or email'), for: 'user_login', class: 'label-bold'
+ = f.text_field :login, class: 'form-control top', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field' }
.form-group
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
+ = f.password_field :password, class: 'form-control bottom', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' }
- if devise_mapping.rememberable?
.remember-me
- %label{ for: "user_remember_me" }
+ %label{ for: 'user_remember_me' }
= f.check_box :remember_me, class: 'remember-me-checkbox'
%span Remember me
- .float-right.forgot-password
- = link_to "Forgot your password?", new_password_path(:user)
+ .float-right
+ - if unconfirmed_email?
+ = link_to _('Resend confirmation email'), new_user_confirmation_path
+ - else
+ = link_to _('Forgot your password?'), new_password_path(:user)
%div
- if captcha_enabled?
= recaptcha_tags
.submit-container.move-submit-down
- = f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' }
+ = f.submit _('Sign in'), class: 'btn btn-success', data: { qa_selector: 'sign_in_button' }
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index f856773526d..31c4bb0e33e 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -3,13 +3,13 @@
= form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user', class: "gl-show-field-errors") do
.form-group
= label_tag :username, "#{server['label']} Username"
- = text_field_tag :username, nil, { class: "form-control top qa-username-field", title: "This field is required.", autofocus: "autofocus", required: true }
+ = text_field_tag :username, nil, { class: "form-control top", title: "This field is required.", autofocus: "autofocus", data: { qa_selector: 'username_field' }, required: true }
.form-group
= label_tag :password
- = password_field_tag :password, nil, { class: "form-control bottom qa-password-field", title: "This field is required.", required: true }
+ = password_field_tag :password, nil, { class: "form-control bottom", title: "This field is required.", data: { qa_selector: 'password_field' }, required: true }
- if devise_mapping.rememberable?
.remember-me
%label{ for: "remember_me" }
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
- = submit_tag "Sign in", class: "btn-success btn qa-sign-in-button"
+ = submit_tag "Sign in", class: "btn-success btn", data: { qa_selector: 'sign_in_button' }
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 074edf645ba..2cd77af6877 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -5,6 +5,8 @@
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
+ - if Feature.enabled?(:invisible_captcha)
+ = invisible_captcha
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
= f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
diff --git a/app/views/devise/shared/_tabs_ldap.html.haml b/app/views/devise/shared/_tabs_ldap.html.haml
index b1a9470cf1c..db54c166a53 100644
--- a/app/views/devise/shared/_tabs_ldap.html.haml
+++ b/app/views/devise/shared/_tabs_ldap.html.haml
@@ -5,13 +5,13 @@
= render_if_exists "devise/shared/kerberos_tab"
- @ldap_servers.each_with_index do |server, i|
%li.nav-item
- = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))} qa-ldap-tab", 'data-toggle' => 'tab'
+ = link_to server['label'], "##{server['provider_name']}", class: "nav-link #{active_when(i.zero? && form_based_auth_provider_has_active_class?(:ldapmain))}", data: { toggle: 'tab', qa_selector: 'ldap_tab' }
= render_if_exists 'devise/shared/tab_smartcard'
- if password_authentication_enabled_for_web?
%li.nav-item
- = link_to 'Standard', '#login-pane', class: 'nav-link qa-standard-tab', 'data-toggle' => 'tab'
+ = link_to 'Standard', '#login-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'standard_tab' }
- if allow_signup?
%li.nav-item
- = link_to 'Register', '#register-pane', class: 'nav-link qa-register-tab', 'data-toggle' => 'tab'
+ = link_to 'Register', '#register-pane', class: 'nav-link', data: { toggle: 'tab', qa_selector: 'register_tab' }
diff --git a/app/views/devise/shared/_tabs_normal.html.haml b/app/views/devise/shared/_tabs_normal.html.haml
index ab8c22532fd..b6a1b8805ee 100644
--- a/app/views/devise/shared/_tabs_normal.html.haml
+++ b/app/views/devise/shared/_tabs_normal.html.haml
@@ -1,6 +1,6 @@
%ul.nav-links.new-session-tabs.nav-tabs.nav{ role: 'tablist' }
%li.nav-item{ role: 'presentation' }
- %a.nav-link.qa-sign-in-tab.active{ href: '#login-pane', data: { toggle: 'tab' }, role: 'tab' } Sign in
+ %a.nav-link.active{ href: '#login-pane', data: { toggle: 'tab', qa_selector: 'sign_in_tab' }, role: 'tab' } Sign in
- if allow_signup?
%li.nav-item{ role: 'presentation' }
- %a.nav-link.qa-register-tab{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab' }, role: 'tab' } Register
+ %a.nav-link{ href: '#register-pane', data: { track_label: 'sign_in_register', track_property: '', track_event: 'click_button', track_value: '', toggle: 'tab', qa_selector: 'register_tab' }, role: 'tab' } Register
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/explore/projects/_projects.html.haml b/app/views/explore/projects/_projects.html.haml
index 67f2f897137..35b32662b8a 100644
--- a/app/views/explore/projects/_projects.html.haml
+++ b/app/views/explore/projects/_projects.html.haml
@@ -1 +1,2 @@
-= render 'shared/projects/list', projects: projects, user: current_user
+- is_explore_page = defined?(explore_page) && explore_page
+= render 'shared/projects/list', projects: projects, user: current_user, explore_page: is_explore_page
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index ed508fa2506..153c90e534e 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -10,4 +10,4 @@
= render 'explore/head'
= render 'explore/projects/nav' unless Feature.enabled?(:project_list_filter_bar) && current_user
-= render 'projects', projects: @projects
+= render 'projects', projects: @projects, explore_page: true
diff --git a/app/views/groups/_group_admin_settings.html.haml b/app/views/groups/_group_admin_settings.html.haml
index b8f632d11d3..733cb36cc3d 100644
--- a/app/views/groups/_group_admin_settings.html.haml
+++ b/app/views/groups/_group_admin_settings.html.haml
@@ -17,6 +17,12 @@
= f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, @group.project_creation_level), {}, class: 'form-control'
.form-group.row
+ .col-sm-2.col-form-label
+ = f.label s_('SubgroupCreationlevel|Allowed to create subgroups')
+ .col-sm-10
+ = f.select :subgroup_creation_level, options_for_select(::Gitlab::Access.subgroup_creation_options, @group.subgroup_creation_level), {}, class: 'form-control'
+
+.form-group.row
.col-sm-2.col-form-label.pt-0
= f.label :require_two_factor_authentication, 'Two-factor authentication'
.col-sm-10
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 4daf3683eaf..e50d2b8e994 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -1,4 +1,5 @@
- can_create_subgroups = can?(current_user, :create_subgroup, @group)
+- emails_disabled = @group.emails_disabled?
.group-home-panel
.row.mb-3
@@ -21,7 +22,7 @@
.home-panel-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
.group-buttons
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn'
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn', emails_disabled: emails_disabled
- if can? current_user, :create_projects, @group
- new_project_label = _("New project")
- new_subgroup_label = _("New subgroup")
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 021c0b6c429..882fcc79421 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -1,39 +1,67 @@
-- page_title "Members"
+- page_title _("Members")
- can_manage_members = can?(current_user, :admin_group_member, @group)
+- show_invited_members = can_manage_members && @invited_members.exists?
+- pending_active = params[:search_invited].present?
.project-members-page.prepend-top-default
%h4
- Members
+ = _("Members")
%hr
- if can_manage_members
.project-members-new.append-bottom-default
%p.clearfix
- Add new member to
- %strong= @group.name
+ = _("Add new member to %{strong_start}%{group_name}%{strong_end}").html_safe % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
= render "new_group_member"
= render 'shared/members/requests', membership_source: @group, requesters: @requesters
= render_if_exists 'groups/group_members/ldap_sync'
- .clearfix
- %h5.member.existing-title
- Existing members
- .card
- .card-header.flex-project-members-panel
- %span.flex-project-title
- Members with access to
- %strong= @group.name
- %span.badge.badge-pill= @members.total_count
- = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
- .form-group
- .position-relative.append-right-8
- = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
- %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
- = icon("search")
- - if can_manage_members
- = render 'shared/members/filter_2fa_dropdown'
- = render 'shared/members/sort_dropdown'
- %ul.content-list.members-list
- = render partial: 'shared/members/member', collection: @members, as: :member
- = paginate @members, theme: 'gitlab'
+ %ul.nav-links.mobile-separator.nav.nav-tabs.clearfix
+ %li.nav-item
+ = link_to "#existing_members", class: ["nav-link", ("active" unless pending_active)] , 'data-toggle' => 'tab' do
+ %span
+ = _("Existing")
+ %span.badge.badge-pill= @members.total_count
+ - if show_invited_members
+ %li.nav-item
+ = link_to "#invited_members", class: ["nav-link", ("active" if pending_active)], 'data-toggle' => 'tab' do
+ %span
+ = _("Pending")
+ %span.badge.badge-pill= @invited_members.total_count
+
+ .tab-content
+ #existing_members.tab-pane{ :class => ("active" unless pending_active) }
+ .card.card-without-border
+ .d-flex.flex-column.flex-md-row.row-content-block.second-block
+ %span.flex-grow-1.align-self-md-center.col-form-label
+ = _("Members with access to %{strong_start}%{group_name}%{strong_end}").html_safe % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline user-search-form' do
+ .form-group.flex-grow
+ .position-relative.mr-md-2
+ = search_field_tag :search, params[:search], { placeholder: _('Search'), class: 'form-control', spellcheck: false }
+ %button.user-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") }
+ = icon("search")
+ - if can_manage_members
+ = label_tag '2fa', '2FA', class: 'col-form-label label-bold pr-md-2'
+ = render 'shared/members/filter_2fa_dropdown'
+ = render 'shared/members/sort_dropdown'
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: @members, as: :member
+ = paginate @members, theme: 'gitlab'
+
+ - if show_invited_members
+ #invited_members.tab-pane{ :class => ("active" if pending_active) }
+ .card.card-without-border
+ .d-flex.flex-column.flex-md-row.row-content-block.second-block
+ %span.flex-grow-1
+ = _("Members with pending access to %{strong_start}%{group_name}%{strong_end}").html_safe % { group_name: @group.name, strong_start: '<strong>'.html_safe, strong_end: '</strong>'.html_safe }
+ = form_tag group_group_members_path(@group), method: :get, class: 'form-inline user-search-form' do
+ .form-group
+ .position-relative.mr-md-2
+ = search_field_tag :search_invited, params[:search_invited], { placeholder: _('Search'), class: 'form-control', spellcheck: false }
+ %button.user-search-btn.border-left{ type: "submit", "aria-label" => _("Submit search") }
+ = icon("search")
+ %ul.content-list.members-list
+ = render partial: 'shared/members/member', collection: @invited_members, as: :member
+ = paginate @invited_members, param_name: 'invited_members_page', theme: 'gitlab'
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index f05e269553a..bf077eb09d2 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -1,4 +1,4 @@
-- @can_bulk_update = can?(current_user, :admin_issue, @group)
+- @can_bulk_update = can?(current_user, :admin_issue, @group) && @group.feature_available?(:group_bulk_edit)
- page_title "Issues"
= content_for :meta_tags do
@@ -13,7 +13,7 @@
= render 'shared/issuable/feed_buttons'
- if @can_bulk_update
- = render_if_exists 'shared/issuable/bulk_update_button'
+ = render_if_exists 'shared/issuable/bulk_update_button', type: :issues
= render 'shared/new_project_item_select', path: 'issues/new', label: "New issue", type: :issues, with_feature_enabled: 'issues', with_shared: false, include_projects_in_subgroups: true
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 808bb1309b1..0780fab513b 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -1,3 +1,5 @@
+- @can_bulk_update = can?(current_user, :admin_merge_request, @group) && @group.feature_available?(:group_bulk_edit)
+
- page_title "Merge Requests"
- if group_merge_requests_count(state: 'all').zero?
@@ -7,8 +9,14 @@
= render 'shared/issuable/nav', type: :merge_requests
- if current_user
.nav-controls
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/bulk_update_button', type: :merge_requests
+
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New merge request", type: :merge_requests, with_feature_enabled: 'merge_requests', with_shared: false, include_projects_in_subgroups: true
= render 'shared/issuable/search_bar', type: :merge_requests
+ - if @can_bulk_update
+ = render_if_exists 'shared/issuable/group_bulk_update_sidebar', group: @group, type: :merge_requests
+
= render 'shared/merge_requests'
diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml
index 5d211d0e186..d1eb6478997 100644
--- a/app/views/groups/settings/_advanced.html.haml
+++ b/app/views/groups/settings/_advanced.html.haml
@@ -23,20 +23,19 @@
= f.submit 'Change group path', class: 'btn btn-warning'
-- if supports_nested_groups?
- .sub-section
- %h4.warning-title Transfer group
- = form_for @group, url: transfer_group_path(@group), method: :put do |f|
- .form-group
- = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
- = hidden_field_tag 'new_parent_group_id'
+.sub-section
+ %h4.warning-title Transfer group
+ = form_for @group, url: transfer_group_path(@group), method: :put do |f|
+ .form-group
+ = dropdown_tag('Select parent group', options: { toggle_class: 'js-groups-dropdown', title: 'Parent Group', filter: true, dropdown_class: 'dropdown-open-top dropdown-group-transfer', placeholder: 'Search groups', data: { data: parent_group_options(@group) } })
+ = hidden_field_tag 'new_parent_group_id'
- %ul
- %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
- %li You can only transfer the group to a group you manage.
- %li You will need to update your local repositories to point to the new location.
- %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
- = f.submit 'Transfer group', class: 'btn btn-warning'
+ %ul
+ %li Be careful. Changing a group's parent can have unintended #{link_to 'side effects', 'https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths', target: 'blank'}.
+ %li You can only transfer the group to a group you manage.
+ %li You will need to update your local repositories to point to the new location.
+ %li If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
+ = f.submit 'Transfer group', class: 'btn btn-warning'
.sub-section
%h4.danger-title= _('Remove group')
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index 0da1f1ba7f5..94a938021f9 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -11,15 +11,23 @@
.form-check
= f.check_box :share_with_group_lock, disabled: !can_change_share_with_group_lock?(@group), class: 'form-check-input'
= f.label :share_with_group_lock, class: 'form-check-label' do
- %span
+ %span.d-block
- group_link = link_to @group.name, group_path(@group)
= s_('GroupSettings|Prevent sharing a project within %{group} with other groups').html_safe % { group: group_link }
- %br
%span.descr.text-muted= share_with_group_lock_help_text(@group)
+ .form-group.append-bottom-default
+ .form-check
+ = f.check_box :emails_disabled, checked: @group.emails_disabled?, disabled: !can_disable_group_emails?(@group), class: 'form-check-input'
+ = f.label :emails_disabled, class: 'form-check-label' do
+ %span.d-block= s_('GroupSettings|Disable email notifications')
+ %span.text-muted= s_('GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects.')
+
= render_if_exists 'groups/settings/ip_restriction', f: f, group: @group
+ = render_if_exists 'groups/settings/allowed_email_domain', f: f, group: @group
= render 'groups/settings/lfs', f: f
= render 'groups/settings/project_creation_level', f: f, group: @group
+ = render 'groups/settings/subgroup_creation_level', f: f, group: @group
= render 'groups/settings/two_factor_auth', f: f
= render_if_exists 'groups/member_lock_setting', f: f, group: @group
diff --git a/app/views/groups/settings/_subgroup_creation_level.html.haml b/app/views/groups/settings/_subgroup_creation_level.html.haml
new file mode 100644
index 00000000000..f36ad192bad
--- /dev/null
+++ b/app/views/groups/settings/_subgroup_creation_level.html.haml
@@ -0,0 +1,3 @@
+.form-group
+ = f.label s_('SubgroupCreationLevel|Allowed to create subgroups'), class: 'label-bold'
+ = f.select :subgroup_creation_level, options_for_select(::Gitlab::Access.subgroup_creation_options, group.subgroup_creation_level), {}, class: 'form-control'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index efb3815b257..a996c86a256 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -3,9 +3,9 @@
.modal-content
.modal-header
%h4.modal-title
- Keyboard Shortcuts
+ = _('Keyboard Shortcuts')
%small
- = link_to '(Show all)', '#', class: 'js-more-help-button'
+ = link_to _('(Show all)'), '#', class: 'js-more-help-button'
%button.close{ type: "button", "data-dismiss": "modal", "aria-label" => _('Close') }
%span{ "aria-hidden": true } &times;
.modal-body
@@ -15,281 +15,281 @@
%tbody
%tr
%th
- %th Global Shortcuts
+ %th= _('Global Shortcuts')
%tr
%td.shortcut
%kbd s
- %td Focus Search
+ %td= _('Focus Search')
%tr
%td.shortcut
%kbd f
- %td Focus Filter
- - if performance_bar_enabled?
- %tr
- %td.shortcut
- %kbd p b
- %td Show/hide the Performance Bar
+ %td= _('Focus Filter')
+ %tr
+ %td.shortcut
+ %kbd p
+ %kbd b
+ %td= _('Toggle the Performance Bar')
%tr
%td.shortcut
%kbd ?
- %td Show/hide this dialog
+ %td= _('Show/hide this dialog')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; shift p
- else
%kbd ctrl shift p
- %td Toggle Markdown preview
+ %td= _('Toggle Markdown preview')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-up
- %td Edit last comment (when focused on an empty textarea)
+ %td= _('Edit last comment (when focused on an empty textarea)')
%tr
%td.shortcut
%kbd shift t
%td
- Go to todos
+ = _('Go to todos')
%tr
%td.shortcut
%kbd shift a
%td
- Go to the activity feed
+ = _('Go to the activity feed')
%tr
%td.shortcut
%kbd shift p
%td
- Go to projects
+ = _('Go to projects')
%tr
%td.shortcut
%kbd shift i
%td
- Go to issues
+ = _('Go to issues')
%tr
%td.shortcut
%kbd shift m
%td
- Go to merge requests
+ = _('Go to merge requests')
%tr
%td.shortcut
%kbd shift g
%td
- Go to groups
+ = _('Go to groups')
%tr
%td.shortcut
%kbd shift l
%td
- Go to milestones
+ = _('Go to milestones')
%tr
%td.shortcut
%kbd shift s
%td
- Go to snippets
+ = _('Go to snippets')
%tbody
%tr
%th
- %th Finding Project File
+ %th= _('Finding Project File')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-up
- %td Move selection up
+ %td= _('Move selection up')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-down
- %td Move selection down
+ %td= _('Move selection down')
%tr
%td.shortcut
%kbd enter
- %td Open Selection
+ %td= _('Open Selection')
%tr
%td.shortcut
%kbd esc
- %td Go back
+ %td= _('Go back')
.col-lg-4
%table.shortcut-mappings
%tbody
%tr
%th
- %th Project
+ %th= _('Project')
%tr
%td.shortcut
%kbd g
%kbd p
%td
- Go to the project's overview page
+ = _('Go to the project\'s overview page')
%tr
%td.shortcut
%kbd g
%kbd v
%td
- Go to the project's activity feed
+ = _('Go to the project\'s activity feed')
%tr
%td.shortcut
%kbd g
%kbd f
%td
- Go to files
+ = _('Go to files')
%tr
%td.shortcut
%kbd g
%kbd c
%td
- Go to commits
+ = _('Go to commits')
%tr
%td.shortcut
%kbd g
%kbd j
%td
- Go to jobs
+ = _('Go to jobs')
%tr
%td.shortcut
%kbd g
%kbd n
%td
- Go to network graph
+ = _('Go to network graph')
%tr
%td.shortcut
%kbd g
%kbd d
%td
- Go to repository charts
+ = _('Go to repository charts')
%tr
%td.shortcut
%kbd g
%kbd i
%td
- Go to issues
+ = _('Go to issues')
%tr
%td.shortcut
%kbd g
%kbd b
%td
- Go to issue boards
+ = _('Go to issue boards')
%tr
%td.shortcut
%kbd g
%kbd m
%td
- Go to merge requests
+ = _('Go to merge requests')
%tr
%td.shortcut
%kbd g
%kbd e
%td
- Go to environments
+ = _('Go to environments')
%tr
%td.shortcut
%kbd g
%kbd l
%td
- Go to metrics
+ = _('Go to metrics')
%tr
%td.shortcut
%kbd g
%kbd k
%td
- Go to kubernetes
+ = _('Go to kubernetes')
%tr
%td.shortcut
%kbd g
%kbd s
%td
- Go to snippets
+ = _('Go to snippets')
%tr
%td.shortcut
%kbd g
%kbd w
%td
- Go to wiki
+ = _('Go to wiki')
%tr
%td.shortcut
%kbd t
- %td Go to finding file
+ %td= _('Go to finding file')
%tr
%td.shortcut
%kbd i
- %td New issue
+ %td= _('New issue')
%tbody
%tr
%th
- %th Project Files browsing
+ %th= _('Project Files browsing')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-up
- %td Move selection up
+ %td= _('Move selection up')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-down
- %td Move selection down
+ %td= _('Move selection down')
%tr
%td.shortcut
%kbd enter
- %td Open Selection
+ %td= _('Open Selection')
%tbody
%tr
%th
- %th Project File
+ %th= _('Project File')
%tr
%td.shortcut
%kbd y
- %td Go to file permalink
+ %td= _('Go to file permalink')
%tbody
%tr
%th
- %th Web IDE
+ %th= _('Web IDE')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; p
- else
%kbd ctrl p
- %td Go to file
+ %td= _('Go to file')
%tr
%td.shortcut
- if browser.platform.mac?
%kbd &#8984; enter
- else
%kbd ctrl enter
- %td Commit (when editing commit message)
+ %td= _('Commit (when editing commit message)')
.col-lg-4
%table.shortcut-mappings
- %tbody.hidden-shortcut{ style: 'display:none' }
+ %tbody.hidden-shortcut.network{ style: 'display:none' }
%tr
%th
- %th Network Graph
+ %th= _('Network Graph')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-left
\/
%kbd h
- %td Scroll left
+ %td= _('Scroll left')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-right
\/
%kbd l
- %td Scroll right
+ %td= _('Scroll right')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-up
\/
%kbd k
- %td Scroll up
+ %td= _('Scroll up')
%tr
%td.shortcut
%kbd
%i.fa.fa-arrow-down
\/
%kbd j
- %td Scroll down
+ %td= _('Scroll down')
%tr
%td.shortcut
%kbd
@@ -298,7 +298,7 @@
\/
%kbd
shift k
- %td Scroll to top
+ %td= _('Scroll to top')
%tr
%td.shortcut
%kbd
@@ -307,72 +307,80 @@
\/
%kbd
shift j
- %td Scroll to bottom
- %tbody.hidden-shortcut{ style: 'display:none' }
+ %td= _('Scroll to bottom')
+ %tbody.hidden-shortcut.issues{ style: 'display:none' }
%tr
%th
- %th Issues
+ %th= _('Issues')
%tr
%td.shortcut
%kbd a
- %td Change assignee
+ %td= _('Change assignee')
%tr
%td.shortcut
%kbd m
- %td Change milestone
+ %td= _('Change milestone')
%tr
%td.shortcut
%kbd r
- %td Reply (quoting selected text)
+ %td= _('Reply (quoting selected text)')
%tr
%td.shortcut
%kbd e
- %td Edit issue
+ %td= _('Edit issue')
%tr
%td.shortcut
%kbd l
- %td Change Label
- %tbody.hidden-shortcut{ style: 'display:none' }
+ %td= _('Change Label')
+ %tbody.hidden-shortcut.merge_requests{ style: 'display:none' }
%tr
%th
- %th Merge Requests
+ %th= _('Merge Requests')
%tr
%td.shortcut
%kbd a
- %td Change assignee
+ %td= _('Change assignee')
%tr
%td.shortcut
%kbd m
- %td Change milestone
+ %td= _('Change milestone')
%tr
%td.shortcut
%kbd r
- %td Reply (quoting selected text)
+ %td= _('Reply (quoting selected text)')
%tr
%td.shortcut
%kbd e
- %td Edit merge request
+ %td= _('Edit merge request')
%tr
%td.shortcut
%kbd l
- %td Change Label
+ %td= _('Change Label')
%tr
%td.shortcut
%kbd ]
\/
%kbd j
- %td Move to next file
+ %td= _('Move to next file')
%tr
%td.shortcut
%kbd [
\/
%kbd k
- %td Move to previous file
- %tbody.hidden-shortcut{ style: 'display:none' }
+ %td= _('Move to previous file')
+ %tr
+ %td.shortcut
+ %kbd n
+ %td= _('Move to next unresolved discussion')
+ %tr
+ %td.shortcut
+ %kbd p
+ %td= _('Move to previous unresolved discussion')
+ %tbody.hidden-shortcut.wiki{ style: 'display:none' }
%tr
%th
- %th Wiki pages
+ %th= _('Wiki pages')
%tr
%td.shortcut
%kbd e
- %td Edit wiki page
+ %td= _('Edit wiki page')
diff --git a/app/views/import/bitbucket_server/status.html.haml b/app/views/import/bitbucket_server/status.html.haml
index 40609fddbde..aac09801d91 100644
--- a/app/views/import/bitbucket_server/status.html.haml
+++ b/app/views/import/bitbucket_server/status.html.haml
@@ -17,8 +17,13 @@
= button_tag class: 'btn btn-import btn-success js-import-all' do
= _('Import all projects')
= icon('spinner spin', class: 'loading-icon')
- .btn-group
- = link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post)
+
+.btn-group
+ = link_to('Reconfigure', configure_import_bitbucket_server_path, class: 'btn btn-primary', method: :post)
+
+.input-btn-group.float-right
+ = form_tag status_import_bitbucket_server_path, :method => 'get' do
+ = text_field_tag :filter, sanitize(params[:filter]), class: 'form-control append-bottom-10', placeholder: _('Filter your projects by name'), size: 40, autoFocus: true
.table-responsive.prepend-top-10
%table.table.import-jobs
@@ -62,7 +67,7 @@
= text_field_tag :path, current_user.namespace_path, class: "input-group-text input-large form-control", tabindex: 1, disabled: true
%span.input-group-prepend
.input-group-text /
- = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, autofocus: true, required: true
+ = text_field_tag :path, sanitize_project_name(repo.slug), class: "input-mini form-control", tabindex: 2, required: true
%td.import-actions.job-status
= button_tag class: 'btn btn-import js-add-to-import' do
Import
diff --git a/app/views/layouts/_google_analytics.html.haml b/app/views/layouts/_google_analytics.html.haml
index 98ea96b0b77..e8a5359e791 100644
--- a/app/views/layouts/_google_analytics.html.haml
+++ b/app/views/layouts/_google_analytics.html.haml
@@ -1,11 +1,11 @@
--# haml-lint:disable InlineJavaScript
-:javascript
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']);
- _gaq.push(['_trackPageview']);
+= javascript_tag nonce: true do
+ :plain
+ var _gaq = _gaq || [];
+ _gaq.push(['_setAccount', '#{extra_config.google_analytics_id}']);
+ _gaq.push(['_trackPageview']);
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
+ (function() {
+ var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+ ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+ var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+ })();
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 20b844f9fd8..271b73326fa 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -40,7 +40,7 @@
= stylesheet_link_tag "highlight/themes/#{user_color_scheme}", media: "all"
- = Gon::Base.render_data
+ = Gon::Base.render_data(nonce: content_security_policy_nonce)
- if content_for?(:library_javascripts)
= yield :library_javascripts
@@ -56,6 +56,7 @@
= yield :project_javascripts
= csrf_meta_tags
+ = csp_meta_tag
- unless browser.safari?
%meta{ name: 'referrer', content: 'origin-when-cross-origin' }
@@ -78,4 +79,3 @@
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
= render_if_exists 'layouts/snowplow'
- = render_if_exists 'layouts/pendo' if Feature.enabled?(:pendo_tracking) && !Rails.env.test?
diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml
index 240e03a5d53..82ec92988eb 100644
--- a/app/views/layouts/_init_auto_complete.html.haml
+++ b/app/views/layouts/_init_auto_complete.html.haml
@@ -4,8 +4,8 @@
- datasources = autocomplete_data_sources(object, noteable_type)
- if object
- -# haml-lint:disable InlineJavaScript
- :javascript
- gl = window.gl || {};
- gl.GfmAutoComplete = gl.GfmAutoComplete || {};
- gl.GfmAutoComplete.dataSources = #{datasources.to_json};
+ = javascript_tag nonce: true do
+ :plain
+ gl = window.gl || {};
+ gl.GfmAutoComplete = gl.GfmAutoComplete || {};
+ gl.GfmAutoComplete.dataSources = #{datasources.to_json};
diff --git a/app/views/layouts/_init_client_detection_flags.html.haml b/app/views/layouts/_init_client_detection_flags.html.haml
index c729f8aa696..6537b86085f 100644
--- a/app/views/layouts/_init_client_detection_flags.html.haml
+++ b/app/views/layouts/_init_client_detection_flags.html.haml
@@ -1,7 +1,7 @@
- client = client_js_flags
- if client
- -# haml-lint:disable InlineJavaScript
- :javascript
- gl = window.gl || {};
- gl.client = #{client.to_json};
+ = javascript_tag nonce: true do
+ :plain
+ gl = window.gl || {};
+ gl.client = #{client.to_json};
diff --git a/app/views/layouts/_piwik.html.haml b/app/views/layouts/_piwik.html.haml
index 473b14ce626..2cb2e23433d 100644
--- a/app/views/layouts/_piwik.html.haml
+++ b/app/views/layouts/_piwik.html.haml
@@ -1,15 +1,15 @@
<!-- Piwik -->
--# haml-lint:disable InlineJavaScript
-:javascript
- var _paq = _paq || [];
- _paq.push(['trackPageView']);
- _paq.push(['enableLinkTracking']);
- (function() {
- var u="//#{extra_config.piwik_url}/";
- _paq.push(['setTrackerUrl', u+'piwik.php']);
- _paq.push(['setSiteId', "#{extra_config.piwik_site_id}"]);
- var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
- g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
- })();
-<noscript><p><img src="//#{extra_config.piwik_url}/piwik.php?idsite=#{extra_config.piwik_site_id}" style="border:0;" alt="" /></p></noscript>
-<!-- End Piwik Code -->
+= javascript_tag nonce: true do
+ :plain
+ var _paq = _paq || [];
+ _paq.push(['trackPageView']);
+ _paq.push(['enableLinkTracking']);
+ (function() {
+ var u="//#{extra_config.piwik_url}/";
+ _paq.push(['setTrackerUrl', u+'piwik.php']);
+ _paq.push(['setSiteId', "#{extra_config.piwik_site_id}"]);
+ var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
+ g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
+ })();
+ <noscript><p><img src="//#{extra_config.piwik_url}/piwik.php?idsite=#{extra_config.piwik_site_id}" style="border:0;" alt="" /></p></noscript>
+ <!-- End Piwik Code -->
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index c62dce880c0..e6a235e39da 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -13,7 +13,8 @@
tabindex: '1',
autocomplete: 'off',
data: { issues_path: issues_dashboard_path,
- mr_path: merge_requests_dashboard_path },
+ mr_path: merge_requests_dashboard_path,
+ qa_selector: 'search_term_field' },
aria: { label: _('Search or jump to…') }
%button.hidden.js-dropdown-search-toggle{ type: 'button', data: { toggle: 'dropdown' } }
.dropdown-menu.dropdown-select.js-dashboard-search-options
@@ -45,5 +46,7 @@
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
+ = hidden_field_tag :nav_source, 'navbar'
+ -# workaround for non-JS feature specs, for JS you need to use find('#search').send_keys(:enter)
= button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{ :'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
diff --git a/app/views/layouts/_snowplow.html.haml b/app/views/layouts/_snowplow.html.haml
new file mode 100644
index 00000000000..5f5c5e984c5
--- /dev/null
+++ b/app/views/layouts/_snowplow.html.haml
@@ -0,0 +1,29 @@
+- return unless Gitlab::CurrentSettings.snowplow_enabled?
+
+= javascript_tag nonce: true do
+ :plain
+ ;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
+ p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
+ };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
+ n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
+
+ window.snowplow('newTracker', '#{Gitlab::SnowplowTracker::NAMESPACE}', '#{Gitlab::CurrentSettings.snowplow_collector_hostname}', {
+ appId: '#{Gitlab::CurrentSettings.snowplow_site_id}',
+ cookieDomain: '#{Gitlab::CurrentSettings.snowplow_cookie_domain}',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ post: true,
+ contexts: { webPage: true },
+ stateStorageStrategy: "localStorage"
+ });
+
+ window.snowplow('enableActivityTracking', 30, 30);
+ window.snowplow('trackPageView');
+
+- return unless Feature.enabled?(:additional_snowplow_tracking, @group)
+
+= javascript_tag nonce: true do
+ :plain
+ window.snowplow('enableFormTracking');
+ window.snowplow('enableLinkClickTracking');
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index 06069a72951..dc924a0e25d 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -8,12 +8,16 @@
%body
.page-container
= yield
- -# haml-lint:disable InlineJavaScript
- :javascript
- (function(){
- var goBackElement = document.querySelector('.js-go-back');
+ = javascript_tag nonce: true do
+ :plain
+ (function(){
+ var goBackElement = document.querySelector('.js-go-back');
- if (goBackElement && history.length > 1) {
- goBackElement.style.display = 'block';
- }
- }());
+ if (goBackElement && history.length > 1) {
+ goBackElement.removeAttribute('hidden');
+
+ goBackElement.querySelector('button').addEventListener('click', function() {
+ history.back();
+ });
+ }
+ }());
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 1d40b78fa83..49de821f1c2 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -6,8 +6,8 @@
- content_for :page_specific_javascripts do
- if current_user
- -# haml-lint:disable InlineJavaScript
- :javascript
- window.uploads_path = "#{group_uploads_path(@group)}";
+ = javascript_tag nonce: true do
+ :plain
+ window.uploads_path = "#{group_uploads_path(@group)}";
= render template: "layouts/application"
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 45fc39dbbdb..808290afcad 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -20,8 +20,8 @@
= link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username }
- if current_user_menu?(:settings)
%li
- = link_to s_("CurrentUser|Settings"), profile_path
+ = link_to s_("CurrentUser|Settings"), profile_path, data: { qa_selector: 'settings_link' }
- if current_user_menu?(:sign_out)
%li.divider
%li
- = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link qa-sign-out-link"
+ = link_to _("Sign out"), destroy_user_session_path, class: "sign-out-link", data: { qa_selector: 'sign_out_link' }
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/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 5643a508ddc..41d7aa3741a 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -2,6 +2,8 @@
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
+ %li
+ = link_to _("Support"), support_url
= render_if_exists "shared/learn_gitlab_menu_item"
%li.divider
%li
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index cbe713b7468..ff0c5b241b2 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -32,6 +32,8 @@
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link' do
= _('Snippets')
+ = render_if_exists 'layouts/nav/sidebar/analytics_link'
+
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
%li.header-more.dropdown.d-xl-none{ class: ('d-lg-none' unless has_extra_nav_icons?) }
%a{ href: "#", data: { toggle: "dropdown" } }
@@ -53,6 +55,9 @@
= nav_link(controller: 'dashboard/snippets') do
= link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets' do
= _('Snippets')
+
+ = render_if_exists 'layouts/nav/sidebar/analytics_more_link'
+
%li.dropdown.d-lg-none
= render_if_exists 'dashboard/operations/nav_link_list'
- if can?(current_user, :read_instance_statistics)
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 87133c7ba22..cb39c830170 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -236,7 +236,7 @@
%span
= _('General')
= nav_link(path: 'application_settings#integrations') do
- = link_to integrations_admin_application_settings_path, title: _('Integrations') do
+ = link_to integrations_admin_application_settings_path, title: _('Integrations'), data: { qa_selector: 'integration_settings_link' } do
%span
= _('Integrations')
= nav_link(path: 'application_settings#repository') do
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 4b5ccc33716..48c9f19f89f 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -36,8 +36,6 @@
%span
= _('Activity')
- = render_if_exists 'groups/sidebar/security_dashboard' # EE-specific
-
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
= link_to group_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right' } do
@@ -105,6 +103,8 @@
= _('Merge Requests')
%span.badge.badge-pill.count.merge_counter.js-merge-counter.fly-out-badge= number_with_delimiter(merge_requests_count)
+ = render_if_exists "layouts/nav/ee/security_link" # EE-specific
+
- if group_sidebar_link?(:kubernetes)
= nav_link(controller: [:clusters]) do
= link_to group_clusters_path(@group) do
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index a9af5ba5008..02ecf816e90 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -9,7 +9,7 @@
= @project.name
%ul.sidebar-top-level-items
= nav_link(path: sidebar_projects_paths, html_options: { class: 'home' }) do
- = link_to project_path(@project), class: 'shortcuts-project qa-link-project' do
+ = link_to project_path(@project), class: 'shortcuts-project rspec-project-link', data: { qa_selector: 'project_link' } do
.nav-icon-container
= sprite_icon('home')
%span.nav-item-name
@@ -26,7 +26,7 @@
%span= _('Details')
= nav_link(path: 'projects#activity') do
- = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity qa-activity-link' do
+ = link_to activity_project_path(@project), title: _('Activity'), class: 'shortcuts-project-activity', data: { qa_selector: 'activity_link' } do
%span= _('Activity')
- if project_nav_tab?(:releases)
@@ -34,10 +34,6 @@
= link_to project_releases_path(@project), title: _('Releases'), class: 'shortcuts-project-releases' do
%span= _('Releases')
- = render_if_exists 'projects/sidebar/security_dashboard'
-
- = render_if_exists 'projects/sidebar/dependencies'
-
- if can?(current_user, :read_cycle_analytics, @project)
= nav_link(path: 'cycle_analytics#show') do
= link_to project_cycle_analytics_path(@project), title: _('Cycle Analytics'), class: 'shortcuts-project-cycle-analytics' do
@@ -150,7 +146,7 @@
- if project_nav_tab? :merge_requests
= nav_link(controller: @project.issues_enabled? ? :merge_requests : [:merge_requests, :labels, :milestones]) do
- = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests qa-merge-requests-link' do
+ = link_to project_merge_requests_path(@project), class: 'shortcuts-merge_requests', data: { qa_selector: 'merge_requests_link' } do
.nav-icon-container
= sprite_icon('git-merge')
%span.nav-item-name#js-onboarding-mr-link
@@ -167,7 +163,7 @@
- if project_nav_tab? :pipelines
= nav_link(controller: [:pipelines, :builds, :jobs, :pipeline_schedules, :artifacts]) do
- = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines' do
+ = link_to project_pipelines_path(@project), class: 'shortcuts-pipelines qa-link-pipelines rspec-link-pipelines' do
.nav-icon-container
= sprite_icon('rocket')
%span.nav-item-name#js-onboarding-pipelines-link
@@ -203,6 +199,8 @@
%span
= _('Charts')
+ = render_if_exists 'layouts/nav/sidebar/project_security_link' # EE-specific
+
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
= link_to sidebar_operations_link_path, class: 'shortcuts-operations qa-link-operations' do
@@ -274,25 +272,12 @@
= render_if_exists 'layouts/nav/sidebar/project_feature_flags_link'
- - if project_nav_tab? :container_registry
- = nav_link(controller: %w[projects/registry/repositories]) do
- = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry' do
- .nav-icon-container
- = sprite_icon('disk')
- %span.nav-item-name
- = _('Registry')
- %ul.sidebar-sub-level-items.is-fly-out-only
- = nav_link(controller: %w[projects/registry/repositories], html_options: { class: "fly-out-top-item" } ) do
- = link_to project_container_registry_index_path(@project) do
- %strong.fly-out-top-item-name
- = _('Registry')
-
= render_if_exists 'layouts/nav/sidebar/project_packages_link'
- if project_nav_tab? :wiki
- wiki_url = project_wiki_path(@project, :home)
= nav_link(controller: :wikis) do
- = link_to wiki_url, class: 'shortcuts-wiki qa-wiki-link' do
+ = link_to wiki_url, class: 'shortcuts-wiki', data: { qa_selector: 'wiki_link' } do
.nav-icon-container
= sprite_icon('book')
%span.nav-item-name
diff --git a/app/views/layouts/nav/sidebar/_project_packages_link.html.haml b/app/views/layouts/nav/sidebar/_project_packages_link.html.haml
new file mode 100644
index 00000000000..0fdfc6cd2ab
--- /dev/null
+++ b/app/views/layouts/nav/sidebar/_project_packages_link.html.haml
@@ -0,0 +1,16 @@
+- if project_nav_tab? :container_registry
+ = nav_link controller: :repositories do
+ = link_to project_container_registry_index_path(@project) do
+ .nav-icon-container
+ = sprite_icon('package')
+ %span.nav-item-name
+ = _('Packages')
+ %ul.sidebar-sub-level-items
+ = nav_link(controller: :repositories, html_options: { class: "fly-out-top-item" } ) do
+ = link_to project_container_registry_index_path(@project) do
+ %strong.fly-out-top-item-name
+ = _('Packages')
+ %li.divider.fly-out-top-item
+ = nav_link controller: :repositories do
+ = link_to project_container_registry_index_path(@project), class: 'shortcuts-container-registry', title: _('Container Registry') do
+ %span= _('Container Registry')
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 6b51483810e..b8ef38272fc 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -7,8 +7,8 @@
- content_for :project_javascripts do
- project = @target_project || @project
- if current_user
- -# haml-lint:disable InlineJavaScript
- :javascript
- window.uploads_path = "#{project_uploads_path(project)}";
+ = javascript_tag nonce: true do
+ :plain
+ window.uploads_path = "#{project_uploads_path(project)}";
= render template: "layouts/application"
diff --git a/app/views/layouts/snippets.html.haml b/app/views/layouts/snippets.html.haml
index 841b2a5e79c..cde2b467392 100644
--- a/app/views/layouts/snippets.html.haml
+++ b/app/views/layouts/snippets.html.haml
@@ -3,8 +3,8 @@
- content_for :page_specific_javascripts do
- if snippets_upload_path
- -# haml-lint:disable InlineJavaScript
- :javascript
- window.uploads_path = "#{snippets_upload_path}";
+ = javascript_tag nonce: true do
+ :plain
+ window.uploads_path = "#{snippets_upload_path}";
= render template: "layouts/application"
diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml
index 2ddea0b9f16..8fecdc6e8a6 100644
--- a/app/views/notify/new_merge_request_email.html.haml
+++ b/app/views/notify/new_merge_request_email.html.haml
@@ -5,7 +5,7 @@
.branch
= merge_path_description(@merge_request, 'to')
.author
- Author #{@merge_request.author_name}
+ Author: #{@merge_request.author_name}
.assignee
= assignees_label(@merge_request)
.approvers
diff --git a/app/views/peek/_bar.html.haml b/app/views/peek/_bar.html.haml
index 89d3b931f88..5228930293c 100644
--- a/app/views/peek/_bar.html.haml
+++ b/app/views/peek/_bar.html.haml
@@ -2,6 +2,5 @@
#js-peek{ data: { env: Peek.env,
request_id: Peek.request_id,
- peek_url: "#{peek_routes_path}/results",
- profile_url: url_for(safe_params.merge(lineprofiler: 'true')) },
+ peek_url: "#{peek_routes_path}/results" },
class: Peek.env }
diff --git a/app/views/peek/views/_gc.html.haml b/app/views/peek/views/_gc.html.haml
deleted file mode 100644
index 2a586261ce1..00000000000
--- a/app/views/peek/views/_gc.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- local_assigns.fetch(:view)
-
-%span.bold
- %span{ title: _('Invoke Time'), data: { defer_to: "#{view.defer_key}-gc_time" } }...
- \/
- %span{ title: _('Invoke Count'), data: { defer_to: "#{view.defer_key}-invokes" } }...
-gc
diff --git a/app/views/peek/views/_redis.html.haml b/app/views/peek/views/_redis.html.haml
deleted file mode 100644
index f7fba6c95fc..00000000000
--- a/app/views/peek/views/_redis.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- local_assigns.fetch(:view)
-
-%span.bold
- %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
- \/
- %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
-redis
diff --git a/app/views/peek/views/_sidekiq.html.haml b/app/views/peek/views/_sidekiq.html.haml
deleted file mode 100644
index 7efbc05890d..00000000000
--- a/app/views/peek/views/_sidekiq.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- local_assigns.fetch(:view)
-
-%span.bold
- %span{ data: { defer_to: "#{view.defer_key}-duration" } }...
- \/
- %span{ data: { defer_to: "#{view.defer_key}-calls" } }...
-sidekiq
diff --git a/app/views/profiles/notifications/_group_settings.html.haml b/app/views/profiles/notifications/_group_settings.html.haml
index cf17ee44145..1776d260e19 100644
--- a/app/views/profiles/notifications/_group_settings.html.haml
+++ b/app/views/profiles/notifications/_group_settings.html.haml
@@ -1,16 +1,15 @@
+- emails_disabled = group.emails_disabled?
+
.gl-responsive-table-row.notification-list-item
.table-section.section-40
%span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+ = notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
= link_to group.name, group_path(group)
.table-section.section-30.text-right
- = render 'shared/notifications/button', notification_setting: setting
+ = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
.table-section.section-30
= form_for @user.notification_settings.find { |ns| ns.source == group }, url: profile_notifications_group_path(group), method: :put, html: { class: 'update-notifications' } do |f|
diff --git a/app/views/profiles/notifications/_project_settings.html.haml b/app/views/profiles/notifications/_project_settings.html.haml
index 823fec3fab4..63a77b335b6 100644
--- a/app/views/profiles/notifications/_project_settings.html.haml
+++ b/app/views/profiles/notifications/_project_settings.html.haml
@@ -1,12 +1,11 @@
+- emails_disabled = project.emails_disabled?
+
%li.notification-list-item
%span.notification.fa.fa-holder.append-right-5
- - if setting.global?
- = notification_icon(current_user.global_notification_setting.level)
- - else
- = notification_icon(setting.level)
+ = notification_icon(notification_icon_level(setting, emails_disabled))
%span.str-truncated
= link_to_project(project)
.float-right
- = render 'shared/notifications/button', notification_setting: setting
+ = render 'shared/notifications/button', notification_setting: setting, emails_disabled: emails_disabled
diff --git a/app/views/profiles/personal_access_tokens/index.html.haml b/app/views/profiles/personal_access_tokens/index.html.haml
index 65ef9690062..08a39fc4f58 100644
--- a/app/views/profiles/personal_access_tokens/index.html.haml
+++ b/app/views/profiles/personal_access_tokens/index.html.haml
@@ -31,7 +31,7 @@
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.feed-token-reset
= label_tag :feed_token, s_('AccessTokens|Feed token'), class: "label-bold"
- = text_field_tag :feed_token, current_user.feed_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ = text_field_tag :feed_token, current_user.feed_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :feed_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any RSS or calendar URLs currently in use will stop working.') }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can read activity and issue RSS feeds or your calendar feed as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link }
@@ -49,7 +49,7 @@
= s_('AccessTokens|It cannot be used to access any other data.')
.col-lg-8.incoming-email-token-reset
= label_tag :incoming_email_token, s_('AccessTokens|Incoming email token'), class: "label-bold"
- = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control', readonly: true, onclick: 'this.select()'
+ = text_field_tag :incoming_email_token, current_user.incoming_email_token, class: 'form-control js-select-on-focus', readonly: true
%p.form-text.text-muted
- reset_link = link_to s_('AccessTokens|reset it'), [:reset, :incoming_email_token, :profile], method: :put, data: { confirm: s_('AccessTokens|Are you sure? Any issue email addresses currently in use will stop working.') }
- reset_message = s_('AccessTokens|Keep this token secret. Anyone who gets ahold of it can create issues as if they were you. You should %{link_reset_it} if that ever happens.') % { link_reset_it: reset_link }
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index d16e2dddbe0..d99063e344f 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -45,20 +45,20 @@
.form-group
= f.label :layout, class: 'label-bold' do
= s_('Preferences|Layout width')
- = f.select :layout, layout_choices, {}, class: 'form-control'
+ = f.select :layout, layout_choices, {}, class: 'select2'
.form-text.text-muted
= s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
= s_('Preferences|Default dashboard')
- = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ = f.select :dashboard, dashboard_choices, {}, class: 'select2'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
= s_('Preferences|Project overview content')
- = f.select :project_view, project_view_choices, {}, class: 'form-control'
+ = f.select :project_view, project_view_choices, {}, class: 'select2'
.form-text.text-muted
= s_('Preferences|Choose what content you want to see on a project’s overview page.')
@@ -82,7 +82,7 @@
.form-group
= f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week')
- = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
+ = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'select2'
- if Feature.enabled?(:user_time_settings)
.col-sm-12
%hr
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index e36d5192a29..ffb90bbd354 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -89,10 +89,10 @@
.col-lg-8
.row
- if @user.read_only_attribute?(:name)
- = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name' },
+ = f.text_field :name, required: true, readonly: true, wrapper: { class: 'col-md-9 qa-full-name rspec-full-name' },
help: s_("Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you") % { provider_label: attribute_provider_label(:name) }
- else
- = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
+ = f.text_field :name, label: s_('Profiles|Full name'), required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name rspec-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: s_('Profiles|User ID'), wrapper: { class: 'col-md-3' }
= render_if_exists 'profiles/email_settings', form: f
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index 1056977886a..e42772c2dd9 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -15,6 +15,7 @@
%li= _('Project configuration, including services')
%li= _('Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities')
%li= _('LFS objects')
+ %li= _('Issue Boards')
%p= _('The following items will NOT be exported:')
%ul
%li= _('Job traces and artifacts')
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index d95045c9cce..f9222387e97 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -5,7 +5,7 @@
- if current_user && can?(current_user, :download_code, project)
= render 'shared/no_ssh'
= render 'shared/no_password'
- = render_if_exists 'shared/shared_runners_minutes_limit', project: project
- unless project.empty_repo?
= render 'shared/auto_devops_implicitly_enabled_banner', project: project
= render_if_exists 'projects/above_size_limit_warning', project: project
+ = render_if_exists 'shared/shared_runners_minutes_limit', project: project, classes: [container_class, ("limit-container-width" unless fluid_layout)]
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 824fe3c791d..4783b10cf6d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,6 +1,8 @@
- empty_repo = @project.empty_repo?
- show_auto_devops_callout = show_auto_devops_callout?(@project)
- max_project_topic_length = 15
+- emails_disabled = @project.emails_disabled?
+
.project-home-panel{ class: [("empty-project" if empty_repo), ("js-keep-hidden-on-navigation" if vue_file_list_enabled?)] }
.row.append-bottom-8
.home-panel-title-row.col-md-12.col-lg-6.d-flex
@@ -41,7 +43,7 @@
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
- if current_user
.d-inline-flex
- = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs'
+ = render 'shared/notifications/new_button', notification_setting: @notification_setting, btn_class: 'btn-xs', emails_disabled: emails_disabled
.count-buttons.d-inline-flex
= render 'projects/buttons/star'
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
index 090d1549aa7..02e5297528b 100644
--- a/app/views/projects/buttons/_star.html.haml
+++ b/app/views/projects/buttons/_star.html.haml
@@ -8,7 +8,8 @@
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center
- = @project.star_count
+ = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
+ = @project.star_count
- else
.count-badge.d-inline-flex.align-item-stretch.append-right-8
@@ -16,4 +17,5 @@
= sprite_icon('star-o', { css_class: 'icon' })
%span= s_('ProjectOverview|Star')
%span.star-count.count-badge-count.d-flex.align-items-center
- = @project.star_count
+ = link_to project_starrers_path(@project), title: n_(s_('ProjectOverview|Starrer'), s_('ProjectOverview|Starrers'), @project.star_count), class: 'count' do
+ = @project.star_count
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/edit.html.haml b/app/views/projects/edit.html.haml
index 3403564992e..763cc764144 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -23,7 +23,7 @@
.js-project-permissions-form
= f.submit _('Save changes'), class: "btn btn-success"
-%section.qa-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
+%section.qa-merge-request-settings.rspec-merge-request-settings.settings.merge-requests-feature.no-animate#js-merge-request-settings{ class: [('expanded' if expanded), ('hidden' if @project.project_feature.send(:merge_requests_access_level) == 0)] }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Merge requests')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }= expanded ? _('Collapse') : _('Expand')
@@ -35,7 +35,7 @@
= form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "merge-request-settings-form js-mr-settings-form" }, authenticity_token: true do |f|
%input{ name: 'update_section', type: 'hidden', value: 'js-merge-request-settings' }
= render 'projects/merge_request_settings', form: f
- = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes"
+ = f.submit _('Save changes'), class: "btn btn-success qa-save-merge-request-changes rspec-save-merge-request-changes"
= render_if_exists 'projects/merge_request_approvals_settings', expanded: expanded
diff --git a/app/views/projects/forks/_fork_button.html.haml b/app/views/projects/forks/_fork_button.html.haml
index 3f0798a898d..c7ed6a5094d 100644
--- a/app/views/projects/forks/_fork_button.html.haml
+++ b/app/views/projects/forks/_fork_button.html.haml
@@ -5,9 +5,9 @@
.bordered-box.fork-thumbnail.text-center.prepend-left-default.append-right-default.prepend-top-default.append-bottom-default.forked
= link_to project_path(forked_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = group_icon(namespace, class: "avatar rect-avatar s100 identicon")
+ = group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto")
- else
- .avatar-container.s100
+ .avatar-container.s100.mx-auto
= image_tag(avatar, class: "avatar s100")
%h5.prepend-top-default
= namespace.human_name
@@ -18,9 +18,9 @@
class: ("disabled has-tooltip" unless can_create_project),
title: (_('You have reached your project limit') unless can_create_project) do
- if /no_((\w*)_)*avatar/.match(avatar)
- = group_icon(namespace, class: "avatar rect-avatar s100 identicon")
+ = group_icon(namespace, class: "avatar rect-avatar s100 identicon mx-auto")
- else
- .avatar-container.s100
+ .avatar-container.s100.mx-auto
= image_tag(avatar, class: "avatar s100")
%h5.prepend-top-default
= namespace.human_name
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 4759991449e..de0c21e7cf6 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -45,7 +45,7 @@
%ul.controls
- if issue.closed?
%li.issuable-status
- CLOSED
+ = _('CLOSED')
- if issue.assignees.any?
%li
= render 'shared/issuable/assignees', project: @project, issuable: issue
@@ -53,4 +53,5 @@
= render 'shared/issuable_meta_data', issuable: issue
.float-right.issuable-updated-at.d-none.d-sm-inline-block
- %span updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
+ %span
+ = _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago') }
diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml
index 81a53f22f67..a3688c17041 100644
--- a/app/views/projects/jobs/show.html.haml
+++ b/app/views/projects/jobs/show.html.haml
@@ -7,8 +7,10 @@
= stylesheet_link_tag 'page_bundles/xterm'
%div{ class: container_class }
- #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json),
+ #js-job-vue-app{ data: { endpoint: project_job_path(@project, @build, format: :json), project_path: @project.full_path,
deployment_help_url: help_page_path('user/project/clusters/index.html', anchor: 'troubleshooting-failed-deployment-jobs'),
runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner'),
runner_settings_url: project_runners_path(@build.project, anchor: 'js-runners-settings'),
+ variables_settings_url: project_variables_path(@build.project, anchor: 'js-cicd-variables-settings'),
+ page_path: project_job_path(@project, @build), build_status: @build.status, build_stage: @build.stage, log_state: '',
build_options: javascript_build_options } }
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index dee3931ff04..36f19ee6175 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -41,11 +41,11 @@
%ul.controls.d-flex.align-items-end
- if merge_request.merged?
%li.issuable-status.d-none.d-sm-inline-block
- MERGED
+ = _('MERGED')
- elsif merge_request.closed?
%li.issuable-status.d-none.d-sm-inline-block
= icon('ban')
- CLOSED
+ = _('CLOSED')
- if can?(current_user, :read_pipeline, merge_request.head_pipeline)
%li.issuable-pipeline-status.d-none.d-sm-flex
= render 'ci/status/icon', status: merge_request.head_pipeline.detailed_status(current_user), option_css_classes: 'd-flex'
@@ -61,4 +61,5 @@
= render 'shared/issuable_meta_data', issuable: merge_request
.float-right.issuable-updated-at.d-none.d-sm-inline-block
- %span updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
+ %span
+ = _('updated %{time_ago}').html_safe % { time_ago: time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago') }
diff --git a/app/views/projects/merge_requests/creations/_new_compare.html.haml b/app/views/projects/merge_requests/creations/_new_compare.html.haml
index 11272a67f93..be01905dd35 100644
--- a/app/views/projects/merge_requests/creations/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_compare.html.haml
@@ -2,6 +2,8 @@
New Merge Request
= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: project_new_merge_request_path(@project), method: :get, html: { class: "merge-request-form js-requires-input" } do |f|
+ - if params[:nav_source].present?
+ = hidden_field_tag(:nav_source, params[:nav_source])
.hide.alert.alert-danger.mr-compare-errors
.js-merge-request-new-compare.row{ 'data-source-branch-url': project_new_merge_request_branch_from_path(@source_project), 'data-target-branch-url': project_new_merge_request_branch_to_path(@source_project) }
.col-lg-6
diff --git a/app/views/projects/merge_requests/creations/_new_submit.html.haml b/app/views/projects/merge_requests/creations/_new_submit.html.haml
index 464f8fa65e9..543441b9479 100644
--- a/app/views/projects/merge_requests/creations/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/creations/_new_submit.html.haml
@@ -17,6 +17,9 @@
= f.hidden_field :target_project_id
= f.hidden_field :target_branch, id: ''
+ - if params[:nav_source].present?
+ = hidden_field_tag(:nav_source, params[:nav_source])
+
.mr-compare.merge-request.js-merge-request-new-submit{ 'data-mr-submit-action': "#{j params[:tab].presence || 'new'}" }
- if @commits.empty?
.commits-empty
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index 2084ca6f905..af3bd8dcd69 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -16,13 +16,13 @@
- if @merge_request.source_branch_exists?
= render "projects/merge_requests/how_to_merge"
- -# haml-lint:disable InlineJavaScript
- :javascript
- window.gl = window.gl || {};
- window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget', issues_links: true)}
+ = javascript_tag nonce: true do
+ :plain
+ window.gl = window.gl || {};
+ window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget', issues_links: true)}
- window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
- window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/index.md', anchor: 'troubleshooting')}';
+ window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
+ window.gl.mrWidgetData.troubleshooting_docs_path = '#{help_page_path('user/project/merge_requests/index.md', anchor: 'troubleshooting')}';
#js-vue-mr-widget.mr-widget
@@ -32,7 +32,7 @@
.merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') }
.merge-request-tabs-container
%ul.merge-request-tabs.nav-tabs.nav.nav-links
- %li.notes-tab.qa-notes-tab
+ %li.notes-tab{ data: { qa_selector: 'notes_tab'} }
= tab_link_for @merge_request, :show, force_link: @commit.present? do
= _("Discussion")
%span.badge.badge-pill= @merge_request.related_notes.user.count
diff --git a/app/views/projects/mirrors/_disabled_mirror_badge.html.haml b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
index 356cb43f07f..9c11b650f75 100644
--- a/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
+++ b/app/views/projects/mirrors/_disabled_mirror_badge.html.haml
@@ -1 +1 @@
-.badge.badge-warning.qa-disabled-mirror-badge{ data: { toggle: 'tooltip', html: 'true' }, title: _('Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them.') }= _('Disabled')
+.badge.badge-warning.qa-disabled-mirror-badge.rspec-disabled-mirror-badge{ data: { toggle: 'tooltip', html: 'true' }, title: _('Disabled mirrors can only be enabled by instance owners. It is recommended that you delete them.') }= _('Disabled')
diff --git a/app/views/projects/mirrors/_instructions.html.haml b/app/views/projects/mirrors/_instructions.html.haml
index 33e5a6e67c3..1a163cc4a54 100644
--- a/app/views/projects/mirrors/_instructions.html.haml
+++ b/app/views/projects/mirrors/_instructions.html.haml
@@ -2,7 +2,7 @@
%ul
%li
= _('The repository must be accessible over <code>http://</code>,
- <code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
+ <code>https://</code>, <code>ssh://</code> or <code>git://</code>.').html_safe
%li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
%li
- minutes = Gitlab.config.gitlab_shell.git_timeout / 60
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index e68fa5d08c7..eb100e5cf47 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -43,16 +43,19 @@
= _('Mirrored repositories')
= render_if_exists 'projects/mirrors/mirrored_repositories_count'
%th= _('Direction')
- %th= _('Last update')
+ %th= _('Last update attempt')
+ %th= _('Last successful update')
%th
%th
%tbody.js-mirrors-table-body
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- next if mirror.new_record?
- %tr.qa-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
+ %tr.qa-mirrored-repository-row.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?) }
%td.qa-mirror-repository-url= mirror.safe_url
%td= _('Push')
+ %td
+ = mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never')
%td.qa-mirror-last-update-at= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td
- if mirror.disabled?
@@ -64,4 +67,4 @@
- if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'))
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
- %button.js-delete-mirror.qa-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
+ %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
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 5d307d6a70d..9614f33fe2f 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -43,7 +43,7 @@
} }
Auto DevOps
- if @pipeline.detached_merge_request_pipeline?
- %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run on the source branch" }
+ %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/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index 4d1d078661d..6b4110e07d2 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -2,12 +2,9 @@
- page_title _('CI / CD Charts')
%div{ class: container_class }
+
#charts.ci-charts
- .row
- .col-md-6
- = render 'projects/pipelines/charts/overall'
- .col-md-6
- = render 'projects/pipelines/charts/pipeline_times'
+ = render 'projects/pipelines/charts/overall'
%hr
= render 'projects/pipelines/charts/pipelines'
diff --git a/app/views/projects/pipelines/charts/_overall.haml b/app/views/projects/pipelines/charts/_overall.haml
index 66786c7ff59..651f9217455 100644
--- a/app/views/projects/pipelines/charts/_overall.haml
+++ b/app/views/projects/pipelines/charts/_overall.haml
@@ -1,15 +1,6 @@
-%h4= s_("PipelineCharts|Overall statistics")
-%ul
- %li
- = s_("PipelineCharts|Total:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
- %li
- = s_("PipelineCharts|Successful:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
- %li
- = s_("PipelineCharts|Failed:")
- %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
- %li
- = s_("PipelineCharts|Success ratio:")
- %strong
- #{success_ratio(@counts)}%
+%h4.mt-4.mb-4= s_("PipelineCharts|Overall statistics")
+.row
+ .col-md-6
+ = render 'projects/pipelines/charts/pipeline_statistics'
+ .col-md-6
+ = render 'projects/pipelines/charts/pipeline_times'
diff --git a/app/views/projects/pipelines/charts/_pipeline_statistics.haml b/app/views/projects/pipelines/charts/_pipeline_statistics.haml
new file mode 100644
index 00000000000..b323e290ed4
--- /dev/null
+++ b/app/views/projects/pipelines/charts/_pipeline_statistics.haml
@@ -0,0 +1,14 @@
+%ul
+ %li
+ = s_("PipelineCharts|Total:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:total]) % @counts[:total]
+ %li
+ = s_("PipelineCharts|Successful:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:success]) % @counts[:success]
+ %li
+ = s_("PipelineCharts|Failed:")
+ %strong= n_("1 pipeline", "%d pipelines", @counts[:failed]) % @counts[:failed]
+ %li
+ = s_("PipelineCharts|Success ratio:")
+ %strong
+ #{success_ratio(@counts)}%
diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml
index 47f1f074210..afff9e82e45 100644
--- a/app/views/projects/pipelines/charts/_pipelines.haml
+++ b/app/views/projects/pipelines/charts/_pipelines.haml
@@ -1,4 +1,4 @@
-%h4= _("Pipelines charts")
+%h4.mt-4.mb-4= _("Pipelines charts")
%p
&nbsp;
%span.legend-success
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index efabb7f7b19..149b0d6cddd 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -2,7 +2,7 @@
.col-sm-12
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
- = label_tag :user_ids, _("Select members to invite"), class: "label-bold"
+ = label_tag :user_ids, _("GitLab member or Email address"), class: "label-bold"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
= label_tag :access_level, _("Choose a role permission"), class: "label-bold"
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index f220299ec30..5310c1fad01 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -6,11 +6,11 @@
%span.flex-project-title
= _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize(project.name, tags: []) }
%span.badge.badge-pill= members.total_count
- = form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
+ = form_tag project_project_members_path(project), method: :get, class: 'form-inline user-search-form flex-users-form' do
.form-group
.position-relative
= search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false }
- %button.member-search-btn{ type: "submit", "aria-label" => _("Submit search") }
+ %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= icon("search")
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list.qa-members-list
diff --git a/app/views/projects/protected_branches/_create_protected_branch.html.haml b/app/views/projects/protected_branches/_create_protected_branch.html.haml
index 24b53555cdc..ee359a01e74 100644
--- a/app/views/projects/protected_branches/_create_protected_branch.html.haml
+++ b/app/views/projects/protected_branches/_create_protected_branch.html.haml
@@ -2,13 +2,13 @@
.merge_access_levels-container
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-merge qa-allowed-to-merge-select wide',
- dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown capitalize-header',
+ dropdown_class: 'dropdown-menu-selectable qa-allowed-to-merge-dropdown rspec-allowed-to-merge-dropdown capitalize-header',
data: { field_name: 'protected_branch[merge_access_levels_attributes][0][access_level]', input_id: 'merge_access_levels_attributes' }})
- content_for :push_access_levels do
.push_access_levels-container
= dropdown_tag('Select',
options: { toggle_class: 'js-allowed-to-push qa-allowed-to-push-select wide',
- dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown capitalize-header',
+ dropdown_class: 'dropdown-menu-selectable qa-allowed-to-push-dropdown rspec-allowed-to-push-dropdown capitalize-header',
data: { field_name: 'protected_branch[push_access_levels_attributes][0][access_level]', input_id: 'push_access_levels_attributes' }})
= render 'projects/protected_branches/shared/create_protected_branch'
diff --git a/app/views/projects/services/prometheus/_metrics.html.haml b/app/views/projects/services/prometheus/_metrics.html.haml
index a1d74b91002..7685dee08fc 100644
--- a/app/views/projects/services/prometheus/_metrics.html.haml
+++ b/app/views/projects/services/prometheus/_metrics.html.haml
@@ -1,28 +1,35 @@
- project = local_assigns.fetch(:project)
-.card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } }
- .card-header
- = s_('PrometheusService|Common metrics')
- %span.badge.badge-pill.js-monitored-count 0
- .card-body
- .loading-metrics.js-loading-metrics
- %p.prepend-top-10.prepend-left-10
- = icon('spinner spin', class: 'metrics-load-spinner')
- = s_('PrometheusService|Finding and configuring metrics...')
- .empty-metrics.hidden.js-empty-metrics
- %p.text-tertiary.prepend-top-10.prepend-left-10
- = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics')
- %ul.list-unstyled.metrics-list.hidden.js-metrics-list
+.col-lg-3
+ %p
+ = s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.')
+ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer"
-.card.hidden.js-panel-missing-env-vars
- .card-header
- = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
- = s_('PrometheusService|Missing environment variable')
- %span.badge.badge-pill.js-env-var-count 0
- .card-body.hidden
- .flash-container
- .flash-notice
- .flash-text
- = s_("PrometheusService|To set up automatic monitoring, add the environment variable %{variable} to exporter's queries." % { variable: "<code>$CI_ENVIRONMENT_SLUG</code>" }).html_safe
- = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
- %ul.list-unstyled.metrics-list.js-missing-var-metrics-list
+.col-lg-9
+ .card.js-panel-monitored-metrics{ data: { active_metrics: active_common_project_prometheus_metrics_path(project, :json), metrics_help_path: help_page_path('user/project/integrations/prometheus_library/index') } }
+ .card-header
+ %strong
+ = s_('PrometheusService|Common metrics')
+ %span.badge.badge-pill.js-monitored-count 0
+ .card-body
+ .loading-metrics.js-loading-metrics
+ %p.m-3
+ = icon('spinner spin', class: 'metrics-load-spinner')
+ = s_('PrometheusService|Finding and configuring metrics...')
+ .empty-metrics.hidden.js-empty-metrics
+ %p.text-tertiary.m-3
+ = s_('PrometheusService|Waiting for your first deployment to an environment to find common metrics')
+ %ul.list-unstyled.metrics-list.hidden.js-metrics-list
+
+ .card.hidden.js-panel-missing-env-vars
+ .card-header
+ = icon('caret-right lg fw', class: 'panel-toggle js-panel-toggle', 'aria-label' => 'Toggle panel')
+ = s_('PrometheusService|Missing environment variable')
+ %span.badge.badge-pill.js-env-var-count 0
+ .card-body.hidden
+ .flash-container
+ .flash-notice
+ .flash-text
+ = s_("PrometheusService|To set up automatic monitoring, add the environment variable %{variable} to exporter's queries." % { variable: "<code>$CI_ENVIRONMENT_SLUG</code>" }).html_safe
+ = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus', anchor: 'metrics-and-labels')
+ %ul.list-unstyled.metrics-list.js-missing-var-metrics-list
diff --git a/app/views/projects/services/prometheus/_show.html.haml b/app/views/projects/services/prometheus/_show.html.haml
index 6aafa85e99a..c719661d8e8 100644
--- a/app/views/projects/services/prometheus/_show.html.haml
+++ b/app/views/projects/services/prometheus/_show.html.haml
@@ -1,12 +1,9 @@
-.row.prepend-top-default.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
+.row
.col-lg-3
%h4.prepend-top-0
= s_('PrometheusService|Metrics')
- %p
- = s_('PrometheusService|Common metrics are automatically monitored based on a library of metrics from popular exporters.')
- = link_to s_('PrometheusService|More information'), help_page_path('user/project/integrations/prometheus_library/index'), target: '_blank', rel: "noopener noreferrer"
- .col-lg-9
- = render 'projects/services/prometheus/metrics', project: @project
+.row.append-bottom-default.prometheus-metrics-monitoring.js-prometheus-metrics-monitoring
+ = render 'projects/services/prometheus/metrics', project: @project
= render_if_exists 'projects/services/prometheus/external_alerts', project: @project
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index fe74dc122c3..04b77fb987a 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -8,7 +8,7 @@
.card.auto-devops-card
.card-body
.form-check
- = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled
+ = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled, data: { qa_selector: 'enable_autodevops_checkbox' }
= form.label :enabled, class: 'form-check-label' do
%strong= s_('CICD|Default to Auto DevOps pipeline')
- if auto_devops_enabled
@@ -42,4 +42,4 @@
= s_('CICD|Automatic deployment to staging, manual deployment to production')
= link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank'
- = f.submit _('Save changes'), class: "btn btn-success prepend-top-15"
+ = f.submit _('Save changes'), class: "btn btn-success prepend-top-15", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml
index 2d108a1cba5..498a9744783 100644
--- a/app/views/projects/settings/ci_cd/_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_form.html.haml
@@ -99,7 +99,7 @@
%code \(\d+.\d+\%\) covered
%li
pytest-cov (Python) -
- %code ^TOTAL\s+\d+\s+\d+\s+(\d+\%)$
+ %code ^TOTAL.+?(\d+\%)$
%li
phpunit --coverage-text --colors=never (PHP) -
%code ^\s*Lines:\s*\d+.\d+\%
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 5e3e1076c2c..87000e8270b 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -16,7 +16,7 @@
.settings-content
= render 'form'
-%section.qa-autodevops-settings.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded) }
+%section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded), data: { qa_selector: 'autodevops_settings_content' } }
.settings-header
%h4
= s_('CICD|Auto DevOps')
@@ -30,7 +30,7 @@
= render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded
-%section.qa-runners-settings.settings.no-animate#js-runners-settings{ class: ('expanded' if expanded) }
+%section.settings.no-animate#js-runners-settings{ class: ('expanded' if expanded), data: { qa_selector: 'runners_settings_content' } }
.settings-header
%h4
= _("Runners")
@@ -41,7 +41,7 @@
.settings-content
= render 'projects/runners/index'
-%section.qa-variables-settings.settings.no-animate{ class: ('expanded' if expanded) }
+%section.qa-variables-settings.settings.no-animate#js-cicd-variables-settings{ class: ('expanded' if expanded), data: { qa_selector: 'variables_settings_content' } }
.settings-header
= render 'ci/variables/header', expanded: expanded
.settings-content
diff --git a/app/views/projects/starrers/_starrer.html.haml b/app/views/projects/starrers/_starrer.html.haml
new file mode 100644
index 00000000000..377d62f8abd
--- /dev/null
+++ b/app/views/projects/starrers/_starrer.html.haml
@@ -0,0 +1,19 @@
+- starrer = local_assigns.fetch(:starrer)
+
+.col-lg-3.col-md-4.col-sm-12
+ .card
+ .card-body
+ = image_tag avatar_icon_for_user(starrer.user, 40), class: "avatar s40", alt: ''
+
+ .user-info
+ .block-truncated
+ = link_to starrer.user.name, user_path(starrer.user), class: 'user js-user-link', data: { user_id: starrer.user.id }
+
+ .block-truncated
+ %span.cgray= starrer.user.to_reference
+
+ - if starrer.user == current_user
+ %span.badge.badge-success.prepend-left-5= _("It's you")
+
+ .block-truncated
+ = time_ago_with_tooltip(starrer.starred_since)
diff --git a/app/views/projects/starrers/index.html.haml b/app/views/projects/starrers/index.html.haml
new file mode 100644
index 00000000000..e55ed99f643
--- /dev/null
+++ b/app/views/projects/starrers/index.html.haml
@@ -0,0 +1,32 @@
+- page_title _("Starrers")
+
+.top-area.adjust
+ .nav-text
+ - full_count_title = "#{@public_count} public and #{@private_count} private"
+ #{pluralize(@total_count, 'starrer')}: #{full_count_title}
+ - if @starrers.size > 0 || params[:search].present?
+ .nav-controls
+ = form_tag request.original_url, method: :get, class: 'form-inline user-search-form flex-users-form' do
+ .form-group
+ .position-relative
+ = search_field_tag :search, params[:search], { placeholder: _('Search'), class: 'form-control', spellcheck: false }
+ %button.user-search-btn{ type: "submit", "aria-label" => _("Submit search") }
+ = icon("search")
+ .dropdown.inline.user-sort-dropdown
+ = dropdown_toggle(starrers_sort_options_hash[@sort], { toggle: 'dropdown' })
+ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
+ %li.dropdown-header
+ = _("Sort by")
+ - starrers_sort_options_hash.each do |value, title|
+ %li
+ = link_to filter_starrer_path(sort: value), class: ("is-active" if @sort == value) do
+ = title
+- if @starrers.size > 0
+ .row.prepend-top-10
+ = render partial: 'starrer', collection: @starrers, as: :starrer
+ = paginate @starrers, theme: 'gitlab'
+- else
+ - if params[:search].present?
+ .nothing-here-block= _('No starrers matched your search')
+ - else
+ .nothing-here-block= _('Nobody has starred this repository yet')
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index 02f6ef02843..78cce58938e 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -4,7 +4,7 @@
- page_title @tag.name, s_('TagsPage|Tags')
%div{ class: container_class }
- .top-area.multi-line
+ .top-area.multi-line.flex-wrap
.nav-text
.title
%span.item-title.ref-name
diff --git a/app/views/projects/triggers/_trigger.html.haml b/app/views/projects/triggers/_trigger.html.haml
index 31a598ccd5e..9899cf9c6de 100644
--- a/app/views/projects/triggers/_trigger.html.haml
+++ b/app/views/projects/triggers/_trigger.html.haml
@@ -33,10 +33,7 @@
Never
%td.text-right.trigger-actions
- - take_ownership_confirmation = "By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?"
- revoke_trigger_confirmation = "By revoking a trigger you will break any processes making use of it. Are you sure?"
- - if trigger.owner != current_user && can?(current_user, :manage_trigger, trigger)
- = link_to 'Take ownership', take_ownership_project_trigger_path(@project, trigger), data: { confirm: take_ownership_confirmation }, method: :post, class: "btn btn-default btn-sm btn-trigger-take-ownership"
- if can?(current_user, :admin_trigger, trigger)
= link_to edit_project_trigger_path(@project, trigger), method: :get, title: "Edit", class: "btn btn-default btn-sm" do
%i.fa.fa-pencil
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 70f1bf8ef46..c5eecc900b2 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -4,7 +4,7 @@
location.reload();
- else
:plain
- $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
+ $(".flash-container").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
$('.edit-project .js-btn-success-general-project-settings').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 5bb69563b51..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.edit-wiki-page-slug-tip
- = 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?
@@ -51,6 +53,6 @@
.float-right
= link_to _("Cancel"), project_wiki_path(@project, @page), class: 'btn btn-cancel btn-grouped'
- else
- = f.submit s_("Wiki|Create page"), class: 'btn-success btn qa-create-page-button'
+ = f.submit s_("Wiki|Create page"), class: 'btn-success btn qa-create-page-button rspec-create-page-button'
.float-right
= link_to _("Cancel"), project_wiki_path(@project, :home), class: 'btn btn-cancel'
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 dc12e368b35..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.new-wiki-page-slug-tip
- = icon('lightbulb-o')
- = s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
- .form-actions
- = button_tag s_("Wiki|Create page"), class: "build-new-wiki btn btn-success"
diff --git a/app/views/projects/wikis/_sidebar.html.haml b/app/views/projects/wikis/_sidebar.html.haml
index 28353927135..a9d21470944 100644
--- a/app/views/projects/wikis/_sidebar.html.haml
+++ b/app/views/projects/wikis/_sidebar.html.haml
@@ -19,5 +19,3 @@
.block
= link_to project_wikis_pages_path(@project), class: 'btn btn-block' do
= s_("Wiki|More Pages")
-
-= render 'projects/wikis/new'
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 1277ea6c743..815b4a51261 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.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
@@ -13,14 +13,11 @@
%h2.wiki-page-title
- if @page.persisted?
= link_to @page.human_title, project_wiki_path(@project, @page)
- - else
- = @page.human_title
- %span.light
- &middot;
- - if @page.persisted?
+ %span.light
+ &middot;
= s_("Wiki|Edit Page")
- - else
- = s_("Wiki|Create Page")
+ - else
+ = s_("Wiki|Create New Page")
.nav-controls
- if @page.persisted?
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 8c2cbd495a0..009133be117 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,7 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title s_("WikiClone|Git Access"), _("Wiki")
-.wiki-page-header.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%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')
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index c5fbeeafa54..f8468ef9a78 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.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%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 2191e5ab287..f7999c3f1bd 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,7 +5,7 @@
- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class }
- .wiki-page-header
+ .wiki-page-header.top-area
.nav-text.flex-fill
%h2.wiki-page-title
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 95cd3356ec8..1d649886331 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.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index ee7d89a9bd8..eae2a491ceb 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,10 +1,6 @@
- users = capture_haml do
- if search_tabs?(:members)
- %li{ class: active_when(@scope == 'users') }
- = link_to search_filter_path(scope: 'users') do
- Users
- %span.badge.badge-pill
- = limited_count(@search_results.limited_users_count)
+ = search_filter_link 'users', _("Users")
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
@@ -12,80 +8,28 @@
%ul.nav-links.search-filter.scrolling-tabs.nav.nav-tabs
- if @project
- if project_search_tabs?(:blobs)
- %li{ class: active_when(@scope == 'blobs') }
- = link_to search_filter_path(scope: 'blobs') do
- = _("Code")
- %span.badge.badge-pill
- = @search_results.blobs_count
+ = search_filter_link 'blobs', _("Code"), data: { qa_selector: 'code_tab' }
- if project_search_tabs?(:issues)
- %li{ class: active_when(@scope == 'issues') }
- = link_to search_filter_path(scope: 'issues') do
- = _("Issues")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_issues_count)
+ = search_filter_link 'issues', _("Issues")
- if project_search_tabs?(:merge_requests)
- %li{ class: active_when(@scope == 'merge_requests') }
- = link_to search_filter_path(scope: 'merge_requests') do
- = _("Merge requests")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_merge_requests_count)
+ = search_filter_link 'merge_requests', _("Merge requests")
- if project_search_tabs?(:milestones)
- %li{ class: active_when(@scope == 'milestones') }
- = link_to search_filter_path(scope: 'milestones') do
- = _("Milestones")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_milestones_count)
+ = search_filter_link 'milestones', _("Milestones")
- if project_search_tabs?(:notes)
- %li{ class: active_when(@scope == 'notes') }
- = link_to search_filter_path(scope: 'notes') do
- = _("Comments")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_notes_count)
+ = search_filter_link 'notes', _("Comments")
- if project_search_tabs?(:wiki)
- %li{ class: active_when(@scope == 'wiki_blobs') }
- = link_to search_filter_path(scope: 'wiki_blobs') do
- = _("Wiki")
- %span.badge.badge-pill
- = @search_results.wiki_blobs_count
+ = search_filter_link 'wiki_blobs', _("Wiki")
- if project_search_tabs?(:commits)
- %li{ class: active_when(@scope == 'commits') }
- = link_to search_filter_path(scope: 'commits') do
- = _("Commits")
- %span.badge.badge-pill
- = @search_results.commits_count
+ = search_filter_link 'commits', _("Commits")
= users
- elsif @show_snippets
- %li{ class: active_when(@scope == 'snippet_blobs') }
- = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- = _("Snippet Contents")
- %span.badge.badge-pill
- = @search_results.snippet_blobs_count
- %li{ class: active_when(@scope == 'snippet_titles') }
- = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- = _("Titles and Filenames")
- %span.badge.badge-pill
- = @search_results.snippet_titles_count
+ = search_filter_link 'snippet_blobs', _("Snippet Contents"), search: { snippets: true, group_id: nil, project_id: nil }
+ = search_filter_link 'snippet_titles', _("Titles and Filenames"), search: { snippets: true, group_id: nil, project_id: nil }
- else
- %li{ class: active_when(@scope == 'projects') }
- = link_to search_filter_path(scope: 'projects') do
- = _("Projects")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_projects_count)
- %li{ class: active_when(@scope == 'issues') }
- = link_to search_filter_path(scope: 'issues') do
- = _("Issues")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_issues_count)
- %li{ class: active_when(@scope == 'merge_requests') }
- = link_to search_filter_path(scope: 'merge_requests') do
- = _("Merge requests")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_merge_requests_count)
- %li{ class: active_when(@scope == 'milestones') }
- = link_to search_filter_path(scope: 'milestones') do
- = _("Milestones")
- %span.badge.badge-pill
- = limited_count(@search_results.limited_milestones_count)
+ = search_filter_link 'projects', _("Projects")
+ = search_filter_link 'issues', _("Issues")
+ = search_filter_link 'merge_requests', _("Merge requests")
+ = search_filter_link 'milestones', _("Milestones")
= render_if_exists 'search/category_elasticsearch'
= users
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index c8b6a3258ab..bee4aff605f 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -2,10 +2,11 @@
= hidden_field_tag :group_id, params[:group_id]
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
-.dropdown
- %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Group:'), group_id: params[:group_id] } }
+.dropdown.form-group.mb-lg-0.mx-lg-1
+ %label.d-block{ for: "dashboard_search_group" }
+ = _("Group")
+ %button.dropdown-menu-toggle.js-search-group-dropdown.mt-0{ type: "button", id: "dashboard_search_group", data: { toggle: "dropdown", group_id: params[:group_id] } }
%span.dropdown-toggle-text
- = _("Group:")
- if @group.present?
= @group.name
- else
@@ -17,10 +18,11 @@
= dropdown_content
= dropdown_loading
-.dropdown.project-filter
- %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Project:') } }
+.dropdown.project-filter.form-group.mb-lg-0.mx-lg-1
+ %label.d-block{ for: "dashboard_search_project" }
+ = _("Project")
+ %button.dropdown-menu-toggle.js-search-project-dropdown.mt-0{ type: "button", id: "dashboard_search_project", data: { toggle: "dropdown"} }
%span.dropdown-toggle-text
- = _("Project:")
- if @project.present?
= @project.full_name
- else
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index db0dcc8adfb..464db94b7f4 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -1,16 +1,21 @@
-= form_tag search_path, method: :get, class: 'js-search-form' do |f|
+= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f|
= hidden_field_tag :snippets, params[:snippets]
= hidden_field_tag :scope, params[:scope]
+ = hidden_field_tag :repository_ref, params[:repository_ref]
- .search-holder
- .search-field-holder
- = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
- = icon("search", class: "search-icon")
- %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
- = icon("times-circle")
- %span.sr-only
- = _("Clear search")
+ .d-lg-flex.align-items-end
+ .search-field-holder.form-group.mr-lg-1.mb-lg-0
+ %label{ for: "dashboard_search" }
+ = _("What are you searching for?")
+ .position-relative
+ = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = icon("search", class: "search-icon")
+ %button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
+ = icon("times-circle")
+ %span.sr-only
+ = _("Clear search")
- unless params[:snippets].eql? 'true'
= render 'filter'
- = button_tag _("Search"), class: "btn btn-success btn-search"
- = render_if_exists 'search/form_elasticsearch'
+ .d-flex-center.flex-column.flex-lg-row
+ = button_tag _("Search"), class: "btn btn-success btn-search form-control mt-lg-0 ml-lg-1 align-self-end"
+ = render_if_exists 'search/form_elasticsearch'
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index cb8a8a24be8..de9947528cf 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -2,17 +2,25 @@
= render partial: "search/results/empty"
= render_if_exists 'shared/promotions/promote_advanced_search'
- else
- .row-content-block
+ .row-content-block.d-md-flex.text-left.align-items-center
- unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
- = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
+ - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project], class: 'ml-md-1')
+ - if @scope == 'blobs'
+ - repository_ref = params[:repository_ref].to_s.presence || @project.default_branch
+ = s_("SearchCodeResults|in")
+ .mx-md-1
+ = render partial: "shared/ref_switcher", locals: { ref: repository_ref, form_path: request.fullpath, field_name: 'repository_ref' }
+ = s_('SearchCodeResults|of %{link_to_project}').html_safe % { link_to_project: link_to_project }
+ - else
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- - link_to_group = link_to(@group.name, @group)
+ - link_to_group = link_to(@group.name, @group, class: 'ml-md-1')
= _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
= render_if_exists 'shared/promotions/promote_advanced_search'
+
.results.prepend-top-10
- if @scope == 'commits'
%ul.content-list.commit-list
diff --git a/app/views/search/results/_blob_data.html.haml b/app/views/search/results/_blob_data.html.haml
index 143e9f91ca3..36b6ea7bd37 100644
--- a/app/views/search/results/_blob_data.html.haml
+++ b/app/views/search/results/_blob_data.html.haml
@@ -1,10 +1,10 @@
-.blob-result
+.blob-result{ data: { qa_selector: 'result_item_content' } }
.file-holder
- .js-file-title.file-title
+ .js-file-title.file-title{ data: { qa_selector: 'file_title_content' } }
= link_to blob_link do
%i.fa.fa-file
%strong
= search_blob_title(project, file_name)
- if blob.data
- .file-content.code.term
+ .file-content.code.term{ data: { qa_selector: 'file_text_content' } }
= render 'shared/file_highlight', blob: blob, first_line_number: blob.startline
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 3260d05f509..9235678bc1d 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,6 +1,10 @@
- @hide_top_links = true
-- breadcrumb_title _("Search")
- page_title @search_term
+- @hide_breadcrumbs = true
+
+.page-title-holder.d-flex.align-items-center
+ %h1.page-title<
+ = _('Search')
.prepend-top-default
= render 'search/form'
diff --git a/app/views/shared/_issuable_meta_data.html.haml b/app/views/shared/_issuable_meta_data.html.haml
index 71b13a5d741..7807371285c 100644
--- a/app/views/shared/_issuable_meta_data.html.haml
+++ b/app/views/shared/_issuable_meta_data.html.haml
@@ -1,7 +1,7 @@
- note_count = @issuable_meta_data[issuable.id].user_notes_count
- issue_votes = @issuable_meta_data[issuable.id]
- upvotes, downvotes = issue_votes.upvotes, issue_votes.downvotes
-- issuable_url = @collection_type == "Issue" ? issue_path(issuable, anchor: 'notes') : merge_request_path(issuable, anchor: 'notes')
+- issuable_path = issuable_path(issuable, anchor: 'notes')
- issuable_mr = @issuable_meta_data[issuable.id].merge_requests_count(current_user)
- if issuable_mr > 0
@@ -20,6 +20,6 @@
= downvotes
%li.issuable-comments.d-none.d-sm-block
- = link_to issuable_url, class: ['has-tooltip', ('no-comments' if note_count.zero?)], title: _('Comments') do
+ = link_to issuable_path, class: ['has-tooltip', ('no-comments' if note_count.zero?)], title: _('Comments') do
= icon('comments')
= note_count
diff --git a/app/views/shared/_label_row.html.haml b/app/views/shared/_label_row.html.haml
index af11ce94ec5..b05d903fabe 100644
--- a/app/views/shared/_label_row.html.haml
+++ b/app/views/shared/_label_row.html.haml
@@ -1,7 +1,7 @@
- force_priority = local_assigns.fetch(:force_priority, false)
- subject_or_group_defined = defined?(@project) || defined?(@group)
-- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues, project: @project)
-- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests, project: @project)
+- show_label_issues_link = subject_or_group_defined && show_label_issuables_link?(label, :issues)
+- show_label_merge_requests_link = subject_or_group_defined && show_label_issuables_link?(label, :merge_requests)
.label-name
= render_label(label, tooltip: false)
diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml
index 7cbc5810c10..74e0a088656 100644
--- a/app/views/shared/_ref_switcher.html.haml
+++ b/app/views/shared/_ref_switcher.html.haml
@@ -1,12 +1,19 @@
-- dropdown_toggle_text = @ref || @project.default_branch
-= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do
- = hidden_field_tag :destination, destination
+- return unless @project
+
+- ref = local_assigns.fetch(:ref, @ref)
+- form_path = local_assigns.fetch(:form_path, switch_project_refs_path(@project))
+- dropdown_toggle_text = ref || @project.default_branch
+- field_name = local_assigns.fetch(:field_name, 'ref')
+
+= form_tag form_path, method: :get, class: "project-refs-form" do
+ - if defined?(destination)
+ = hidden_field_tag :destination, destination
- if defined?(path)
= hidden_field_tag :path, path
- @options && @options.each do |key, value|
= hidden_field_tag key, value, id: nil
.dropdown
- = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: @ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: 'ref', submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
+ = dropdown_toggle dropdown_toggle_text, { toggle: "dropdown", selected: dropdown_toggle_text, ref: ref, refs_url: refs_project_path(@project, sort: 'updated_desc'), field_name: field_name, submit_form_on_click: true, visit: true }, { toggle_class: "js-project-refs-dropdown qa-branches-select" }
.dropdown-menu.dropdown-menu-selectable.git-revision-dropdown.dropdown-menu-paging.qa-branches-dropdown{ class: ("dropdown-menu-right" if local_assigns[:align_right]) }
.dropdown-page-one
= dropdown_title _("Switch branch/tag")
diff --git a/app/views/shared/_remote_mirror_update_button.html.haml b/app/views/shared/_remote_mirror_update_button.html.haml
index 8da2ae5111a..4b39c8b06e9 100644
--- a/app/views/shared/_remote_mirror_update_button.html.haml
+++ b/app/views/shared/_remote_mirror_update_button.html.haml
@@ -2,5 +2,5 @@
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
= icon("refresh spin")
- elsif remote_mirror.enabled?
- = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
+ = link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn qa-update-now-button rspec-update-now-button", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
= icon("refresh")
diff --git a/app/views/shared/_sidebar_toggle_button.html.haml b/app/views/shared/_sidebar_toggle_button.html.haml
index d499bc0a253..c7546073e5c 100644
--- a/app/views/shared/_sidebar_toggle_button.html.haml
+++ b/app/views/shared/_sidebar_toggle_button.html.haml
@@ -1,4 +1,4 @@
-%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
+%a.toggle-sidebar-button.js-toggle-sidebar.qa-toggle-sidebar.rspec-toggle-sidebar{ role: "button", type: "button", title: "Toggle sidebar" }
= sprite_icon('angle-double-left', css_class: 'icon-angle-double-left')
= sprite_icon('angle-double-right', css_class: 'icon-angle-double-right')
%span.collapse-text= _("Collapse sidebar")
diff --git a/app/views/shared/boards/_switcher.html.haml b/app/views/shared/boards/_switcher.html.haml
new file mode 100644
index 00000000000..79118630762
--- /dev/null
+++ b/app/views/shared/boards/_switcher.html.haml
@@ -0,0 +1,16 @@
+- parent = board.parent
+- milestone_filter_opts = { format: :json }
+- milestone_filter_opts = milestone_filter_opts.merge(only_group_milestones: true) if board.group_board?
+- weights = Gitlab.ee? ? ([Issue::WEIGHT_ANY] + Issue.weight_options) : []
+
+#js-multiple-boards-switcher.inline.boards-switcher{ data: { current_board: current_board_json.to_json,
+ milestone_path: milestones_filter_path(milestone_filter_opts),
+ board_base_url: board_base_url,
+ has_missing_boards: (!multiple_boards_available? && current_board_parent.boards.size > 1).to_s,
+ can_admin_board: can?(current_user, :admin_board, parent).to_s,
+ multiple_issue_boards_available: parent.multiple_issue_boards_available?.to_s,
+ labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: true),
+ project_id: @project&.id,
+ group_id: @group&.id,
+ scoped_issue_board_feature_enabled: Gitlab.ee? && parent.feature_available?(:scoped_issue_board) ? 'true' : 'false',
+ weights: weights.to_json } }
diff --git a/app/views/shared/form_elements/_description.html.haml b/app/views/shared/form_elements/_description.html.haml
index b11cb8a3076..be78fd0ccfb 100644
--- a/app/views/shared/form_elements/_description.html.haml
+++ b/app/views/shared/form_elements/_description.html.haml
@@ -15,7 +15,7 @@
= render layout: 'projects/md_preview', locals: { url: preview_url, referenced_users: true } do
= render 'projects/zen', f: form, attr: :description,
- classes: 'note-textarea qa-issuable-form-description',
+ classes: 'note-textarea qa-issuable-form-description rspec-issuable-form-description',
placeholder: "Write a comment or drag your files here…",
supports_quick_actions: supports_quick_actions
= render 'shared/notes/hints', supports_quick_actions: supports_quick_actions
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 483652852b6..bca5db16bd3 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,4 +1,5 @@
- project = @target_project || @project
+- edit_context = local_assigns.fetch(:edit_context, nil) || project
- show_create = local_assigns.fetch(:show_create, true)
- extra_options = local_assigns.fetch(:extra_options, true)
- filter_submit = local_assigns.fetch(:filter_submit, true)
@@ -8,7 +9,7 @@
- classes = local_assigns.fetch(:classes, [])
- selected = local_assigns.fetch(:selected, nil)
- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label")
-- dropdown_data = label_dropdown_data(@project, labels: labels_filter_path_with_defaults, default_label: "Labels")
+- dropdown_data = label_dropdown_data(edit_context, labels: labels_filter_path_with_defaults(only_group_labels: edit_context.is_a?(Group)), default_label: "Labels")
- dropdown_data.merge!(data_options)
- label_name = local_assigns.fetch(:label_name, "Labels")
- no_default_styles = local_assigns.fetch(:no_default_styles, false)
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index a97ac5e2a2d..c9458475aa5 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -1,12 +1,13 @@
- type = local_assigns.fetch(:type)
- board = local_assigns.fetch(:board, nil)
-- block_css_class = type != :boards_modal ? 'row-content-block second-block' : ''
+- is_not_boards_modal_or_productivity_analytics = type != :boards_modal && type != :productivity_analytics
+- block_css_class = is_not_boards_modal_or_productivity_analytics ? 'row-content-block second-block' : ''
- user_can_admin_list = board && can?(current_user, :admin_list, board.parent)
.issues-filters{ class: ("w-100" if type == :boards_modal) }
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }
- if type == :boards
- = render_if_exists "shared/boards/switcher", board: board
+ = render "shared/boards/switcher", board: board
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
- if params[:search].present?
= hidden_field_tag :search, params[:search]
@@ -155,5 +156,5 @@
- if @project
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
#js-toggle-focus-btn
- - elsif type != :boards_modal
+ - elsif is_not_boards_modal_or_productivity_analytics
= render 'shared/issuable/sort_dropdown'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index b4f8377c008..825088a58e7 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -137,7 +137,11 @@
.js-sidebar-participants-entry-point
- if signed_in
- .js-sidebar-subscriptions-entry-point
+ - if issuable_sidebar[:project_emails_disabled]
+ .block.js-emails-disabled
+ = notification_description(:owner_disabled)
+ - else
+ .js-sidebar-subscriptions-entry-point
- project_ref = issuable_sidebar[:reference]
.block.project-reference
diff --git a/app/views/shared/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/issuable/_sort_dropdown.html.haml b/app/views/shared/issuable/_sort_dropdown.html.haml
index 403e001bfe8..8260915c2ab 100644
--- a/app/views/shared/issuable/_sort_dropdown.html.haml
+++ b/app/views/shared/issuable/_sort_dropdown.html.haml
@@ -1,7 +1,7 @@
- sort_value = @sort
- sort_title = issuable_sort_option_title(sort_value)
- viewing_issues = controller.controller_name == 'issues' || controller.action_name == 'issues'
-- manual_sorting = viewing_issues && controller.controller_name != 'dashboard' && Feature.enabled?(:manual_sorting)
+- manual_sorting = viewing_issues && controller.controller_name != 'dashboard'
.dropdown.inline.prepend-left-10.issue-sort-dropdown
.btn-group{ role: 'group' }
diff --git a/app/views/shared/issuable/form/_metadata.html.haml b/app/views/shared/issuable/form/_metadata.html.haml
index 1e03440a5dc..90a6a98235d 100644
--- a/app/views/shared/issuable/form/_metadata.html.haml
+++ b/app/views/shared/issuable/form/_metadata.html.haml
@@ -23,6 +23,7 @@
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
+ = render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
- if has_due_date
.col-lg-6
diff --git a/app/views/shared/members/_filter_2fa_dropdown.html.haml b/app/views/shared/members/_filter_2fa_dropdown.html.haml
index 3e98587aeaa..44ea844028e 100644
--- a/app/views/shared/members/_filter_2fa_dropdown.html.haml
+++ b/app/views/shared/members/_filter_2fa_dropdown.html.haml
@@ -1,7 +1,7 @@
- filter = params[:two_factor] || 'everyone'
- filter_options = { 'everyone' => _('Everyone'), 'enabled' => _('Enabled'), 'disabled' => _('Disabled') }
-.dropdown.inline.member-filter-2fa-dropdown
- = dropdown_toggle('2FA: ' + filter_options[filter], { toggle: 'dropdown' })
+.dropdown.inline.member-filter-2fa-dropdown.pr-md-2
+ = dropdown_toggle(filter_options[filter], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-align-right.dropdown-menu-selectable
%li.dropdown-header
= _("Filter by two-factor authentication")
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/members/_sort_dropdown.html.haml b/app/views/shared/members/_sort_dropdown.html.haml
index 59bdfb73e6e..feca109dade 100644
--- a/app/views/shared/members/_sort_dropdown.html.haml
+++ b/app/views/shared/members/_sort_dropdown.html.haml
@@ -1,4 +1,5 @@
-.dropdown.inline.member-sort-dropdown
+= label_tag :sort_by, 'Sort by', class: 'col-form-label label-bold pr-2'
+.dropdown.inline.qa-user-sort-dropdown
= dropdown_toggle(member_sort_options_hash[@sort], { toggle: 'dropdown' })
%ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable
%li.dropdown-header
diff --git a/app/views/shared/milestones/_top.html.haml b/app/views/shared/milestones/_top.html.haml
index 43503e1d08a..fd3317341f6 100644
--- a/app/views/shared/milestones/_top.html.haml
+++ b/app/views/shared/milestones/_top.html.haml
@@ -53,7 +53,7 @@
- close_msg = group ? 'You may close the milestone now.' : 'Navigate to the project to close the milestone.'
%span All issues for this milestone are closed. #{close_msg}
-= render_if_exists 'shared/milestones/burndown', milestone: @milestone, project: @project
+= render_if_exists 'shared/milestones/burndown', milestone: milestone, project: @project
- if is_dynamic_milestone
.table-holder
diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml
index 749aa258af6..b4266937a4e 100644
--- a/app/views/shared/notifications/_button.html.haml
+++ b/app/views/shared/notifications/_button.html.haml
@@ -1,6 +1,15 @@
-- btn_class = local_assigns.fetch(:btn_class, nil)
+- btn_class = local_assigns.fetch(:btn_class, '')
+- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting
+ - if emails_disabled
+ - button_title = notification_description(:owner_disabled)
+ - aria_label = button_title
+ - btn_class << " disabled"
+ - else
+ - button_title = _("Notification setting")
+ - aria_label = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
+
.js-notification-dropdown.notification-dropdown.mr-md-2.home-panel-action-button.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f|
= hidden_setting_source_input(notification_setting)
@@ -8,14 +17,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn.text-left#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= icon('caret-down')
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting"), class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => aria_label, data: { container: "body", toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
.float-left
= icon("bell", class: "js-notification-loading")
= notification_title(notification_setting.level)
diff --git a/app/views/shared/notifications/_new_button.html.haml b/app/views/shared/notifications/_new_button.html.haml
index 052e6da5bae..3c8cc023848 100644
--- a/app/views/shared/notifications/_new_button.html.haml
+++ b/app/views/shared/notifications/_new_button.html.haml
@@ -1,6 +1,13 @@
-- btn_class = local_assigns.fetch(:btn_class, nil)
+- btn_class = local_assigns.fetch(:btn_class, '')
+- emails_disabled = local_assigns.fetch(:emails_disabled, false)
- if notification_setting
+ - if emails_disabled
+ - button_title = notification_description(:owner_disabled)
+ - btn_class << " disabled"
+ - else
+ - button_title = _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }
+
.js-notification-dropdown.notification-dropdown.home-panel-action-button.prepend-top-default.append-right-8.dropdown.inline
= form_for notification_setting, remote: true, html: { class: "inline notification-form no-label" } do |f|
= hidden_setting_source_input(notification_setting)
@@ -9,14 +16,14 @@
.js-notification-toggle-btns
%div{ class: ("btn-group" if notification_setting.custom?) }
- if notification_setting.custom?
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "modal", target: "#" + notifications_menu_identifier("modal", notification_setting), display: 'static' } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
%button.btn.dropdown-toggle{ data: { toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" }, class: "#{btn_class}" }
= sprite_icon("arrow-down", css_class: "icon mr-0")
.sr-only Toggle dropdown
- else
- %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, class: "#{btn_class}", "aria-label" => _("Notification setting - %{notification_title}") % { notification_title: notification_title(notification_setting.level) }, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
+ %button.dropdown-new.btn.btn-default.has-tooltip.notifications-btn#notifications-button{ type: "button", title: button_title, class: "#{btn_class}", "aria-label" => button_title, data: { container: "body", placement: 'top', toggle: "dropdown", target: notifications_menu_identifier("dropdown", notification_setting), flip: "false" } }
= notification_setting_icon(notification_setting)
%span.js-notification-loading.fa.hidden
= sprite_icon("arrow-down", css_class: "icon")
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 576ec3e1782..bb05658c719 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -17,14 +17,20 @@
- contributed_projects_illustration_path = 'illustrations/profile-page/contributed-projects.svg'
- contributed_projects_current_user_empty_message_header = s_('UserProfile|Explore public groups to find projects to contribute to.')
- contributed_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t contributed to any projects')
+- starred_projects_illustration_path = 'illustrations/starred_empty.svg'
+- starred_projects_current_user_empty_message_header = s_('UserProfile|Star projects to track their progress and show your appreciation.')
+- starred_projects_visitor_empty_message = s_('UserProfile|This user hasn\'t starred any projects')
- own_projects_illustration_path = 'illustrations/profile-page/personal-project.svg'
- own_projects_current_user_empty_message_header = s_('UserProfile|You haven\'t created any personal projects.')
- own_projects_current_user_empty_message_description = s_('UserProfile|Your projects can be available publicly, internally, or privately, at your choice.')
- own_projects_visitor_empty_message = s_('UserProfile|This user doesn\'t have any personal projects')
-- primary_button_label = _('New project')
-- primary_button_link = new_project_path
-- secondary_button_label = _('Explore groups')
-- secondary_button_link = explore_groups_path
+- explore_page_empty_message = s_('UserProfile|Explore public groups to find projects to contribute to.')
+- new_project_button_label = _('New project')
+- new_project_button_link = new_project_path
+- explore_projects_button_label = _('Explore projects')
+- explore_projects_button_link = explore_projects_path
+- explore_groups_button_label = _('Explore groups')
+- explore_groups_button_link = explore_groups_path
.js-projects-list-holder
- if any_projects?(projects)
@@ -47,15 +53,21 @@
- if @contributed_projects
= render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: contributed_projects_illustration_path,
current_user_empty_message_header: contributed_projects_current_user_empty_message_header,
- primary_button_label: primary_button_label,
- primary_button_link: primary_button_link,
- secondary_button_label: secondary_button_label,
- secondary_button_link: secondary_button_link,
+ primary_button_label: new_project_button_label,
+ primary_button_link: new_project_button_link,
+ secondary_button_label: explore_groups_button_label,
+ secondary_button_link: explore_groups_button_link,
visitor_empty_message: contributed_projects_visitor_empty_message }
+ - elsif @starred_projects
+ = render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: starred_projects_illustration_path,
+ current_user_empty_message_header: starred_projects_current_user_empty_message_header,
+ primary_button_label: explore_projects_button_label,
+ primary_button_link: explore_projects_button_link,
+ visitor_empty_message: starred_projects_visitor_empty_message }
- else
= render partial: 'shared/empty_states/profile_tabs', locals: { illustration_path: own_projects_illustration_path,
current_user_empty_message_header: own_projects_current_user_empty_message_header,
current_user_empty_message_description: own_projects_current_user_empty_message_description,
- primary_button_label: primary_button_label,
- primary_button_link: primary_button_link,
- visitor_empty_message: own_projects_visitor_empty_message }
+ primary_button_label: new_project_button_label,
+ primary_button_link: new_project_button_link,
+ visitor_empty_message: defined?(explore_page) && explore_page ? explore_page_empty_message : own_projects_visitor_empty_message }
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index 71bd9320593..b7474d891dc 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -37,7 +37,7 @@
%span.project-name<
= project.name
- %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
+ %span.metadata-info.visibility-icon.append-right-10.prepend-top-8.text-secondary.has-tooltip{ data: { container: 'body', placement: 'top' }, title: visibility_icon_description(project) }
= visibility_level_icon(project.visibility_level, fw: true)
- if explore_projects_tab? && project.repository.license
@@ -63,7 +63,9 @@
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
- if stars
- %span.d-flex.align-items-center.icon-wrapper.stars.has-tooltip{ data: { container: 'body', placement: 'top' }, title: _('Stars') }
+ = link_to project_starrers_path(project),
+ class: "d-flex align-items-center icon-wrapper stars has-tooltip",
+ title: _('Stars'), data: { container: 'body', placement: 'top' } do
= sprite_icon('star', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.star_count)
- if forks
@@ -89,4 +91,6 @@
%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
.updated-note
- %span Updated #{updated_tooltip}
+ %span
+ = _('Updated')
+ = updated_tooltip
diff --git a/app/views/u2f/_register.html.haml b/app/views/u2f/_register.html.haml
index f6724f72307..ef3835332a7 100644
--- a/app/views/u2f/_register.html.haml
+++ b/app/views/u2f/_register.html.haml
@@ -16,7 +16,7 @@
.col-md-4
%button#js-setup-u2f-device.btn.btn-info.btn-block{ disabled: true }= _("Set up new U2F device")
.col-md-8
- %p.text-warning= _("You need to register a two-factor authentication app before you can set up a U2F device.")
+ %p= _("You need to register a two-factor authentication app before you can set up a U2F device.")
%script#js-register-u2f-in-progress{ type: "text/template" }
%p= _("Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now.")
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index b3a73030859..73bee7c2586 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -111,6 +111,10 @@
%li.js-projects-tab
= link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do
= s_('UserProfile|Personal projects')
+ - if profile_tab?(:starred)
+ %li.js-starred-tab
+ = link_to user_starred_projects_path, data: { target: 'div#starred', action: 'starred', toggle: 'tab', endpoint: user_starred_projects_path(format: :json) } do
+ = s_('UserProfile|Starred projects')
- if profile_tab?(:snippets)
%li.js-snippets-tab
= link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do
@@ -142,6 +146,10 @@
#projects.tab-pane
-# This tab is always loaded via AJAX
+ - if profile_tab?(:starred)
+ #starred.tab-pane
+ -# This tab is always loaded via AJAX
+
- if profile_tab?(:snippets)
#snippets.tab-pane
-# This tab is always loaded via AJAX
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 3d34bfc05c7..991a177018e 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -3,6 +3,12 @@
- auto_merge:auto_merge_process
+- chaos:chaos_cpu_spin
+- chaos:chaos_db_spin
+- chaos:chaos_kill
+- chaos:chaos_leak_mem
+- chaos:chaos_sleep
+
- cronjob:admin_email
- cronjob:expire_build_artifacts
- cronjob:gitlab_usage_ping
diff --git a/app/workers/archive_trace_worker.rb b/app/workers/archive_trace_worker.rb
index 4a9becf0ca7..66f9b8d9e80 100644
--- a/app/workers/archive_trace_worker.rb
+++ b/app/workers/archive_trace_worker.rb
@@ -7,7 +7,7 @@ class ArchiveTraceWorker
# rubocop: disable CodeReuse/ActiveRecord
def perform(job_id)
Ci::Build.without_archived_trace.find_by(id: job_id).try do |job|
- Ci::ArchiveTraceService.new.execute(job)
+ Ci::ArchiveTraceService.new.execute(job, worker_name: self.class.name)
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index 688b600649a..b83412b5e6e 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -76,8 +76,6 @@ class BackgroundMigrationWorker
# class_name - The name of the background migration that we might want to
# run.
def healthy_database?
- return true unless Gitlab::Database.postgresql?
-
!Postgresql::ReplicationSlot.lag_too_great?
end
diff --git a/app/workers/chaos/cpu_spin_worker.rb b/app/workers/chaos/cpu_spin_worker.rb
new file mode 100644
index 00000000000..43a32c3274f
--- /dev/null
+++ b/app/workers/chaos/cpu_spin_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Chaos
+ class CpuSpinWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ def perform(duration_s)
+ Gitlab::Chaos.cpu_spin(duration_s)
+ end
+ end
+end
diff --git a/app/workers/chaos/db_spin_worker.rb b/app/workers/chaos/db_spin_worker.rb
new file mode 100644
index 00000000000..217ddabbcb6
--- /dev/null
+++ b/app/workers/chaos/db_spin_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Chaos
+ class DbSpinWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ def perform(duration_s, interval_s)
+ Gitlab::Chaos.db_spin(duration_s, interval_s)
+ end
+ end
+end
diff --git a/app/workers/chaos/kill_worker.rb b/app/workers/chaos/kill_worker.rb
new file mode 100644
index 00000000000..80f04db1be4
--- /dev/null
+++ b/app/workers/chaos/kill_worker.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Chaos
+ class KillWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ sidekiq_options retry: false
+
+ def perform
+ Gitlab::Chaos.kill
+ end
+ end
+end
diff --git a/app/workers/chaos/leak_mem_worker.rb b/app/workers/chaos/leak_mem_worker.rb
new file mode 100644
index 00000000000..0caa99e0de9
--- /dev/null
+++ b/app/workers/chaos/leak_mem_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Chaos
+ class LeakMemWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ def perform(memory_mb, duration_s)
+ Gitlab::Chaos.leak_mem(memory_mb, duration_s)
+ end
+ end
+end
diff --git a/app/workers/chaos/sleep_worker.rb b/app/workers/chaos/sleep_worker.rb
new file mode 100644
index 00000000000..7c724c4cb4e
--- /dev/null
+++ b/app/workers/chaos/sleep_worker.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Chaos
+ class SleepWorker
+ include ApplicationWorker
+ include ChaosQueue
+
+ def perform(duration_s)
+ Gitlab::Chaos.sleep(duration_s)
+ end
+ end
+end
diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb
index f65ff239866..75e68d0233a 100644
--- a/app/workers/ci/archive_traces_cron_worker.rb
+++ b/app/workers/ci/archive_traces_cron_worker.rb
@@ -11,7 +11,7 @@ module Ci
# 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::ArchiveTraceService.new.execute(build)
+ Ci::ArchiveTraceService.new.execute(build, worker_name: self.class.name)
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/cluster_configure_worker.rb b/app/workers/cluster_configure_worker.rb
index 6f64b7ea0ab..b0e551d4e03 100644
--- a/app/workers/cluster_configure_worker.rb
+++ b/app/workers/cluster_configure_worker.rb
@@ -5,10 +5,6 @@ class ClusterConfigureWorker
include ClusterQueue
def perform(cluster_id)
- Clusters::Cluster.managed.find_by_id(cluster_id).try do |cluster|
- if cluster.project_type?
- Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster)
- end
- end
+ # Scheduled for removal in https://gitlab.com/gitlab-org/gitlab-ce/issues/59319
end
end
diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb
index 497e57c0d0b..8f48eca4d86 100644
--- a/app/workers/cluster_project_configure_worker.rb
+++ b/app/workers/cluster_project_configure_worker.rb
@@ -5,8 +5,6 @@ class ClusterProjectConfigureWorker
include ClusterQueue
def perform(project_id)
- project = Project.find(project_id)
-
- ::Clusters::RefreshService.create_or_update_namespaces_for_project(project)
+ # Scheduled for removal in https://gitlab.com/gitlab-org/gitlab-ce/issues/59319
end
end
diff --git a/app/workers/concerns/chaos_queue.rb b/app/workers/concerns/chaos_queue.rb
new file mode 100644
index 00000000000..e406509d12d
--- /dev/null
+++ b/app/workers/concerns/chaos_queue.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+#
+module ChaosQueue
+ extend ActiveSupport::Concern
+
+ included do
+ queue_namespace :chaos
+ end
+end
diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb
index b75e724ca98..a5e22f88a3b 100644
--- a/app/workers/gitlab_usage_ping_worker.rb
+++ b/app/workers/gitlab_usage_ping_worker.rb
@@ -6,10 +6,16 @@ class GitlabUsagePingWorker
include ApplicationWorker
include CronjobQueue
+ # Retry for up to approximately three hours then give up.
+ sidekiq_options retry: 10, dead: false
+
def perform
# Multiple Sidekiq workers could run this. We should only do this at most once a day.
return unless try_obtain_lease
+ # Splay the request over a minute to avoid thundering herd problems.
+ sleep(rand(0.0..60.0).round(3))
+
SubmitUsagePingService.new.execute
end
diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb
index 48876825564..0c1ca5eb975 100644
--- a/app/workers/namespaces/root_statistics_worker.rb
+++ b/app/workers/namespaces/root_statistics_worker.rb
@@ -9,7 +9,7 @@ module Namespaces
def perform(namespace_id)
namespace = Namespace.find(namespace_id)
- return unless update_statistics_enabled_for?(namespace) && namespace.aggregation_scheduled?
+ return unless namespace.aggregation_scheduled?
Namespaces::StatisticsRefresherService.new.execute(namespace)
@@ -23,9 +23,5 @@ module Namespaces
def log_error(namespace_path, error_message)
Gitlab::SidekiqLogger.error("Namespace statistics can't be updated for #{namespace_path}: #{error_message}")
end
-
- def update_statistics_enabled_for?(namespace)
- Feature.enabled?(:update_statistics_namespace, namespace)
- end
end
end
diff --git a/app/workers/namespaces/schedule_aggregation_worker.rb b/app/workers/namespaces/schedule_aggregation_worker.rb
index a4594b84b13..983ce4bef4a 100644
--- a/app/workers/namespaces/schedule_aggregation_worker.rb
+++ b/app/workers/namespaces/schedule_aggregation_worker.rb
@@ -12,7 +12,7 @@ module Namespaces
namespace = Namespace.find(namespace_id)
root_ancestor = namespace.root_ancestor
- return unless update_statistics_enabled_for?(root_ancestor) && !root_ancestor.aggregation_scheduled?
+ return if root_ancestor.aggregation_scheduled?
Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: root_ancestor.id)
rescue ActiveRecord::RecordNotFound
@@ -37,9 +37,5 @@ module Namespaces
def log_error(root_ancestor_id)
Gitlab::SidekiqLogger.error("Namespace can't be scheduled for aggregation: #{root_ancestor_id} does not exist")
end
-
- def update_statistics_enabled_for?(root_ancestor)
- Feature.enabled?(:update_statistics_namespace, root_ancestor)
- end
end
end
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index f2aa17acb51..96524d93f8d 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -7,9 +7,10 @@ class PipelineProcessWorker
queue_namespace :pipeline_processing
# rubocop: disable CodeReuse/ActiveRecord
- def perform(pipeline_id)
- Ci::Pipeline.find_by(id: pipeline_id)
- .try(:process!)
+ def perform(pipeline_id, build_ids = nil)
+ Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline|
+ pipeline.process!(build_ids)
+ end
end
# rubocop: enable CodeReuse/ActiveRecord
end
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index dba7837bd12..61d34981458 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -42,6 +42,9 @@ class PostReceive
user = identify_user(post_received)
return false unless user
+ # We only need to expire certain caches once per push
+ expire_caches(post_received)
+
post_received.enum_for(:changes_refs).with_index do |(oldrev, newrev, ref), index|
service_klass =
if Gitlab::Git.tag_ref?(ref)
@@ -69,9 +72,34 @@ class PostReceive
after_project_changes_hooks(post_received, user, refs.to_a, changes)
end
+ # Expire the project, branch, and tag cache once per push. Schedule an
+ # update for the repository size and commit count if necessary.
+ def expire_caches(post_received)
+ project = post_received.project
+
+ project.repository.expire_status_cache if project.empty_repo?
+ project.repository.expire_branches_cache if post_received.includes_branches?
+ project.repository.expire_caches_for_tags if post_received.includes_tags?
+
+ enqueue_repository_cache_update(post_received)
+ end
+
+ def enqueue_repository_cache_update(post_received)
+ stats_to_invalidate = [:repository_size]
+ stats_to_invalidate << :commit_count if post_received.includes_default_branch?
+
+ ProjectCacheWorker.perform_async(
+ post_received.project.id,
+ [],
+ stats_to_invalidate,
+ true
+ )
+ end
+
def after_project_changes_hooks(post_received, user, refs, changes)
hook_data = Gitlab::DataBuilder::Repository.update(post_received.project, user, changes, refs)
SystemHooksService.new.execute_hooks(hook_data, :repository_update_hooks)
+ Gitlab::UsageDataCounters::SourceCodeCounter.count(:pushes)
end
def process_wiki_changes(post_received)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index 3efb5343a96..f6ebe4ab006 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -2,7 +2,8 @@
# Worker for processing individual commit messages pushed to a repository.
#
-# Jobs for this worker are scheduled for every commit that is being pushed. As a
+# Jobs for this worker are scheduled for every commit that contains mentionable
+# references in its message and does not exist in the upstream project. As a
# result of this the workload of this worker should be kept to a bare minimum.
# Consider using an extra worker if you need to add any extra (and potentially
# slow) processing of commits.
@@ -19,7 +20,6 @@ class ProcessCommitWorker
project = Project.find_by(id: project_id)
return unless project
- return if commit_exists_in_upstream?(project, commit_hash)
user = User.find_by(id: user_id)
@@ -77,17 +77,4 @@ class ProcessCommitWorker
Commit.from_hash(hash, project)
end
-
- private
-
- # Avoid reprocessing commits that already exist in the upstream
- # when project is forked. This will also prevent duplicated system notes.
- def commit_exists_in_upstream?(project, commit_hash)
- upstream_project = project.fork_source
-
- return false unless upstream_project
-
- commit_id = commit_hash.with_indifferent_access[:id]
- upstream_project.commit(commit_id).present?
- end
end
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index 4e8ea903139..5ac860c93e0 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -12,13 +12,15 @@ class ProjectCacheWorker
# CHANGELOG.
# statistics - An Array containing columns from ProjectStatistics to
# refresh, if empty all columns will be refreshed
+ # refresh_statistics - A boolean that determines whether project statistics should
+ # be updated.
# rubocop: disable CodeReuse/ActiveRecord
- def perform(project_id, files = [], statistics = [])
+ def perform(project_id, files = [], statistics = [], refresh_statistics = true)
project = Project.find_by(id: project_id)
return unless project
- update_statistics(project, statistics)
+ update_statistics(project, statistics) if refresh_statistics
return unless project.repository.exists?
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
index 5bafe8e2046..368abfeda99 100644
--- a/app/workers/remote_mirror_notification_worker.rb
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -4,7 +4,7 @@ class RemoteMirrorNotificationWorker
include ApplicationWorker
def perform(remote_mirror_id)
- remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
+ remote_mirror = RemoteMirror.find_by_id(remote_mirror_id)
# We check again if there's an error because a newer run since this job was
# fired could've completed successfully.
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index 03a7ff2cd7a..d13c7641eb3 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -1,50 +1,53 @@
# frozen_string_literal: true
class RepositoryUpdateRemoteMirrorWorker
- UpdateAlreadyInProgressError = Class.new(StandardError)
UpdateError = Class.new(StandardError)
include ApplicationWorker
+ include Gitlab::ExclusiveLeaseHelpers
sidekiq_options retry: 3, dead: false
- sidekiq_retry_in { |count| 30 * count }
+ LOCK_WAIT_TIME = 30.seconds
+ MAX_TRIES = 3
- sidekiq_retries_exhausted do |msg, _|
- Sidekiq.logger.warn "Failed #{msg['class']} with #{msg['args']}: #{msg['error_message']}"
- end
-
- def perform(remote_mirror_id, scheduled_time)
- remote_mirror = RemoteMirrorFinder.new(id: remote_mirror_id).execute
+ def perform(remote_mirror_id, scheduled_time, tries = 0)
+ remote_mirror = RemoteMirror.find_by_id(remote_mirror_id)
+ return unless remote_mirror
return if remote_mirror.updated_since?(scheduled_time)
- raise UpdateAlreadyInProgressError if remote_mirror.update_in_progress?
+ # If the update is already running, wait for it to finish before running again
+ # This will wait for a total of 90 seconds in 3 steps
+ in_lock(remote_mirror_update_lock(remote_mirror.id),
+ retries: 3,
+ ttl: remote_mirror.max_runtime,
+ sleep_sec: LOCK_WAIT_TIME) do
+ update_mirror(remote_mirror, scheduled_time, tries)
+ end
+ rescue Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError
+ # If an update runs longer than 1.5 minutes, we'll reschedule it
+ # with a backoff. The next run will check if the previous update would
+ # include the changes that triggered this update and become a no-op.
+ self.class.perform_in(remote_mirror.backoff_delay, remote_mirror.id, scheduled_time, tries)
+ end
- remote_mirror.update_start
+ private
- project = remote_mirror.project
+ def update_mirror(mirror, scheduled_time, tries)
+ project = mirror.project
current_user = project.creator
- result = Projects::UpdateRemoteMirrorService.new(project, current_user).execute(remote_mirror)
- raise UpdateError, result[:message] if result[:status] == :error
-
- remote_mirror.update_finish
- rescue UpdateAlreadyInProgressError
- raise
- rescue UpdateError => ex
- fail_remote_mirror(remote_mirror, ex.message)
- raise
- rescue => ex
- return unless remote_mirror
+ result = Projects::UpdateRemoteMirrorService.new(project, current_user).execute(mirror, tries)
- fail_remote_mirror(remote_mirror, ex.message)
- raise UpdateError, "#{ex.class}: #{ex.message}"
+ if result[:status] == :error && mirror.to_retry?
+ schedule_retry(mirror, scheduled_time, tries)
+ end
end
- private
-
- def fail_remote_mirror(remote_mirror, message)
- remote_mirror.mark_as_failed(message)
+ def remote_mirror_update_lock(mirror_id)
+ [self.class.name, mirror_id].join(':')
+ end
- Rails.logger.error(message) # rubocop:disable Gitlab/RailsLogger
+ def schedule_retry(mirror, scheduled_time, tries)
+ self.class.perform_in(mirror.backoff_delay, mirror.id, scheduled_time, tries + 1)
end
end
diff --git a/bin/changelog b/bin/changelog
index 328d9495b96..ec068d06507 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -23,7 +23,7 @@ module ChangelogHelpers
Abort = Class.new(StandardError)
Done = Class.new(StandardError)
- MAX_FILENAME_LENGTH = 140 # ecryptfs has a limit of 140 characters
+ MAX_FILENAME_LENGTH = 99 # GNU tar has a 99 character limit
def capture_stdout(cmd)
output = IO.popen(cmd, &:read)
diff --git a/bin/secpick b/bin/secpick
index d01304285b6..8a61356a088 100755
--- a/bin/secpick
+++ b/bin/secpick
@@ -45,7 +45,7 @@ module Secpick
def git_commands
["git fetch #{@options[:remote]} #{stable_branch}",
- "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch}",
+ "git checkout -B #{source_branch} #{@options[:remote]}/#{stable_branch} --no-track",
"git cherry-pick #{@options[:sha]}",
"git push #{@options[:remote]} #{source_branch}",
"git checkout #{original_branch}"]
diff --git a/bin/web_puma b/bin/web_puma
index 178fe84800d..29d72cd4a41 100755
--- a/bin/web_puma
+++ b/bin/web_puma
@@ -10,7 +10,7 @@ puma_config="$app_root/config/puma.rb"
spawn_puma()
{
- exec bundle exec puma --config "${puma_config}" "$@"
+ exec bundle exec puma --config "${puma_config}" --environment "$RAILS_ENV" "$@"
}
get_puma_pid()
diff --git a/changelogs/README.md b/changelogs/README.md
index c4113ccb863..d408a74157a 100644
--- a/changelogs/README.md
+++ b/changelogs/README.md
@@ -3,7 +3,7 @@
To generate and validate your changelog entries:
1. Run `bin/changelog` to generate.
-1. Run `scripts/lint-changelog-yaml` to validate.
+1. Run `yamllint changelogs` to validate.
See [development/changelog] documentation for detailed usage.
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 1e6642ec102..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/10-adjust-copy-for-adding-additional-members.yml b/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
new file mode 100644
index 00000000000..f249eff572c
--- /dev/null
+++ b/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d93e7634ae5
--- /dev/null
+++ b/changelogs/unreleased/10972-be-allow-restricting-group-members-by-a-domain-whitelist-ce.yml
@@ -0,0 +1,5 @@
+---
+title: Add new table to store email domain per group
+merge_request: 31071
+author:
+type: added
diff --git a/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml b/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml
deleted file mode 100644
index 10c5eed9556..00000000000
--- a/changelogs/unreleased/11039-moved-code-difference-from-EE-to-CE.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Moved EE/CE code differences for file `app/views/search/_category.html.haml` into CE"
-merge_request: 28755
-author: Michel Engelen
-type: other
diff --git a/changelogs/unreleased/11090-export-design-management-lfs-data.yml b/changelogs/unreleased/11090-export-design-management-lfs-data.yml
new file mode 100644
index 00000000000..36b773124d7
--- /dev/null
+++ b/changelogs/unreleased/11090-export-design-management-lfs-data.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for exporting repository type data for LFS objects
+merge_request: 30830
+author:
+type: changed
diff --git a/changelogs/unreleased/11448-fix-cs-with-k8s-runners.yml b/changelogs/unreleased/11448-fix-cs-with-k8s-runners.yml
deleted file mode 100644
index 191e64df4f1..00000000000
--- a/changelogs/unreleased/11448-fix-cs-with-k8s-runners.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Container Scanning job timeout when using the kubernetes executor
-merge_request: 29706
-author:
-type: fixed
diff --git a/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml b/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml
deleted file mode 100644
index 606abe818b4..00000000000
--- a/changelogs/unreleased/11888-regression-deploy-correlation-markers-on-monitoring-graphs-not-clickable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Turn commit sha in monitor charts popover to link
-merge_request: 29914
-author:
-type: fixed
diff --git a/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml b/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
new file mode 100644
index 00000000000..ccfd929b6ba
--- /dev/null
+++ b/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Track page views for cycle analytics show page
+merge_request: 31717
+author:
+type: added
diff --git a/changelogs/unreleased/12533-shared-runners-warning.yml b/changelogs/unreleased/12533-shared-runners-warning.yml
deleted file mode 100644
index b2b526cc2e4..00000000000
--- a/changelogs/unreleased/12533-shared-runners-warning.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes EE differences for app/views/admin/users/show.html.haml
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/12550-fullscrean.yml b/changelogs/unreleased/12550-fullscrean.yml
deleted file mode 100644
index f20b191f411..00000000000
--- a/changelogs/unreleased/12550-fullscrean.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes EE differences for app/views/layouts/fullscreen.html.haml
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/12553-preferences.yml b/changelogs/unreleased/12553-preferences.yml
deleted file mode 100644
index c41a8c98e4e..00000000000
--- a/changelogs/unreleased/12553-preferences.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes EE diff for app/views/profiles/preferences/show.html.haml
-merge_request:
-author:
-type: other
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
new file mode 100644
index 00000000000..93936d441e7
--- /dev/null
+++ b/changelogs/unreleased/17276-breakage-in-displaying-svg-in-the-same-repository.yml
@@ -0,0 +1,5 @@
+---
+title: Fix inline rendering of relative paths to SVGs from the current repository
+merge_request: 31352
+author:
+type: fixed
diff --git a/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml b/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml
deleted file mode 100644
index 741a0faf469..00000000000
--- a/changelogs/unreleased/17690-Protect-TeamCity-builds-for-triggering-when-a-branch-is-deleted-And-add-MR-option.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Protect TeamCity builds from triggering when a branch has been deleted. And a MR-option
-merge_request: 29836
-author: Nikolay Novikov, Raphael Tweitmann
-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
new file mode 100644
index 00000000000..705621d06f7
--- /dev/null
+++ b/changelogs/unreleased/19186-redirect-wiki-git-route-to-wiki.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d597b06f224
--- /dev/null
+++ b/changelogs/unreleased/20137-starrers.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..b7b0f5fa0c7
--- /dev/null
+++ b/changelogs/unreleased/21671-multiple-pipeline-status-api.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..5254bd36b9c
--- /dev/null
+++ b/changelogs/unreleased/24705-multi-selection-for-delete-on-registry-page.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..adbd7971a14
--- /dev/null
+++ b/changelogs/unreleased/26866-api-endpoint-to-list-the-docker-images-tags-of-a-group.yml
@@ -0,0 +1,6 @@
+---
+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/29775-fix-nested-lists-unnecessary-margin.yml b/changelogs/unreleased/29775-fix-nested-lists-unnecessary-margin.yml
deleted file mode 100644
index e7e43c54bab..00000000000
--- a/changelogs/unreleased/29775-fix-nested-lists-unnecessary-margin.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix nested lists unnecessary margin
-merge_request: 29775
-author: Kuba Kopeć
-type: fixed
diff --git a/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml b/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml
deleted file mode 100644
index b0252f9e81b..00000000000
--- a/changelogs/unreleased/30355-use-hours-only-for-time-tracking.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add option to limit time tracking units to hours
-merge_request: 29469
-author: Jon Kolb
-type: added
diff --git a/changelogs/unreleased/30974-issue-search-by-number.yml b/changelogs/unreleased/30974-issue-search-by-number.yml
new file mode 100644
index 00000000000..da50ee32c83
--- /dev/null
+++ b/changelogs/unreleased/30974-issue-search-by-number.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..fd270a236dc
--- /dev/null
+++ b/changelogs/unreleased/31434-make-issue-boards-importable.yml
@@ -0,0 +1,5 @@
+---
+title: Make issue boards importable
+merge_request: 31434
+author: Jason Colyer
+type: changed
diff --git a/changelogs/unreleased/32452-multiple-discussions.yml b/changelogs/unreleased/32452-multiple-discussions.yml
deleted file mode 100644
index 5552340ee66..00000000000
--- a/changelogs/unreleased/32452-multiple-discussions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Multiple discussions per line in merge request diffs
-merge_request: 28748
-author:
-type: added
diff --git a/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml b/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml
new file mode 100644
index 00000000000..b7b39303c2e
--- /dev/null
+++ b/changelogs/unreleased/32495-improve-slack-notification-on-pipeline-status.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..f0cc7fe9b6d
--- /dev/null
+++ b/changelogs/unreleased/34414-update-personal-access-token-scope-descriptions-to-reflect-registry-permissions.yml
@@ -0,0 +1,6 @@
+---
+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/35757-move-issues-in-boards-pderichs.yml b/changelogs/unreleased/35757-move-issues-in-boards-pderichs.yml
deleted file mode 100644
index ae50350f70d..00000000000
--- a/changelogs/unreleased/35757-move-issues-in-boards-pderichs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add endpoint to move multiple issues in boards
-merge_request: 30216
-author:
-type: added
diff --git a/changelogs/unreleased/38105-pre-release-tag.yml b/changelogs/unreleased/38105-pre-release-tag.yml
deleted file mode 100644
index d4c5dcacd19..00000000000
--- a/changelogs/unreleased/38105-pre-release-tag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Renders a pre-release tag for releases
-merge_request: 29797
-author:
-type: changed
diff --git a/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml b/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml
new file mode 100644
index 00000000000..e13e3e86a37
--- /dev/null
+++ b/changelogs/unreleased/39217-remove-kubernetes-service-integration.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Kubernetes service integration page
+merge_request: 31365
+author:
+type: removed
diff --git a/changelogs/unreleased/40379-CJK-search-min-chars.yml b/changelogs/unreleased/40379-CJK-search-min-chars.yml
deleted file mode 100644
index 6f6c4df464f..00000000000
--- a/changelogs/unreleased/40379-CJK-search-min-chars.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove minimum character limits for fuzzy searches when using a CTE
-merge_request: 29810
-author:
-type: fixed
diff --git a/changelogs/unreleased/42399-registry-confirm-deletion.yml b/changelogs/unreleased/42399-registry-confirm-deletion.yml
deleted file mode 100644
index 4d720e16721..00000000000
--- a/changelogs/unreleased/42399-registry-confirm-deletion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add confirmation for registry image deletion
-merge_request: 29505
-author:
-type: added
diff --git a/changelogs/unreleased/43080-speed-up-deploy-keys.yml b/changelogs/unreleased/43080-speed-up-deploy-keys.yml
new file mode 100644
index 00000000000..73c9a9e5f82
--- /dev/null
+++ b/changelogs/unreleased/43080-speed-up-deploy-keys.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..674d53286e6
--- /dev/null
+++ b/changelogs/unreleased/44036-fix-someone-edited-the-issue-at-the-same-time-false-warning.yml
@@ -0,0 +1,5 @@
+---
+title: Fix flashing conflict warning when editing issues
+merge_request: 31469
+author:
+type: fixed
diff --git a/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml b/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml
deleted file mode 100644
index 6e78d61ebc4..00000000000
--- a/changelogs/unreleased/44106-include-subgroups-in-group-activity.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include events from subgroups in group's activity
-merge_request: 29953
-author: Fabian Schneider @fabsrc
-type: changed
diff --git a/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml b/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
deleted file mode 100644
index efc6af7845c..00000000000
--- a/changelogs/unreleased/44949-do-not-update-updated_at-on-an-issue-when-reordering-it.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Will not update issue timestamps when changing positions in a list
-merge_request: 29677
-author:
-type: changed
diff --git a/changelogs/unreleased/45104-special-characters-in-project-name-path-prevent-users-from-using-the-container-registry.yml b/changelogs/unreleased/45104-special-characters-in-project-name-path-prevent-users-from-using-the-container-registry.yml
deleted file mode 100644
index ddde0cc9c39..00000000000
--- a/changelogs/unreleased/45104-special-characters-in-project-name-path-prevent-users-from-using-the-container-registry.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updated container registry to display error message when special characters in path. Documentation has also been updated.
-merge_request: 29616
-author:
-type: changed
diff --git a/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml b/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml
deleted file mode 100644
index 592612c2615..00000000000
--- a/changelogs/unreleased/45120-fix-ide-editor-to-update-size-on-show-change.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix IDE editor not showing when switching back from preview
-merge_request: 30135
-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
new file mode 100644
index 00000000000..b4f10150d13
--- /dev/null
+++ b/changelogs/unreleased/47814-search-view-labels.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..38ee95a7553
--- /dev/null
+++ b/changelogs/unreleased/48717-rate-limit-raw-controller-show.yml
@@ -0,0 +1,5 @@
+---
+title: Add Rate Request Limiter to RawController#show endpoint
+merge_request: 30635
+author:
+type: added
diff --git a/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml b/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml
deleted file mode 100644
index e598247b5d8..00000000000
--- a/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Resolve Label picker: Line break on long label titles'
-merge_request: 30610
-author:
-type: fixed
diff --git a/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml b/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml
deleted file mode 100644
index db1391edd73..00000000000
--- a/changelogs/unreleased/49814-display-in-admin-area-if-emails-are-verified-or-not.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a verified pill next to email addresses under the admin users section.
-merge_request: 29669
-author:
-type: added
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
new file mode 100644
index 00000000000..9137e9339aa
--- /dev/null
+++ b/changelogs/unreleased/50020-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a5fe7b1d18e
--- /dev/null
+++ b/changelogs/unreleased/50020-fe-allow-email-notifications-to-be-disabled-for-all-users-of-a-group.yml
@@ -0,0 +1,5 @@
+---
+title: UI for disabling group/project email notifications
+merge_request: 30961
+author: Dustin Spicuzza
+type: added
diff --git a/changelogs/unreleased/50070-legacy-attachments.yml b/changelogs/unreleased/50070-legacy-attachments.yml
new file mode 100644
index 00000000000..03f1cec0f67
--- /dev/null
+++ b/changelogs/unreleased/50070-legacy-attachments.yml
@@ -0,0 +1,5 @@
+---
+title: Create rake tasks for migrating legacy uploads out of deprecated paths
+merge_request: 29409
+author:
+type: other
diff --git a/changelogs/unreleased/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
new file mode 100644
index 00000000000..dc718572cfb
--- /dev/null
+++ b/changelogs/unreleased/50130-cluster-cluster-details-update-automatically-after-cluster-is-created.yml
@@ -0,0 +1,5 @@
+---
+title: Update cluster page automatically when cluster is created
+merge_request: 27189
+author:
+type: changed
diff --git a/changelogs/unreleased/50228-deploy-tokens-custom-username.yml b/changelogs/unreleased/50228-deploy-tokens-custom-username.yml
deleted file mode 100644
index fdafa7b1113..00000000000
--- a/changelogs/unreleased/50228-deploy-tokens-custom-username.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow custom username for deploy tokens
-merge_request: 29639
-author:
-type: added
diff --git a/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml b/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml
deleted file mode 100644
index b51079d5c74..00000000000
--- a/changelogs/unreleased/50834-change-http-status-code-when-repository-disabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Changed HTTP Status Code for disabled repository on /branches and /commits to 404"
-merge_request: 29585
-author: Sam Battalio
-type: changed
diff --git a/changelogs/unreleased/51794-add-ordering-to-runner-jobs-api.yml b/changelogs/unreleased/51794-add-ordering-to-runner-jobs-api.yml
deleted file mode 100644
index 908a132688c..00000000000
--- a/changelogs/unreleased/51794-add-ordering-to-runner-jobs-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add order_by and sort params to list runner jobs api
-merge_request: 29629
-author: Sujay Patel
-type: added
diff --git a/changelogs/unreleased/51952-forking-via-webide.yml b/changelogs/unreleased/51952-forking-via-webide.yml
deleted file mode 100644
index 4497c6b6ca4..00000000000
--- a/changelogs/unreleased/51952-forking-via-webide.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve "500 error when forking via the web IDE button"
-merge_request: 29909
-author:
-type: fixed
diff --git a/changelogs/unreleased/51952-redirect-to-webide-in-fork.yml b/changelogs/unreleased/51952-redirect-to-webide-in-fork.yml
deleted file mode 100644
index ada0efca968..00000000000
--- a/changelogs/unreleased/51952-redirect-to-webide-in-fork.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Open WebIDE in fork when user doesn't have access
-merge_request: 30642
-author:
-type: changed
diff --git a/changelogs/unreleased/52366-improved-group-lists-ui.yml b/changelogs/unreleased/52366-improved-group-lists-ui.yml
deleted file mode 100644
index 99e5b67a56c..00000000000
--- a/changelogs/unreleased/52366-improved-group-lists-ui.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve group list UI
-merge_request: 26542
-author:
-type: changed
diff --git a/changelogs/unreleased/52442-minimal-remove-mysql-support.yml b/changelogs/unreleased/52442-minimal-remove-mysql-support.yml
deleted file mode 100644
index f1a50657383..00000000000
--- a/changelogs/unreleased/52442-minimal-remove-mysql-support.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove MySQL support
-merge_request: 29790
-author:
-type: removed
diff --git a/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml b/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml
new file mode 100644
index 00000000000..645c92127a3
--- /dev/null
+++ b/changelogs/unreleased/52494-separate-namespace-per-project-environment-slug.yml
@@ -0,0 +1,5 @@
+---
+title: Use separate Kubernetes namespaces per environment
+merge_request: 30711
+author:
+type: added
diff --git a/changelogs/unreleased/52954-allow-developers-to-delete-tags.yml b/changelogs/unreleased/52954-allow-developers-to-delete-tags.yml
deleted file mode 100644
index 38c65a67f2a..00000000000
--- a/changelogs/unreleased/52954-allow-developers-to-delete-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow developers to delete tags
-merge_request: 29668
-author:
-type: changed
diff --git a/changelogs/unreleased/53357-fix-plus-in-upload-file-names.yml b/changelogs/unreleased/53357-fix-plus-in-upload-file-names.yml
deleted file mode 100644
index c11d74bd4fe..00000000000
--- a/changelogs/unreleased/53357-fix-plus-in-upload-file-names.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken URLs for uploads with a plus in the filename
-merge_request: 29915
-author:
-type: fixed
diff --git a/changelogs/unreleased/53811-issue-boards-to-core-projects-backend-ce.yml b/changelogs/unreleased/53811-issue-boards-to-core-projects-backend-ce.yml
deleted file mode 100644
index d8fbd7ec362..00000000000
--- a/changelogs/unreleased/53811-issue-boards-to-core-projects-backend-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move Multiple Issue Boards for Projects to Core
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/54117-transactional-rebase.yml b/changelogs/unreleased/54117-transactional-rebase.yml
deleted file mode 100644
index d0c93114c49..00000000000
--- a/changelogs/unreleased/54117-transactional-rebase.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow asynchronous rebase operations to be monitored
-merge_request: 29940
-author:
-type: fixed
diff --git a/changelogs/unreleased/54595-incorrect-reaction-emoji-placement-in-discussion.yml b/changelogs/unreleased/54595-incorrect-reaction-emoji-placement-in-discussion.yml
deleted file mode 100644
index 639eefb50cb..00000000000
--- a/changelogs/unreleased/54595-incorrect-reaction-emoji-placement-in-discussion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix incorrect emoji placement in commit diff discussion
-merge_request: 29445
-author:
-type: fixed
diff --git a/changelogs/unreleased/55487-enable-group-terminals-button.yml b/changelogs/unreleased/55487-enable-group-terminals-button.yml
deleted file mode 100644
index 6bf16eaadd1..00000000000
--- a/changelogs/unreleased/55487-enable-group-terminals-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable terminals button for group clusters
-merge_request: 30255
-author:
-type: added
diff --git a/changelogs/unreleased/55564-remove-if-in-before-after-action.yml b/changelogs/unreleased/55564-remove-if-in-before-after-action.yml
new file mode 100644
index 00000000000..a787faa8a9c
--- /dev/null
+++ b/changelogs/unreleased/55564-remove-if-in-before-after-action.yml
@@ -0,0 +1,5 @@
+---
+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/55623-group-cluster-apis.yml b/changelogs/unreleased/55623-group-cluster-apis.yml
deleted file mode 100644
index fe987ef4a82..00000000000
--- a/changelogs/unreleased/55623-group-cluster-apis.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add API for CRUD group clusters
-merge_request: 30213
-author:
-type: added
diff --git a/changelogs/unreleased/55902-disable-creation-of-non-rbac-kubernetes-clusters.yml b/changelogs/unreleased/55902-disable-creation-of-non-rbac-kubernetes-clusters.yml
deleted file mode 100644
index 2af2d229c6c..00000000000
--- a/changelogs/unreleased/55902-disable-creation-of-non-rbac-kubernetes-clusters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove support for creating non-RBAC kubernetes clusters
-merge_request: 29614
-author:
-type: removed
diff --git a/changelogs/unreleased/55953-renamed-discussion-to-thread.yml b/changelogs/unreleased/55953-renamed-discussion-to-thread.yml
deleted file mode 100644
index a5203ecf2e0..00000000000
--- a/changelogs/unreleased/55953-renamed-discussion-to-thread.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "renamed discussion to thread in merge-request and issue timeline"
-merge_request: 29553
-author: Michel Engelen
-type: changed
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
new file mode 100644
index 00000000000..a2fa07c6ed2
--- /dev/null
+++ b/changelogs/unreleased/56100-make-quick-action-commands-applied-banner-more-useful.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7d1e84bbaa4
--- /dev/null
+++ b/changelogs/unreleased/56130-deployment-date.yml
@@ -0,0 +1,5 @@
+---
+title: Add finished_at to the internal API Deployment entity
+merge_request: 31808
+author:
+type: other
diff --git a/changelogs/unreleased/57538-normalize-users-private-profile-field.yml b/changelogs/unreleased/57538-normalize-users-private-profile-field.yml
deleted file mode 100644
index 85fc4cbf33c..00000000000
--- a/changelogs/unreleased/57538-normalize-users-private-profile-field.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Migrate NULL values for users.private_profile column and update users API to reject null value for private_profile.
-merge_request: 29888
-author:
-type: changed
diff --git a/changelogs/unreleased/57793-fix-line-age.yml b/changelogs/unreleased/57793-fix-line-age.yml
deleted file mode 100644
index cf4e328e54a..00000000000
--- a/changelogs/unreleased/57793-fix-line-age.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support note position tracing on an image
-merge_request: 30158
-author:
-type: fixed
diff --git a/changelogs/unreleased/57815.yml b/changelogs/unreleased/57815.yml
deleted file mode 100644
index ae8ec7036ce..00000000000
--- a/changelogs/unreleased/57815.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enforced requirements for UltraAuth users
-merge_request: 28941
-author: Kartikey Tanna
-type: changed
diff --git a/changelogs/unreleased/57918-encrypt-feature-flags-tokens-changelog.yml b/changelogs/unreleased/57918-encrypt-feature-flags-tokens-changelog.yml
deleted file mode 100644
index 9701c8bc4a5..00000000000
--- a/changelogs/unreleased/57918-encrypt-feature-flags-tokens-changelog.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add token_encrypted column to operations_feature_flags_clients table
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml b/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml
new file mode 100644
index 00000000000..f634c0cd98a
--- /dev/null
+++ b/changelogs/unreleased/57953-fix-unfolded-diff-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: Fix suggestion on lines that are not part of an MR
+merge_request: 30606
+author:
+type: fixed
diff --git a/changelogs/unreleased/57973-errors-in-application-settings-panel-shows-wrong-panel.yml b/changelogs/unreleased/57973-errors-in-application-settings-panel-shows-wrong-panel.yml
deleted file mode 100644
index 2b62992eda0..00000000000
--- a/changelogs/unreleased/57973-errors-in-application-settings-panel-shows-wrong-panel.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show poper panel when validation error occurs in admin settings panels
-merge_request: 25434
-author:
-type: fixed
diff --git a/changelogs/unreleased/58035-expand-mr-diff.yml b/changelogs/unreleased/58035-expand-mr-diff.yml
new file mode 100644
index 00000000000..7163cce29f2
--- /dev/null
+++ b/changelogs/unreleased/58035-expand-mr-diff.yml
@@ -0,0 +1,5 @@
+---
+title: Add new expansion options for merge request diffs
+merge_request: 30927
+author:
+type: added
diff --git a/changelogs/unreleased/58065-uniform-html-txt-email.yml b/changelogs/unreleased/58065-uniform-html-txt-email.yml
deleted file mode 100644
index be34e93ff83..00000000000
--- a/changelogs/unreleased/58065-uniform-html-txt-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Always shows author of created issue/started discussion/comment in HTML body and text of email
-merge_request: 29886
-author: Frank van Rest
-type: fixed
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
new file mode 100644
index 00000000000..f719338b9cb
--- /dev/null
+++ b/changelogs/unreleased/58256-incorrect-empty-state-message-displayed-on-explore-projects-tab.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Incorrect empty state message on Explore projects
+merge_request: 25578
+author:
+type: fixed
diff --git a/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml b/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
deleted file mode 100644
index bf6f314f0ce..00000000000
--- a/changelogs/unreleased/58689-regroup-jump-button-in-discussion.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Improve discussion reply buttons layout and how jump to next discussion button
- appears
-merge_request: 29779
-author:
-type: changed
diff --git a/changelogs/unreleased/58802-rename-webide.yml b/changelogs/unreleased/58802-rename-webide.yml
deleted file mode 100644
index 40471d967ce..00000000000
--- a/changelogs/unreleased/58802-rename-webide.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Re-name files in Web IDE in a more natural way
-merge_request: 29948
-author:
-type: changed
diff --git a/changelogs/unreleased/58808-fix-image-diff-on-text.yml b/changelogs/unreleased/58808-fix-image-diff-on-text.yml
deleted file mode 100644
index 78955c24186..00000000000
--- a/changelogs/unreleased/58808-fix-image-diff-on-text.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't show image diff note on text file
-merge_request: 30221
-author:
-type: fixed
diff --git a/changelogs/unreleased/59028-fix-extra-plus-in-diffs.yml b/changelogs/unreleased/59028-fix-extra-plus-in-diffs.yml
deleted file mode 100644
index 0786f4dbc10..00000000000
--- a/changelogs/unreleased/59028-fix-extra-plus-in-diffs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate trailing +/- char in merge request discussions
-merge_request: 29518
-author:
-type: fixed
diff --git a/changelogs/unreleased/59257-find-new-branches-harder.yml b/changelogs/unreleased/59257-find-new-branches-harder.yml
deleted file mode 100644
index 631eaa22ef0..00000000000
--- a/changelogs/unreleased/59257-find-new-branches-harder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Look for new branches more carefully
-merge_request: 29761
-author:
-type: fixed
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
new file mode 100644
index 00000000000..38cfa0f273e
--- /dev/null
+++ b/changelogs/unreleased/59325-units-are-not-shown-on-the-performance-dashboard-2.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..4c93a108f2b
--- /dev/null
+++ b/changelogs/unreleased/59521-job-sidebar-has-a-blank-block.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..02e81c7fc87
--- /dev/null
+++ b/changelogs/unreleased/59590-keyboard-shortcut-for-jump-to-next-unresolved-discussion.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..964962cb817
--- /dev/null
+++ b/changelogs/unreleased/59712-resolve-the-search-problem-issue.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..48242a77c6b
--- /dev/null
+++ b/changelogs/unreleased/59829-fix-style-lint-wiki.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..ef11e8743f6
--- /dev/null
+++ b/changelogs/unreleased/60449-reduce-gitaly-calls-when-rendering-commits-in-md.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..db25e7b3338
--- /dev/null
+++ b/changelogs/unreleased/60516-uninstall-tiller.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Helm to be uninstalled from the UI
+merge_request: 27359
+author:
+type: added
diff --git a/changelogs/unreleased/60617-enable-project-cluster-jit.yml b/changelogs/unreleased/60617-enable-project-cluster-jit.yml
deleted file mode 100644
index b7d745d4385..00000000000
--- a/changelogs/unreleased/60617-enable-project-cluster-jit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable just-in-time Kubernetes resource creation for project-level clusters
-merge_request: 29515
-author:
-type: changed
diff --git a/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml b/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml
new file mode 100644
index 00000000000..efc3ec241e2
--- /dev/null
+++ b/changelogs/unreleased/60664-kubernetes-applications-uninstall-cert-manager.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Cert-Manager to be uninstalled
+merge_request: 31166
+author:
+type: added
diff --git a/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml b/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml
deleted file mode 100644
index 3632c8eec20..00000000000
--- a/changelogs/unreleased/60666-kubernetes-applications-uninstall-runner.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow GitLab Runner to be uninstalled from the UI
-merge_request: 30176
-author:
-type: added
diff --git a/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml b/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml
new file mode 100644
index 00000000000..c33dc0f50cd
--- /dev/null
+++ b/changelogs/unreleased/60668-kubernetes-applications-uninstall-knative.yml
@@ -0,0 +1,5 @@
+---
+title: Allow Knative to be uninstalled from the UI
+merge_request: 30458
+author:
+type: added
diff --git a/changelogs/unreleased/60856-deleting-binary-file.yml b/changelogs/unreleased/60856-deleting-binary-file.yml
deleted file mode 100644
index 9b587ed591b..00000000000
--- a/changelogs/unreleased/60856-deleting-binary-file.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removing an image should not output binary data
-merge_request: 30314
-author:
-type: fixed
diff --git a/changelogs/unreleased/60859-upload-after-delete.yml b/changelogs/unreleased/60859-upload-after-delete.yml
deleted file mode 100644
index c36dfb84bfe..00000000000
--- a/changelogs/unreleased/60859-upload-after-delete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: In WebIDE allow adding new entries of the same name as deleted entry
-merge_request: 30239
-author:
-type: fixed
diff --git a/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml b/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml
deleted file mode 100644
index 237d0fd6aef..00000000000
--- a/changelogs/unreleased/60860-keep-empty-folders-in-tree.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Keep the empty folders in the tree
-merge_request: 29196
-author:
-type: fixed
diff --git a/changelogs/unreleased/60879-fix-reports-timing-out.yml b/changelogs/unreleased/60879-fix-reports-timing-out.yml
deleted file mode 100644
index 845162fe10f..00000000000
--- a/changelogs/unreleased/60879-fix-reports-timing-out.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix reports jobs timing out because of cache
-merge_request: 29780
-author:
-type: fixed
diff --git a/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml b/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml
new file mode 100644
index 00000000000..17763b4c69e
--- /dev/null
+++ b/changelogs/unreleased/60948-display-groupid-on-group-admin-page.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..3ff83ede2fa
--- /dev/null
+++ b/changelogs/unreleased/60949-display-projectid-on-project-admin-page.yml
@@ -0,0 +1,5 @@
+---
+title: Display project id on project admin page
+merge_request: 29734
+author: Zsolt Kovari
+type: added
diff --git a/changelogs/unreleased/61005-grafanaInAdminSettingsMonitoringMenu.yml b/changelogs/unreleased/61005-grafanaInAdminSettingsMonitoringMenu.yml
deleted file mode 100644
index 3ee512f3448..00000000000
--- a/changelogs/unreleased/61005-grafanaInAdminSettingsMonitoringMenu.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds link to Grafana in Admin > Monitoring settings when grafana is enabled in config
-merge_request: 28937
-author: Romain Maneschi
-type: added
diff --git a/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml b/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml
deleted file mode 100644
index 0b8d301352b..00000000000
--- a/changelogs/unreleased/61156-instance-level-cluster-pod-terminal-access.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable terminals for instance and group clusters
-merge_request: 28613
-author:
-type: added
diff --git a/changelogs/unreleased/61201-pass-identities-to-external-authorization.yml b/changelogs/unreleased/61201-pass-identities-to-external-authorization.yml
deleted file mode 100644
index 82eea653de6..00000000000
--- a/changelogs/unreleased/61201-pass-identities-to-external-authorization.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add identity information to external authorization requests
-merge_request: 29461
-author:
-type: changed
diff --git a/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml b/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml
new file mode 100644
index 00000000000..99fc817d703
--- /dev/null
+++ b/changelogs/unreleased/61207-adjusted-hoverable-area-in-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: "Adjusted the clickable area of collapsed sidebar elements"
+merge_request: 30974
+author: Michel Engelen
+type: changed
diff --git a/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml b/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml
deleted file mode 100644
index 3445057f7fb..00000000000
--- a/changelogs/unreleased/61284-frontend-follow-up-from-add-packages_size-to-projectstatistics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improved readability of storage statistics in group / project admin area
-merge_request: 30406
-author:
-type: other
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
new file mode 100644
index 00000000000..1f5e507d48d
--- /dev/null
+++ b/changelogs/unreleased/61332-web-ide-mr-branch-dropdown-closes-unexpectedly.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d524d91b246
--- /dev/null
+++ b/changelogs/unreleased/61335-fix-file-icon-status.yml
@@ -0,0 +1,5 @@
+---
+title: Fix IDE new files icon in tree
+merge_request: 31560
+author:
+type: fixed
diff --git a/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml b/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml
deleted file mode 100644
index f4ed4551aab..00000000000
--- a/changelogs/unreleased/61342-commit-search-result-doesn-t-pass-wcag-color-audit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change color for namespace in commit search
-merge_request: 30312
-author:
-type: other
diff --git a/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml b/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml
new file mode 100644
index 00000000000..58e29212462
--- /dev/null
+++ b/changelogs/unreleased/61445-prevent-persisting-auto-switch-discussion-filter.yml
@@ -0,0 +1,6 @@
+---
+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/61613-spacing-mr-widgets.yml b/changelogs/unreleased/61613-spacing-mr-widgets.yml
deleted file mode 100644
index 7d37ef8da2e..00000000000
--- a/changelogs/unreleased/61613-spacing-mr-widgets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Left align mr widget icons and text
-merge_request: 28561
-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
new file mode 100644
index 00000000000..988eb77db12
--- /dev/null
+++ b/changelogs/unreleased/61776-fixing-the-U2F-warning-message-text-colour.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..ec6e9c5aff8
--- /dev/null
+++ b/changelogs/unreleased/61787-broadcast-messages-colour-selector-provide-default-options-with-descriptive-labels.yml
@@ -0,0 +1,5 @@
+---
+title: add color selector to broadcast messages form
+merge_request: 30988
+author:
+type: other
diff --git a/changelogs/unreleased/62088-search-back.yml b/changelogs/unreleased/62088-search-back.yml
deleted file mode 100644
index 4758e927880..00000000000
--- a/changelogs/unreleased/62088-search-back.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed back navigation for projects filter
-merge_request: 30373
-author:
-type: fixed
diff --git a/changelogs/unreleased/62124-new-threaded-discussion-design.yml b/changelogs/unreleased/62124-new-threaded-discussion-design.yml
deleted file mode 100644
index 6614e05be74..00000000000
--- a/changelogs/unreleased/62124-new-threaded-discussion-design.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Implement borderless discussion design with new reply field
-merge_request: 28580
-author:
-type: added
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
new file mode 100644
index 00000000000..ccc3195e6ae
--- /dev/null
+++ b/changelogs/unreleased/62137-add-tooltip-to-improve-clarity-of-detached-label-state-in-the-merge-request-pipeline.yml
@@ -0,0 +1,5 @@
+---
+title: Updated the detached pipeline badge tooltip text to offer a better explanation
+merge_request: 31626
+author:
+type: other
diff --git a/changelogs/unreleased/62183-update-response-code-for-bulk-delete-api-for-container-registry.yml b/changelogs/unreleased/62183-update-response-code-for-bulk-delete-api-for-container-registry.yml
deleted file mode 100644
index ce75a55efdc..00000000000
--- a/changelogs/unreleased/62183-update-response-code-for-bulk-delete-api-for-container-registry.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Return 400 when deleting tags more often than once per hour.
-merge_request: 29448
-author:
-type: changed
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
new file mode 100644
index 00000000000..10f2b7eaed5
--- /dev/null
+++ b/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Harmonize selections in user settings
+merge_request: 31110
+author: Marc Schwede
+type: other
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
new file mode 100644
index 00000000000..f8e4a26dad8
--- /dev/null
+++ b/changelogs/unreleased/62609-test-summary-loading-spinner-is-too-large.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust size and align MR-widget loading icon
+merge_request: 31503
+author:
+type: fixed
diff --git a/changelogs/unreleased/62772-disable-kubernetes-credential-passthrough.yml b/changelogs/unreleased/62772-disable-kubernetes-credential-passthrough.yml
deleted file mode 100644
index 35771e80821..00000000000
--- a/changelogs/unreleased/62772-disable-kubernetes-credential-passthrough.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable Kubernetes credential passthrough for managed project-level clusters
-merge_request: 29262
-author:
-type: removed
diff --git a/changelogs/unreleased/62772-migrate-managed-clusters-to-unmanaged.yml b/changelogs/unreleased/62772-migrate-managed-clusters-to-unmanaged.yml
deleted file mode 100644
index 62a67c7b78d..00000000000
--- a/changelogs/unreleased/62772-migrate-managed-clusters-to-unmanaged.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Migrate GitLab managed project-level clusters to unmanaged if a Kubernetes
- namespace was unable to be created
-merge_request: 29251
-author:
-type: other
diff --git a/changelogs/unreleased/62826-graphql-emoji-mutations.yml b/changelogs/unreleased/62826-graphql-emoji-mutations.yml
deleted file mode 100644
index 0c0aaedf844..00000000000
--- a/changelogs/unreleased/62826-graphql-emoji-mutations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: GraphQL mutations for add, remove and toggle emoji
-merge_request: 29919
-author:
-type: added
diff --git a/changelogs/unreleased/62826-graphql-note-mutations.yml b/changelogs/unreleased/62826-graphql-note-mutations.yml
deleted file mode 100644
index 85273186bb6..00000000000
--- a/changelogs/unreleased/62826-graphql-note-mutations.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: GraphQL mutations for managing Notes
-merge_request: 30210
-author:
-type: added
diff --git a/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml b/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml
deleted file mode 100644
index 6652e495869..00000000000
--- a/changelogs/unreleased/62938-wcag-aa-edited-text-color.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use darker gray color for system note metadata and edited text
-merge_request: 30054
-author:
-type: other
diff --git a/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml b/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
deleted file mode 100644
index 749fe6a9cb0..00000000000
--- a/changelogs/unreleased/62968-environment-details-header-border-misaligned.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Environment details header border misaligned
-merge_request: 30011
-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
new file mode 100644
index 00000000000..b6bc03f4003
--- /dev/null
+++ b/changelogs/unreleased/62971-embed-specific-metrics-chart-in-issue.yml
@@ -0,0 +1,5 @@
+---
+title: Embed specific metrics chart in issue
+merge_request: 31644
+author:
+type: added
diff --git a/changelogs/unreleased/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
new file mode 100644
index 00000000000..aaf0ddfa48d
--- /dev/null
+++ b/changelogs/unreleased/62973-specify-time-frame-in-shareable-link-for-embedding-metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Allow links to metrics dashboard at a specific time
+merge_request: 31283
+author:
+type: added
diff --git a/changelogs/unreleased/62980-username-availability-checker-breaks-inline-validation.yml b/changelogs/unreleased/62980-username-availability-checker-breaks-inline-validation.yml
deleted file mode 100644
index 7436f5d278e..00000000000
--- a/changelogs/unreleased/62980-username-availability-checker-breaks-inline-validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the signup form's username validation messages not displaying
-merge_request: 29678
-author: Jiaan Louw
-type: fixed
diff --git a/changelogs/unreleased/63079-migrate-clusters-with-no-token-to-unmanaged.yml b/changelogs/unreleased/63079-migrate-clusters-with-no-token-to-unmanaged.yml
deleted file mode 100644
index 92133af03f7..00000000000
--- a/changelogs/unreleased/63079-migrate-clusters-with-no-token-to-unmanaged.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Migrate GitLab managed project-level clusters to unmanaged if they are missing
- a Kubernetes service account token
-merge_request: 29648
-author:
-type: other
diff --git a/changelogs/unreleased/63181-collapsible-line.yml b/changelogs/unreleased/63181-collapsible-line.yml
new file mode 100644
index 00000000000..c13d4eeab6c
--- /dev/null
+++ b/changelogs/unreleased/63181-collapsible-line.yml
@@ -0,0 +1,5 @@
+---
+title: Makes collapsible title clickable in job log
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/63200-reply-button-broken.yml b/changelogs/unreleased/63200-reply-button-broken.yml
deleted file mode 100644
index 11f81dbd925..00000000000
--- a/changelogs/unreleased/63200-reply-button-broken.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix unresponsive reply button in discussions
-merge_request: 29936
-author:
-type: fixed
diff --git a/changelogs/unreleased/63227-fix-double-border.yml b/changelogs/unreleased/63227-fix-double-border.yml
deleted file mode 100644
index 6cc4040d333..00000000000
--- a/changelogs/unreleased/63227-fix-double-border.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Double Border in Profile Page
-merge_request: 29784
-author: Yoginth <@yo>
-type: fixed
diff --git a/changelogs/unreleased/63247-add-conf-toast-and-link.yml b/changelogs/unreleased/63247-add-conf-toast-and-link.yml
deleted file mode 100644
index 915cc20dcc8..00000000000
--- a/changelogs/unreleased/63247-add-conf-toast-and-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include a link back to the MR for Visual Review feedback form
-merge_request: 29719
-author:
-type: changed
diff --git a/changelogs/unreleased/63261-the-graphql-query-for-the-mr-popover-failes-on-the-frontend.yml b/changelogs/unreleased/63261-the-graphql-query-for-the-mr-popover-failes-on-the-frontend.yml
deleted file mode 100644
index 87d74e3f4b4..00000000000
--- a/changelogs/unreleased/63261-the-graphql-query-for-the-mr-popover-failes-on-the-frontend.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Make sure we are receiving the proper information on the MR Popover by updating
- the IID in the graphql query
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/63298-prevent-excessive-sanitization-asciidoc.yml b/changelogs/unreleased/63298-prevent-excessive-sanitization-asciidoc.yml
deleted file mode 100644
index cd8206cdb99..00000000000
--- a/changelogs/unreleased/63298-prevent-excessive-sanitization-asciidoc.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Prevent excessive sanitization of AsciiDoc ouptut"
-merge_request: 30290
-author: Guillaume Grossetie
-type: added \ No newline at end of file
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
new file mode 100644
index 00000000000..815010e15ae
--- /dev/null
+++ b/changelogs/unreleased/63438-oauth2-support-with-gitlab-personal-access-token.yml
@@ -0,0 +1,5 @@
+---
+title: Personal access tokens are accepted using OAuth2 header format
+merge_request: 30277
+author:
+type: added
diff --git a/changelogs/unreleased/63475-fix-n-1.yml b/changelogs/unreleased/63475-fix-n-1.yml
deleted file mode 100644
index 3ed825290fd..00000000000
--- a/changelogs/unreleased/63475-fix-n-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of MergeRequestsController#ci_environment_status endpoint
-merge_request: 30224
-author:
-type: performance
diff --git a/changelogs/unreleased/63479-jira-capitalization.yml b/changelogs/unreleased/63479-jira-capitalization.yml
deleted file mode 100644
index a4cc32beba6..00000000000
--- a/changelogs/unreleased/63479-jira-capitalization.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Replace 'JIRA' with 'Jira'
-merge_request: 29849
-author: Takuya Noguchi
-type: other
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
new file mode 100644
index 00000000000..c3ee3108216
--- /dev/null
+++ b/changelogs/unreleased/63485-fix-pipeline-emails-to-use-group-setting.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipeline emails not respecting group notification email setting
+merge_request: 30907
+author:
+type: fixed
diff --git a/changelogs/unreleased/63507-fix-race-condition-fetching-token.yml b/changelogs/unreleased/63507-fix-race-condition-fetching-token.yml
deleted file mode 100644
index 7f2b59fc9eb..00000000000
--- a/changelogs/unreleased/63507-fix-race-condition-fetching-token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Retry fetching Kubernetes Secret#token (#63507)
-merge_request: 29922
-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
new file mode 100644
index 00000000000..387c01dc135
--- /dev/null
+++ b/changelogs/unreleased/63547-add-system-notes-for-when-a-zoom-call-was-added-removed-from-an-issue.yml
@@ -0,0 +1,5 @@
+---
+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/63559-remove-avatar-from-sign-in.yml b/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml
deleted file mode 100644
index 3f7a8e19de5..00000000000
--- a/changelogs/unreleased/63559-remove-avatar-from-sign-in.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Avatar in Please sign in pattern too large
-merge_request: 29944
-author:
-type: fixed
diff --git a/changelogs/unreleased/63568-access-email-notifications-custom-email.yml b/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
new file mode 100644
index 00000000000..ece6442d7cf
--- /dev/null
+++ b/changelogs/unreleased/63568-access-email-notifications-custom-email.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7943d9573f3
--- /dev/null
+++ b/changelogs/unreleased/63571-fix-gc-profiler-data-being-wiped.yml
@@ -0,0 +1,5 @@
+---
+title: Fix GC::Profiler metrics fetching
+merge_request: 31331
+author:
+type: fixed
diff --git a/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml b/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml
deleted file mode 100644
index a1e7d4679d8..00000000000
--- a/changelogs/unreleased/63590-pipeline-actions-cause-full-refresh.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pipelines table to update without refreshing after action
-merge_request: 30190
-author:
-type: fixed
diff --git a/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml b/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml
deleted file mode 100644
index 08c415f4a1c..00000000000
--- a/changelogs/unreleased/63656-runner-tags-search-dropdown-is-empty.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix runner tags search dropdown being empty when there are tags
-merge_request: 29985
-author:
-type: fixed
diff --git a/changelogs/unreleased/63667-hashed-storage-migration-count-correctly.yml b/changelogs/unreleased/63667-hashed-storage-migration-count-correctly.yml
deleted file mode 100644
index ead79be2505..00000000000
--- a/changelogs/unreleased/63667-hashed-storage-migration-count-correctly.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display the correct amount of projects being migrated/rolled-back to Hashed Storage when specifying ranges
-merge_request: 29996
-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
new file mode 100644
index 00000000000..ab116674ced
--- /dev/null
+++ b/changelogs/unreleased/63671-remove-extra-padding-from-the-disabled-comment-area.yml
@@ -0,0 +1,5 @@
+---
+title: Remove extra padding from disabled comment box
+merge_request: 31603
+author:
+type: fixed
diff --git a/changelogs/unreleased/63691-fix-doc-link.yml b/changelogs/unreleased/63691-fix-doc-link.yml
deleted file mode 100644
index e63756e8227..00000000000
--- a/changelogs/unreleased/63691-fix-doc-link.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correct link to docs for External Dashboard
-merge_request: 30019
-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
new file mode 100644
index 00000000000..a1e2ae0e5df
--- /dev/null
+++ b/changelogs/unreleased/63730-fix-500-status-labels-pd.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..24d6bca3842
--- /dev/null
+++ b/changelogs/unreleased/63833-fix-jira-issues-url.yml
@@ -0,0 +1,5 @@
+---
+title: Handle trailing slashes when generating Jira issue URLs
+merge_request: 30911
+author:
+type: fixed
diff --git a/changelogs/unreleased/63873-process-start-time.yml b/changelogs/unreleased/63873-process-start-time.yml
deleted file mode 100644
index b11a66ca106..00000000000
--- a/changelogs/unreleased/63873-process-start-time.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Change ruby_process_start_time_seconds metric to unix timestamp instead of
- seconds from boot.
-merge_request: 30195
-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
new file mode 100644
index 00000000000..1a5a552b120
--- /dev/null
+++ b/changelogs/unreleased/63888-snippets-usage-ping-for-create-smau.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..741763403a5
--- /dev/null
+++ b/changelogs/unreleased/63942-remove-config-action_dispatch-use_authenticated_cookie_encryption-configuration.yml
@@ -0,0 +1,5 @@
+---
+title: Enable authenticated cookie encryption
+merge_request: 31463
+author:
+type: other
diff --git a/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml b/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml
deleted file mode 100644
index a0ef34f3700..00000000000
--- a/changelogs/unreleased/63945-update-mixin-deep-to-1-3-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update mixin-deep to 1.3.2
-merge_request: 30223
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/63971-remove-istanbul.yml b/changelogs/unreleased/63971-remove-istanbul.yml
deleted file mode 100644
index 674df82db35..00000000000
--- a/changelogs/unreleased/63971-remove-istanbul.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove istanbul JavaScript package
-merge_request: 30232
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/64066-fix-uneven-click-areas.yml b/changelogs/unreleased/64066-fix-uneven-click-areas.yml
deleted file mode 100644
index ce0572cad34..00000000000
--- a/changelogs/unreleased/64066-fix-uneven-click-areas.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix spacing issues for toasts
-merge_request: 30345
-author:
-type: fixed
diff --git a/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml b/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml
deleted file mode 100644
index 51c1537a159..00000000000
--- a/changelogs/unreleased/64070-asciidoctor-enable-section-anchors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Enable section anchors in Asciidoctor"
-merge_request: 30666
-author: Guillaume Grossetie
-type: added \ No newline at end of file
diff --git a/changelogs/unreleased/64081-override-helm-release-name.yml b/changelogs/unreleased/64081-override-helm-release-name.yml
new file mode 100644
index 00000000000..2bf39b17c03
--- /dev/null
+++ b/changelogs/unreleased/64081-override-helm-release-name.yml
@@ -0,0 +1,5 @@
+---
+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/64091-fix-broken-terminal.yml b/changelogs/unreleased/64091-fix-broken-terminal.yml
deleted file mode 100644
index 156f6de5008..00000000000
--- a/changelogs/unreleased/64091-fix-broken-terminal.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix environments broken terminal
-merge_request: 30401
-author:
-type: fixed
diff --git a/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml b/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml
new file mode 100644
index 00000000000..272c830a914
--- /dev/null
+++ b/changelogs/unreleased/64092-removes-update-statistics-namespace-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..12416a611ed
--- /dev/null
+++ b/changelogs/unreleased/64160-fix-duplicate-buttons.yml
@@ -0,0 +1,5 @@
+---
+title: Remove duplicate buttons in diff discussion
+merge_request: 30757
+author:
+type: fixed
diff --git a/changelogs/unreleased/64161-gitlab-fqdn.yml b/changelogs/unreleased/64161-gitlab-fqdn.yml
deleted file mode 100644
index 2946be37caa..00000000000
--- a/changelogs/unreleased/64161-gitlab-fqdn.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add CI variable to provide GitLab HOST
-merge_request: 30417
-author:
-type: added
diff --git a/changelogs/unreleased/64176-fix-error-handling.yml b/changelogs/unreleased/64176-fix-error-handling.yml
deleted file mode 100644
index e7a9a5897ae..00000000000
--- a/changelogs/unreleased/64176-fix-error-handling.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix invalid SSL certificate errors on Drone CI service
-merge_request: 30422
-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
new file mode 100644
index 00000000000..f86c63a15b6
--- /dev/null
+++ b/changelogs/unreleased/64180-membersfinder-contains-slow-database-query-with-or-conditions.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..08340d01fd8
--- /dev/null
+++ b/changelogs/unreleased/64190-add-mr-form.yml
@@ -0,0 +1,5 @@
+---
+title: Add MR form to Visual Review (EE) runtime configuration
+merge_request: 30481
+author:
+type: changed
diff --git a/changelogs/unreleased/64249-align-container-registry-empty-state-with-design-guidelines.yml b/changelogs/unreleased/64249-align-container-registry-empty-state-with-design-guidelines.yml
deleted file mode 100644
index ecdb4b6bed1..00000000000
--- a/changelogs/unreleased/64249-align-container-registry-empty-state-with-design-guidelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Alignign empty container registry message with design guidelines
-merge_request: 30502
-author:
-type: other
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
new file mode 100644
index 00000000000..df3cd98830e
--- /dev/null
+++ b/changelogs/unreleased/64257-active_session_lookup_key_cleanup.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7b6818876fb
--- /dev/null
+++ b/changelogs/unreleased/64257-warden_set_user_fix.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..cd4253b63c6
--- /dev/null
+++ b/changelogs/unreleased/64265-center-loading-icon.yml
@@ -0,0 +1,5 @@
+---
+title: Center loading icon in CI action component
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/64295-predictable-environment-slugs.yml b/changelogs/unreleased/64295-predictable-environment-slugs.yml
new file mode 100644
index 00000000000..de581b2b8e1
--- /dev/null
+++ b/changelogs/unreleased/64295-predictable-environment-slugs.yml
@@ -0,0 +1,5 @@
+---
+title: Use predictable environment slugs
+merge_request: 30551
+author:
+type: added
diff --git a/changelogs/unreleased/64314-ci-icon.yml b/changelogs/unreleased/64314-ci-icon.yml
deleted file mode 100644
index 8a550b6fa5b..00000000000
--- a/changelogs/unreleased/64314-ci-icon.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Aligns CI icon in Merge Request dashboard
-merge_request: 30558
-author:
-type: fixed
diff --git a/changelogs/unreleased/64315-mget_sessions_in_chunks.yml b/changelogs/unreleased/64315-mget_sessions_in_chunks.yml
deleted file mode 100644
index d50d86726e2..00000000000
--- a/changelogs/unreleased/64315-mget_sessions_in_chunks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do Redis lookup in batches in ActiveSession.sessions_from_ids
-merge_request: 30561
-author:
-type: performance
diff --git a/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml b/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml
deleted file mode 100644
index 825247db3e7..00000000000
--- a/changelogs/unreleased/64321-wrong-url-when-creating-milestones-from-instance-milestones-dashboard.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix wrong URL when creating milestones from instance milestones dashboard
-merge_request: 30512
-author:
-type: fixed
diff --git a/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml b/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml
deleted file mode 100644
index 7d67b94869d..00000000000
--- a/changelogs/unreleased/64331-Assignee-field-in-a-new-issue-has-an-incorrect-line-wrap.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed incorrect line wrap for assignee label in issues
-merge_request: 30523
-author: Marc Schwede
-type: fixed
diff --git a/changelogs/unreleased/64341-user-callout-deferred-link-support.yml b/changelogs/unreleased/64341-user-callout-deferred-link-support.yml
new file mode 100644
index 00000000000..05230ddc124
--- /dev/null
+++ b/changelogs/unreleased/64341-user-callout-deferred-link-support.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for deferred links in persistent user callouts.
+merge_request: 30818
+author:
+type: added
diff --git a/changelogs/unreleased/64407-vfazio-quirk-omniauth-strategies-openidconnect.yml b/changelogs/unreleased/64407-vfazio-quirk-omniauth-strategies-openidconnect.yml
deleted file mode 100644
index c2e300863fb..00000000000
--- a/changelogs/unreleased/64407-vfazio-quirk-omniauth-strategies-openidconnect.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow client authentication method to be configured for OpenID Connect
-merge_request: 30683
-author: Vincent Fazio
-type: fixed
diff --git a/changelogs/unreleased/64416-lodash-4-6-2-for-prototype-pollution.yml b/changelogs/unreleased/64416-lodash-4-6-2-for-prototype-pollution.yml
deleted file mode 100644
index cd8885233de..00000000000
--- a/changelogs/unreleased/64416-lodash-4-6-2-for-prototype-pollution.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update lodash to 4.7.14 and lodash.mergewith to 4.6.2
-merge_request: 30602
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/64608-double-tooltips.yml b/changelogs/unreleased/64608-double-tooltips.yml
new file mode 100644
index 00000000000..f6cb1944d26
--- /dev/null
+++ b/changelogs/unreleased/64608-double-tooltips.yml
@@ -0,0 +1,5 @@
+---
+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/64645-asciidoctor-preserve-footnote-link-ids.yml b/changelogs/unreleased/64645-asciidoctor-preserve-footnote-link-ids.yml
deleted file mode 100644
index 5427a035478..00000000000
--- a/changelogs/unreleased/64645-asciidoctor-preserve-footnote-link-ids.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Preserve footnote link ids in Asciidoctor"
-merge_request: 30790
-author: Guillaume Grossetie
-type: added \ No newline at end of file
diff --git a/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml b/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml
new file mode 100644
index 00000000000..f35261fcd6c
--- /dev/null
+++ b/changelogs/unreleased/64675-Dashboard-URL-legend-border.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..00664d64050
--- /dev/null
+++ b/changelogs/unreleased/64697-markdown-issues-checkbox-inside-blockquote-status-won-t-be-saved.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..0d2fbaf01ed
--- /dev/null
+++ b/changelogs/unreleased/64700-fix-the-color-of-the-visibility-icon-on-project-lists.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..c564b98bb41
--- /dev/null
+++ b/changelogs/unreleased/64730-metrics-dashboard-menu-is-cramped-with-new-features-enabled.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..fb0f4cedc62
--- /dev/null
+++ b/changelogs/unreleased/64746-Commit-authors-avatar-sretched-in-commit-view-if-no-image-is-loaded.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..db6b1f31506
--- /dev/null
+++ b/changelogs/unreleased/64763-fix-tags-page-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Fix tag page layout
+merge_request:
+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
new file mode 100644
index 00000000000..2a45eec78ef
--- /dev/null
+++ b/changelogs/unreleased/64831-add-padding-to-merged-by-widget.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..21771c76873
--- /dev/null
+++ b/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..f80111f0bd9
--- /dev/null
+++ b/changelogs/unreleased/64972-fix-unicorn-workers-metric.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..4fa3b7783c5
--- /dev/null
+++ b/changelogs/unreleased/64974-remove-livesum-from-ruby-sampler-metrics.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..dd74b8443bc
--- /dev/null
+++ b/changelogs/unreleased/65088-incorrect-message-interpolation-on-project-listing.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..47b2a2ed329
--- /dev/null
+++ b/changelogs/unreleased/65263-manual-action.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..fb9d6fa251d
--- /dev/null
+++ b/changelogs/unreleased/65278-fix-puma-master-counter-wipe.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a5f62dbcd56
--- /dev/null
+++ b/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..fc29a514c42
--- /dev/null
+++ b/changelogs/unreleased/65530-add-externalization-and-fix-regression-in-shortcuts-helper-modal.yml
@@ -0,0 +1,6 @@
+---
+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
new file mode 100644
index 00000000000..c0cb40ce169
--- /dev/null
+++ b/changelogs/unreleased/65660-update-karma-to-4-2-0.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a6f8576ae0b
--- /dev/null
+++ b/changelogs/unreleased/65671-update-mini_magick-to-4-9-5.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..bacc3f5fc11
--- /dev/null
+++ b/changelogs/unreleased/65700-document-max-replication-slots-pg-ha.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..b92e28f9d68
--- /dev/null
+++ b/changelogs/unreleased/65705-two-buttons.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..2531a3730ed
--- /dev/null
+++ b/changelogs/unreleased/65790-highlight.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..217db8aa05a
--- /dev/null
+++ b/changelogs/unreleased/65803-invalidate-branches-cache-on-refresh.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..df0ac649ac1
--- /dev/null
+++ b/changelogs/unreleased/66008-fix-project-image-in-slack-notifications.yml
@@ -0,0 +1,5 @@
+---
+title: Fix project avatar image in Slack pipeline notifications
+merge_request: 31788
+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
new file mode 100644
index 00000000000..1caa5fa84ce
--- /dev/null
+++ b/changelogs/unreleased/66023-starrers-count-do-not-match-after-searching.yml
@@ -0,0 +1,5 @@
+---
+title: Fix starrers counts after searching
+merge_request: 31823
+author:
+type: fixed
diff --git a/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml b/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml
new file mode 100644
index 00000000000..826d5548999
--- /dev/null
+++ b/changelogs/unreleased/66061-update-tooltip-of-detached-label-state.yml
@@ -0,0 +1,5 @@
+---
+title: Updates tooltip of 'detached' label/state
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml b/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
new file mode 100644
index 00000000000..13607ae938a
--- /dev/null
+++ b/changelogs/unreleased/66066-dark-theme-style-for-expansion-on-mr-diffs.yml
@@ -0,0 +1,5 @@
+---
+title: Add syntax highlighting for line expansion
+merge_request: 31821
+author:
+type: fixed
diff --git a/changelogs/unreleased/66161-replace-expand-icons.yml b/changelogs/unreleased/66161-replace-expand-icons.yml
new file mode 100644
index 00000000000..e28e01108a8
--- /dev/null
+++ b/changelogs/unreleased/66161-replace-expand-icons.yml
@@ -0,0 +1,5 @@
+---
+title: Replaced expand diff icons
+merge_request: 31907
+author:
+type: changed
diff --git a/changelogs/unreleased/9928ee-add-rule_type-to-approval-project-rules.yml b/changelogs/unreleased/9928ee-add-rule_type-to-approval-project-rules.yml
deleted file mode 100644
index 698ecebb971..00000000000
--- a/changelogs/unreleased/9928ee-add-rule_type-to-approval-project-rules.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add migration for adding rule_type to approval_project_rules
-merge_request: 30575
-author:
-type: added
diff --git a/changelogs/unreleased/FixLocaleEN.yml b/changelogs/unreleased/FixLocaleEN.yml
new file mode 100644
index 00000000000..49738a6d127
--- /dev/null
+++ b/changelogs/unreleased/FixLocaleEN.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..304bd63d150
--- /dev/null
+++ b/changelogs/unreleased/GL-12412.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..e58ecf9259f
--- /dev/null
+++ b/changelogs/unreleased/GL-12757.yml
@@ -0,0 +1,5 @@
+---
+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/Remove-unresolved-class-in-discussion-header.yml b/changelogs/unreleased/Remove-unresolved-class-in-discussion-header.yml
deleted file mode 100644
index 3695f3063f3..00000000000
--- a/changelogs/unreleased/Remove-unresolved-class-in-discussion-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unresolved class and fixed height in discussion header
-merge_request: 28440
-author: David Palubin
-type: other
diff --git a/changelogs/unreleased/ab-add-index-on-environments.yml b/changelogs/unreleased/ab-add-index-on-environments.yml
new file mode 100644
index 00000000000..6c7641912f4
--- /dev/null
+++ b/changelogs/unreleased/ab-add-index-on-environments.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..bd95ff45d6f
--- /dev/null
+++ b/changelogs/unreleased/ab-count-strategies.yml
@@ -0,0 +1,5 @@
+---
+title: Use tablesample approximate counting by default.
+merge_request: 31048
+author:
+type: performance
diff --git a/changelogs/unreleased/add-caching-to-archive-endpoint.yml b/changelogs/unreleased/add-caching-to-archive-endpoint.yml
new file mode 100644
index 00000000000..770ec16e804
--- /dev/null
+++ b/changelogs/unreleased/add-caching-to-archive-endpoint.yml
@@ -0,0 +1,5 @@
+---
+title: Return an ETag header for the archive endpoint
+merge_request: 30581
+author:
+type: added
diff --git a/changelogs/unreleased/add-clusters-to-deployment.yml b/changelogs/unreleased/add-clusters-to-deployment.yml
deleted file mode 100644
index c85bd3635cc..00000000000
--- a/changelogs/unreleased/add-clusters-to-deployment.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Persist the cluster a deployment was deployed to
-merge_request: 29960
-author:
-type: fixed
diff --git a/changelogs/unreleased/add-git-blame-api.yml b/changelogs/unreleased/add-git-blame-api.yml
new file mode 100644
index 00000000000..cdb77041433
--- /dev/null
+++ b/changelogs/unreleased/add-git-blame-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add git blame to GitLab API
+merge_request: 30675
+author: Oleg Zubchenko
+type: added
diff --git a/changelogs/unreleased/add-metrics-dashboard-permission-check.yml b/changelogs/unreleased/add-metrics-dashboard-permission-check.yml
deleted file mode 100644
index 0ea2c4c8e41..00000000000
--- a/changelogs/unreleased/add-metrics-dashboard-permission-check.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add permission check to metrics dashboards endpoint
-merge_request: 30017
-author:
-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
new file mode 100644
index 00000000000..9b50175f536
--- /dev/null
+++ b/changelogs/unreleased/add-outbound-requests-whitelist-for-local-networks.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d11e7c725f7
--- /dev/null
+++ b/changelogs/unreleased/add-release-to-github-importer.yml
@@ -0,0 +1,5 @@
+---
+title: Add a field for released_at to GH importer
+merge_request: 31496
+author:
+type: fixed
diff --git a/changelogs/unreleased/add-salesforce-logo.yml b/changelogs/unreleased/add-salesforce-logo.yml
deleted file mode 100644
index 13766821b88..00000000000
--- a/changelogs/unreleased/add-salesforce-logo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add salesforce logo for salesforce SSO
-merge_request: 28857
-author:
-type: changed
diff --git a/changelogs/unreleased/add-strategies-column-to-scopes-table.yml b/changelogs/unreleased/add-strategies-column-to-scopes-table.yml
deleted file mode 100644
index 0bb87fca014..00000000000
--- a/changelogs/unreleased/add-strategies-column-to-scopes-table.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add strategies column to operations_feature_flag_scopes table
-merge_request: 29808
-author:
-type: other
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
new file mode 100644
index 00000000000..f810c2c5ada
--- /dev/null
+++ b/changelogs/unreleased/add-support-for-start-sha-to-commits-api.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for start_sha to commits API
+merge_request: 29598
+author:
+type: changed
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
new file mode 100644
index 00000000000..5e138e1059c
--- /dev/null
+++ b/changelogs/unreleased/adjust-group-level-analytics-to-accept-multiple-ids.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..58f969ed742
--- /dev/null
+++ b/changelogs/unreleased/alipniagov-fix-wiki_can_not_be_created_total-counter.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7423fa079cc
--- /dev/null
+++ b/changelogs/unreleased/allow-all-users-to-see-history.yml
@@ -0,0 +1,4 @@
+---
+title: Align access permissions for wiki history to those of wiki pages
+merge_request: 30470
+type: fixed
diff --git a/changelogs/unreleased/allow-reactive-caching-of-nil.yml b/changelogs/unreleased/allow-reactive-caching-of-nil.yml
deleted file mode 100644
index abd88c09d74..00000000000
--- a/changelogs/unreleased/allow-reactive-caching-of-nil.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow ReactiveCaching to support nil value
-merge_request: 30456
-author:
-type: performance
diff --git a/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml b/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml
deleted file mode 100644
index acd944ea684..00000000000
--- a/changelogs/unreleased/always-allow-prometheus-access-in-dev.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Always allow access to health endpoints from localhost in dev
-merge_request: 29930
-author:
-type: other
diff --git a/changelogs/unreleased/always-display-environment-selector.yml b/changelogs/unreleased/always-display-environment-selector.yml
deleted file mode 100644
index 7a55e8f3e5d..00000000000
--- a/changelogs/unreleased/always-display-environment-selector.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken environment selector and always display it on monitoring dashboard
-merge_request: 29705
-author:
-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
new file mode 100644
index 00000000000..cede35c95cc
--- /dev/null
+++ b/changelogs/unreleased/an-sidekiq-chaos.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..2d6f462745e
--- /dev/null
+++ b/changelogs/unreleased/an-sidekiq-scheduling_latency.yml
@@ -0,0 +1,5 @@
+---
+title: Adds Sidekiq scheduling latency structured logging field
+merge_request: 30784
+author:
+type: other
diff --git a/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml b/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml
deleted file mode 100644
index 0500978a2e1..00000000000
--- a/changelogs/unreleased/api-doc-negative-commit-message-push-rule.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Document the negative commit message push rule for the API."
-merge_request: 14004
-author: Maikel Vlasman
-type: added
diff --git a/changelogs/unreleased/asciidoc-enable-syntax-highlighting.yml b/changelogs/unreleased/asciidoc-enable-syntax-highlighting.yml
deleted file mode 100644
index 558ee7b6224..00000000000
--- a/changelogs/unreleased/asciidoc-enable-syntax-highlighting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Enable syntax highlighting for AsciiDoc"
-merge_request: 29835
-author: Guillaume Grossetie
-type: added \ No newline at end of file
diff --git a/changelogs/unreleased/asciidoctor-upgrade.yml b/changelogs/unreleased/asciidoctor-upgrade.yml
deleted file mode 100644
index 50a7cb21e7d..00000000000
--- a/changelogs/unreleased/asciidoctor-upgrade.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade asciidoctor version to 2.0.10
-merge_request: 29741
-author: Rajendra Kadam
-type: added
diff --git a/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml b/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml
deleted file mode 100644
index f614e076268..00000000000
--- a/changelogs/unreleased/backstage-gb-improve-performance-environment-statuses-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance of fetching environments statuses
-merge_request: 30560
-author:
-type: performance
diff --git a/changelogs/unreleased/bjk-64064_cache_metrics.yml b/changelogs/unreleased/bjk-64064_cache_metrics.yml
new file mode 100644
index 00000000000..c9baff7cb7b
--- /dev/null
+++ b/changelogs/unreleased/bjk-64064_cache_metrics.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust redis cache metrics
+merge_request: 30572
+author:
+type: changed
diff --git a/changelogs/unreleased/bjk-fix_prom_example.yml b/changelogs/unreleased/bjk-fix_prom_example.yml
deleted file mode 100644
index 2f81bc6196b..00000000000
--- a/changelogs/unreleased/bjk-fix_prom_example.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update example Prometheus scrape config
-merge_request: 30739
-author:
-type: other
diff --git a/changelogs/unreleased/bjk-usage_ping.yml b/changelogs/unreleased/bjk-usage_ping.yml
new file mode 100644
index 00000000000..dee6c1ad291
--- /dev/null
+++ b/changelogs/unreleased/bjk-usage_ping.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..48dfe662206
--- /dev/null
+++ b/changelogs/unreleased/bring-scoped-environment-variables-to-core.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d768462e130
--- /dev/null
+++ b/changelogs/unreleased/bump_helm_kubectl_gitlab.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..1db0a4952b2
--- /dev/null
+++ b/changelogs/unreleased/bvl-mark-remote-mirrors-as-failed-sooner.yml
@@ -0,0 +1,5 @@
+---
+title: Mark push mirrors as failed after 1 hour
+merge_request: 30999
+author:
+type: changed
diff --git a/changelogs/unreleased/bvl-markdown-graphql.yml b/changelogs/unreleased/bvl-markdown-graphql.yml
deleted file mode 100644
index c2432b3ae81..00000000000
--- a/changelogs/unreleased/bvl-markdown-graphql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Render GFM in GraphQL
-merge_request: 29700
-author:
-type: added
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
new file mode 100644
index 00000000000..962376086b0
--- /dev/null
+++ b/changelogs/unreleased/bvl-remote-mirror-exception-handling.yml
@@ -0,0 +1,6 @@
+---
+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/bvl-rename-routes-after-user-rename.yml b/changelogs/unreleased/bvl-rename-routes-after-user-rename.yml
deleted file mode 100644
index 8b0a01dea53..00000000000
--- a/changelogs/unreleased/bvl-rename-routes-after-user-rename.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update a user's routes after updating their name
-merge_request: 23272
-author:
-type: fixed
diff --git a/changelogs/unreleased/bw-add-index-for-relative-position.yml b/changelogs/unreleased/bw-add-index-for-relative-position.yml
new file mode 100644
index 00000000000..80ca20992e6
--- /dev/null
+++ b/changelogs/unreleased/bw-add-index-for-relative-position.yml
@@ -0,0 +1,5 @@
+---
+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/caneldem-master-patch-77839.yml b/changelogs/unreleased/caneldem-master-patch-77839.yml
deleted file mode 100644
index 6239bcf67c4..00000000000
--- a/changelogs/unreleased/caneldem-master-patch-77839.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Propagate python version variable
-merge_request:
-author: Can Eldem
-type: changed
diff --git a/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml b/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
deleted file mode 100644
index 9f6a2040095..00000000000
--- a/changelogs/unreleased/ce-11098-update-merge-request-settings-description-text.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update merge requests section description text on project settings page
-merge_request: 27838
-author:
-type: changed \ No newline at end of file
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
new file mode 100644
index 00000000000..9eb692c948b
--- /dev/null
+++ b/changelogs/unreleased/ce-xanf-add-links-to-admin-area.yml
@@ -0,0 +1,5 @@
+---
+title: Add links to relevant configuration areas in admin area overview
+merge_request: 29306
+author:
+type: added
diff --git a/changelogs/unreleased/centralize-markdownlint-config.yml b/changelogs/unreleased/centralize-markdownlint-config.yml
deleted file mode 100644
index 9ca36078cf1..00000000000
--- a/changelogs/unreleased/centralize-markdownlint-config.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Centralize markdownlint configuration
-merge_request: 30263
-author:
-type: other
diff --git a/changelogs/unreleased/check-min-schema-migrate.yml b/changelogs/unreleased/check-min-schema-migrate.yml
deleted file mode 100644
index d0f4ae1f5d7..00000000000
--- a/changelogs/unreleased/check-min-schema-migrate.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added a min schema version check to db:migrate
-merge_request: 29882
-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/clusters-group-cte.yml b/changelogs/unreleased/clusters-group-cte.yml
deleted file mode 100644
index 4b51249062d..00000000000
--- a/changelogs/unreleased/clusters-group-cte.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use CTE to fetch clusters hierarchy in single query
-merge_request: 30063
-author:
-type: performance
diff --git a/changelogs/unreleased/create-merge-train-ref-ce.yml b/changelogs/unreleased/create-merge-train-ref-ce.yml
deleted file mode 100644
index b0b95275f58..00000000000
--- a/changelogs/unreleased/create-merge-train-ref-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extend `MergeToRefService` to create merge ref from an arbitrary ref
-merge_request: 30361
-author:
-type: added
diff --git a/changelogs/unreleased/db-update-geo-nodes-primary.yml b/changelogs/unreleased/db-update-geo-nodes-primary.yml
deleted file mode 100644
index 7c5203353ac..00000000000
--- a/changelogs/unreleased/db-update-geo-nodes-primary.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disallow `NULL` values for `geo_nodes.primary` column
-merge_request: 29818
-author: Arun Kumar Mohan
-type: other
diff --git a/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml b/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml
new file mode 100644
index 00000000000..4f119d46a1f
--- /dev/null
+++ b/changelogs/unreleased/dblessing-fix-admin-user-radio-labels.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..615a1571e95
--- /dev/null
+++ b/changelogs/unreleased/dblessing-fix-public-project-ssh-only-ci-failure.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a678e4f93b9
--- /dev/null
+++ b/changelogs/unreleased/delete-designs-v2.yml
@@ -0,0 +1,4 @@
+---
+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
new file mode 100644
index 00000000000..0bd7de6730a
--- /dev/null
+++ b/changelogs/unreleased/dm-process-commit-worker-n-1.yml
@@ -0,0 +1,5 @@
+---
+title: Look up upstream commits once before queuing ProcessCommitWorkers
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/dohtaset.yml b/changelogs/unreleased/dohtaset.yml
deleted file mode 100644
index 5b917bd06d8..00000000000
--- a/changelogs/unreleased/dohtaset.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix charts on Cluster health page
-merge_request: 30073
-author:
-type: fixed
diff --git a/changelogs/unreleased/double-slash-64592.yml b/changelogs/unreleased/double-slash-64592.yml
new file mode 100644
index 00000000000..e3b5b197ac5
--- /dev/null
+++ b/changelogs/unreleased/double-slash-64592.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent double slash in review apps path
+merge_request: 31212
+author:
+type: fixed
diff --git a/changelogs/unreleased/dz-remove-deprecated-group-routes.yml b/changelogs/unreleased/dz-remove-deprecated-group-routes.yml
deleted file mode 100644
index bfa62c620d5..00000000000
--- a/changelogs/unreleased/dz-remove-deprecated-group-routes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove deprecated group routes
-merge_request: 29351
-author:
-type: removed
diff --git a/changelogs/unreleased/dz-remove-deprecated-user-routes.yml b/changelogs/unreleased/dz-remove-deprecated-user-routes.yml
deleted file mode 100644
index 92c2e39dd20..00000000000
--- a/changelogs/unreleased/dz-remove-deprecated-user-routes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove depreated /u/:username routing
-merge_request: 30044
-author:
-type: removed
diff --git a/changelogs/unreleased/embedded-metrics-be-2.yml b/changelogs/unreleased/embedded-metrics-be-2.yml
deleted file mode 100644
index 2623b4a2e0c..00000000000
--- a/changelogs/unreleased/embedded-metrics-be-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose placeholder element for metrics charts in GFM
-merge_request: 29861
-author:
-type: added
diff --git a/changelogs/unreleased/enable-specific-embeds.yml b/changelogs/unreleased/enable-specific-embeds.yml
new file mode 100644
index 00000000000..f2e591621a8
--- /dev/null
+++ b/changelogs/unreleased/enable-specific-embeds.yml
@@ -0,0 +1,5 @@
+---
+title: Enable embedding of specific metrics charts in GFM
+merge_request: 31304
+author:
+type: added
diff --git a/changelogs/unreleased/expose-saml-provider-id-to-users-api.yml b/changelogs/unreleased/expose-saml-provider-id-to-users-api.yml
deleted file mode 100644
index 6f0e4f4cf7f..00000000000
--- a/changelogs/unreleased/expose-saml-provider-id-to-users-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose saml_provider_id in the users API
-merge_request: 14045
-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
new file mode 100644
index 00000000000..ff0d1f3bd71
--- /dev/null
+++ b/changelogs/unreleased/extract_auto_deploy_into_base_image.yml
@@ -0,0 +1,5 @@
+---
+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-on-component-modal-footer.yml b/changelogs/unreleased/fe-delete-old-boardservice-on-component-modal-footer.yml
deleted file mode 100644
index 4392b72443b..00000000000
--- a/changelogs/unreleased/fe-delete-old-boardservice-on-component-modal-footer.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: change the use of boardService in favor of boardsStore on footer for the board
- component
-merge_request: 30616
-author: eduarmreyes
-type: other
diff --git a/changelogs/unreleased/fe-delete-old-boardservice.yml b/changelogs/unreleased/fe-delete-old-boardservice.yml
new file mode 100644
index 00000000000..bb06bfe80d5
--- /dev/null
+++ b/changelogs/unreleased/fe-delete-old-boardservice.yml
@@ -0,0 +1,6 @@
+---
+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-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/fe-issue-reorder.yml b/changelogs/unreleased/fe-issue-reorder.yml
deleted file mode 100644
index aca334b6149..00000000000
--- a/changelogs/unreleased/fe-issue-reorder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring Manual Ordering on Issue List
-merge_request: 29410
-author:
-type: added
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
new file mode 100644
index 00000000000..2cddd52212e
--- /dev/null
+++ b/changelogs/unreleased/feat-add-support-page-link-in-help-menu.yml
@@ -0,0 +1,5 @@
+---
+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/feature-gb-serverless-app-deployment-template.yml b/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
new file mode 100644
index 00000000000..bd9001bd671
--- /dev/null
+++ b/changelogs/unreleased/feature-gb-serverless-app-deployment-template.yml
@@ -0,0 +1,5 @@
+---
+title: Deploy serverless apps with gitlabktl
+merge_request: 30740
+author:
+type: added
diff --git a/changelogs/unreleased/feature-uninstall_cluster_ingress.yml b/changelogs/unreleased/feature-uninstall_cluster_ingress.yml
deleted file mode 100644
index c3f8464c4b4..00000000000
--- a/changelogs/unreleased/feature-uninstall_cluster_ingress.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow Ingress to be uninstalled from the UI
-merge_request: 29977
-author:
-type: added
diff --git a/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml b/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml
deleted file mode 100644
index 28753aa719c..00000000000
--- a/changelogs/unreleased/feature-uninstall_jupyter_hub_app.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow JupyterHub to be uninstalled from the UI
-merge_request: 30097
-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
new file mode 100644
index 00000000000..8b592790629
--- /dev/null
+++ b/changelogs/unreleased/filter-title-description-and-body-from-logs.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..5339b6d764d
--- /dev/null
+++ b/changelogs/unreleased/fix-alignment-on-security-reports.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a0564369b02
--- /dev/null
+++ b/changelogs/unreleased/fix-bin-web-puma-script-to-consider-rails-env.yml
@@ -0,0 +1,5 @@
+---
+title: Make `bin/web_puma` consider RAILS_ENV
+merge_request: 31378
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-broken-vue-i18n-strings.yml b/changelogs/unreleased/fix-broken-vue-i18n-strings.yml
deleted file mode 100644
index 69cec8a6b1b..00000000000
--- a/changelogs/unreleased/fix-broken-vue-i18n-strings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken warnings while Editing Issues and Edit File on MR
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-comment-race-condition.yml b/changelogs/unreleased/fix-comment-race-condition.yml
deleted file mode 100644
index d1f5b8fe01f..00000000000
--- a/changelogs/unreleased/fix-comment-race-condition.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix commenting before discussions are loaded
-merge_request: 30724
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-commits-api-empty-refname.yml b/changelogs/unreleased/fix-commits-api-empty-refname.yml
new file mode 100644
index 00000000000..efdb950e45d
--- /dev/null
+++ b/changelogs/unreleased/fix-commits-api-empty-refname.yml
@@ -0,0 +1,5 @@
+---
+title: Fix 500 errors in commits api caused by empty ref_name parameter
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-facivon-url-if-uploads-object-store-enabled.yml b/changelogs/unreleased/fix-facivon-url-if-uploads-object-store-enabled.yml
deleted file mode 100644
index 4e6d9c087ef..00000000000
--- a/changelogs/unreleased/fix-facivon-url-if-uploads-object-store-enabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Fix favicon path with uploads of object store'
-merge_request: 29482
-author: Roger Meier
-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
new file mode 100644
index 00000000000..0dd545aaecc
--- /dev/null
+++ b/changelogs/unreleased/fix-job-log-formatting.yml
@@ -0,0 +1,5 @@
+---
+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-jupyter-git-v3.yml b/changelogs/unreleased/fix-jupyter-git-v3.yml
deleted file mode 100644
index 8aaaaf249fb..00000000000
--- a/changelogs/unreleased/fix-jupyter-git-v3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Jupyter-Git integration
-merge_request: 30020
-author: Amit Rathi
-type: fixed
diff --git a/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml b/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml
deleted file mode 100644
index 6ae6db08ba1..00000000000
--- a/changelogs/unreleased/fix-median-counting-for-cycle-analytics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix median counting for cycle analytics
-merge_request: 30229
-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
new file mode 100644
index 00000000000..7d171c2cf5b
--- /dev/null
+++ b/changelogs/unreleased/fix-name-vs-path-problem-for-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+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/fix-pipeline-schedule-edge-case.yml b/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml
deleted file mode 100644
index 2b7e3611567..00000000000
--- a/changelogs/unreleased/fix-pipeline-schedule-edge-case.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix pipeline schedule does not run correctly when it's scheduled at the same
- time with the cron worker
-merge_request: 29848
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-sidekiq-transaction-check-race.yml b/changelogs/unreleased/fix-sidekiq-transaction-check-race.yml
deleted file mode 100644
index 89ae4abfe11..00000000000
--- a/changelogs/unreleased/fix-sidekiq-transaction-check-race.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix race in forbid_sidekiq_in_transactions.rb
-merge_request: 30359
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml b/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml
deleted file mode 100644
index 9a263b7f460..00000000000
--- a/changelogs/unreleased/fix-unicorn-sampler-workers-count.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make unicorn_workers to return meaningful results
-merge_request: 30506
-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
new file mode 100644
index 00000000000..f1077f2d56d
--- /dev/null
+++ b/changelogs/unreleased/fj-avoid-incresaing-usage-ping-when-not-enabled.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..adcd0af37e8
--- /dev/null
+++ b/changelogs/unreleased/fj-count-web-ide-merge-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Add Web IDE Usage Ping for Create SMAU
+merge_request: 30800
+author:
+type: changed
diff --git a/changelogs/unreleased/fj-fix-subgroup-search-url.yml b/changelogs/unreleased/fj-fix-subgroup-search-url.yml
deleted file mode 100644
index bee6e97fb6f..00000000000
--- a/changelogs/unreleased/fj-fix-subgroup-search-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix subgroup url in search drop down
-merge_request: 30457
-author:
-type: fixed
diff --git a/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml b/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml
new file mode 100644
index 00000000000..ab7c1697fd6
--- /dev/null
+++ b/changelogs/unreleased/fj-navbar-searches-usage-ping-counter.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..e28dbd6f0c4
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml
@@ -0,0 +1,6 @@
+---
+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
new file mode 100644
index 00000000000..c455b4cf642
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-51260-add-filtering-to-bitbucket-server-import.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..fb1acb1e9f5
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-55474-outbound-setting-system-hooks.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..451aac9c2e3
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-63408-user-mapping.yml
@@ -0,0 +1,5 @@
+---
+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-63955-fix-import-with-source-branch-deleted.yml b/changelogs/unreleased/georgekoltsov-63955-fix-import-with-source-branch-deleted.yml
deleted file mode 100644
index 72e2621c52a..00000000000
--- a/changelogs/unreleased/georgekoltsov-63955-fix-import-with-source-branch-deleted.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix a bug that prevented projects containing merge request diff comments from being imported
-merge_request: 30630
-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
new file mode 100644
index 00000000000..18af16e5216
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-64311-set-visibility-private-if-internal-restricted.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..9557e633f76
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-64377-add-better-log-msg-to-members-mapper.yml
@@ -0,0 +1,6 @@
+---
+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-53-0.yml b/changelogs/unreleased/gitaly-version-v1-53-0.yml
deleted file mode 100644
index 7d3e5ce3cb3..00000000000
--- a/changelogs/unreleased/gitaly-version-v1-53-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.53.0
-merge_request: 30614
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.49.0.yml b/changelogs/unreleased/gitaly-version-v1.49.0.yml
deleted file mode 100644
index 8795bab0209..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.49.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.49.0
-merge_request: 29990
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.51.0.yml b/changelogs/unreleased/gitaly-version-v1.51.0.yml
deleted file mode 100644
index 00d52a190f3..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.51.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.51.0
-merge_request: 30353
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.52.0.yml b/changelogs/unreleased/gitaly-version-v1.52.0.yml
deleted file mode 100644
index d56a392c4ff..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.52.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.52.0
-merge_request: 30568
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.57.0.yml b/changelogs/unreleased/gitaly-version-v1.57.0.yml
new file mode 100644
index 00000000000..65596199e16
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.57.0.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d103f6b248e
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.59.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.59.0
+merge_request: 31743
+author:
+type: changed
diff --git a/changelogs/unreleased/graphql-tree-last-commit.yml b/changelogs/unreleased/graphql-tree-last-commit.yml
deleted file mode 100644
index 5104ca6687e..00000000000
--- a/changelogs/unreleased/graphql-tree-last-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added commit type to tree GraphQL response
-merge_request: 29412
-author:
-type: added
diff --git a/changelogs/unreleased/group-milestones-dashboard-blunceford.yml b/changelogs/unreleased/group-milestones-dashboard-blunceford.yml
new file mode 100644
index 00000000000..a6ded27cb8c
--- /dev/null
+++ b/changelogs/unreleased/group-milestones-dashboard-blunceford.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug in dashboard display of closed milestones
+merge_request: 30820
+author:
+type: fixed
diff --git a/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml b/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml
deleted file mode 100644
index d9ca20d9d4d..00000000000
--- a/changelogs/unreleased/gt-remove-tooltip-directive-on-project-avatar-image-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove tooltip directive on project avatar image component
-merge_request: 29631
-author: George Tsiolis
-type: performance
diff --git a/changelogs/unreleased/hfy-apply-knative-cluster-role-on-service-account-creation.yml b/changelogs/unreleased/hfy-apply-knative-cluster-role-on-service-account-creation.yml
deleted file mode 100644
index 958334cc28e..00000000000
--- a/changelogs/unreleased/hfy-apply-knative-cluster-role-on-service-account-creation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Create Knative role and binding with service account
-merge_request: 30235
-author:
-type: changed
diff --git a/changelogs/unreleased/hide-restricted-visibility-radio.yml b/changelogs/unreleased/hide-restricted-visibility-radio.yml
deleted file mode 100644
index ebc8b787890..00000000000
--- a/changelogs/unreleased/hide-restricted-visibility-radio.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide restricted and disallowed visibility radios
-merge_request: 30590
-author:
-type: fixed
diff --git a/changelogs/unreleased/id-clean-up-mr-assignees.yml b/changelogs/unreleased/id-clean-up-mr-assignees.yml
deleted file mode 100644
index 7ff03c9f00b..00000000000
--- a/changelogs/unreleased/id-clean-up-mr-assignees.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add cleanup migration for MR's multiple assignees
-merge_request: 30261
-author:
-type: changed
diff --git a/changelogs/unreleased/id-extract-widget-into-different-request.yml b/changelogs/unreleased/id-extract-widget-into-different-request.yml
deleted file mode 100644
index 3b9f5fdd6bd..00000000000
--- a/changelogs/unreleased/id-extract-widget-into-different-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a separate endpoint for fetching MRs serialized as widgets
-merge_request: 29979
-author:
-type: performance
diff --git a/changelogs/unreleased/id-mr-widget-etag-caching.yml b/changelogs/unreleased/id-mr-widget-etag-caching.yml
new file mode 100644
index 00000000000..69bf89991d4
--- /dev/null
+++ b/changelogs/unreleased/id-mr-widget-etag-caching.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..6ba5068544e
--- /dev/null
+++ b/changelogs/unreleased/id-source-code-smau.yml
@@ -0,0 +1,5 @@
+---
+title: Add usage pings for source code pushes
+merge_request: 31734
+author:
+type: added
diff --git a/changelogs/unreleased/id-stale-branches.yml b/changelogs/unreleased/id-stale-branches.yml
deleted file mode 100644
index 2f35c5a12c9..00000000000
--- a/changelogs/unreleased/id-stale-branches.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add endpoint for fetching diverging commit counts
-merge_request: 29802
-author:
-type: performance
diff --git a/changelogs/unreleased/implement-dag.yml b/changelogs/unreleased/implement-dag.yml
new file mode 100644
index 00000000000..72f3f9a510c
--- /dev/null
+++ b/changelogs/unreleased/implement-dag.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..86f8c251860
--- /dev/null
+++ b/changelogs/unreleased/improve-quick-action-messages.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..606c60721b8
--- /dev/null
+++ b/changelogs/unreleased/issue-61873-no-error-message-for-general-settings.yml
@@ -0,0 +1,5 @@
+---
+title: error message for general settings
+merge_request: 31636
+author: Mesut Güneş
+type: fixed
diff --git a/changelogs/unreleased/issue-63222.yml b/changelogs/unreleased/issue-63222.yml
deleted file mode 100644
index bc6520b9fc5..00000000000
--- a/changelogs/unreleased/issue-63222.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set default sort method for dashboard projects list
-merge_request: 29830
-author: David Palubin
-type: fixed
diff --git a/changelogs/unreleased/issue-zoom-url.yml b/changelogs/unreleased/issue-zoom-url.yml
deleted file mode 100644
index e0bd5478192..00000000000
--- a/changelogs/unreleased/issue-zoom-url.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extract zoom link from issue and pass to frontend
-merge_request: 29910
-author: raju249
-type: added
diff --git a/changelogs/unreleased/issue_58494.yml b/changelogs/unreleased/issue_58494.yml
new file mode 100644
index 00000000000..c74768fc020
--- /dev/null
+++ b/changelogs/unreleased/issue_58494.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent turning plain links into embedded when moving issues
+merge_request: 31489
+author:
+type: fixed
diff --git a/changelogs/unreleased/issue_64021.yml b/changelogs/unreleased/issue_64021.yml
deleted file mode 100644
index 088d9348619..00000000000
--- a/changelogs/unreleased/issue_64021.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Skip spam check for task list updates
-merge_request: 30279
-author:
-type: fixed
diff --git a/changelogs/unreleased/jc-detect-nfs-for-rugged.yml b/changelogs/unreleased/jc-detect-nfs-for-rugged.yml
deleted file mode 100644
index ca738181a55..00000000000
--- a/changelogs/unreleased/jc-detect-nfs-for-rugged.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use Rugged if we detect storage is NFS and we can access the disk
-merge_request: 29725
-author:
-type: performance
diff --git a/changelogs/unreleased/jc-remove-catfile-flag.yml b/changelogs/unreleased/jc-remove-catfile-flag.yml
deleted file mode 100644
index 6b72de7bc45..00000000000
--- a/changelogs/unreleased/jc-remove-catfile-flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove catfile cache feature flag
-merge_request: 30750
-author:
-type: performance
diff --git a/changelogs/unreleased/je-separate-namespace-fe.yml b/changelogs/unreleased/je-separate-namespace-fe.yml
new file mode 100644
index 00000000000..fb6be071eb6
--- /dev/null
+++ b/changelogs/unreleased/je-separate-namespace-fe.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7b81ee82582
--- /dev/null
+++ b/changelogs/unreleased/jivanvl-add-chart-empty-state.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..5d703008bba
--- /dev/null
+++ b/changelogs/unreleased/jprovazn-fix-positioning.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..f76d4858924
--- /dev/null
+++ b/changelogs/unreleased/jprovazn-project-search.yml
@@ -0,0 +1,5 @@
+---
+title: Order projects in 'Move issue' dropdown by name.
+merge_request: 30778
+author:
+type: fixed
diff --git a/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml b/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml
deleted file mode 100644
index b953d7c0fc8..00000000000
--- a/changelogs/unreleased/jramsay-enable-object-dedupe-by-default.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable Git object pools
-merge_request: 29595
-author: jramsay
-type: changed
diff --git a/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml b/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml
new file mode 100644
index 00000000000..4c049dffc58
--- /dev/null
+++ b/changelogs/unreleased/jramsay-fix-mirroring-help-text-typo.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..7a34f273c90
--- /dev/null
+++ b/changelogs/unreleased/jupyter-fixes-v1.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..22b0877336d
--- /dev/null
+++ b/changelogs/unreleased/khair1-master-patch-79459.yml
@@ -0,0 +1,5 @@
+---
+title: Update Packer.gitlab-ci.yml to use latest image
+merge_request:
+author: Kelly Hair
+type: other
diff --git a/changelogs/unreleased/knative-0-6.yml b/changelogs/unreleased/knative-0-6.yml
deleted file mode 100644
index 66c5c7f343d..00000000000
--- a/changelogs/unreleased/knative-0-6.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Knative version bump 0.5 -> 0.6
-merge_request: 28798
-author: Chris Baumbauer
-type: changed
diff --git a/changelogs/unreleased/label-descr-push-opts.yml b/changelogs/unreleased/label-descr-push-opts.yml
new file mode 100644
index 00000000000..9b01cfdaed2
--- /dev/null
+++ b/changelogs/unreleased/label-descr-push-opts.yml
@@ -0,0 +1,5 @@
+---
+title: Support setting of merge request title and description using git push options
+merge_request: 31068
+author:
+type: added
diff --git a/changelogs/unreleased/limit-amount-of-tests-returned.yml b/changelogs/unreleased/limit-amount-of-tests-returned.yml
deleted file mode 100644
index 0e80a64b6b7..00000000000
--- a/changelogs/unreleased/limit-amount-of-tests-returned.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Limit amount of JUnit tests returned
-merge_request: 30274
-author:
-type: performance
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
new file mode 100644
index 00000000000..59f12fca1f1
--- /dev/null
+++ b/changelogs/unreleased/lm-download-csv-of-charts-from-metrics-dashboard.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..1f466450e76
--- /dev/null
+++ b/changelogs/unreleased/load-search-counts-async.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..180f0f7247f
--- /dev/null
+++ b/changelogs/unreleased/maintainers-can-create-subgroup.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..9b8eff8e043
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-add-at-colon-variable-masking.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..a71cabfe303
--- /dev/null
+++ b/changelogs/unreleased/mc-feature-manual-job-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Allow specifying variables when running manual jobs
+merge_request: 30485
+author:
+type: added
diff --git a/changelogs/unreleased/mh-board-tooltips.yml b/changelogs/unreleased/mh-board-tooltips.yml
deleted file mode 100644
index 06fc64c52a7..00000000000
--- a/changelogs/unreleased/mh-board-tooltips.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "'Open' and 'Closed' issue board lists no longer display a redundant tooltip"
-merge_request: 30187
-author:
-type: fixed
diff --git a/changelogs/unreleased/mh-boards-filter-bar.yml b/changelogs/unreleased/mh-boards-filter-bar.yml
deleted file mode 100644
index 3e91b5ef443..00000000000
--- a/changelogs/unreleased/mh-boards-filter-bar.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display boards filter bar on mobile
-merge_request: 30120
-author:
-type: fixed
diff --git a/changelogs/unreleased/mh-collapsible-boards.yml b/changelogs/unreleased/mh-collapsible-boards.yml
deleted file mode 100644
index b69d6e81cc4..00000000000
--- a/changelogs/unreleased/mh-collapsible-boards.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Labeled issue boards can now collapse
-merge_request: 29955
-author:
-type: added
diff --git a/changelogs/unreleased/mh-colon-autocomplete.yml b/changelogs/unreleased/mh-colon-autocomplete.yml
deleted file mode 100644
index 8b169c22588..00000000000
--- a/changelogs/unreleased/mh-colon-autocomplete.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow auto-completing scoped labels
-merge_request: 29749
-author:
-type: added
diff --git a/changelogs/unreleased/mh-mermaid-linebreaks.yml b/changelogs/unreleased/mh-mermaid-linebreaks.yml
deleted file mode 100644
index e38820d8ce3..00000000000
--- a/changelogs/unreleased/mh-mermaid-linebreaks.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix linebreak rendering in Mermaid flowcharts
-merge_request: 30730
-author:
-type: fixed
diff --git a/changelogs/unreleased/move-all-configs-to-global.yml b/changelogs/unreleased/move-all-configs-to-global.yml
deleted file mode 100644
index ff311d57f8d..00000000000
--- a/changelogs/unreleased/move-all-configs-to-global.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Introduce default: for gitlab-ci.yml'
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/mw-project-list-color-fix.yml b/changelogs/unreleased/mw-project-list-color-fix.yml
deleted file mode 100644
index 6f8b2742ec6..00000000000
--- a/changelogs/unreleased/mw-project-list-color-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add text-secondary to controls in project list
-merge_request: 30567
-author:
-type: fixed
diff --git a/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml b/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml
new file mode 100644
index 00000000000..d56a07fe569
--- /dev/null
+++ b/changelogs/unreleased/new-cycle-analytics-backend-migrations.yml
@@ -0,0 +1,5 @@
+---
+title: Create database tables for the new cycle analytics backend
+merge_request: 31621
+author:
+type: other
diff --git a/changelogs/unreleased/optimize-note-indexes.yml b/changelogs/unreleased/optimize-note-indexes.yml
new file mode 100644
index 00000000000..bfb84779abf
--- /dev/null
+++ b/changelogs/unreleased/optimize-note-indexes.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize DB indexes for ES indexing of notes
+merge_request: 31846
+author:
+type: performance
diff --git a/changelogs/unreleased/osw-persist-tmp-snippet-uploads.yml b/changelogs/unreleased/osw-persist-tmp-snippet-uploads.yml
deleted file mode 100644
index 9348626c41d..00000000000
--- a/changelogs/unreleased/osw-persist-tmp-snippet-uploads.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Persist tmp snippet uploads at users
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml b/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
deleted file mode 100644
index d2744cddebd..00000000000
--- a/changelogs/unreleased/osw-sync-merge-ref-upon-mergeability-check.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sync merge ref upon mergeability check
-merge_request: 29569
-author:
-type: added
diff --git a/changelogs/unreleased/paginate-license-management.yml b/changelogs/unreleased/paginate-license-management.yml
deleted file mode 100644
index c5134978612..00000000000
--- a/changelogs/unreleased/paginate-license-management.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Backport and Docs for Paginate license management and add license search
-merge_request: 27602
-author:
-type: changed
diff --git a/changelogs/unreleased/patch-29.yml b/changelogs/unreleased/patch-29.yml
deleted file mode 100644
index 674c06e1259..00000000000
--- a/changelogs/unreleased/patch-29.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updates PHP template to php:latest to ensure always targeting latest stable
-merge_request: 30319
-author: Paul Giberson
-type: changed
diff --git a/changelogs/unreleased/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/po-raw-changes-encoding.yml b/changelogs/unreleased/po-raw-changes-encoding.yml
deleted file mode 100644
index 051d18f50c7..00000000000
--- a/changelogs/unreleased/po-raw-changes-encoding.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expect bytes from Gitaly RPC GetRawChanges
-merge_request: 28164
-author:
-type: fixed
diff --git a/changelogs/unreleased/post-migrate-private-profile.yml b/changelogs/unreleased/post-migrate-private-profile.yml
new file mode 100644
index 00000000000..53a55661aa0
--- /dev/null
+++ b/changelogs/unreleased/post-migrate-private-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate remaining users with null private_profile
+merge_request: 31708
+author:
+type: other
diff --git a/changelogs/unreleased/pre-releases-38105a.yml b/changelogs/unreleased/pre-releases-38105a.yml
deleted file mode 100644
index 8b7cf6065d4..00000000000
--- a/changelogs/unreleased/pre-releases-38105a.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show an Upcoming Status for Releases
-merge_request: 29577
-author:
-type: added
diff --git a/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml b/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml
deleted file mode 100644
index d7bfc67b208..00000000000
--- a/changelogs/unreleased/prepare-cycle-analytics-for-group-level.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Modify cycle analytics on project level
-merge_request: 30356
-author:
-type: changed
diff --git a/changelogs/unreleased/project_api.yml b/changelogs/unreleased/project_api.yml
deleted file mode 100644
index a04f9bb5608..00000000000
--- a/changelogs/unreleased/project_api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve Project API
-merge_request: 28327
-author: Mathieu Parent
-type: added
diff --git a/changelogs/unreleased/rails-template-update.yml b/changelogs/unreleased/rails-template-update.yml
new file mode 100644
index 00000000000..53eb57c84ff
--- /dev/null
+++ b/changelogs/unreleased/rails-template-update.yml
@@ -0,0 +1,5 @@
+---
+title: Update 'Ruby on Rails' project template
+merge_request: 31310
+author:
+type: other
diff --git a/changelogs/unreleased/refactor-sentry.yml b/changelogs/unreleased/refactor-sentry.yml
deleted file mode 100644
index 25c5534fae0..00000000000
--- a/changelogs/unreleased/refactor-sentry.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Sentry from application settings
-merge_request: 28447
-author: Roger Meier
-type: added
diff --git a/changelogs/unreleased/registry-fix-multi-delete-modal.yml b/changelogs/unreleased/registry-fix-multi-delete-modal.yml
deleted file mode 100644
index 94a2df7a7e7..00000000000
--- a/changelogs/unreleased/registry-fix-multi-delete-modal.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent multiple confirmation modals from opening when deleting a repository
-merge_request: 30532
-author:
-type: fixed
diff --git a/changelogs/unreleased/remove-auto-ssl-ff.yml b/changelogs/unreleased/remove-auto-ssl-ff.yml
deleted file mode 100644
index 1b4d0dcde08..00000000000
--- a/changelogs/unreleased/remove-auto-ssl-ff.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Add support for generating SSL certificates for custon pages domains through
- Let's Encrypt
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/remove-kubernetes-service-deployment-platform.yml b/changelogs/unreleased/remove-kubernetes-service-deployment-platform.yml
deleted file mode 100644
index 17421fca234..00000000000
--- a/changelogs/unreleased/remove-kubernetes-service-deployment-platform.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove Kubernetes service integration and Kubernetes service template from available deployment platforms
-merge_request: 29786
-author:
-type: removed
diff --git a/changelogs/unreleased/remove-line-profile-from-performance-bar.yml b/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
new file mode 100644
index 00000000000..c1c7450fbbd
--- /dev/null
+++ b/changelogs/unreleased/remove-line-profile-from-performance-bar.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..9412cd7c9a6
--- /dev/null
+++ b/changelogs/unreleased/remove-peek-gc.yml
@@ -0,0 +1,5 @@
+---
+title: Remove GC metrics from performance bar
+merge_request:
+author:
+type: removed
diff --git a/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml b/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml
deleted file mode 100644
index 3f4d4bbd432..00000000000
--- a/changelogs/unreleased/remove-support-for-legacy-pipeline-triggers.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove support for legacy pipeline triggers
-merge_request: 30133
-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
new file mode 100644
index 00000000000..d32cbd1d8e0
--- /dev/null
+++ b/changelogs/unreleased/remove_deployment_metrics_deployment_platform_fallback.yml
@@ -0,0 +1,6 @@
+---
+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/remove_group_and_instance_clusters_feature_flag.yml b/changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml
deleted file mode 100644
index fcc6c564345..00000000000
--- a/changelogs/unreleased/remove_group_and_instance_clusters_feature_flag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove group and instance clusters feature flag
-merge_request: 30124
-author:
-type: changed
diff --git a/changelogs/unreleased/report-missing-job-dependency.yml b/changelogs/unreleased/report-missing-job-dependency.yml
new file mode 100644
index 00000000000..660cdfc856e
--- /dev/null
+++ b/changelogs/unreleased/report-missing-job-dependency.yml
@@ -0,0 +1,6 @@
+---
+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/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml b/changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml
deleted file mode 100644
index c105287532b..00000000000
--- a/changelogs/unreleased/require-pipeline-when-enabling-only-allow-merge-if-pipeline-succeeds.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enforce presence of pipeline when "Pipeline must succeed" project setting is enabled
-merge_request: 29926
-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
new file mode 100644
index 00000000000..f412ba11b91
--- /dev/null
+++ b/changelogs/unreleased/rf-remove-group-overview-security-dashboard-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Security Dashboard feature flag
+merge_request: 31820
+author:
+type: other
diff --git a/changelogs/unreleased/rj-fix-manual-order.yml b/changelogs/unreleased/rj-fix-manual-order.yml
deleted file mode 100644
index ecc39b78b06..00000000000
--- a/changelogs/unreleased/rj-fix-manual-order.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't let logged out user do manual order
-merge_request: 30264
-author:
-type: fixed
diff --git a/changelogs/unreleased/rm-src-branch.yml b/changelogs/unreleased/rm-src-branch.yml
new file mode 100644
index 00000000000..03b91d0c7db
--- /dev/null
+++ b/changelogs/unreleased/rm-src-branch.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..2b9070bacfe
--- /dev/null
+++ b/changelogs/unreleased/safe-archiving-for-traces.yml
@@ -0,0 +1,5 @@
+---
+title: Extra logging for new live trace architecture
+merge_request: 30892
+author:
+type: fixed
diff --git a/changelogs/unreleased/sanitize_rake_ldap_check_output.yml b/changelogs/unreleased/sanitize_rake_ldap_check_output.yml
deleted file mode 100644
index 92824d1dd48..00000000000
--- a/changelogs/unreleased/sanitize_rake_ldap_check_output.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sanitize LDAP output in Rake tasks
-merge_request: 28427
-author:
-type: fixed
diff --git a/changelogs/unreleased/search-blob-basenames.yml b/changelogs/unreleased/search-blob-basenames.yml
deleted file mode 100644
index 48ad1130e3f..00000000000
--- a/changelogs/unreleased/search-blob-basenames.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Build correct basenames for title search results
-merge_request: 29898
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-2858-fix-color-validation.yml b/changelogs/unreleased/security-2858-fix-color-validation.yml
deleted file mode 100644
index 3430207a2b6..00000000000
--- a/changelogs/unreleased/security-2858-fix-color-validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix DoS vulnerability in color validation regex
-merge_request:
-author:
-type: security
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
new file mode 100644
index 00000000000..cd31fe0f35c
--- /dev/null
+++ b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml
@@ -0,0 +1,5 @@
+---
+title: Restrict slash commands to users who can log in
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-59581-related-merge-requests-count.yml b/changelogs/unreleased/security-59581-related-merge-requests-count.yml
deleted file mode 100644
index 83faa2f7c13..00000000000
--- a/changelogs/unreleased/security-59581-related-merge-requests-count.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose merge requests count based on user access
-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
new file mode 100644
index 00000000000..7d7096833a7
--- /dev/null
+++ b/changelogs/unreleased/security-60551-fix-upload-scope.yml
@@ -0,0 +1,5 @@
+---
+title: Queries for Upload should be scoped by model
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/security-DOS_issue_comments_banzai.yml b/changelogs/unreleased/security-DOS_issue_comments_banzai.yml
deleted file mode 100644
index 2405b1a4f5f..00000000000
--- a/changelogs/unreleased/security-DOS_issue_comments_banzai.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Denial of Service for comments when rendering issues/MR comments
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-bvl-enforce-graphql-type-authorization.yml b/changelogs/unreleased/security-bvl-enforce-graphql-type-authorization.yml
deleted file mode 100644
index 7dedb9f6230..00000000000
--- a/changelogs/unreleased/security-bvl-enforce-graphql-type-authorization.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add missing authorizations in GraphQL
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fp-prevent-billion-laughs-attack.yml b/changelogs/unreleased/security-fp-prevent-billion-laughs-attack.yml
deleted file mode 100644
index 4e0cf848931..00000000000
--- a/changelogs/unreleased/security-fp-prevent-billion-laughs-attack.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent Billion Laughs attack
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-mr-head-pipeline-leak.yml b/changelogs/unreleased/security-mr-head-pipeline-leak.yml
deleted file mode 100644
index fe8c4dfb3c8..00000000000
--- a/changelogs/unreleased/security-mr-head-pipeline-leak.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Gate MR head_pipeline behind read_pipeline ability.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-notes-in-private-snippets.yml b/changelogs/unreleased/security-notes-in-private-snippets.yml
deleted file mode 100644
index 907d98cb16d..00000000000
--- a/changelogs/unreleased/security-notes-in-private-snippets.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correctly check permissions when creating snippet notes
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-prevent-detection-of-merge-request-template-name.yml b/changelogs/unreleased/security-prevent-detection-of-merge-request-template-name.yml
deleted file mode 100644
index d7bb884cb4b..00000000000
--- a/changelogs/unreleased/security-prevent-detection-of-merge-request-template-name.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent the detection of merge request templates by unauthorized users
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/set-higher-ttl-for-trace-write.yml b/changelogs/unreleased/set-higher-ttl-for-trace-write.yml
deleted file mode 100644
index 9f17172100c..00000000000
--- a/changelogs/unreleased/set-higher-ttl-for-trace-write.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Set higher TTL for write lock of trace to prevent concurrent archiving
-merge_request: 30064
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml b/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
new file mode 100644
index 00000000000..f4686484e33
--- /dev/null
+++ b/changelogs/unreleased/sh-add-cmaps-for-pdfjs.yml
@@ -0,0 +1,5 @@
+---
+title: Make pdf.js render CJK characters
+merge_request: 31220
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-add-force-random-password-user-api.yml b/changelogs/unreleased/sh-add-force-random-password-user-api.yml
deleted file mode 100644
index 29f36978a0f..00000000000
--- a/changelogs/unreleased/sh-add-force-random-password-user-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for creating random passwords in user creation API
-merge_request: 30138
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml b/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml
new file mode 100644
index 00000000000..d2143e83045
--- /dev/null
+++ b/changelogs/unreleased/sh-add-gitaly-and-rugged-data-sidekiq.yml
@@ -0,0 +1,5 @@
+---
+title: Add Gitaly and Rugged call timing in Sidekiq logs
+merge_request: 31651
+author:
+type: other
diff --git a/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml b/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml
deleted file mode 100644
index d4be28e9883..00000000000
--- a/changelogs/unreleased/sh-add-gitaly-ref-caching-search-controller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable Gitaly ref caching for SearchController
-merge_request: 30105
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-add-index-extern-uid.yml b/changelogs/unreleased/sh-add-index-extern-uid.yml
new file mode 100644
index 00000000000..531770237a8
--- /dev/null
+++ b/changelogs/unreleased/sh-add-index-extern-uid.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..656eb8e9c37
--- /dev/null
+++ b/changelogs/unreleased/sh-add-missing-csp-report-uri.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..1f464dd92ff
--- /dev/null
+++ b/changelogs/unreleased/sh-add-rugged-logs.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..8a030f3daf2
--- /dev/null
+++ b/changelogs/unreleased/sh-add-rugged-to-peek.yml
@@ -0,0 +1,5 @@
+---
+title: Add Rugged calls to performance bar
+merge_request: 30983
+author:
+type: other
diff --git a/changelogs/unreleased/sh-add-thread-memory-cache.yml b/changelogs/unreleased/sh-add-thread-memory-cache.yml
deleted file mode 100644
index 025ad6d9f14..00000000000
--- a/changelogs/unreleased/sh-add-thread-memory-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a memory cache local to the thread to reduce Redis load
-merge_request: 30233
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-audit-event-json-log-format-from-and-to.yml b/changelogs/unreleased/sh-audit-event-json-log-format-from-and-to.yml
deleted file mode 100644
index 5bd21d7ef2b..00000000000
--- a/changelogs/unreleased/sh-audit-event-json-log-format-from-and-to.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Format `from` and `to` fields in JSON audit log
-merge_request: 30333
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml b/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml
deleted file mode 100644
index 2dead948786..00000000000
--- a/changelogs/unreleased/sh-avoid-loading-pipeline-status.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid loading pipeline status in search results
-merge_request: 30111
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-break-out-invited-group-members.yml b/changelogs/unreleased/sh-break-out-invited-group-members.yml
new file mode 100644
index 00000000000..091f1d48843
--- /dev/null
+++ b/changelogs/unreleased/sh-break-out-invited-group-members.yml
@@ -0,0 +1,5 @@
+---
+title: Make it easier to find invited group members
+merge_request: 28436
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-bump-fog-aws.yml b/changelogs/unreleased/sh-bump-fog-aws.yml
deleted file mode 100644
index a936b81ff02..00000000000
--- a/changelogs/unreleased/sh-bump-fog-aws.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump fog-aws to v3.5.2
-merge_request: 30803
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-cache-feature-flag-names.yml b/changelogs/unreleased/sh-cache-feature-flag-names.yml
deleted file mode 100644
index 6120c4870f8..00000000000
--- a/changelogs/unreleased/sh-cache-feature-flag-names.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache feature flag names in Redis for a minute
-merge_request: 29816
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml b/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml
deleted file mode 100644
index 125b6244d80..00000000000
--- a/changelogs/unreleased/sh-cache-flipper-checks-in-memory.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache Flipper feature flags in L1 and L2 caches
-merge_request: 30276
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-cache-flipper-names-memory-cache.yml b/changelogs/unreleased/sh-cache-flipper-names-memory-cache.yml
deleted file mode 100644
index 00443e81244..00000000000
--- a/changelogs/unreleased/sh-cache-flipper-names-memory-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Cache Flipper persisted names directly to local memory storage
-merge_request: 30265
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml b/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml
deleted file mode 100644
index 98eb13ee620..00000000000
--- a/changelogs/unreleased/sh-cache-negative-entries-find-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow caching of negative FindCommit matches
-merge_request: 29952
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-clean-up-bitbucket-import-errors.yml b/changelogs/unreleased/sh-clean-up-bitbucket-import-errors.yml
deleted file mode 100644
index e4c9de74e6a..00000000000
--- a/changelogs/unreleased/sh-clean-up-bitbucket-import-errors.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Avoid storing backtraces from Bitbucket Cloud imports in the database
-merge_request: 29862
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-disable-reactive-caching-automatic-retries.yml b/changelogs/unreleased/sh-disable-reactive-caching-automatic-retries.yml
deleted file mode 100644
index a0db68adb78..00000000000
--- a/changelogs/unreleased/sh-disable-reactive-caching-automatic-retries.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent amplification of ReactiveCachingWorker jobs upon failures
-merge_request: 30432
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-disable-redis-peek.yml b/changelogs/unreleased/sh-disable-redis-peek.yml
new file mode 100644
index 00000000000..de86c0031c7
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-redis-peek.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..180b983e07c
--- /dev/null
+++ b/changelogs/unreleased/sh-disable-registry-delete.yml
@@ -0,0 +1,5 @@
+---
+title: Don't attempt to contact registry if it is disabled
+merge_request: 31553
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-enable-bootsnap.yml b/changelogs/unreleased/sh-enable-bootsnap.yml
new file mode 100644
index 00000000000..674a900ee01
--- /dev/null
+++ b/changelogs/unreleased/sh-enable-bootsnap.yml
@@ -0,0 +1,5 @@
+---
+title: Make Bootsnap available via ENABLE_BOOTSNAP=1
+merge_request: 30963
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml b/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml
deleted file mode 100644
index 12f4a5a499d..00000000000
--- a/changelogs/unreleased/sh-enable-ref-name-caching-discussions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable Gitaly ref name caching for discussions.json
-merge_request: 29951
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-fix-discussions-api-perf.yml b/changelogs/unreleased/sh-fix-discussions-api-perf.yml
new file mode 100644
index 00000000000..8cdbbf03dab
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-discussions-api-perf.yml
@@ -0,0 +1,5 @@
+---
+title: Eliminate many Gitaly calls in discussions API
+merge_request: 31834
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-httpclient-ssl.yml b/changelogs/unreleased/sh-fix-httpclient-ssl.yml
deleted file mode 100644
index fda4e2e7084..00000000000
--- a/changelogs/unreleased/sh-fix-httpclient-ssl.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make httpclient respect system SSL configuration
-merge_request: 30749
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-import-export-suggestions.yml b/changelogs/unreleased/sh-fix-import-export-suggestions.yml
new file mode 100644
index 00000000000..4b15fc3858f
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-import-export-suggestions.yml
@@ -0,0 +1,5 @@
+---
+title: Properly save suggestions in project exports
+merge_request: 31690
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-63349.yml b/changelogs/unreleased/sh-fix-issue-63349.yml
deleted file mode 100644
index 0e51a6b7b20..00000000000
--- a/changelogs/unreleased/sh-fix-issue-63349.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make Housekeeping button do a full garbage collection
-merge_request: 30289
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-63910.yml b/changelogs/unreleased/sh-fix-issue-63910.yml
deleted file mode 100644
index 50312558c57..00000000000
--- a/changelogs/unreleased/sh-fix-issue-63910.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix attachments using the wrong URLs in e-mails
-merge_request: 30197
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml b/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
new file mode 100644
index 00000000000..3177cb8d18c
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issues-api-gitaly-nplusone.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Gitaly N+1 calls with listing issues/MRs via API
+merge_request: 31938
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml b/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml
new file mode 100644
index 00000000000..a6937eae588
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-pipelines-not-being-created.yml
@@ -0,0 +1,5 @@
+---
+title: Fix pipelines not always being created after a push
+merge_request: 31927
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-special-role-error-500.yml b/changelogs/unreleased/sh-fix-special-role-error-500.yml
new file mode 100644
index 00000000000..9aed0710da3
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-special-role-error-500.yml
@@ -0,0 +1,5 @@
+---
+title: Fix first-time contributor notes not rendering
+merge_request: 31340
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-handle-nil-replication-lag.yml b/changelogs/unreleased/sh-handle-nil-replication-lag.yml
deleted file mode 100644
index 5638d7e79e3..00000000000
--- a/changelogs/unreleased/sh-handle-nil-replication-lag.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix background migrations failing with unused replication slot
-merge_request: 30042
-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
new file mode 100644
index 00000000000..f5e2147f00e
--- /dev/null
+++ b/changelogs/unreleased/sh-ignore-git-errors-delete-project.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore Gitaly errors if cache flushing fails on project destruction
+merge_request: 31164
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-improve-redis-peek.yml b/changelogs/unreleased/sh-improve-redis-peek.yml
deleted file mode 100644
index 940be103ab7..00000000000
--- a/changelogs/unreleased/sh-improve-redis-peek.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Redis call details in Peek performance bar
-merge_request: 30191
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-make-githost-json.yml b/changelogs/unreleased/sh-make-githost-json.yml
new file mode 100644
index 00000000000..f4113a693cc
--- /dev/null
+++ b/changelogs/unreleased/sh-make-githost-json.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..502fc22ebbd
--- /dev/null
+++ b/changelogs/unreleased/sh-only-flush-tags-once-per-push.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..cd63b9bf425
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce Gitaly calls in PostReceive
+merge_request: 31741
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-optimize-todos-controller.yml b/changelogs/unreleased/sh-optimize-todos-controller.yml
deleted file mode 100644
index 181ddd1b3bc..00000000000
--- a/changelogs/unreleased/sh-optimize-todos-controller.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Eliminate N+1 queries in Dashboard::TodosController
-merge_request: 29954
-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
new file mode 100644
index 00000000000..b677adf78d9
--- /dev/null
+++ b/changelogs/unreleased/sh-post-receive-cache-clear-once.yml
@@ -0,0 +1,5 @@
+---
+title: Expire project caches once per push instead of once per ref
+merge_request: 31876
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-remove-import-columns-from-projects.yml b/changelogs/unreleased/sh-remove-import-columns-from-projects.yml
deleted file mode 100644
index f4052b2bef5..00000000000
--- a/changelogs/unreleased/sh-remove-import-columns-from-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove import columns from projects table
-merge_request: 29863
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml b/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
new file mode 100644
index 00000000000..a00ceedf3f4
--- /dev/null
+++ b/changelogs/unreleased/sh-remove-pdfjs-deprecations.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..24fcd1e9781
--- /dev/null
+++ b/changelogs/unreleased/sh-rename-githost-to-gitjson.yml
@@ -0,0 +1,5 @@
+---
+title: Rename githost.log -> git_json.log
+merge_request: 31634
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-service-template-bug.yml b/changelogs/unreleased/sh-service-template-bug.yml
deleted file mode 100644
index 1ea5ac84f26..00000000000
--- a/changelogs/unreleased/sh-service-template-bug.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable Rails SQL query cache when applying service templates
-merge_request: 30060
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-strong-memoize-appearances.yml b/changelogs/unreleased/sh-strong-memoize-appearances.yml
deleted file mode 100644
index dc4fe1c4d8e..00000000000
--- a/changelogs/unreleased/sh-strong-memoize-appearances.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Memoize non-existent custom appearances
-merge_request: 29957
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-support-csp-nonce.yml b/changelogs/unreleased/sh-support-csp-nonce.yml
new file mode 100644
index 00000000000..3e6ac1e4a32
--- /dev/null
+++ b/changelogs/unreleased/sh-support-csp-nonce.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for Content-Security-Policy
+merge_request: 31402
+author:
+type: added
diff --git a/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml b/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml
deleted file mode 100644
index 3e78c58c764..00000000000
--- a/changelogs/unreleased/sh-support-subnets-ip-rate-limiter.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support CIDR notation in IP rate limiter
-merge_request: 30146
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-update-mermaid.yml b/changelogs/unreleased/sh-update-mermaid.yml
index 9a7726cf716..58d94ec6235 100644
--- a/changelogs/unreleased/sh-update-mermaid.yml
+++ b/changelogs/unreleased/sh-update-mermaid.yml
@@ -1,5 +1,5 @@
---
-title: Update Mermaid to 8.1.0
-merge_request: 30036
+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
new file mode 100644
index 00000000000..6828f48863c
--- /dev/null
+++ b/changelogs/unreleased/sh-update-rouge-3-7-0.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..86446564e12
--- /dev/null
+++ b/changelogs/unreleased/sh-update-rugged-0-28-3.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Rugged to 0.28.3
+merge_request: 31794
+author:
+type: security
diff --git a/changelogs/unreleased/sh-upgrade-rouge-3-5-1.yml b/changelogs/unreleased/sh-upgrade-rouge-3-5-1.yml
deleted file mode 100644
index b408019c736..00000000000
--- a/changelogs/unreleased/sh-upgrade-rouge-3-5-1.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade Rouge to 3.5.1
-merge_request: 30431
-author:
-type: changed
diff --git a/changelogs/unreleased/sh-use-redis-caching-store.yml b/changelogs/unreleased/sh-use-redis-caching-store.yml
new file mode 100644
index 00000000000..e61bdb490bc
--- /dev/null
+++ b/changelogs/unreleased/sh-use-redis-caching-store.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..5e72f23d7ad
--- /dev/null
+++ b/changelogs/unreleased/sh-use-shared-state-cluster-pubsub.yml
@@ -0,0 +1,5 @@
+---
+title: Use persistent Redis cluster for Workhorse pub/sub notifications
+merge_request: 30990
+author:
+type: fixed
diff --git a/changelogs/unreleased/slugify.yml b/changelogs/unreleased/slugify.yml
deleted file mode 100644
index 853e90b8bed..00000000000
--- a/changelogs/unreleased/slugify.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Replace slugifyWithHyphens with improved slugify function
-merge_request: 30172
-author: Luke Ward
-type: fixed
diff --git a/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml b/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml
deleted file mode 100644
index 20d7a822cde..00000000000
--- a/changelogs/unreleased/small-s-in-elasticsearch-in-code.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo in code comments about Elasticsearch
-merge_request: 30163
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/small-s-in-elasticsearch.yml b/changelogs/unreleased/small-s-in-elasticsearch.yml
deleted file mode 100644
index 7cab5c37125..00000000000
--- a/changelogs/unreleased/small-s-in-elasticsearch.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo in docs about Elasticsearch
-merge_request: 30162
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/snowplow-ee-to-ce.yml b/changelogs/unreleased/snowplow-ee-to-ce.yml
new file mode 100644
index 00000000000..85c959f0510
--- /dev/null
+++ b/changelogs/unreleased/snowplow-ee-to-ce.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..d5ac4313414
--- /dev/null
+++ b/changelogs/unreleased/speed-up-labels-api.yml
@@ -0,0 +1,5 @@
+---
+title: Remove counts from default labels API responses
+merge_request: 31543
+author:
+type: changed
diff --git a/changelogs/unreleased/support-jsonb-default-value.yml b/changelogs/unreleased/support-jsonb-default-value.yml
deleted file mode 100644
index d46156276f9..00000000000
--- a/changelogs/unreleased/support-jsonb-default-value.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support jsonb default in add_column_with_default migration helper
-merge_request: 29871
-author:
-type: other
diff --git a/changelogs/unreleased/tc-rake-orphan-artifacts.yml b/changelogs/unreleased/tc-rake-orphan-artifacts.yml
deleted file mode 100644
index 7081bee640a..00000000000
--- a/changelogs/unreleased/tc-rake-orphan-artifacts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add rake task to clean orphan artifact files
-merge_request: 29681
-author:
-type: added
diff --git a/changelogs/unreleased/tr-embed-metric-links.yml b/changelogs/unreleased/tr-embed-metric-links.yml
new file mode 100644
index 00000000000..6918114a4ae
--- /dev/null
+++ b/changelogs/unreleased/tr-embed-metric-links.yml
@@ -0,0 +1,5 @@
+---
+title: Generate shareable link for specific metric charts
+merge_request: 31339
+author:
+type: added
diff --git a/changelogs/unreleased/tr-param-undefined-fix.yml b/changelogs/unreleased/tr-param-undefined-fix.yml
new file mode 100644
index 00000000000..0a9051485bd
--- /dev/null
+++ b/changelogs/unreleased/tr-param-undefined-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Fix for embedded metrics undefined params
+merge_request: 31975
+author:
+type: fixed
diff --git a/changelogs/unreleased/tr-remove-embed-metrics-flag.yml b/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
new file mode 100644
index 00000000000..a327a6868d3
--- /dev/null
+++ b/changelogs/unreleased/tr-remove-embed-metrics-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Link and embed metrics in GitLab Flavored Markdown
+merge_request: 31106
+author:
+type: added
diff --git a/changelogs/unreleased/transaction-metrics.yml b/changelogs/unreleased/transaction-metrics.yml
deleted file mode 100644
index 8b6e9c7d9d1..00000000000
--- a/changelogs/unreleased/transaction-metrics.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds metrics to measure cost of expensive operations
-merge_request: 29928
-author:
-type: other
diff --git a/changelogs/unreleased/tz-update-mr-count-over-tabs.yml b/changelogs/unreleased/tz-update-mr-count-over-tabs.yml
deleted file mode 100644
index 61a49dd8ecc..00000000000
--- a/changelogs/unreleased/tz-update-mr-count-over-tabs.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: New API for User Counts, updates on success of an MR the count on top and in
- other tabs
-merge_request: 29441
-author:
-type: added
diff --git a/changelogs/unreleased/unicorn-sampler-fix.yml b/changelogs/unreleased/unicorn-sampler-fix.yml
deleted file mode 100644
index 3f0e509f15f..00000000000
--- a/changelogs/unreleased/unicorn-sampler-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make sure UnicornSampler is started only in master process.
-merge_request: 30215
-author:
-type: fixed
diff --git a/changelogs/unreleased/update-babel-to-7-5-5.yml b/changelogs/unreleased/update-babel-to-7-5-5.yml
new file mode 100644
index 00000000000..c498e2adfe8
--- /dev/null
+++ b/changelogs/unreleased/update-babel-to-7-5-5.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade babel to 7.5.5
+merge_request: 31819
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/update-clair-version.yml b/changelogs/unreleased/update-clair-version.yml
deleted file mode 100644
index 59b6e113fd5..00000000000
--- a/changelogs/unreleased/update-clair-version.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Extract clair version as CLAIR_EXECUTABLE_VERSION variable and update clair
- executable from v8 to v11
-merge_request: 30396
-author:
-type: changed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml
deleted file mode 100644
index 6719fa94b19..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-6-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.6.0
-merge_request: 29982
-author:
-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
new file mode 100644
index 00000000000..ab1e7d77520
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-7-0.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..898fdef830a
--- /dev/null
+++ b/changelogs/unreleased/update-graphicsmagick-to-1-3-33.yml
@@ -0,0 +1,5 @@
+---
+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-pages-to-1-7-0.yml b/changelogs/unreleased/update-pages-to-1-7-0.yml
deleted file mode 100644
index d41346f021c..00000000000
--- a/changelogs/unreleased/update-pages-to-1-7-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Pages to v1.7.0
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/update-pagination-texts.yml b/changelogs/unreleased/update-pagination-texts.yml
deleted file mode 100644
index 6a398e26242..00000000000
--- a/changelogs/unreleased/update-pagination-texts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update pagination prev and next texts
-merge_request: 29911
-author:
-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
new file mode 100644
index 00000000000..8c1a033dd29
--- /dev/null
+++ b/changelogs/unreleased/update-pipelines-minutes-expiry-banner-to-an-alert-component-type.yml
@@ -0,0 +1,5 @@
+---
+title: Enhance style of the shared runners limit
+merge_request: 31386
+author:
+type: other
diff --git a/changelogs/unreleased/update-tar-to-2-2-2.yml b/changelogs/unreleased/update-tar-to-2-2-2.yml
deleted file mode 100644
index f142fe59448..00000000000
--- a/changelogs/unreleased/update-tar-to-2-2-2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update tar to 2.2.2
-merge_request: 29949
-author: Takuya Noguchi
-type: security
diff --git a/changelogs/unreleased/update-todo-in-ui.yml b/changelogs/unreleased/update-todo-in-ui.yml
deleted file mode 100644
index dddcf0f3983..00000000000
--- a/changelogs/unreleased/update-todo-in-ui.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Changes "Todo" to "To Do" in the UI for clarity
-merge_request: 28844
-author:
-type: other
diff --git a/changelogs/unreleased/use-pg-9-6-11-on-ci.yml b/changelogs/unreleased/use-pg-9-6-11-on-ci.yml
deleted file mode 100644
index 785eb352895..00000000000
--- a/changelogs/unreleased/use-pg-9-6-11-on-ci.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use PostgreSQL 9.6.11 in CI tests
-merge_request: 30270
-author: Takuya Noguchi
-type: other
diff --git a/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml b/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml
new file mode 100644
index 00000000000..4c9b048aaa3
--- /dev/null
+++ b/changelogs/unreleased/visual-review-tools-constant-storage-keys.yml
@@ -0,0 +1,5 @@
+---
+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
new file mode 100644
index 00000000000..c3d084228c3
--- /dev/null
+++ b/changelogs/unreleased/wiki-usage-pings.yml
@@ -0,0 +1,5 @@
+---
+title: Count wiki creation, update and delete events
+merge_request: 30864
+author:
+type: added
diff --git a/changelogs/unreleased/winh-jest-markdown-header.yml b/changelogs/unreleased/winh-jest-markdown-header.yml
deleted file mode 100644
index 6bf9d75cc93..00000000000
--- a/changelogs/unreleased/winh-jest-markdown-header.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Migrate markdown header_spec.js to Jest
-merge_request: 30228
-author: Martin Hobert
-type: other
diff --git a/changelogs/unreleased/winh-notes-service-applySuggestion.yml b/changelogs/unreleased/winh-notes-service-applySuggestion.yml
deleted file mode 100644
index 30e540237b6..00000000000
--- a/changelogs/unreleased/winh-notes-service-applySuggestion.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove applySuggestion from notes service
-merge_request: 30399
-author: Frank van Rest
-type: other
diff --git a/changelogs/unreleased/winh-notes-service-deleteNote.yml b/changelogs/unreleased/winh-notes-service-deleteNote.yml
deleted file mode 100644
index cf2b37755a2..00000000000
--- a/changelogs/unreleased/winh-notes-service-deleteNote.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove deleteNote from notes service
-merge_request: 30537
-author: Frank van Rest
-type: other
diff --git a/changelogs/unreleased/winh-notes-service-toggleAward.yml b/changelogs/unreleased/winh-notes-service-toggleAward.yml
deleted file mode 100644
index 0471888c285..00000000000
--- a/changelogs/unreleased/winh-notes-service-toggleAward.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove toggleAward from notes service
-merge_request: 30536
-author: Frank van Rest
-type: other
diff --git a/changelogs/unreleased/winh-updateResolvableDiscussionsCounts-typo.yml b/changelogs/unreleased/winh-updateResolvableDiscussionsCounts-typo.yml
deleted file mode 100644
index 24b10bb3edc..00000000000
--- a/changelogs/unreleased/winh-updateResolvableDiscussionsCounts-typo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix typo in updateResolvableDiscussionsCounts action
-merge_request: 30278
-author: Frank van Rest
-type: other
diff --git a/changelogs/unreleased/z-index-fix-for-diff-file-dropdown.yml b/changelogs/unreleased/z-index-fix-for-diff-file-dropdown.yml
deleted file mode 100644
index 72108f6ab77..00000000000
--- a/changelogs/unreleased/z-index-fix-for-diff-file-dropdown.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: I fixed z index bug in diff page
-merge_request: 30657
-author: Faruk Can
-type: fixed
diff --git a/changelogs/unreleased/z-index-tools.yml b/changelogs/unreleased/z-index-tools.yml
deleted file mode 100644
index 1102612670b..00000000000
--- a/changelogs/unreleased/z-index-tools.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Review Tools: Add large z-index to toolbar'
-merge_request: 30583
-author:
-type: fixed
diff --git a/changelogs/unreleased/zj-gitaly-usage-data.yml b/changelogs/unreleased/zj-gitaly-usage-data.yml
deleted file mode 100644
index ce5087292ed..00000000000
--- a/changelogs/unreleased/zj-gitaly-usage-data.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Gitaly data to the usage ping
-merge_request:
-author:
-type: added
diff --git a/config.ru b/config.ru
index 6f6fb85d8fa..750c84f7642 100644
--- a/config.ru
+++ b/config.ru
@@ -17,7 +17,17 @@ end
require ::File.expand_path('../config/environment', __FILE__)
+def master_process?
+ Prometheus::PidProvider.worker_id.in? %w(unicorn_master puma_master)
+end
+
warmup do |app|
+ # 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('/')
end
diff --git a/config/application.rb b/config/application.rb
index de386506233..2554dd8cca2 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -22,11 +22,6 @@ module Gitlab
require_dependency Rails.root.join('lib/gitlab/middleware/read_only')
require_dependency Rails.root.join('lib/gitlab/middleware/basic_health_check')
- # This needs to be loaded before DB connection is made
- # to make sure that all connections have NO_ZERO_DATE
- # setting disabled
- require_dependency Rails.root.join('lib/mysql_zero_date')
-
# Settings in config/environments/* take precedence over those specified here.
# Application configuration should go into files in config/initializers
# -- all .rb files in that directory are automatically loaded.
@@ -46,11 +41,6 @@ module Gitlab
#{config.root}/app/models/hooks
#{config.root}/app/models/members
#{config.root}/app/models/project_services
- #{config.root}/app/workers/concerns
- #{config.root}/app/policies/concerns
- #{config.root}/app/services/concerns
- #{config.root}/app/serializers/concerns
- #{config.root}/app/finders/concerns
#{config.root}/app/graphql/resolvers/concerns
#{config.root}/app/graphql/mutations/concerns])
@@ -110,10 +100,23 @@ module Gitlab
# - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content)
# - Jira shared secret (:sharedSecret)
+ # - Titles, bodies, and descriptions for notes, issues, etc.
#
- # NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
- # introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
- config.filter_parameters += [/token$/, /password/, /secret/, /key$/, /^note$/, /^text$/]
+ # NOTE: It is **IMPORTANT** to also update labkit's filter when
+ # adding parameters here to not introduce another security
+ # vulnerability:
+ # https://gitlab.com/gitlab-org/labkit/blob/master/mask/matchers.go
+ config.filter_parameters += [
+ /token$/,
+ /password/,
+ /secret/,
+ /key$/,
+ /^body$/,
+ /^description$/,
+ /^note$/,
+ /^text$/,
+ /^title$/
+ ]
config.filter_parameters += %i(
certificate
encrypted_key
@@ -168,7 +171,6 @@ module Gitlab
# Import gitlab-svgs directly from vendored directory
config.assets.paths << "#{config.root}/node_modules/@gitlab/svgs/dist"
- config.assets.paths << "#{config.root}/node_modules"
config.assets.precompile << "icons.svg"
config.assets.precompile << "icons.json"
config.assets.precompile << "illustrations/*.svg"
@@ -183,9 +185,19 @@ module Gitlab
config.assets.precompile << "pages/jira_connect.css"
end
+ # Import path for EE specific SCSS entry point
+ # In CE it will import a noop file, in EE a functioning file
+ # Order is important, so that the ee file takes precedence:
+ config.assets.paths << "#{config.root}/ee/app/assets/stylesheets/_ee"
+ config.assets.paths << "#{config.root}/app/assets/stylesheets/_ee"
+
config.assets.paths << "#{config.root}/vendor/assets/javascripts/"
config.assets.precompile << "snowplow/sp.js"
+ # This path must come last to avoid confusing sprockets
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/64091#note_194512508
+ config.assets.paths << "#{config.root}/node_modules"
+
# Compile non-JS/CSS assets in the ee/app/assets folder by default
# Mimic sprockets-rails default: https://github.com/rails/sprockets-rails/blob/v3.2.1/lib/sprockets/railtie.rb#L84-L87
LOOSE_EE_APP_ASSETS = lambda do |logical_path, filename|
@@ -229,7 +241,10 @@ 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
@@ -237,7 +252,7 @@ module Gitlab
caching_config_hash[:pool_timeout] = 1
end
- config.cache_store = :redis_store, caching_config_hash
+ config.cache_store = :redis_cache_store, caching_config_hash
config.active_job.queue_adapter = :sidekiq
@@ -273,10 +288,5 @@ module Gitlab
Gitlab::Routing.add_helpers(project_url_helpers)
Gitlab::Routing.add_helpers(MilestonesRoutingHelper)
end
-
- # This makes generated cookies to be compatible with Rails 5.1 and older
- # We can remove this when we're confident that there are no issues with the Rails 5.2 upgrade
- # and we won't need to rollback to older versions
- config.action_dispatch.use_authenticated_cookie_encryption = false
end
end
diff --git a/config/boot.rb b/config/boot.rb
index b76b26a5e75..da4e6b7290c 100644
--- a/config/boot.rb
+++ b/config/boot.rb
@@ -2,8 +2,4 @@ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
# Set up gems listed in the Gemfile.
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
-begin
- require 'bootsnap/setup' unless ENV['DISABLE_BOOTSNAP']
-rescue LoadError
- # bootsnap is an optional dependency, so if we don't have it, it's fine
-end
+require 'bootsnap/setup' if ENV['RAILS_ENV'] != 'production' || %w(1 yes true).include?(ENV['ENABLE_BOOTSNAP'])
diff --git a/config/database.yml.mysql b/config/database.yml.mysql
deleted file mode 100644
index 98c2abe9f5e..00000000000
--- a/config/database.yml.mysql
+++ /dev/null
@@ -1,60 +0,0 @@
-#
-# PRODUCTION
-#
-production:
- adapter: mysql2
- encoding: utf8
- collation: utf8_general_ci
- reconnect: false
- database: gitlabhq_production
- pool: 10
- username: git
- password: "secure password"
- host: localhost
- # socket: /tmp/mysql.sock
-
-#
-# Development specific
-#
-development:
- adapter: mysql2
- encoding: utf8
- collation: utf8_general_ci
- reconnect: false
- database: gitlabhq_development
- pool: 5
- username: root
- password: "secure password"
- host: localhost
- # socket: /tmp/mysql.sock
-
-#
-# Staging specific
-#
-staging:
- adapter: mysql2
- encoding: utf8
- collation: utf8_general_ci
- reconnect: false
- database: gitlabhq_staging
- pool: 10
- username: git
- password: "secure password"
- host: localhost
- # socket: /tmp/mysql.sock
-
-# Warning: The database defined as "test" will be erased and
-# re-generated from your development database when you run "rake".
-# Do not set this db to the same as development or production.
-test: &test
- adapter: mysql2
- encoding: utf8mb4
- collation: utf8mb4_general_ci
- reconnect: false
- database: gitlabhq_test
- pool: 5
- username: root
- password:
- host: localhost
- # socket: /tmp/mysql.sock
- prepared_statements: false
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 0e78980350f..226f2ec3722 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -47,6 +47,29 @@ production: &base
#
# relative_url_root: /gitlab
+ # Content Security Policy
+ # See https://guides.rubyonrails.org/security.html#content-security-policy
+ content_security_policy:
+ enabled: false
+ report_only: false
+ directives:
+ base_uri:
+ child_src:
+ connect_src: "'self' http://localhost:3808 ws://localhost:3808 wss://localhost:3000"
+ 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: "'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:
+ style_src: "'self' 'unsafe-inline'"
+ worker_src: "http://localhost:3000 blob:"
+ report_uri:
+
# Trusted Proxies
# Customize if you have GitLab behind a reverse proxy which is running on a different machine.
# Add the IP address for your reverse proxy to the list, otherwise users will appear signed in from that address.
@@ -400,6 +423,15 @@ production: &base
# path: shared/registry
# issuer: gitlab-issuer
+ # Add notification settings if you plan to use Geo Replication for the registry
+ # notifications:
+ # - name: geo_event
+ # url: https://example.com/api/v4/container_registry_event/events
+ # timeout: 2s
+ # threshold: 5
+ # backoff: 1s
+ # headers:
+ # Authorization: secret_phrase
## Error Reporting and Logging with Sentry
sentry:
@@ -418,6 +450,11 @@ production: &base
# If it is blank, it defaults to external_url.
node_name: ''
+ registry_replication:
+ # enabled: true
+ # primary_api_url: http://localhost:5000/ # internal address to the primary registry, will be used by GitLab to directly communicate with primary registry API
+
+
#
# 2. GitLab CI settings
# ==========================
diff --git a/config/initializers/0_inflections.rb b/config/initializers/0_inflections.rb
index 4d1f4917275..d317825c1b8 100644
--- a/config/initializers/0_inflections.rb
+++ b/config/initializers/0_inflections.rb
@@ -19,6 +19,7 @@ ActiveSupport::Inflector.inflections do |inflect|
project_registry
file_registry
job_artifact_registry
+ container_repository_registry
vulnerability_feedback
vulnerabilities_feedback
group_view
diff --git a/config/initializers/0_inject_enterprise_edition_module.rb b/config/initializers/0_inject_enterprise_edition_module.rb
new file mode 100644
index 00000000000..39595e23abe
--- /dev/null
+++ b/config/initializers/0_inject_enterprise_edition_module.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'active_support/inflector'
+
+module InjectEnterpriseEditionModule
+ def prepend_if_ee(constant)
+ prepend(constant.constantize) if Gitlab.ee?
+ end
+
+ def extend_if_ee(constant)
+ extend(constant.constantize) if Gitlab.ee?
+ end
+
+ def include_if_ee(constant)
+ include(constant.constantize) if Gitlab.ee?
+ end
+end
+
+Module.prepend(InjectEnterpriseEditionModule)
diff --git a/config/initializers/0_license.rb b/config/initializers/0_license.rb
index f750022dfdf..19c71c34904 100644
--- a/config/initializers/0_license.rb
+++ b/config/initializers/0_license.rb
@@ -10,7 +10,7 @@ Gitlab.ee do
end
# Needed to run migration
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
message = LicenseHelper.license_message(signed_in: true, is_admin: true, in_html: false)
if ::License.block_changes? && message.present?
warn "WARNING: #{message}"
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 3a121addc98..828732126b6 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -200,6 +200,7 @@ Settings.gitlab.default_projects_features['visibility_level'] = Settings.__sen
Settings.gitlab['domain_whitelist'] ||= []
Settings.gitlab['import_sources'] ||= Gitlab::ImportSources.values
Settings.gitlab['trusted_proxies'] ||= []
+Settings.gitlab['content_security_policy'] ||= Gitlab::ContentSecurityPolicy::ConfigLoader.default_settings_hash
Settings.gitlab['no_todos_messages'] ||= YAML.load_file(Rails.root.join('config', 'no_todos_messages.yml'))
Settings.gitlab['impersonation_enabled'] ||= true if Settings.gitlab['impersonation_enabled'].nil?
Settings.gitlab['usage_ping_enabled'] = true if Settings.gitlab['usage_ping_enabled'].nil?
@@ -217,6 +218,7 @@ Gitlab.ee do
Settings['elasticsearch'] ||= Settingslogic.new({})
Settings.elasticsearch['enabled'] = false if Settings.elasticsearch['enabled'].nil?
Settings.elasticsearch['url'] = ENV['ELASTIC_URL'] || "http://localhost:9200"
+ Settings.elasticsearch['indexer_path'] ||= Gitlab::Utils.which('gitlab-elasticsearch-indexer')
end
#
@@ -258,6 +260,7 @@ Settings.registry['key'] ||= nil
Settings.registry['issuer'] ||= nil
Settings.registry['host_port'] ||= [Settings.registry['host'], Settings.registry['port']].compact.join(':')
Settings.registry['path'] = Settings.absolute(Settings.registry['path'] || File.join(Settings.shared['path'], 'registry'))
+Settings.registry['notifications'] ||= []
#
# Error Reporting and Logging with Sentry
@@ -294,6 +297,12 @@ Gitlab.ee do
Settings['geo'] ||= Settingslogic.new({})
# For backwards compatibility, default to gitlab_url and if so, ensure it ends with "/"
Settings.geo['node_name'] = Settings.geo['node_name'].presence || Settings.gitlab['url'].chomp('/').concat('/')
+
+ #
+ # Registry replication
+ #
+ Settings.geo['registry_replication'] ||= Settingslogic.new({})
+ Settings.geo.registry_replication['enabled'] ||= false
end
#
@@ -471,6 +480,9 @@ Gitlab.ee do
Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker']['cron'] ||= '*/1 * * * *'
Settings.cron_jobs['geo_repository_verification_secondary_scheduler_worker']['job_class'] ||= 'Geo::RepositoryVerification::Secondary::SchedulerWorker'
+ Settings.cron_jobs['geo_container_repository_sync_worker'] ||= Settingslogic.new({})
+ Settings.cron_jobs['geo_container_repository_sync_worker']['cron'] ||= '*/1 * * * *'
+ Settings.cron_jobs['geo_container_repository_sync_worker']['job_class'] ||= 'Geo::ContainerRepositorySyncDispatchWorker'
Settings.cron_jobs['historical_data_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['historical_data_worker']['cron'] ||= '0 12 * * *'
Settings.cron_jobs['historical_data_worker']['job_class'] = 'HistoricalDataWorker'
diff --git a/config/initializers/7_prometheus_metrics.rb b/config/initializers/7_prometheus_metrics.rb
index 53c3eac3c74..143b34b5368 100644
--- a/config/initializers/7_prometheus_metrics.rb
+++ b/config/initializers/7_prometheus_metrics.rb
@@ -1,5 +1,4 @@
require 'prometheus/client'
-require 'prometheus/client/support/unicorn'
# Keep separate directories for separate processes
def prometheus_default_multiproc_dir
@@ -23,7 +22,7 @@ Prometheus::Client.configure do |config|
config.multiprocess_files_dir = ENV['prometheus_multiproc_dir'] || prometheus_default_multiproc_dir
- config.pid_provider = Prometheus::Client::Support::Unicorn.method(:worker_pid_provider)
+ config.pid_provider = Prometheus::PidProvider.method(:worker_id)
end
Gitlab::Application.configure do |config|
@@ -33,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
@@ -52,22 +54,3 @@ if !Rails.env.test? && Gitlab::Metrics.prometheus_metrics_enabled?
end
end
end
-
-def cleanup_prometheus_multiproc_dir
- # The following is necessary to ensure stale Prometheus metrics don't
- # accumulate over time. It needs to be done in this hook as opposed to
- # inside an init script to ensure metrics files aren't deleted after new
- # unicorn workers start after a SIGUSR2 is received.
- if dir = ::Prometheus::Client.configuration.multiprocess_files_dir
- old_metrics = Dir[File.join(dir, '*.db')]
- FileUtils.rm_rf(old_metrics)
- end
-end
-
-Gitlab::Cluster::LifecycleEvents.on_master_start do
- cleanup_prometheus_multiproc_dir
-end
-
-Gitlab::Cluster::LifecycleEvents.on_master_restart do
- cleanup_prometheus_multiproc_dir
-end
diff --git a/config/initializers/8_devise.rb b/config/initializers/8_devise.rb
index 3dd12c7e64d..8ef9ff6b7fc 100644
--- a/config/initializers/8_devise.rb
+++ b/config/initializers/8_devise.rb
@@ -81,7 +81,7 @@ Devise.setup do |config|
# You can use this to let your user access some features of your application
# without confirming the account, but blocking it after a certain period
# (ie 2 days).
- # config.allow_unconfirmed_access_for = 2.days
+ config.allow_unconfirmed_access_for = 30.days
# Defines which key will be used when confirming an account
# config.confirmation_keys = [ :email ]
diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb
index 151bce4d130..846f28e6f66 100644
--- a/config/initializers/active_record_data_types.rb
+++ b/config/initializers/active_record_data_types.rb
@@ -1,83 +1,45 @@
# ActiveRecord custom data type for storing datetimes with timezone information.
# See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11229
-if Gitlab::Database.postgresql?
- require 'active_record/connection_adapters/postgresql_adapter'
+require 'active_record/connection_adapters/postgresql_adapter'
- module ActiveRecord::ConnectionAdapters::PostgreSQL::OID
- # Add the class `DateTimeWithTimeZone` so we can map `timestamptz` to it.
- class DateTimeWithTimeZone < DateTime
- def type
- :datetime_with_timezone
- end
+module ActiveRecord::ConnectionAdapters::PostgreSQL::OID
+ # Add the class `DateTimeWithTimeZone` so we can map `timestamptz` to it.
+ class DateTimeWithTimeZone < DateTime
+ def type
+ :datetime_with_timezone
end
end
+end
- module RegisterDateTimeWithTimeZone
- # Run original `initialize_type_map` and then register `timestamptz` as a
- # `DateTimeWithTimeZone`.
- #
- # Apparently it does not matter that the original `initialize_type_map`
- # aliases `timestamptz` to `timestamp`.
- #
- # When schema dumping, `timestamptz` columns will be output as
- # `t.datetime_with_timezone`.
- def initialize_type_map(mapping = type_map)
- super mapping
-
- mapping.register_type 'timestamptz' do |_, _, sql_type|
- precision = extract_precision(sql_type)
- ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::DateTimeWithTimeZone.new(precision: precision)
- end
- end
- end
-
- class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
- prepend RegisterDateTimeWithTimeZone
-
- # Add column type `datetime_with_timezone` so we can do this in
- # migrations:
- #
- # add_column(:users, :datetime_with_timezone)
- #
- NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamptz' }
- end
-elsif Gitlab::Database.mysql?
- require 'active_record/connection_adapters/mysql2_adapter'
-
- module RegisterDateTimeWithTimeZone
- # Run original `initialize_type_map` and then register `timestamp` as a
- # `MysqlDateTimeWithTimeZone`.
- #
- # When schema dumping, `timestamp` columns will be output as
- # `t.datetime_with_timezone`.
- def initialize_type_map(mapping = type_map)
- super mapping
-
- mapping.register_type(/timestamp/i) do |sql_type|
- precision = extract_precision(sql_type)
- ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlDateTimeWithTimeZone.new(precision: precision)
- end
+module RegisterDateTimeWithTimeZone
+ # Run original `initialize_type_map` and then register `timestamptz` as a
+ # `DateTimeWithTimeZone`.
+ #
+ # Apparently it does not matter that the original `initialize_type_map`
+ # aliases `timestamptz` to `timestamp`.
+ #
+ # When schema dumping, `timestamptz` columns will be output as
+ # `t.datetime_with_timezone`.
+ def initialize_type_map(mapping = type_map)
+ super mapping
+
+ mapping.register_type 'timestamptz' do |_, _, sql_type|
+ precision = extract_precision(sql_type)
+ ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::OID::DateTimeWithTimeZone.new(precision: precision)
end
end
+end
- class ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter
- prepend RegisterDateTimeWithTimeZone
-
- # Add the class `DateTimeWithTimeZone` so we can map `timestamp` to it.
- class MysqlDateTimeWithTimeZone < ActiveRecord::Type::DateTime
- def type
- :datetime_with_timezone
- end
- end
+class ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
+ prepend RegisterDateTimeWithTimeZone
- # Add column type `datetime_with_timezone` so we can do this in
- # migrations:
- #
- # add_column(:users, :datetime_with_timezone)
- #
- NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamp' }
- end
+ # Add column type `datetime_with_timezone` so we can do this in
+ # migrations:
+ #
+ # add_column(:users, :datetime_with_timezone)
+ #
+ NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamptz' }
end
# Ensure `datetime_with_timezone` columns are correctly written to schema.rb
diff --git a/config/initializers/active_record_migration.rb b/config/initializers/active_record_migration.rb
deleted file mode 100644
index 04c06be7834..00000000000
--- a/config/initializers/active_record_migration.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-require 'active_record/migration'
-
-module ActiveRecord
- class Migration
- # data_source_exists? is not available in 4.2.10, table_exists deprecated in 5.0
- def table_exists?(table_name)
- ActiveRecord::Base.connection.data_source_exists?(table_name)
- end
- end
-end
diff --git a/config/initializers/active_record_mysql_timestamp.rb b/config/initializers/active_record_mysql_timestamp.rb
deleted file mode 100644
index af74c4ff6fb..00000000000
--- a/config/initializers/active_record_mysql_timestamp.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# Make sure that MySQL won't try to use CURRENT_TIMESTAMP when the timestamp
-# column is NOT NULL. See https://gitlab.com/gitlab-org/gitlab-ce/issues/36405
-# And also: https://bugs.mysql.com/bug.php?id=75098
-# This patch was based on:
-# https://github.com/rails/rails/blob/15ef55efb591e5379486ccf53dd3e13f416564f6/activerecord/lib/active_record/connection_adapters/mysql/schema_creation.rb#L34-L36
-
-if Gitlab::Database.mysql?
- require 'active_record/connection_adapters/abstract/schema_creation'
-
- module MySQLTimestampFix
- def add_column_options!(sql, options)
- # By default, TIMESTAMP columns are NOT NULL, cannot contain NULL values,
- # and assigning NULL assigns the current timestamp. To permit a TIMESTAMP
- # column to contain NULL, explicitly declare it with the NULL attribute.
- # See http://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
- if sql.end_with?('timestamp') && !options[:primary_key]
- if options[:null] != false
- sql << ' NULL'
- elsif options[:column].default.nil?
- sql << ' DEFAULT 0'
- end
- end
-
- super
- end
- end
-
- ActiveRecord::ConnectionAdapters::AbstractAdapter::SchemaCreation
- .prepend(MySQLTimestampFix)
-end
diff --git a/config/initializers/ar_mysql_jsonb_support.rb b/config/initializers/ar_mysql_jsonb_support.rb
deleted file mode 100644
index 63a0b05119a..00000000000
--- a/config/initializers/ar_mysql_jsonb_support.rb
+++ /dev/null
@@ -1,31 +0,0 @@
-# frozen_string_literal: true
-
-require 'active_record/connection_adapters/abstract_mysql_adapter'
-require 'active_record/connection_adapters/mysql/schema_definitions'
-
-# MySQL (5.6) and MariaDB (10.1) are currently supported versions within GitLab,
-# Since they do not support native `json` datatype we force to emulate it as `text`
-
-if Gitlab::Database.mysql?
- module ActiveRecord
- module ConnectionAdapters
- class AbstractMysqlAdapter
- JSON_DATASIZE = 1.megabyte
-
- NATIVE_DATABASE_TYPES.merge!(
- json: { name: "text", limit: JSON_DATASIZE },
- jsonb: { name: "text", limit: JSON_DATASIZE }
- )
- end
-
- module MySQL
- module ColumnMethods
- # We add `jsonb` helper, as `json` is already defined for `MySQL` since Rails 5
- def jsonb(*args, **options)
- args.each { |name| column(name, :json, options) }
- end
- end
- end
- end
- end
-end
diff --git a/config/initializers/ar_native_database_types.rb b/config/initializers/ar_native_database_types.rb
deleted file mode 100644
index 6d397661f75..00000000000
--- a/config/initializers/ar_native_database_types.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-require 'active_record/connection_adapters/abstract_mysql_adapter'
-
-module ActiveRecord
- module ConnectionAdapters
- class AbstractMysqlAdapter
- NATIVE_DATABASE_TYPES.merge!(
- bigserial: { name: 'bigint(20) auto_increment PRIMARY KEY' },
- serial: { name: 'int auto_increment PRIMARY KEY' }
- )
- end
- end
-end
diff --git a/config/initializers/connection_fix.rb b/config/initializers/connection_fix.rb
deleted file mode 100644
index d0b1444f607..00000000000
--- a/config/initializers/connection_fix.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-# from http://gist.github.com/238999
-#
-# If your workers are inactive for a long period of time, they'll lose
-# their MySQL connection.
-#
-# This hack ensures we re-connect whenever a connection is
-# lost. Because, really. why not?
-#
-# Stick this in RAILS_ROOT/config/initializers/connection_fix.rb (or somewhere similar)
-#
-# From:
-# http://coderrr.wordpress.com/2009/01/08/activerecord-threading-issues-and-resolutions/
-
-if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
- module ActiveRecord::ConnectionAdapters
- class Mysql2Adapter
- alias_method :execute_without_retry, :execute
-
- def execute(*args)
- execute_without_retry(*args)
- rescue ActiveRecord::StatementInvalid => e
- if e.message =~ /server has gone away/i
- warn "Lost connection to MySQL server during query"
- reconnect!
- retry
- else
- raise e
- end
- end
- end
- end
-end
diff --git a/config/initializers/content_security_policy.rb b/config/initializers/content_security_policy.rb
new file mode 100644
index 00000000000..608d0401a96
--- /dev/null
+++ b/config/initializers/content_security_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+csp_settings = Settings.gitlab.content_security_policy
+
+if csp_settings['enabled']
+ # See https://guides.rubyonrails.org/security.html#content-security-policy
+ Rails.application.config.content_security_policy do |policy|
+ directives = csp_settings.fetch('directives', {})
+ loader = ::Gitlab::ContentSecurityPolicy::ConfigLoader.new(directives)
+ loader.load(policy)
+ end
+
+ Rails.application.config.content_security_policy_report_only = csp_settings['report_only']
+ Rails.application.config.content_security_policy_nonce_generator = ->(request) { SecureRandom.base64(16) }
+end
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
index 2ecb7956007..a1abb29838b 100644
--- a/config/initializers/elastic_client_setup.rb
+++ b/config/initializers/elastic_client_setup.rb
@@ -5,46 +5,42 @@
require 'gitlab/current_settings'
Gitlab.ee do
+ require 'elasticsearch/model'
+
+ ### Modified from elasticsearch-model/lib/elasticsearch/model.rb
+
+ [
+ Elasticsearch::Model::Client::ClassMethods,
+ Elasticsearch::Model::Naming::ClassMethods,
+ Elasticsearch::Model::Indexing::ClassMethods,
+ Elasticsearch::Model::Searching::ClassMethods
+ ].each do |mod|
+ Elasticsearch::Model::Proxy::ClassMethodsProxy.include mod
+ end
+
+ [
+ Elasticsearch::Model::Client::InstanceMethods,
+ Elasticsearch::Model::Naming::InstanceMethods,
+ Elasticsearch::Model::Indexing::InstanceMethods,
+ Elasticsearch::Model::Serializing::InstanceMethods
+ ].each do |mod|
+ Elasticsearch::Model::Proxy::InstanceMethodsProxy.include mod
+ end
+
+ Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def as_indexed_json(options={})
+ target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super
+ end
+ CODE
+
+ ### Monkey patches
+
Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
-
- module Elasticsearch
- module Model
- module Client
- # This mutex is only used to synchronize *creation* of a new client, so
- # all including classes can share the same client instance
- CLIENT_MUTEX = Mutex.new
-
- cattr_accessor :cached_client
- cattr_accessor :cached_config
-
- module ClassMethods
- # Override the default ::Elasticsearch::Model::Client implementation to
- # return a client configured from application settings. All including
- # classes will use the same instance, which is refreshed automatically
- # if the settings change.
- #
- # _client is present to match the arity of the overridden method, where
- # it is also not used.
- #
- # @return [Elasticsearch::Transport::Client]
- def client(_client = nil)
- store = ::Elasticsearch::Model::Client
-
- store::CLIENT_MUTEX.synchronize do
- config = Gitlab::CurrentSettings.elasticsearch_config
-
- if store.cached_client.nil? || config != store.cached_config
- store.cached_client = ::Gitlab::Elastic::Client.build(config)
- store.cached_config = config
- end
- end
-
- store.cached_client
- end
- end
- end
- end
- end
+ Elasticsearch::Model::Adapter::ActiveRecord::Importing.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Importing
+ Elasticsearch::Model::Client::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model::Client::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model.singleton_class.prepend GemExtensions::Elasticsearch::Model::Client
end
diff --git a/config/initializers/invisible_captcha.rb b/config/initializers/invisible_captcha.rb
new file mode 100644
index 00000000000..5177c730596
--- /dev/null
+++ b/config/initializers/invisible_captcha.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+InvisibleCaptcha.setup do |config|
+ config.honeypots = %w(firstname lastname)
+ config.timestamp_enabled = true
+ config.timestamp_threshold = 4
+end
diff --git a/config/initializers/load_balancing.rb b/config/initializers/load_balancing.rb
index 029c0ff4277..a49bcbe1f96 100644
--- a/config/initializers/load_balancing.rb
+++ b/config/initializers/load_balancing.rb
@@ -3,7 +3,7 @@
# We need to run this initializer after migrations are done so it doesn't fail on CI
Gitlab.ee do
- if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.data_source_exists?('licenses')
+ if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('licenses')
if Gitlab::Database::LoadBalancing.enable?
Gitlab::Database.disable_prepared_statements
diff --git a/config/initializers/lograge.rb b/config/initializers/lograge.rb
index fbec28186eb..346725e4080 100644
--- a/config/initializers/lograge.rb
+++ b/config/initializers/lograge.rb
@@ -27,12 +27,7 @@ unless Sidekiq.server?
queue_duration: event.payload[:queue_duration]
}
- gitaly_calls = Gitlab::GitalyClient.get_request_count
-
- if gitaly_calls > 0
- payload[:gitaly_calls] = gitaly_calls
- payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms
- end
+ ::Gitlab::InstrumentationHelper.add_instrumentation_data(payload)
payload[:response] = event.payload[:response] if event.payload[:response]
payload[Labkit::Correlation::CorrelationId::LOG_KEY] = Labkit::Correlation::CorrelationId.current_id
diff --git a/config/initializers/mysql_ignore_postgresql_options.rb b/config/initializers/mysql_ignore_postgresql_options.rb
deleted file mode 100644
index e6a7d9bef52..00000000000
--- a/config/initializers/mysql_ignore_postgresql_options.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-# This patches ActiveRecord so indexes created using the MySQL adapter ignore
-# any PostgreSQL specific options (e.g. `using: :gin`).
-#
-# These patches do the following for MySQL:
-#
-# 1. Indexes created using the :opclasses option are ignored (as they serve no
-# purpose on MySQL).
-# 2. When creating an index with `using: :gin` the `using` option is discarded
-# as :gin is not a valid value for MySQL.
-# 3. The `:opclasses` option is stripped from add_index_options in case it's
-# used anywhere other than in the add_index methods.
-
-if defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)
- module ActiveRecord
- module ConnectionAdapters
- class Mysql2Adapter < AbstractMysqlAdapter
- alias_method :__gitlab_add_index, :add_index
- alias_method :__gitlab_add_index_options, :add_index_options
-
- def add_index(table_name, column_name, options = {})
- unless options[:opclasses]
- __gitlab_add_index(table_name, column_name, options)
- end
- end
-
- def add_index_options(table_name, column_name, options = {})
- if options[:using] && options[:using] == :gin
- options = options.dup
- options.delete(:using)
- end
-
- if options[:opclasses]
- options = options.dup
- options.delete(:opclasses)
- end
-
- __gitlab_add_index_options(table_name, column_name, options)
- end
- end
- end
- end
-end
diff --git a/config/initializers/mysql_set_length_for_binary_indexes.rb b/config/initializers/mysql_set_length_for_binary_indexes.rb
deleted file mode 100644
index 552f3a20a95..00000000000
--- a/config/initializers/mysql_set_length_for_binary_indexes.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# This patches ActiveRecord so indexes for binary columns created using the
-# MySQL adapter apply a length of 20. Otherwise MySQL can't create an index on
-# binary columns.
-
-module MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema
- # This method is used in Rails 5 schema loading as t.index
- def index(column_names, options = {})
- # Ignore indexes that use opclasses,
- # also see config/initializers/mysql_ignore_postgresql_options.rb
- if options[:opclasses]
- warn "WARNING: index on columns #{column_names} uses unsupported option, skipping."
- return
- end
-
- options[:length] ||= {}
- Array(column_names).each do |column_name|
- column = columns.find { |c| c.name == column_name }
-
- if column&.type == :binary
- options[:length][column_name] = 20
- end
- end
-
- super(column_names, options)
- end
-end
-
-if defined?(ActiveRecord::ConnectionAdapters::MySQL::TableDefinition)
- ActiveRecord::ConnectionAdapters::MySQL::TableDefinition.send(:prepend, MysqlSetLengthForBinaryIndexAndIgnorePostgresOptionsForSchema)
-end
diff --git a/config/initializers/octokit.rb b/config/initializers/octokit.rb
new file mode 100644
index 00000000000..b3749258ec5
--- /dev/null
+++ b/config/initializers/octokit.rb
@@ -0,0 +1 @@
+Octokit.middleware.insert_after Octokit::Middleware::FollowRedirects, Gitlab::Octokit::Middleware
diff --git a/config/initializers/peek.rb b/config/initializers/peek.rb
index d492b60705d..f9055285e5c 100644
--- a/config/initializers/peek.rb
+++ b/config/initializers/peek.rb
@@ -1,47 +1,13 @@
-Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
-
-Peek.into Peek::Views::Host
+require 'peek/adapters/redis'
-if Gitlab::Database.mysql?
- require 'peek-mysql2'
- PEEK_DB_CLIENT = ::Mysql2::Client
- PEEK_DB_VIEW = Peek::Views::Mysql2
-elsif Gitlab::Database.postgresql?
- require 'peek-pg'
- PEEK_DB_CLIENT = ::PG::Connection
- PEEK_DB_VIEW = Peek::Views::PG
+Peek::Adapters::Redis.prepend ::Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled
- # Remove once we have https://github.com/peek/peek-pg/pull/10
- module ::Peek::PGInstrumented
- def exec_params(*args)
- start = Time.now
- super(*args)
- ensure
- duration = (Time.now - start)
- PEEK_DB_CLIENT.query_time.update { |value| value + duration }
- PEEK_DB_CLIENT.query_count.update { |value| value + 1 }
- end
- end
-else
- raise "Unsupported database adapter for peek!"
-end
+Rails.application.config.peek.adapter = :redis, { client: ::Redis.new(Gitlab::Redis::Cache.params) }
-Peek.into PEEK_DB_VIEW
+Peek.into Peek::Views::Host
+Peek.into Peek::Views::ActiveRecord
Peek.into Peek::Views::Gitaly
-Peek.into Peek::Views::Rblineprof
-Peek.into Peek::Views::Redis
-Peek.into Peek::Views::GC
-Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
-
-# rubocop:disable Naming/ClassAndModuleCamelCase
-class PEEK_DB_CLIENT
- class << self
- attr_accessor :query_details
- end
- self.query_details = Concurrent::Array.new
-end
-
-PEEK_DB_VIEW.prepend ::Gitlab::PerformanceBar::PeekQueryTracker
+Peek.into Peek::Views::RedisDetailed
+Peek.into Peek::Views::Rugged
-require 'peek/adapters/redis'
-Peek::Adapters::Redis.prepend ::Gitlab::PerformanceBar::RedisAdapterWhenPeekEnabled
+Peek.into Peek::Views::Tracing if Labkit::Tracing.tracing_url_enabled?
diff --git a/config/initializers/postgresql_opclasses_support.rb b/config/initializers/postgresql_opclasses_support.rb
deleted file mode 100644
index 7e912180820..00000000000
--- a/config/initializers/postgresql_opclasses_support.rb
+++ /dev/null
@@ -1,211 +0,0 @@
-# rubocop:disable all
-
-# These changes add support for PostgreSQL operator classes when creating
-# indexes and dumping/loading schemas. Taken from Rails pull request
-# https://github.com/rails/rails/pull/19090.
-#
-# License:
-#
-# Copyright (c) 2004-2016 David Heinemeier Hansson
-#
-# Permission is hereby granted, free of charge, to any person obtaining
-# a copy of this software and associated documentation files (the
-# "Software"), to deal in the Software without restriction, including
-# without limitation the rights to use, copy, modify, merge, publish,
-# distribute, sublicense, and/or sell copies of the Software, and to
-# permit persons to whom the Software is furnished to do so, subject to
-# the following conditions:
-#
-# The above copyright notice and this permission notice shall be
-# included in all copies or substantial portions of the Software.
-#
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
-# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
-# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-
-require 'date'
-require 'set'
-require 'bigdecimal'
-require 'bigdecimal/util'
-
-# As the Struct definition is changed in this PR/patch we have to first remove
-# the existing one.
-ActiveRecord::ConnectionAdapters.send(:remove_const, :IndexDefinition)
-
-module ActiveRecord
- module ConnectionAdapters #:nodoc:
- # Abstract representation of an index definition on a table. Instances of
- # this type are typically created and returned by methods in database
- # adapters. e.g. ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#indexes
- attrs = [:table, :name, :unique, :columns, :lengths, :orders, :where, :type, :using, :comment, :opclasses]
-
- class IndexDefinition < Struct.new(*attrs) #:nodoc:
- end
- end
-end
-
-
-module ActiveRecord
- module ConnectionAdapters # :nodoc:
- module SchemaStatements
- def add_index_options(table_name, column_name, options = {}) #:nodoc:
- column_names = Array(column_name)
- index_name = index_name(table_name, column: column_names)
-
- options.assert_valid_keys(:unique, :order, :name, :where, :length, :internal, :using, :algorithm, :type, :opclasses)
-
- index_type = options[:unique] ? "UNIQUE" : ""
- index_type = options[:type].to_s if options.key?(:type)
- index_name = options[:name].to_s if options.key?(:name)
- max_index_length = options.fetch(:internal, false) ? index_name_length : allowed_index_name_length
-
- if options.key?(:algorithm)
- algorithm = index_algorithms.fetch(options[:algorithm]) {
- raise ArgumentError.new("Algorithm must be one of the following: #{index_algorithms.keys.map(&:inspect).join(', ')}")
- }
- end
-
- using = "USING #{options[:using]}" if options[:using].present?
-
- if supports_partial_index?
- index_options = options[:where] ? " WHERE #{options[:where]}" : ""
- end
-
- if index_name.length > max_index_length
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' is too long; the limit is #{max_index_length} characters"
- end
- if data_source_exists?(table_name) && index_name_exists?(table_name, index_name)
- raise ArgumentError, "Index name '#{index_name}' on table '#{table_name}' already exists"
- end
- index_columns = quoted_columns_for_index(column_names, options).join(", ")
-
- [index_name, index_type, index_columns, index_options, algorithm, using]
- end
- end
- end
-end
-
-module ActiveRecord
- module ConnectionAdapters
- module PostgreSQL
- module SchemaStatements
- # Returns an array of indexes for the given table.
- def indexes(table_name, name = nil)
- result = query(<<-SQL, 'SCHEMA')
- SELECT distinct i.relname, d.indisunique, d.indkey, pg_get_indexdef(d.indexrelid), t.oid
- FROM pg_class t
- INNER JOIN pg_index d ON t.oid = d.indrelid
- INNER JOIN pg_class i ON d.indexrelid = i.oid
- WHERE i.relkind = 'i'
- AND d.indisprimary = 'f'
- AND t.relname = '#{table_name}'
- AND i.relnamespace IN (SELECT oid FROM pg_namespace WHERE nspname = ANY (current_schemas(false)) )
- ORDER BY i.relname
- SQL
-
- result.map do |row|
- index_name = row[0]
- unique = row[1]
- indkey = row[2].split(" ").map(&:to_i)
- inddef = row[3]
- oid = row[4]
-
- columns = Hash[query(<<-SQL, "SCHEMA")]
- SELECT a.attnum, a.attname
- FROM pg_attribute a
- WHERE a.attrelid = #{oid}
- AND a.attnum IN (#{indkey.join(",")})
- SQL
-
- column_names = columns.values_at(*indkey).compact
-
- unless column_names.empty?
- # add info on sort order for columns (only desc order is explicitly specified, asc is the default)
- desc_order_columns = inddef.scan(/(\w+) DESC/).flatten
- orders = desc_order_columns.any? ? Hash[desc_order_columns.map {|order_column| [order_column, :desc]}] : {}
- where = inddef.scan(/WHERE (.+)$/).flatten[0]
- using = inddef.scan(/USING (.+?) /).flatten[0].to_sym
- opclasses = Hash[inddef.scan(/\((.+?)\)(?:$| WHERE )/).flatten[0].split(',').map do |column_and_opclass|
- column, opclass = column_and_opclass.split(' ').map(&:strip)
- end.reject do |column, opclass|
- ['desc', 'asc'].include?(opclass&.downcase)
- end.map do |column, opclass|
- [column, opclass] if opclass
- end.compact]
-
- index_attrs = [table_name, index_name, unique, column_names, [], orders, where, nil, using, nil, opclasses]
-
- IndexDefinition.new(*index_attrs)
- end
- end.compact
- end
-
- def add_index(table_name, column_name, options = {}) #:nodoc:
- index_name, index_type, index_columns_and_opclasses, index_options, index_algorithm, index_using = add_index_options(table_name, column_name, options)
- execute "CREATE #{index_type} INDEX #{index_algorithm} #{quote_column_name(index_name)} ON #{quote_table_name(table_name)} #{index_using} (#{index_columns_and_opclasses})#{index_options}"
- end
-
- protected
-
- def quoted_columns_for_index(column_names, options = {})
- column_opclasses = options[:opclasses] || {}
- column_names.map {|name| "#{quote_column_name(name)} #{column_opclasses[name]}"}
-
- quoted_columns = Hash[column_names.map { |name| [name.to_sym, "#{quote_column_name(name)} #{column_opclasses[name]}"] }]
- add_options_for_index_columns(quoted_columns, options).values
- end
- end
- end
- end
-end
-
-module ActiveRecord
- class SchemaDumper
- private
-
- def indexes(table, stream)
- if (indexes = @connection.indexes(table)).any?
- add_index_statements = indexes.map do |index|
- table_name = remove_prefix_and_suffix(index.table).inspect
- " add_index #{([table_name]+index_parts(index)).join(', ')}"
- end
-
- stream.puts add_index_statements.sort.join("\n")
- stream.puts
- end
- end
-
- def indexes_in_create(table, stream)
- if (indexes = @connection.indexes(table)).any?
- index_statements = indexes.map do |index|
- " t.index #{index_parts(index).join(', ')}"
- end
- stream.puts index_statements.sort.join("\n")
- end
- end
-
- def index_parts(index)
- index_parts = [
- index.columns.inspect,
- "name: #{index.name.inspect}",
- ]
- index_parts << "unique: true" if index.unique
- index_parts << "length: { #{format_options(index.lengths)} }" if index.lengths.present?
- index_parts << "order: { #{format_options(index.orders)} }" if index.orders.present?
- index_parts << "where: #{index.where.inspect}" if index.where
- index_parts << "using: #{index.using.inspect}" if index.using
- index_parts << "type: #{index.type.inspect}" if index.type
- index_parts << "opclasses: #{index.opclasses.inspect}" if index.opclasses.present?
- index_parts << "comment: #{index.comment.inspect}" if index.comment
- index_parts
- end
-
- def format_options(options)
- options.map { |key, value| "#{key}: #{value.inspect}" }.join(", ")
- end
- end
-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 166fb9b6916..7217f098fd9 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -33,11 +33,13 @@ Sidekiq.configure_server do |config|
config.redis = queues_config_hash
config.server_middleware do |chain|
+ 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']
chain.add Gitlab::SidekiqMiddleware::RequestStoreMiddleware unless ENV['SIDEKIQ_REQUEST_STORE'] == '0'
chain.add Gitlab::SidekiqMiddleware::BatchLoader
chain.add Gitlab::SidekiqMiddleware::CorrelationLogger
+ chain.add Gitlab::SidekiqMiddleware::InstrumentationLogger
chain.add Gitlab::SidekiqStatus::ServerMiddleware
end
diff --git a/config/karma.config.js b/config/karma.config.js
index 2a5bf3581e0..97794225a3f 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -107,7 +107,8 @@ if (specFilters.length) {
module.exports = function(config) {
process.env.TZ = 'Etc/UTC';
- const fixturesPath = `${IS_EE ? 'ee/' : ''}spec/javascripts/fixtures`;
+ const fixturesPath = `tmp/tests/frontend/fixtures${IS_EE ? '-ee' : ''}`;
+ const staticFixturesPath = 'spec/frontend/fixtures/static';
const karmaConfig = {
basePath: ROOT_PATH,
@@ -131,8 +132,13 @@ module.exports = function(config) {
frameworks: ['jasmine'],
files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false },
- { pattern: `${fixturesPath}/**/*@(.json|.html|.png|.bmpr|.pdf)`, included: false },
+ { pattern: `${fixturesPath}/**/*`, included: false },
+ { pattern: `${staticFixturesPath}/**/*`, included: false },
],
+ proxies: {
+ '/fixtures/': `/base/${fixturesPath}/`,
+ '/fixtures/static/': `/base/${staticFixturesPath}/`,
+ },
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
'ee/spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
diff --git a/config/knative/api_resources.yml b/config/knative/api_resources.yml
new file mode 100644
index 00000000000..43427b730db
--- /dev/null
+++ b/config/knative/api_resources.yml
@@ -0,0 +1,64 @@
+---
+
+- meshpolicies.authentication.istio.io
+- policies.authentication.istio.io
+- adapters.config.istio.io
+- apikeys.config.istio.io
+- attributemanifests.config.istio.io
+- authorizations.config.istio.io
+- bypasses.config.istio.io
+- podautoscalers.autoscaling.internal.knative.dev
+- builds.build.knative.dev
+- buildtemplates.build.knative.dev
+- clusterbuildtemplates.build.knative.dev
+- images.caching.internal.knative.dev
+- certificates.networking.internal.knative.dev
+- clusteringresses.networking.internal.knative.dev
+- serverlessservices.networking.internal.knative.dev
+- configurations.serving.knative.dev
+- revisions.serving.knative.dev
+- routes.serving.knative.dev
+- services.serving.knative.dev
+- checknothings.config.istio.io
+- circonuses.config.istio.io
+- deniers.config.istio.io
+- edges.config.istio.io
+- fluentds.config.istio.io
+- handlers.config.istio.io
+- httpapispecbindings.config.istio.io
+- httpapispecs.config.istio.io
+- instances.config.istio.io
+- kubernetesenvs.config.istio.io
+- kuberneteses.config.istio.io
+- listcheckers.config.istio.io
+- listentries.config.istio.io
+- logentries.config.istio.io
+- memquotas.config.istio.io
+- metrics.config.istio.io
+- noops.config.istio.io
+- opas.config.istio.io
+- prometheuses.config.istio.io
+- quotas.config.istio.io
+- quotaspecbindings.config.istio.io
+- quotaspecs.config.istio.io
+- rbacs.config.istio.io
+- redisquotas.config.istio.io
+- reportnothings.config.istio.io
+- rules.config.istio.io
+- servicecontrolreports.config.istio.io
+- servicecontrols.config.istio.io
+- signalfxs.config.istio.io
+- solarwindses.config.istio.io
+- stackdrivers.config.istio.io
+- statsds.config.istio.io
+- stdios.config.istio.io
+- templates.config.istio.io
+- tracespans.config.istio.io
+- destinationrules.networking.istio.io
+- envoyfilters.networking.istio.io
+- gateways.networking.istio.io
+- serviceentries.networking.istio.io
+- virtualservices.networking.istio.io
+- rbacconfigs.rbac.istio.io
+- servicerolebindings.rbac.istio.io
+- serviceroles.rbac.istio.io \ No newline at end of file
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index a8234263275..258d8a99986 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -69,7 +69,7 @@ en:
email: Allows read-only access to the user's primary email address using OpenID Connect
scope_desc:
api:
- Grants complete read/write access to the API, including all groups and projects.
+ Grants complete read/write access to the API, including all groups and projects, the container registry, and the package registry.
read_user:
Grants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users.
read_repository:
diff --git a/config/locales/en.yml b/config/locales/en.yml
index a3dceb2fb62..a60f86e1d80 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -16,12 +16,6 @@ en:
api_url: "Sentry API URL"
project/metrics_setting:
external_dashboard_url: "External dashboard URL"
- errors:
- messages:
- label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
- wrong_size: "is the wrong size (should be %{file_size})"
- size_too_small: "is too small (should be at least %{file_size})"
- size_too_big: "is too big (should be at most %{file_size})"
views:
pagination:
previous: "Prev"
diff --git a/config/locales/invisible_captcha.en.yml b/config/locales/invisible_captcha.en.yml
new file mode 100644
index 00000000000..5978549c0c3
--- /dev/null
+++ b/config/locales/invisible_captcha.en.yml
@@ -0,0 +1,4 @@
+en:
+ invisible_captcha:
+ sentence_for_humans: If you are human, please ignore this field.
+ timestamp_error_message: That was a bit too quick! Please resubmit.
diff --git a/config/prometheus/cluster_metrics.yml b/config/prometheus/cluster_metrics.yml
index 3df76b0974f..f2a41e4c337 100644
--- a/config/prometheus/cluster_metrics.yml
+++ b/config/prometheus/cluster_metrics.yml
@@ -2,12 +2,12 @@
priority: 1
metrics:
- title: "CPU Usage"
- y_label: "CPU"
+ y_label: "CPU (cores)"
required_metrics: ['container_cpu_usage_seconds_total']
weight: 1
queries:
- query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{id="/"}[15m])) by (job)) without (job)'
- label: Usage
+ label: Usage (cores)
unit: "cores"
appearance:
line:
@@ -15,7 +15,7 @@
area:
opacity: 0
- query_range: 'sum(kube_pod_container_resource_requests_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Requested
+ label: Requested (cores)
unit: "cores"
appearance:
line:
@@ -23,7 +23,7 @@
area:
opacity: 0
- query_range: 'sum(kube_node_status_capacity_cpu_cores{kubernetes_namespace="gitlab-managed-apps"})'
- label: Capacity
+ label: Capacity (cores)
unit: "cores"
appearance:
line:
@@ -32,12 +32,12 @@
area:
opacity: 0
- title: "Memory usage"
- y_label: "Memory"
+ y_label: "Memory (GiB)"
required_metrics: ['container_memory_usage_bytes']
weight: 1
queries:
- query_range: 'avg(sum(container_memory_usage_bytes{id="/"}) by (job)) without (job) / 2^30'
- label: Usage
+ label: Usage (GiB)
unit: "GiB"
appearance:
line:
@@ -45,7 +45,7 @@
area:
opacity: 0
- query_range: 'sum(kube_pod_container_resource_requests_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Requested
+ label: Requested (GiB)
unit: "GiB"
appearance:
line:
@@ -53,7 +53,7 @@
area:
opacity: 0
- query_range: 'sum(kube_node_status_capacity_memory_bytes{kubernetes_namespace="gitlab-managed-apps"})/2^30'
- label: Capacity
+ label: Capacity (GiB)
unit: "GiB"
appearance:
line:
diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml
index f9ce5a6f365..08504d6f7d5 100644
--- a/config/prometheus/common_metrics.yml
+++ b/config/prometheus/common_metrics.yml
@@ -21,16 +21,16 @@ panel_groups:
metrics:
- id: response_metrics_nginx_ingress_latency_pod_average
query_range: 'avg(nginx_upstream_response_msecs_avg{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"})'
- label: Pod average
+ label: Pod average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
- y_label: "HTTP Errors"
+ y_label: "HTTP Errors (%)"
weight: 1
metrics:
- id: response_metrics_nginx_ingress_http_error_rate
query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100'
- label: 5xx Errors
+ label: 5xx Errors (%)
unit: "%"
# NGINX Ingress metrics for post-0.16.0 versions
- group: Response metrics (NGINX Ingress)
@@ -52,16 +52,16 @@ panel_groups:
metrics:
- id: response_metrics_nginx_ingress_16_latency_pod_average
query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000'
- label: Pod average
+ label: Pod average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
- y_label: "HTTP Errors"
+ y_label: "HTTP Errors (%)"
weight: 1
metrics:
- id: response_metrics_nginx_ingress_16_http_error_rate
query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100'
- label: 5xx Errors
+ label: 5xx Errors (%)
unit: "%"
- group: Response metrics (HA Proxy)
priority: 10
@@ -82,7 +82,7 @@ panel_groups:
metrics:
- id: response_metrics_ha_proxy_http_error_rate
query_range: 'sum(rate(haproxy_frontend_http_responses_total{code="5xx",%{environment_filter}}[2m])) / sum(rate(haproxy_frontend_http_responses_total{%{environment_filter}}[2m]))'
- label: HTTP Errors
+ label: HTTP Errors (%)
unit: "%"
- group: Response metrics (AWS ELB)
priority: 10
@@ -94,7 +94,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_throughput_requests
query_range: 'sum(aws_elb_request_count_sum{%{environment_filter}}) / 60'
- label: Total
+ label: Total (req/sec)
unit: req / sec
- title: "Latency"
type: "area-chart"
@@ -103,7 +103,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_latency_average
query_range: 'avg(aws_elb_latency_average{%{environment_filter}}) * 1000'
- label: Average
+ label: Average (ms)
unit: ms
- title: "HTTP Error Rate"
type: "area-chart"
@@ -112,7 +112,7 @@ panel_groups:
metrics:
- id: response_metrics_aws_elb_http_error_rate
query_range: 'sum(aws_elb_httpcode_backend_5_xx_sum{%{environment_filter}}) / sum(aws_elb_request_count_sum{%{environment_filter}})'
- label: HTTP Errors
+ label: HTTP Errors (%)
unit: "%"
- group: Response metrics (NGINX)
priority: 10
@@ -133,9 +133,9 @@ panel_groups:
metrics:
- id: response_metrics_nginx_latency
query_range: 'avg(nginx_server_requestMsec{%{environment_filter}})'
- label: Upstream
+ label: Upstream (ms)
unit: ms
- - title: "HTTP Error Rate"
+ - title: "HTTP Error Rate (Errors / Sec)"
type: "area-chart"
y_label: "HTTP 500 Errors / Sec"
weight: 1
@@ -149,12 +149,12 @@ panel_groups:
panels:
- title: "Memory Usage (Total)"
type: "area-chart"
- y_label: "Total Memory Used"
+ y_label: "Total Memory Used (GB)"
weight: 4
metrics:
- id: system_metrics_kubernetes_container_memory_total
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) /1024/1024/1024'
- label: Total
+ label: Total (GB)
unit: GB
- title: "Core Usage (Total)"
type: "area-chart"
@@ -163,44 +163,44 @@ panel_groups:
metrics:
- id: system_metrics_kubernetes_container_cores_total
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job)'
- label: Total
+ label: Total (cores)
unit: "cores"
- title: "Memory Usage (Pod average)"
- type: "area-chart"
- y_label: "Memory Used per Pod"
+ type: "line-chart"
+ y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- label: Pod average
+ label: Pod average (MB)
unit: MB
- title: "Canary: Memory Usage (Pod Average)"
- type: "area-chart"
- y_label: "Memory Used per Pod"
+ type: "line-chart"
+ y_label: "Memory Used per Pod (MB)"
weight: 2
metrics:
- id: system_metrics_kubernetes_container_memory_average_canary
query_range: 'avg(sum(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) by (job)) without (job) / count(avg(container_memory_usage_bytes{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}) without (job)) /1024/1024'
- label: Pod average
+ label: Pod average (MB)
unit: MB
track: canary
- title: "Core Usage (Pod Average)"
- type: "area-chart"
+ type: "line-chart"
y_label: "Cores per Pod"
weight: 1
metrics:
- id: system_metrics_kubernetes_container_core_usage
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-([^c].*|c([^a]|a([^n]|n([^a]|a([^r]|r[^y])))).*|)-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
- label: Pod average
+ 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:
- id: system_metrics_kubernetes_container_core_usage_canary
query_range: 'avg(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (job)) without (job) / count(sum(rate(container_cpu_usage_seconds_total{container_name!="POD",pod_name=~"^%{ci_environment_slug}-canary-(.*)",namespace="%{kube_namespace}"}[15m])) by (pod_name))'
- label: Pod average
+ label: Pod average (cores)
unit: "cores"
track: canary
- title: "Knative function invocations"
diff --git a/config/routes.rb b/config/routes.rb
index 641807203bf..d633228a495 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -58,6 +58,7 @@ Rails.application.routes.draw do
# Search
get 'search' => 'search#show'
get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete
+ get 'search/count' => 'search#count', as: :search_count
# JSON Web Token
get 'jwt/auth' => 'jwt#auth'
@@ -108,6 +109,7 @@ Rails.application.routes.draw do
Gitlab.ee do
draw :smartcard
draw :jira_connect
+ draw :username
end
Gitlab.ee do
@@ -116,7 +118,7 @@ Rails.application.routes.draw do
end
end
- if ENV['GITLAB_CHAOS_SECRET'] || Rails.env.development?
+ if ENV['GITLAB_CHAOS_SECRET'] || Rails.env.development? || Rails.env.test?
resource :chaos, only: [] do
get :leakmem
get :cpu_spin
diff --git a/config/routes/admin.rb b/config/routes/admin.rb
index f609739d9fd..6f9a5552564 100644
--- a/config/routes/admin.rb
+++ b/config/routes/admin.rb
@@ -73,7 +73,7 @@ namespace :admin do
resource :background_jobs, controller: 'background_jobs', only: [:show]
resource :system_info, controller: 'system_info', only: [:show]
- resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ }
+ resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.(html|txt)/ }
resources :projects, only: [:index]
diff --git a/config/routes/git_http.rb b/config/routes/git_http.rb
index a959d40881b..aac6d418a92 100644
--- a/config/routes/git_http.rb
+++ b/config/routes/git_http.rb
@@ -34,6 +34,18 @@ scope(path: '*namespace_id/:project_id',
end
end
+ # Redirect /group/project.wiki.git to the project wiki
+ scope(format: true, constraints: { project_id: Gitlab::PathRegex.project_wiki_git_route_regex, format: :git }) do
+ wiki_redirect = redirect do |params, request|
+ project_id = params[:project_id].delete_suffix('.wiki')
+ path = [params[:namespace_id], project_id, 'wikis'].join('/')
+ path << "?#{request.query_string}" unless request.query_string.blank?
+ path
+ end
+
+ get '/', to: wiki_redirect
+ end
+
# Redirect /group/project/info/refs to /group/project.git/info/refs
scope(constraints: { project_id: Gitlab::PathRegex.project_route_regex }) do
# Allow /info/refs, /info/refs?service=git-upload-pack, and
diff --git a/config/routes/project.rb b/config/routes/project.rb
index c202463dadb..9a453d101a1 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -170,7 +170,9 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :recent
end
end
+
resources :releases, only: [:index]
+ resources :starrers, only: [:index]
resources :forks, only: [:index, :new, :create]
resources :group_links, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
@@ -265,6 +267,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :pipelines
get :diffs, to: 'merge_requests/diffs#show'
get :widget, to: 'merge_requests/content#widget'
+ get :cached_widget, to: 'merge_requests/content#cached_widget'
end
get :diff_for_path, controller: 'merge_requests/diffs'
@@ -339,11 +342,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :variables, only: [:show, :update]
- resources :triggers, only: [:index, :create, :edit, :update, :destroy] do
- member do
- post :take_ownership
- end
- end
+ resources :triggers, only: [:index, :create, :edit, :update, :destroy]
resource :mirror, only: [:show, :update] do
member do
@@ -478,7 +477,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# in JSON format, or a request for tag named `latest.json`.
scope format: false do
resources :tags, only: [:index, :destroy],
- constraints: { id: Gitlab::Regex.container_registry_tag_regex }
+ constraints: { id: Gitlab::Regex.container_registry_tag_regex } do
+ collection do
+ delete :bulk_destroy
+ end
+ end
end
end
end
@@ -504,6 +507,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :realtime_changes
post :create_merge_request
get :discussions, format: :json
+
+ Gitlab.ee do
+ get 'designs(/*vueroute)', to: 'issues#show', as: :designs, format: false
+ end
end
collection do
diff --git a/config/routes/user.rb b/config/routes/user.rb
index 80f266aa8f9..d4616c8080d 100644
--- a/config/routes/user.rb
+++ b/config/routes/user.rb
@@ -43,13 +43,6 @@ scope '-/users', module: :users do
end
end
-scope '-/users', module: :users do
- resources :terms, only: [:index] do
- post :accept, on: :member
- post :decline, on: :member
- end
-end
-
scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) do
scope(path: 'users/:username',
as: :user,
@@ -59,6 +52,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d
get :groups
get :projects
get :contributed, as: :contributed_projects
+ get :starred, as: :starred_projects
get :snippets
get :exists
get :activity
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/settings.rb b/config/settings.rb
index da459afcce2..8756c120645 100644
--- a/config/settings.rb
+++ b/config/settings.rb
@@ -1,4 +1,5 @@
require 'settingslogic'
+require 'digest/md5'
# We can not use `Rails.root` here, as this file might be loaded without the
# full Rails environment being loaded. We can not use `require_relative` either,
@@ -170,14 +171,17 @@ class Settings < Settingslogic
URI.parse(url_without_path).host
end
- # Runs every minute in a random ten-minute period on Sundays, to balance the
- # load on the server receiving these pings. The usage ping is safe to run
- # multiple times because of a 24 hour exclusive lock.
+ # Runs at a random time of day on a consistent day of the week based on
+ # the instance UUID. This is to balance the load on the service receiving
+ # these pings. The sidekiq job handles temporary http failures.
def cron_for_usage_ping
hour = rand(24)
- minute = rand(6)
+ minute = rand(60)
+ # Set a default UUID for the case when the UUID hasn't been initialized.
+ uuid = Gitlab::CurrentSettings.uuid || 'uuid-not-set'
+ day_of_week = Digest::MD5.hexdigest(uuid).to_i(16) % 7
- "#{minute}0-#{minute}9 #{hour} * * 0"
+ "#{minute} #{hour} * * #{day_of_week}"
end
end
end
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 80791795390..ea165508d29 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -95,6 +95,7 @@
- [update_project_statistics, 1]
- [phabricator_import_import_tasks, 1]
- [update_namespace_statistics, 1]
+ - [chaos, 2]
# EE-specific queues
- [ldap_group_sync, 2]
@@ -112,3 +113,4 @@
- [elastic_namespace_indexer, 1]
- [export_csv, 1]
- [incident_management, 2]
+ - [jira_connect, 1]
diff --git a/config/webpack.config.js b/config/webpack.config.js
index cd793743eb7..442b4b4c21e 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -6,6 +6,7 @@ const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin;
const CompressionPlugin = require('compression-webpack-plugin');
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
+const CopyWebpackPlugin = require('copy-webpack-plugin');
const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
@@ -89,6 +90,12 @@ const alias = {
// the following resolves files which are different between CE and EE
ee_else_ce: path.join(ROOT_PATH, 'app/assets/javascripts'),
+
+ // override loader path for icons.svg so we do not duplicate this asset
+ '@gitlab/svgs/dist/icons.svg': path.join(
+ ROOT_PATH,
+ 'app/assets/javascripts/lib/utils/icons_path.js',
+ ),
};
if (IS_EE) {
@@ -157,7 +164,15 @@ module.exports = {
loader: 'graphql-tag/loader',
},
{
+ test: /icons\.svg$/,
+ loader: 'file-loader',
+ options: {
+ name: '[name].[hash:8].[ext]',
+ },
+ },
+ {
test: /\.svg$/,
+ exclude: /icons\.svg$/,
loader: 'raw-loader',
},
{
@@ -278,6 +293,13 @@ module.exports = {
}
}),
+ new CopyWebpackPlugin([
+ {
+ from: path.join(ROOT_PATH, 'node_modules/pdfjs-dist/cmaps/'),
+ to: path.join(ROOT_PATH, 'public/assets/webpack/cmaps/'),
+ },
+ ]),
+
// compression can require a lot of compute time and is disabled in CI
IS_PRODUCTION && !NO_COMPRESSION && new CompressionPlugin(),
diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile
index 083e95b8da7..3550cb7eabf 100644
--- a/danger/database/Dangerfile
+++ b/danger/database/Dangerfile
@@ -8,6 +8,21 @@ updated too (unless the migration isn't changing the DB schema
and isn't the most recent one).
MSG
+DB_MESSAGE = <<~MSG
+This merge request requires a database review. To make sure these
+changes are reviewed, take the following steps:
+
+1. Ensure the merge request has ~database and ~"database::review pending" labels.
+ If the merge request modifies database files, Danger will do this for you.
+1. Use the [Database changes checklist](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md)
+ template or add the appropriate items to the MR description.
+1. Assign and mention the database reviewer suggested by Reviewer Roulette.
+MSG
+
+DB_FILES_MESSAGE = <<~MSG
+The following files require a review from the Database team:
+MSG
+
non_geo_db_schema_updated = !git.modified_files.grep(%r{\Adb/schema\.rb}).empty?
geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).empty?
@@ -24,27 +39,18 @@ end
db_paths_to_review = helper.changes_by_category[:database]
-unless db_paths_to_review.empty?
+if gitlab.mr_labels.include?('database') || db_paths_to_review.any?
message 'This merge request adds or changes files that require a ' \
- 'review from the Database team.'
-
- markdown(<<~MARKDOWN.strip)
-## Database Review
-
-The following files require a review from the Database team:
-
-* #{db_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
+ 'review from the [Database team](https://gitlab.com/groups/gl-database/-/group_members).'
-To make sure these changes are reviewed, take the following steps:
+ markdown(DB_MESSAGE)
+ markdown(DB_FILES_MESSAGE + helper.markdown_list(db_paths_to_review)) if db_paths_to_review.any?
-1. Edit your merge request, and add `gl-database` to the list of Group
- approvers.
-1. Mention `@gl-database` in a separate comment, and explain what needs to be
- reviewed by the team. Please don't mention the team until your changes are
- ready for review.
- MARKDOWN
+ database_labels = helper.missing_database_labels(gitlab.mr_labels)
- unless gitlab.mr_labels.include?('database')
- warn 'This merge request is missing the ~database label.'
+ if database_labels.any?
+ gitlab.api.update_merge_request(gitlab.mr_json['project_id'],
+ gitlab.mr_json['iid'],
+ labels: (gitlab.mr_labels + database_labels).join(','))
end
end
diff --git a/danger/gemfile/Dangerfile b/danger/gemfile/Dangerfile
index 4e91abc371a..dfe64f79d7b 100644
--- a/danger/gemfile/Dangerfile
+++ b/danger/gemfile/Dangerfile
@@ -3,8 +3,7 @@ GEMFILE_LOCK_NOT_UPDATED_MESSAGE = <<~MSG.freeze
Usually, when %<gemfile>s is updated, you should run
```
-bundle install && \
- bundle install
+bundle install
```
or
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 6718e218233..19a5076778c 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -55,22 +55,15 @@ def spin_for_category(team, project, category, branch_name)
"| #{helper.label_for_category(category)} | #{reviewer&.markdown_name || NO_REVIEWER} | #{maintainer&.markdown_name || NO_MAINTAINER} |"
end
-def build_list(items)
- list = items.map { |filename| "* `#{filename}`" }.join("\n")
-
- if items.size > 10
- "\n<details>\n\n#{list}\n\n</details>"
- else
- list
- end
-end
-
changes = helper.changes_by_category
# Ignore any files that are known but uncategorized. Prompt for any unknown files
changes.delete(:none)
categories = changes.keys - [:unknown]
+# Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
+categories << :database if gitlab.mr_labels.include?('database') && !categories.include?(:database)
+
# Single codebase MRs are reviewed using a slightly different process, so we
# disable the review roulette for such MRs.
# CSS Clean up MRs are reviewed using a slightly different process, so we
@@ -95,5 +88,5 @@ if changes.any? && !gitlab.mr_labels.include?('single codebase') && !gitlab.mr_l
markdown(MESSAGE)
markdown(CATEGORY_TABLE_HEADER + rows.join("\n")) unless rows.empty?
- markdown(UNKNOWN_FILES_MESSAGE + build_list(unknown)) unless unknown.empty?
+ markdown(UNKNOWN_FILES_MESSAGE + helper.markdown_list(unknown)) unless unknown.empty?
end
diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb
index db043e39d2c..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' } },
@@ -79,9 +69,17 @@ class Gitlab::Seeder::Pipelines
def create_master_pipelines
@project.repository.commits('master', limit: 4).map do |commit|
- create_pipeline!(@project, 'master', commit)
+ create_pipeline!(@project, 'master', commit).tap do |pipeline|
+ random_pipeline.tap do |triggered_by_pipeline|
+ triggered_by_pipeline.try(:sourced_pipelines)&.create(
+ source_job: triggered_by_pipeline.builds.all.sample,
+ source_project: triggered_by_pipeline.project,
+ project: pipeline.project,
+ pipeline: pipeline)
+ end
+ end
end
- rescue
+ rescue ActiveRecord::ActiveRecordError
[]
end
@@ -98,7 +96,7 @@ class Gitlab::Seeder::Pipelines
end
pipelines.flatten
- rescue
+ rescue ActiveRecord::ActiveRecordError
[]
end
@@ -119,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.
@@ -159,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"))
@@ -231,6 +175,10 @@ class Gitlab::Seeder::Pipelines
@project.team.users.sample
end
+ def random_pipeline
+ Ci::Pipeline.limit(4).all.sample
+ end
+
def build_status
Ci::Build::AVAILABLE_STATUSES.sample
end
@@ -255,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 ae7541f2475..fa90b37954f 100644
--- a/db/migrate/20171230123729_init_schema.rb
+++ b/db/migrate/20171230123729_init_schema.rb
@@ -788,7 +788,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.datetime_with_timezone "closed_at"
t.index ["author_id"], name: "index_issues_on_author_id", using: :btree
t.index ["confidential"], name: "index_issues_on_confidential", using: :btree
- t.index ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ t.index ["description"], name: "index_issues_on_description_trigram", using: :gin, opclass: {"description"=>"gin_trgm_ops"}
t.index ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree
t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state", using: :btree
@@ -797,7 +797,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state", using: :btree
t.index ["relative_position"], name: "index_issues_on_relative_position", using: :btree
t.index ["state"], name: "index_issues_on_state", using: :btree
- t.index ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ t.index ["title"], name: "index_issues_on_title_trigram", using: :gin, opclass: {"title"=>"gin_trgm_ops"}
t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
end
create_table "keys", id: :serial do |t|
@@ -989,7 +989,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
t.index ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
t.index ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
- t.index ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ t.index ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclass: {"description"=>"gin_trgm_ops"}
t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)", using: :btree
@@ -1001,7 +1001,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", using: :btree
t.index ["title"], name: "index_merge_requests_on_title", using: :btree
- t.index ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ t.index ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclass: {"title"=>"gin_trgm_ops"}
t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
end
create_table "merge_requests_closing_issues", id: :serial do |t|
@@ -1026,12 +1026,12 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.date "start_date"
t.integer "cached_markdown_version"
t.integer "group_id"
- t.index ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ t.index ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclass: {"description"=>"gin_trgm_ops"}
t.index ["due_date"], name: "index_milestones_on_due_date", using: :btree
t.index ["group_id"], name: "index_milestones_on_group_id", using: :btree
t.index ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
t.index ["title"], name: "index_milestones_on_title", using: :btree
- t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclass: {"title"=>"gin_trgm_ops"}
end
create_table "namespaces", id: :serial do |t|
t.string "name", null: false
@@ -1054,11 +1054,11 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.string "runners_token"
t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree
t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree
- t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
+ t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclass: {"name"=>"gin_trgm_ops"}
t.index ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
t.index ["parent_id", "id"], name: "index_namespaces_on_parent_id_and_id", unique: true, using: :btree
t.index ["path"], name: "index_namespaces_on_path", using: :btree
- t.index ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
+ t.index ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclass: {"path"=>"gin_trgm_ops"}
t.index ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication", using: :btree
t.index ["type"], name: "index_namespaces_on_type", using: :btree
end
@@ -1091,7 +1091,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["created_at"], name: "index_notes_on_created_at", using: :btree
t.index ["discussion_id"], name: "index_notes_on_discussion_id", using: :btree
t.index ["line_code"], name: "index_notes_on_line_code", using: :btree
- t.index ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
+ t.index ["note"], name: "index_notes_on_note_trigram", using: :gin, opclass: {"note"=>"gin_trgm_ops"}
t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
t.index ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
@@ -1304,14 +1304,14 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["ci_id"], name: "index_projects_on_ci_id", using: :btree
t.index ["created_at"], name: "index_projects_on_created_at", using: :btree
t.index ["creator_id"], name: "index_projects_on_creator_id", using: :btree
- t.index ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
+ t.index ["description"], name: "index_projects_on_description_trigram", using: :gin, opclass: {"description"=>"gin_trgm_ops"}
t.index ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
t.index ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree
t.index ["last_repository_updated_at"], name: "index_projects_on_last_repository_updated_at", using: :btree
- t.index ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
+ t.index ["name"], name: "index_projects_on_name_trigram", using: :gin, opclass: {"name"=>"gin_trgm_ops"}
t.index ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
t.index ["path"], name: "index_projects_on_path", using: :btree
- t.index ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
+ t.index ["path"], name: "index_projects_on_path_trigram", using: :gin, opclass: {"path"=>"gin_trgm_ops"}
t.index ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
t.index ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree
t.index ["runners_token"], name: "index_projects_on_runners_token", using: :btree
@@ -1375,7 +1375,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.datetime "updated_at", null: false
t.boolean "permanent"
t.index ["path"], name: "index_redirect_routes_on_path", unique: true, using: :btree
- t.index ["path"], name: "index_redirect_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"}
+ t.index ["path"], name: "index_redirect_routes_on_path_text_pattern_ops", using: :btree, opclass: {"path"=>"varchar_pattern_ops"}
t.index ["permanent"], name: "index_redirect_routes_on_permanent", using: :btree
t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
end
@@ -1398,7 +1398,7 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.datetime "updated_at"
t.string "name"
t.index ["path"], name: "index_routes_on_path", unique: true, using: :btree
- t.index ["path"], name: "index_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"}
+ t.index ["path"], name: "index_routes_on_path_text_pattern_ops", using: :btree, opclass: {"path"=>"varchar_pattern_ops"}
t.index ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree
end
create_table "sent_notifications", id: :serial do |t|
@@ -1454,9 +1454,9 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.text "description"
t.text "description_html"
t.index ["author_id"], name: "index_snippets_on_author_id", using: :btree
- t.index ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"}
+ t.index ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclass: {"file_name"=>"gin_trgm_ops"}
t.index ["project_id"], name: "index_snippets_on_project_id", using: :btree
- t.index ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ t.index ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclass: {"title"=>"gin_trgm_ops"}
t.index ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
t.index ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
end
@@ -1663,16 +1663,16 @@ class InitSchema < ActiveRecord::Migration[4.2]
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
t.index ["created_at"], name: "index_users_on_created_at", using: :btree
t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
- t.index ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
+ t.index ["email"], name: "index_users_on_email_trigram", using: :gin, opclass: {"email"=>"gin_trgm_ops"}
t.index ["ghost"], name: "index_users_on_ghost", using: :btree
t.index ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
t.index ["name"], name: "index_users_on_name", using: :btree
- t.index ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
+ t.index ["name"], name: "index_users_on_name_trigram", using: :gin, opclass: {"name"=>"gin_trgm_ops"}
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
t.index ["rss_token"], name: "index_users_on_rss_token", using: :btree
t.index ["state"], name: "index_users_on_state", using: :btree
t.index ["username"], name: "index_users_on_username", using: :btree
- t.index ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
+ t.index ["username"], name: "index_users_on_username_trigram", using: :gin, opclass: {"username"=>"gin_trgm_ops"}
end
create_table "users_star_projects", id: :serial do |t|
t.integer "project_id", null: false
diff --git a/db/migrate/20180206200543_reset_events_primary_key_sequence.rb b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb
index d395c5725e4..e7a18e68395 100644
--- a/db/migrate/20180206200543_reset_events_primary_key_sequence.rb
+++ b/db/migrate/20180206200543_reset_events_primary_key_sequence.rb
@@ -12,24 +12,10 @@ class ResetEventsPrimaryKeySequence < ActiveRecord::Migration[4.2]
end
def up
- if Gitlab::Database.postgresql?
- reset_primary_key_for_postgresql
- else
- reset_primary_key_for_mysql
- end
+ reset_pk_sequence!(Event.table_name)
end
def down
# No-op
end
-
- def reset_primary_key_for_postgresql
- reset_pk_sequence!(Event.table_name)
- end
-
- def reset_primary_key_for_mysql
- amount = Event.pluck('COALESCE(MAX(id), 1)').first
-
- execute "ALTER TABLE #{Event.table_name} AUTO_INCREMENT = #{amount}"
- end
end
diff --git a/db/migrate/20180403035759_create_project_ci_cd_settings.rb b/db/migrate/20180403035759_create_project_ci_cd_settings.rb
index 00028689779..c630dd3c942 100644
--- a/db/migrate/20180403035759_create_project_ci_cd_settings.rb
+++ b/db/migrate/20180403035759_create_project_ci_cd_settings.rb
@@ -30,12 +30,6 @@ class CreateProjectCiCdSettings < ActiveRecord::Migration[4.2]
end
def add_foreign_key_with_retry
- if Gitlab::Database.mysql?
- # When using MySQL we don't support online upgrades, thus projects can't
- # be deleted while we are running this migration.
- return add_project_id_foreign_key
- end
-
# Between the initial INSERT and the addition of the foreign key some
# projects may have been removed, leaving orphaned rows in our new settings
# table.
diff --git a/db/migrate/20180406204716_add_limits_ci_build_trace_chunks_raw_data_for_mysql.rb b/db/migrate/20180406204716_add_limits_ci_build_trace_chunks_raw_data_for_mysql.rb
deleted file mode 100644
index 0b541e94353..00000000000
--- a/db/migrate/20180406204716_add_limits_ci_build_trace_chunks_raw_data_for_mysql.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# See http://doc.gitlab.com/ce/development/migration_style_guide.html
-# for more information on how to write migrations for GitLab.
-require Rails.root.join('db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql')
-
-class AddLimitsCiBuildTraceChunksRawDataForMysql < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- def up
- LimitsCiBuildTraceChunksRawDataForMysql.new.up
- end
-end
diff --git a/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb b/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb
deleted file mode 100644
index 08ce8cc3094..00000000000
--- a/db/migrate/20180521171529_increase_mysql_text_limit_for_gpg_keys.rb
+++ /dev/null
@@ -1 +0,0 @@
-require_relative 'gpg_keys_limits_to_mysql'
diff --git a/db/migrate/20180824202952_add_outbound_requests_whitelist_to_application_settings.rb b/db/migrate/20180824202952_add_outbound_requests_whitelist_to_application_settings.rb
new file mode 100644
index 00000000000..4ee654ce873
--- /dev/null
+++ b/db/migrate/20180824202952_add_outbound_requests_whitelist_to_application_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddOutboundRequestsWhitelistToApplicationSettings < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :outbound_local_requests_whitelist, :string, array: true, limit: 255
+ end
+end
diff --git a/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb b/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb
deleted file mode 100644
index 80c4d11a38e..00000000000
--- a/db/migrate/20180831164904_fix_prometheus_metric_query_limits.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# 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.
-require Rails.root.join('db/migrate/prometheus_metrics_limits_to_mysql')
-
-class FixPrometheusMetricQueryLimits < ActiveRecord::Migration[4.2]
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
-
- def up
- PrometheusMetricsLimitsToMysql.new.up
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb b/db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb
index 2b5cd45e92c..e21eb291282 100644
--- a/db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb
+++ b/db/migrate/20181030154446_add_missing_indexes_for_foreign_keys.rb
@@ -34,10 +34,6 @@ class AddMissingIndexesForForeignKeys < ActiveRecord::Migration[4.2]
end
def down
- # MySQL requires index for FK,
- # thus removal of indexes does fail
- return if Gitlab::Database.mysql?
-
remove_concurrent_index(:application_settings, :usage_stats_set_by_user_id)
remove_concurrent_index(:ci_pipeline_schedules, :owner_id)
remove_concurrent_index(:ci_trigger_requests, :trigger_id)
diff --git a/db/migrate/20190206193120_add_index_to_tags.rb b/db/migrate/20190206193120_add_index_to_tags.rb
index 5257ebba003..d6c0270cb4f 100644
--- a/db/migrate/20190206193120_add_index_to_tags.rb
+++ b/db/migrate/20190206193120_add_index_to_tags.rb
@@ -9,7 +9,7 @@ class AddIndexToTags < ActiveRecord::Migration[5.0]
disable_ddl_transaction!
def up
- add_concurrent_index :tags, :name, name: INDEX_NAME, using: :gin, opclasses: { name: :gin_trgm_ops }
+ add_concurrent_index :tags, :name, name: INDEX_NAME, using: :gin, opclass: { name: :gin_trgm_ops }
end
def down
diff --git a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
index 0048268ca6f..bf7f7b44dec 100644
--- a/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
+++ b/db/migrate/20190222051615_add_indexes_for_merge_request_diffs_query.rb
@@ -35,7 +35,7 @@ class AddIndexesForMergeRequestDiffsQuery < ActiveRecord::Migration[5.0]
end
def down
- INDEX_SPECS.reverse.each do |spec|
+ INDEX_SPECS.reverse_each do |spec|
remove_concurrent_index(*spec)
end
end
diff --git a/db/migrate/20190611100201_add_geo_container_repository_updated_events_table.rb b/db/migrate/20190611100201_add_geo_container_repository_updated_events_table.rb
new file mode 100644
index 00000000000..8963e837e08
--- /dev/null
+++ b/db/migrate/20190611100201_add_geo_container_repository_updated_events_table.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddGeoContainerRepositoryUpdatedEventsTable < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :geo_container_repository_updated_events, force: :cascade do |t|
+ t.integer :container_repository_id, null: false
+
+ t.index :container_repository_id, name: :idx_geo_con_rep_updated_events_on_container_repository_id, using: :btree
+ end
+
+ add_column :geo_event_log, :container_repository_updated_event_id, :bigint
+ end
+end
diff --git a/db/migrate/20190611100202_add_index_to_geo_event_log.rb b/db/migrate/20190611100202_add_index_to_geo_event_log.rb
new file mode 100644
index 00000000000..c5c855fed61
--- /dev/null
+++ b/db/migrate/20190611100202_add_index_to_geo_event_log.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexToGeoEventLog < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :geo_event_log, :container_repository_updated_event_id
+ end
+
+ def down
+ remove_concurrent_index(:geo_event_log, :container_repository_updated_event_id)
+ end
+end
diff --git a/db/migrate/20190612111201_add_geo_container_repository_counters.rb b/db/migrate/20190612111201_add_geo_container_repository_counters.rb
new file mode 100644
index 00000000000..b73bb885ab7
--- /dev/null
+++ b/db/migrate/20190612111201_add_geo_container_repository_counters.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddGeoContainerRepositoryCounters < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_table :geo_node_statuses do |t|
+ t.column :container_repositories_count, :integer
+ t.column :container_repositories_synced_count, :integer
+ t.column :container_repositories_failed_count, :integer
+ t.column :container_repositories_registry_count, :integer
+ end
+ end
+end
diff --git a/db/migrate/20190612111404_add_geo_container_sync_capacity.rb b/db/migrate/20190612111404_add_geo_container_sync_capacity.rb
new file mode 100644
index 00000000000..d4cd569f460
--- /dev/null
+++ b/db/migrate/20190612111404_add_geo_container_sync_capacity.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddGeoContainerSyncCapacity < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ change_table :geo_nodes do |t|
+ t.column :container_repositories_max_capacity, :integer, default: 10, null: false
+ end
+ end
+end
diff --git a/db/migrate/20190626175626_add_group_creation_level_to_namespaces.rb b/db/migrate/20190626175626_add_group_creation_level_to_namespaces.rb
new file mode 100644
index 00000000000..3b75c92e518
--- /dev/null
+++ b/db/migrate/20190626175626_add_group_creation_level_to_namespaces.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddGroupCreationLevelToNamespaces < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column(:namespaces, :subgroup_creation_level, :integer)
+ change_column_default(:namespaces,
+ :subgroup_creation_level,
+ ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+
+ def down
+ remove_column(:namespaces, :subgroup_creation_level)
+ end
+end
diff --git a/db/migrate/20190627100221_add_mr_productivity_metrics.rb b/db/migrate/20190627100221_add_mr_productivity_metrics.rb
new file mode 100644
index 00000000000..b6f1520cb2d
--- /dev/null
+++ b/db/migrate/20190627100221_add_mr_productivity_metrics.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddMrProductivityMetrics < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_column :merge_request_metrics, :first_comment_at, :datetime_with_timezone
+ add_column :merge_request_metrics, :first_commit_at, :datetime_with_timezone
+ add_column :merge_request_metrics, :last_commit_at, :datetime_with_timezone
+ add_column :merge_request_metrics, :diff_size, :integer
+ add_column :merge_request_metrics, :modified_paths_size, :integer
+ add_column :merge_request_metrics, :commits_count, :integer
+ end
+end
diff --git a/db/migrate/20190627122264_add_foreign_keys_for_container_repository.rb b/db/migrate/20190627122264_add_foreign_keys_for_container_repository.rb
new file mode 100644
index 00000000000..cf7fd03e9af
--- /dev/null
+++ b/db/migrate/20190627122264_add_foreign_keys_for_container_repository.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddForeignKeysForContainerRepository < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key(:geo_container_repository_updated_events, :container_repositories, column: :container_repository_id, on_delete: :cascade)
+
+ add_concurrent_foreign_key(:geo_event_log, :geo_container_repository_updated_events, column: :container_repository_updated_event_id, on_delete: :cascade)
+ end
+
+ def down
+ if foreign_key_exists?(:geo_container_repository_updated_events, :container_repositories)
+ remove_foreign_key(:geo_container_repository_updated_events, :container_repositories)
+ end
+
+ if foreign_key_exists?(:geo_event_log, :geo_container_repository_updated_events)
+ remove_foreign_key(:geo_event_log, :geo_container_repository_updated_events)
+ end
+ end
+end
diff --git a/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb b/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb
new file mode 100644
index 00000000000..022400ce585
--- /dev/null
+++ b/db/migrate/20190703043358_add_commit_id_to_draft_notes.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddCommitIdToDraftNotes < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :draft_notes, :commit_id, :binary
+ end
+end
diff --git a/db/migrate/20190709220014_import_common_metrics_y_axis.rb b/db/migrate/20190709220014_import_common_metrics_y_axis.rb
new file mode 100644
index 00000000000..89ecf32ecc1
--- /dev/null
+++ b/db/migrate/20190709220014_import_common_metrics_y_axis.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ImportCommonMetricsYAxis < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def up
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190709220143_add_index_to_issues_relative_position.rb b/db/migrate/20190709220143_add_index_to_issues_relative_position.rb
new file mode 100644
index 00000000000..effab33ce4f
--- /dev/null
+++ b/db/migrate/20190709220143_add_index_to_issues_relative_position.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddIndexToIssuesRelativePosition < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_issues_on_project_id_and_state_and_rel_position_and_id'.freeze
+
+ def up
+ add_concurrent_index :issues, [:project_id, :state, :relative_position, :id], order: { id: :desc }, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :issues, INDEX_NAME
+ end
+end
diff --git a/db/migrate/20190711124721_create_job_variables.rb b/db/migrate/20190711124721_create_job_variables.rb
new file mode 100644
index 00000000000..a860522f39e
--- /dev/null
+++ b/db/migrate/20190711124721_create_job_variables.rb
@@ -0,0 +1,23 @@
+# 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 CreateJobVariables < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :ci_job_variables do |t|
+ t.string :key, null: false
+ t.text :encrypted_value
+ t.string :encrypted_value_iv
+ 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
+
+ add_index :ci_job_variables, [:key, :job_id], unique: true
+ end
+end
diff --git a/db/migrate/20190712040400_add_environment_id_to_clusters_kubernetes_namespaces.rb b/db/migrate/20190712040400_add_environment_id_to_clusters_kubernetes_namespaces.rb
new file mode 100644
index 00000000000..5ab5a9ba2f8
--- /dev/null
+++ b/db/migrate/20190712040400_add_environment_id_to_clusters_kubernetes_namespaces.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddEnvironmentIdToClustersKubernetesNamespaces < ActiveRecord::Migration[5.1]
+ DOWNTIME = false
+
+ def change
+ add_reference :clusters_kubernetes_namespaces, :environment,
+ index: true, type: :bigint, foreign_key: { on_delete: :nullify }
+ end
+end
diff --git a/db/migrate/20190712040412_index_clusters_kubernetes_namespaces_on_environment_id.rb b/db/migrate/20190712040412_index_clusters_kubernetes_namespaces_on_environment_id.rb
new file mode 100644
index 00000000000..23082492091
--- /dev/null
+++ b/db/migrate/20190712040412_index_clusters_kubernetes_namespaces_on_environment_id.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class IndexClustersKubernetesNamespacesOnEnvironmentId < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'index_kubernetes_namespaces_on_cluster_project_environment_id'
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :clusters_kubernetes_namespaces, [:cluster_id, :project_id, :environment_id], unique: true, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index :clusters_kubernetes_namespaces, name: INDEX_NAME
+ end
+end
diff --git a/db/migrate/20190712064021_add_namespace_per_environment_flag_to_clusters.rb b/db/migrate/20190712064021_add_namespace_per_environment_flag_to_clusters.rb
new file mode 100644
index 00000000000..4c8a0ab3def
--- /dev/null
+++ b/db/migrate/20190712064021_add_namespace_per_environment_flag_to_clusters.rb
@@ -0,0 +1,20 @@
+# 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 AddNamespacePerEnvironmentFlagToClusters < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_column_with_default :clusters, :namespace_per_environment, :boolean, default: false
+ end
+
+ def down
+ remove_column :clusters, :namespace_per_environment
+ end
+end
diff --git a/db/migrate/20190715042813_add_issue_id_to_versions.rb b/db/migrate/20190715042813_add_issue_id_to_versions.rb
new file mode 100644
index 00000000000..1cefdbc9df2
--- /dev/null
+++ b/db/migrate/20190715042813_add_issue_id_to_versions.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddIssueIdToVersions < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ add_reference :design_management_versions, :issue, index: true, foreign_key: { on_delete: :cascade }
+ end
+
+ def down
+ remove_reference :design_management_versions, :issue
+ end
+end
diff --git a/db/migrate/20190715043954_set_issue_id_for_all_versions.rb b/db/migrate/20190715043954_set_issue_id_for_all_versions.rb
new file mode 100644
index 00000000000..345b749f1a4
--- /dev/null
+++ b/db/migrate/20190715043954_set_issue_id_for_all_versions.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class SetIssueIdForAllVersions < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ execute('UPDATE design_management_versions as versions SET issue_id = (
+ SELECT design_management_designs.issue_id
+ FROM design_management_designs
+ INNER JOIN design_management_designs_versions ON design_management_designs.id = design_management_designs_versions.design_id
+ WHERE design_management_designs_versions.version_id = versions.id
+ LIMIT 1
+ )')
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb b/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb
new file mode 100644
index 00000000000..81a8b0a3271
--- /dev/null
+++ b/db/migrate/20190715140740_add_event_type_to_design_management_designs_versions.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+# This migration sets up a event enum on the DesignsVersions join table
+class AddEventTypeToDesignManagementDesignsVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ # We disable these cops here because adding this column is safe. The table does not
+ # have any data in it.
+ # rubocop: disable Migration/AddIndex
+ # rubocop: disable Migration/AddColumn
+ def up
+ add_column(:design_management_designs_versions, :event, :integer,
+ limit: 2,
+ null: false,
+ default: 0)
+ add_index(:design_management_designs_versions, :event)
+ end
+
+ # rubocop: disable Migration/RemoveIndex
+ def down
+ remove_index(:design_management_designs_versions, :event)
+ remove_column(:design_management_designs_versions, :event)
+ end
+end
diff --git a/db/migrate/20190715142138_add_raw_blob_request_limit_to_application_settings.rb b/db/migrate/20190715142138_add_raw_blob_request_limit_to_application_settings.rb
new file mode 100644
index 00000000000..e8198e11ea7
--- /dev/null
+++ b/db/migrate/20190715142138_add_raw_blob_request_limit_to_application_settings.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddRawBlobRequestLimitToApplicationSettings < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :raw_blob_request_limit, :integer, default: 300, null: false
+ end
+end
diff --git a/db/migrate/20190715173819_add_object_storage_flag_to_geo_node.rb b/db/migrate/20190715173819_add_object_storage_flag_to_geo_node.rb
new file mode 100644
index 00000000000..2d3243f3357
--- /dev/null
+++ b/db/migrate/20190715173819_add_object_storage_flag_to_geo_node.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 AddObjectStorageFlagToGeoNode < 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
+ add_column_with_default :geo_nodes, :sync_object_storage, :boolean, default: false
+ end
+
+ def down
+ remove_column :geo_nodes, :sync_object_storage
+ end
+end
diff --git a/db/migrate/20190715215532_add_project_emails_disabled.rb b/db/migrate/20190715215532_add_project_emails_disabled.rb
new file mode 100644
index 00000000000..536ea34c0fb
--- /dev/null
+++ b/db/migrate/20190715215532_add_project_emails_disabled.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectEmailsDisabled < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :emails_disabled, :boolean
+ end
+end
diff --git a/db/migrate/20190715215549_add_group_emails_disabled.rb b/db/migrate/20190715215549_add_group_emails_disabled.rb
new file mode 100644
index 00000000000..d3fd4d2d923
--- /dev/null
+++ b/db/migrate/20190715215549_add_group_emails_disabled.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddGroupEmailsDisabled < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column :namespaces, :emails_disabled, :boolean
+ end
+end
diff --git a/db/migrate/20190716144222_create_analytics_cycle_analytics_project_stages.rb b/db/migrate/20190716144222_create_analytics_cycle_analytics_project_stages.rb
new file mode 100644
index 00000000000..5c005377b00
--- /dev/null
+++ b/db/migrate/20190716144222_create_analytics_cycle_analytics_project_stages.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class CreateAnalyticsCycleAnalyticsProjectStages < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ INDEX_PREFIX = 'index_analytics_ca_project_stages_'
+
+ def change
+ create_table :analytics_cycle_analytics_project_stages do |t|
+ t.timestamps_with_timezone
+ t.integer :relative_position
+ t.integer :start_event_identifier, null: false
+ t.integer :end_event_identifier, null: false
+ t.references(:project, {
+ null: false,
+ foreign_key: { to_table: :projects, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_project_id' }
+ })
+ t.references(:start_event_label, {
+ foreign_key: { to_table: :labels, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_start_event_label_id' }
+ })
+ t.references(:end_event_label, {
+ foreign_key: { to_table: :labels, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_end_event_label_id' }
+ })
+ t.boolean :hidden, default: false, null: false
+ t.boolean :custom, default: true, null: false
+ t.string :name, null: false, limit: 255
+ end
+
+ add_index :analytics_cycle_analytics_project_stages, [:project_id, :name], unique: true, name: INDEX_PREFIX + 'on_project_id_and_name'
+ add_index :analytics_cycle_analytics_project_stages, [:relative_position], name: INDEX_PREFIX + 'on_relative_position'
+ end
+end
diff --git a/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb b/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb
new file mode 100644
index 00000000000..c6c5b56ed8b
--- /dev/null
+++ b/db/migrate/20190723153247_create_allowed_email_domains_for_groups.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateAllowedEmailDomainsForGroups < ActiveRecord::Migration[5.2]
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :allowed_email_domains do |t|
+ t.timestamps_with_timezone null: false
+ t.references :group, references: :namespace,
+ column: :group_id,
+ type: :integer,
+ null: false,
+ index: true
+ t.foreign_key :namespaces, column: :group_id, on_delete: :cascade
+ t.string :domain, null: false, limit: 255
+ end
+ end
+end
diff --git a/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb b/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb
new file mode 100644
index 00000000000..ce249a527e6
--- /dev/null
+++ b/db/migrate/20190724112147_add_column_for_self_monitoring_project_id.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+class AddColumnForSelfMonitoringProjectId < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_reference(
+ :application_settings,
+ :instance_administration_project,
+ index: { name: 'index_applicationsettings_on_instance_administration_project_id' },
+ foreign_key: { to_table: :projects, on_delete: :nullify }
+ )
+ end
+end
diff --git a/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb b/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb
new file mode 100644
index 00000000000..21b00e0b7d9
--- /dev/null
+++ b/db/migrate/20190725012225_change_outbound_local_requests_whitelist_default.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class ChangeOutboundLocalRequestsWhitelistDefault < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ class ApplicationSetting < ActiveRecord::Base
+ self.table_name = 'application_settings'
+ end
+
+ def up
+ default_value = []
+
+ change_column_default(:application_settings, :outbound_local_requests_whitelist, default_value)
+
+ ApplicationSetting
+ .where(outbound_local_requests_whitelist: nil)
+ .update(outbound_local_requests_whitelist: default_value)
+
+ change_column_null(:application_settings, :outbound_local_requests_whitelist, false)
+ end
+
+ def down
+ change_column_null(:application_settings, :outbound_local_requests_whitelist, true)
+
+ change_column_default(:application_settings, :outbound_local_requests_whitelist, nil)
+ end
+end
diff --git a/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb b/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb
new file mode 100644
index 00000000000..ac65e8d745c
--- /dev/null
+++ b/db/migrate/20190726101050_rename_allow_local_requests_from_hooks_and_services_application_setting.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameAllowLocalRequestsFromHooksAndServicesApplicationSetting < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :application_settings, :allow_local_requests_from_hooks_and_services, :allow_local_requests_from_web_hooks_and_services
+ end
+
+ def down
+ cleanup_concurrent_column_rename :application_settings, :allow_local_requests_from_web_hooks_and_services, :allow_local_requests_from_hooks_and_services
+ end
+end
diff --git a/db/migrate/20190726101133_add_allow_local_requests_from_system_hooks_to_application_settings.rb b/db/migrate/20190726101133_add_allow_local_requests_from_system_hooks_to_application_settings.rb
new file mode 100644
index 00000000000..95d4f956f93
--- /dev/null
+++ b/db/migrate/20190726101133_add_allow_local_requests_from_system_hooks_to_application_settings.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+class AddAllowLocalRequestsFromSystemHooksToApplicationSettings < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ add_column(:application_settings, :allow_local_requests_from_system_hooks,
+ :boolean,
+ default: true,
+ null: false)
+ end
+
+ def down
+ remove_column(:application_settings, :allow_local_requests_from_system_hooks)
+ end
+end
diff --git a/db/migrate/20190729062536_create_analytics_cycle_analytics_group_stages.rb b/db/migrate/20190729062536_create_analytics_cycle_analytics_group_stages.rb
new file mode 100644
index 00000000000..5b327dc5332
--- /dev/null
+++ b/db/migrate/20190729062536_create_analytics_cycle_analytics_group_stages.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+class CreateAnalyticsCycleAnalyticsGroupStages < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ INDEX_PREFIX = 'index_analytics_ca_group_stages_'
+
+ def change
+ create_table :analytics_cycle_analytics_group_stages do |t|
+ t.timestamps_with_timezone
+ t.integer :relative_position
+ t.integer :start_event_identifier, null: false
+ t.integer :end_event_identifier, null: false
+ t.references(:group, {
+ null: false,
+ foreign_key: { to_table: :namespaces, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_group_id' }
+ })
+ t.references(:start_event_label, {
+ foreign_key: { to_table: :labels, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_start_event_label_id' }
+ })
+ t.references(:end_event_label, {
+ foreign_key: { to_table: :labels, on_delete: :cascade },
+ index: { name: INDEX_PREFIX + 'on_end_event_label_id' }
+ })
+ t.boolean :hidden, default: false, null: false
+ t.boolean :custom, default: true, null: false
+ t.string :name, null: false, limit: 255
+ end
+
+ add_index :analytics_cycle_analytics_group_stages, [:group_id, :name], unique: true, name: INDEX_PREFIX + 'on_group_id_and_name'
+ add_index :analytics_cycle_analytics_group_stages, [:relative_position], name: INDEX_PREFIX + 'on_relative_position'
+ end
+end
diff --git a/db/migrate/20190729090456_add_index_on_environments_with_state.rb b/db/migrate/20190729090456_add_index_on_environments_with_state.rb
new file mode 100644
index 00000000000..9a8d8391415
--- /dev/null
+++ b/db/migrate/20190729090456_add_index_on_environments_with_state.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddIndexOnEnvironmentsWithState < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :environments, [:project_id, :state]
+ end
+
+ def down
+ remove_concurrent_index :environments, [:project_id, :state]
+ end
+end
diff --git a/db/migrate/20190731084415_add_build_need.rb b/db/migrate/20190731084415_add_build_need.rb
new file mode 100644
index 00000000000..45b8abb480d
--- /dev/null
+++ b/db/migrate/20190731084415_add_build_need.rb
@@ -0,0 +1,20 @@
+# 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 AddBuildNeed < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ create_table :ci_build_needs, id: :serial do |t|
+ t.integer :build_id, null: false
+ t.text :name, null: false
+
+ t.index [:build_id, :name], unique: true
+ t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade
+ end
+ end
+end
diff --git a/db/migrate/20190801060809_delete_kubernetes_services.rb b/db/migrate/20190801060809_delete_kubernetes_services.rb
new file mode 100644
index 00000000000..018976584d4
--- /dev/null
+++ b/db/migrate/20190801060809_delete_kubernetes_services.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class DeleteKubernetesServices < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def up
+ Service.where(type: "KubernetesService").delete_all
+ end
+
+ def down
+ # no-op
+ end
+end
diff --git a/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb b/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb
new file mode 100644
index 00000000000..a4bd5841a46
--- /dev/null
+++ b/db/migrate/20190801193427_rename_application_settings_snowplow_collector_uri_column.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RenameApplicationSettingsSnowplowCollectorUriColumn < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ rename_column_concurrently :application_settings, :snowplow_collector_uri, :snowplow_collector_hostname
+ end
+
+ def down
+ cleanup_concurrent_column_rename :application_settings, :snowplow_collector_hostname, :snowplow_collector_uri
+ end
+end
diff --git a/db/migrate/20190802012622_reorder_issues_project_id_relative_position_index.rb b/db/migrate/20190802012622_reorder_issues_project_id_relative_position_index.rb
new file mode 100644
index 00000000000..12088dd763f
--- /dev/null
+++ b/db/migrate/20190802012622_reorder_issues_project_id_relative_position_index.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+class ReorderIssuesProjectIdRelativePositionIndex < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ OLD_INDEX_NAME = 'index_issues_on_project_id_and_state_and_rel_position_and_id'
+ NEW_INDEX_NAME = 'index_issues_on_project_id_and_rel_position_and_state_and_id'
+
+ def up
+ add_concurrent_index :issues, [:project_id, :relative_position, :state, :id], order: { id: :desc }, name: NEW_INDEX_NAME
+
+ remove_concurrent_index_by_name :issues, OLD_INDEX_NAME
+ end
+
+ def down
+ add_concurrent_index :issues, [:project_id, :state, :relative_position, :id], order: { id: :desc }, name: OLD_INDEX_NAME
+
+ remove_concurrent_index_by_name :issues, NEW_INDEX_NAME
+ end
+end
diff --git a/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb b/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb
new file mode 100644
index 00000000000..b95d9037afe
--- /dev/null
+++ b/db/migrate/20190802195602_add_timestamps_columns_to_geo_nodes.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class AddTimestampsColumnsToGeoNodes < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ add_column(:geo_nodes, :created_at, :datetime_with_timezone, null: true)
+ add_column(:geo_nodes, :updated_at, :datetime_with_timezone, null: true)
+ end
+end
diff --git a/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb
new file mode 100644
index 00000000000..f6db90f6637
--- /dev/null
+++ b/db/migrate/20190806071559_remove_epic_issues_default_relative_position.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class RemoveEpicIssuesDefaultRelativePosition < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ change_column_null :epic_issues, :relative_position, true
+ change_column_default :epic_issues, :relative_position, from: 1073741823, to: nil
+ 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/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb b/db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb
new file mode 100644
index 00000000000..cbbece35901
--- /dev/null
+++ b/db/migrate/20190815093936_add_index_notes_on_project_id_and_id_and_system_false.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+class AddIndexNotesOnProjectIdAndIdAndSystemFalse < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index(*index_arguments)
+ end
+
+ def down
+ remove_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :notes,
+ [:project_id, :id],
+ {
+ name: 'index_notes_on_project_id_and_id_and_system_false',
+ where: 'NOT system'
+ }
+ ]
+ end
+end
diff --git a/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb b/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb
new file mode 100644
index 00000000000..158c88e6258
--- /dev/null
+++ b/db/migrate/20190815093949_remove_index_notes_on_noteable_type.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class RemoveIndexNotesOnNoteableType < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index(*index_arguments)
+ end
+
+ def down
+ add_concurrent_index(*index_arguments)
+ end
+
+ private
+
+ def index_arguments
+ [
+ :notes,
+ [:noteable_type],
+ {
+ name: 'index_notes_on_noteable_type'
+ }
+ ]
+ end
+end
diff --git a/db/migrate/gpg_keys_limits_to_mysql.rb b/db/migrate/gpg_keys_limits_to_mysql.rb
deleted file mode 100644
index 2cd347a0463..00000000000
--- a/db/migrate/gpg_keys_limits_to_mysql.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class IncreaseMysqlTextLimitForGpgKeys < ActiveRecord::Migration[4.2]
- # Set this constant to true if this migration requires downtime.
- DOWNTIME = false
-
- def up
- return unless Gitlab::Database.mysql?
-
- change_column :gpg_keys, :key, :text, limit: 16.megabytes - 1
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql.rb b/db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql.rb
deleted file mode 100644
index 92402cf387b..00000000000
--- a/db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-class LimitsCiBuildTraceChunksRawDataForMysql < ActiveRecord::Migration[4.2]
- def up
- return unless Gitlab::Database.mysql?
-
- # Mysql needs MEDIUMTEXT type (up to 16MB) rather than TEXT (up to 64KB)
- # Because 'raw_data' is always capped by Ci::BuildTraceChunk::CHUNK_SIZE, which is 128KB
- change_column :ci_build_trace_chunks, :raw_data, :binary, limit: 16.megabytes - 1 # MEDIUMTEXT
- end
-end
diff --git a/db/migrate/limits_to_mysql.rb b/db/migrate/limits_to_mysql.rb
deleted file mode 100644
index 33cb19aff9e..00000000000
--- a/db/migrate/limits_to_mysql.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-class LimitsToMysql < ActiveRecord::Migration[4.2]
- def up
- return unless ActiveRecord::Base.configurations[Rails.env]['adapter'] =~ /^mysql/
-
- change_column :snippets, :content, :text, limit: 2147483647
- change_column :notes, :st_diff, :text, limit: 2147483647
- end
-end
diff --git a/db/migrate/markdown_cache_limits_to_mysql.rb b/db/migrate/markdown_cache_limits_to_mysql.rb
deleted file mode 100644
index f99d500a137..00000000000
--- a/db/migrate/markdown_cache_limits_to_mysql.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class MarkdownCacheLimitsToMysql < ActiveRecord::Migration[4.2]
- DOWNTIME = false
-
- def up
- return unless Gitlab::Database.mysql?
-
- change_column :snippets, :content_html, :text, limit: 2147483647
- end
-
- def down
- # no-op
- end
-end
diff --git a/db/migrate/merge_request_diff_file_limits_to_mysql.rb b/db/migrate/merge_request_diff_file_limits_to_mysql.rb
deleted file mode 100644
index 65dd0b5b7f7..00000000000
--- a/db/migrate/merge_request_diff_file_limits_to_mysql.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class MergeRequestDiffFileLimitsToMysql < ActiveRecord::Migration[4.2]
- DOWNTIME = false
-
- def up
- return unless Gitlab::Database.mysql?
-
- change_column :merge_request_diff_files, :diff, :text, limit: 2147483647, default: nil
- end
-
- def down
- end
-end
diff --git a/db/migrate/prometheus_metrics_limits_to_mysql.rb b/db/migrate/prometheus_metrics_limits_to_mysql.rb
deleted file mode 100644
index f7a2fcba8c2..00000000000
--- a/db/migrate/prometheus_metrics_limits_to_mysql.rb
+++ /dev/null
@@ -1,12 +0,0 @@
-class PrometheusMetricsLimitsToMysql < ActiveRecord::Migration[4.2]
- DOWNTIME = false
-
- def up
- return unless Gitlab::Database.mysql?
-
- change_column :prometheus_metrics, :query, :text, limit: 4096, default: nil
- end
-
- def down
- end
-end
diff --git a/db/post_migrate/20180409170809_populate_missing_project_ci_cd_settings.rb b/db/post_migrate/20180409170809_populate_missing_project_ci_cd_settings.rb
index 0cda3d76a3d..a400a071e07 100644
--- a/db/post_migrate/20180409170809_populate_missing_project_ci_cd_settings.rb
+++ b/db/post_migrate/20180409170809_populate_missing_project_ci_cd_settings.rb
@@ -9,10 +9,6 @@ class PopulateMissingProjectCiCdSettings < ActiveRecord::Migration[4.2]
disable_ddl_transaction!
def up
- # MySQL does not support online upgrades, thus there can't be any missing
- # rows.
- return if Gitlab::Database.mysql?
-
# Projects created after the initial migration but before the code started
# using ProjectCiCdSetting won't have a corresponding row in
# project_ci_cd_settings, so let's fix that.
diff --git a/db/post_migrate/20181219130552_update_project_import_visibility_level.rb b/db/post_migrate/20181219130552_update_project_import_visibility_level.rb
index 6209de88b31..bfa452578a3 100644
--- a/db/post_migrate/20181219130552_update_project_import_visibility_level.rb
+++ b/db/post_migrate/20181219130552_update_project_import_visibility_level.rb
@@ -49,7 +49,7 @@ class UpdateProjectImportVisibilityLevel < ActiveRecord::Migration[5.0]
def update_projects_visibility(visibility)
say_with_time("Updating project visibility to #{visibility} on #{Project::IMPORT_TYPE} imports.") do
Project.with_group_visibility(visibility).select(:id).each_batch(of: BATCH_SIZE) do |batch, _index|
- batch_sql = Gitlab::Database.mysql? ? batch.pluck(:id).join(', ') : batch.select(:id).to_sql
+ batch_sql = batch.select(:id).to_sql
say("Updating #{batch.size} items.", true)
diff --git a/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb
index 392e64eeade..036b0b64b48 100644
--- a/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb
+++ b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb
@@ -16,26 +16,6 @@ class MigrateAutoDevOpsDomainToClusterDomain < ActiveRecord::Migration[5.0]
private
def update_clusters_domain_query
- if Gitlab::Database.mysql?
- mysql_query
- else
- postgresql_query
- end
- end
-
- def mysql_query
- <<~HEREDOC
- UPDATE clusters, project_auto_devops, cluster_projects
- SET
- clusters.domain = project_auto_devops.domain
- WHERE
- cluster_projects.cluster_id = clusters.id
- AND project_auto_devops.project_id = cluster_projects.project_id
- AND project_auto_devops.domain != ''
- HEREDOC
- end
-
- def postgresql_query
<<~HEREDOC
UPDATE clusters
SET domain = project_auto_devops.domain
diff --git a/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb b/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb
index 447f91ebc7e..dd85ebc8001 100644
--- a/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb
+++ b/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb
@@ -21,34 +21,18 @@ class AddUniqueConstraintToApprovalsUserIdAndMergeRequestId < ActiveRecord::Migr
def remove_duplicates
add_concurrent_index :approvals, [:user_id, :merge_request_id, :id]
- if Gitlab::Database.mysql?
- execute <<-SQL
- DELETE FROM a
- USING approvals AS a
- INNER JOIN (
- SELECT user_id, merge_request_id, MIN(id) as min_id
- FROM approvals
- GROUP BY user_id, merge_request_id
- HAVING COUNT(id) > 1
- ) as approvals_with_duplicates
- ON approvals_with_duplicates.user_id = a.user_id
- AND approvals_with_duplicates.merge_request_id = a.merge_request_id
- WHERE approvals_with_duplicates.min_id <> a.id;
- SQL
- else
- execute <<-SQL
- DELETE FROM approvals
- USING (
- SELECT user_id, merge_request_id, MIN(id) as min_id
- FROM approvals
- GROUP BY user_id, merge_request_id
- HAVING COUNT(id) > 1
- ) as approvals_with_duplicates
- WHERE approvals_with_duplicates.user_id = approvals.user_id
- AND approvals_with_duplicates.merge_request_id = approvals.merge_request_id
- AND approvals_with_duplicates.min_id <> approvals.id;
- SQL
- end
+ execute <<-SQL
+ DELETE FROM approvals
+ USING (
+ SELECT user_id, merge_request_id, MIN(id) as min_id
+ FROM approvals
+ GROUP BY user_id, merge_request_id
+ HAVING COUNT(id) > 1
+ ) as approvals_with_duplicates
+ WHERE approvals_with_duplicates.user_id = approvals.user_id
+ AND approvals_with_duplicates.merge_request_id = approvals.merge_request_id
+ AND approvals_with_duplicates.min_id <> approvals.id;
+ SQL
remove_concurrent_index :approvals, [:user_id, :merge_request_id, :id]
end
diff --git a/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb b/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb
new file mode 100644
index 00000000000..b23abb80dda
--- /dev/null
+++ b/db/post_migrate/20190715043944_remove_sha_index_from_versions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class RemoveShaIndexFromVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ remove_concurrent_index :design_management_versions, :sha
+ end
+
+ def down
+ add_concurrent_index :design_management_versions, :sha, unique: true, using: :btree
+ end
+end
diff --git a/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb b/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb
new file mode 100644
index 00000000000..27b0c9648f9
--- /dev/null
+++ b/db/post_migrate/20190715044501_add_unique_issue_id_sha_index_to_versions.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddUniqueIssueIdShaIndexToVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :design_management_versions, [:sha, :issue_id], unique: true, using: :btree
+ end
+
+ def down
+ remove_concurrent_index :design_management_versions, [:sha, :issue_id]
+ end
+end
diff --git a/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb b/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb
new file mode 100644
index 00000000000..36ecca4821f
--- /dev/null
+++ b/db/post_migrate/20190723105753_add_index_on_identities_lower_extern_uid_and_provider.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexOnIdentitiesLowerExternUidAndProvider < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = "index_on_identities_lower_extern_uid_and_provider"
+
+ def up
+ add_concurrent_index(:identities, 'lower(extern_uid), provider', name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:identities, INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb b/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb
new file mode 100644
index 00000000000..127e44254ac
--- /dev/null
+++ b/db/post_migrate/20190801114109_cleanup_allow_local_requests_from_hooks_and_services_application_setting_rename.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CleanupAllowLocalRequestsFromHooksAndServicesApplicationSettingRename < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :application_settings, :allow_local_requests_from_hooks_and_services, :allow_local_requests_from_web_hooks_and_services
+ end
+
+ def down
+ rename_column_concurrently :application_settings, :allow_local_requests_from_web_hooks_and_services, :allow_local_requests_from_hooks_and_services
+ end
+end
diff --git a/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb b/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb
new file mode 100644
index 00000000000..768ece925c6
--- /dev/null
+++ b/db/post_migrate/20190802091750_cleanup_application_settings_snowplow_collector_uri_rename.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class CleanupApplicationSettingsSnowplowCollectorUriRename < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ cleanup_concurrent_column_rename :application_settings, :snowplow_collector_uri, :snowplow_collector_hostname
+ end
+
+ def down
+ rename_column_concurrently :application_settings, :snowplow_collector_hostname, :snowplow_collector_uri
+ end
+end
diff --git a/db/post_migrate/20190802235445_add_index_on_id_and_type_and_public_to_keys.rb b/db/post_migrate/20190802235445_add_index_on_id_and_type_and_public_to_keys.rb
new file mode 100644
index 00000000000..9b4d74b4bea
--- /dev/null
+++ b/db/post_migrate/20190802235445_add_index_on_id_and_type_and_public_to_keys.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class AddIndexOnIdAndTypeAndPublicToKeys < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ INDEX_NAME = "index_on_deploy_keys_id_and_type_and_public"
+
+ def up
+ add_concurrent_index(:keys,
+ [:id, :type],
+ where: "public = 't'",
+ unique: true,
+ name: INDEX_NAME)
+ end
+
+ def down
+ remove_concurrent_index_by_name(:keys, INDEX_NAME)
+ end
+end
diff --git a/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb b/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb
new file mode 100644
index 00000000000..063c1e16c27
--- /dev/null
+++ b/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class MigratePrivateProfileNulls < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DELAY = 5.minutes.to_i
+ BATCH_SIZE = 1_000
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+
+ include ::EachBatch
+ end
+
+ def up
+ # Migration will take about 7 hours
+ User.where(private_profile: nil).each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck(Arel.sql("MIN(id)"), Arel.sql("MAX(id)")).first
+ delay = index * DELAY
+
+ BackgroundMigrationWorker.perform_in(delay.seconds, 'MigrateNullPrivateProfileToFalse', [*range])
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a5079d3a5bc..3f7917654cf 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_07_15_114644) do
+ActiveRecord::Schema.define(version: 2019_08_15_093949) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -26,6 +26,52 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "cached_markdown_version"
end
+ create_table "allowed_email_domains", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "group_id", null: false
+ t.string "domain", limit: 255, null: false
+ t.index ["group_id"], name: "index_allowed_email_domains_on_group_id"
+ end
+
+ create_table "analytics_cycle_analytics_group_stages", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "relative_position"
+ t.integer "start_event_identifier", null: false
+ t.integer "end_event_identifier", null: false
+ t.bigint "group_id", null: false
+ t.bigint "start_event_label_id"
+ t.bigint "end_event_label_id"
+ t.boolean "hidden", default: false, null: false
+ t.boolean "custom", default: true, null: false
+ t.string "name", limit: 255, null: false
+ t.index ["end_event_label_id"], name: "index_analytics_ca_group_stages_on_end_event_label_id"
+ t.index ["group_id", "name"], name: "index_analytics_ca_group_stages_on_group_id_and_name", unique: true
+ t.index ["group_id"], name: "index_analytics_ca_group_stages_on_group_id"
+ t.index ["relative_position"], name: "index_analytics_ca_group_stages_on_relative_position"
+ t.index ["start_event_label_id"], name: "index_analytics_ca_group_stages_on_start_event_label_id"
+ end
+
+ create_table "analytics_cycle_analytics_project_stages", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "relative_position"
+ t.integer "start_event_identifier", null: false
+ t.integer "end_event_identifier", null: false
+ t.bigint "project_id", null: false
+ t.bigint "start_event_label_id"
+ t.bigint "end_event_label_id"
+ t.boolean "hidden", default: false, null: false
+ t.boolean "custom", default: true, null: false
+ t.string "name", limit: 255, null: false
+ t.index ["end_event_label_id"], name: "index_analytics_ca_project_stages_on_end_event_label_id"
+ t.index ["project_id", "name"], name: "index_analytics_ca_project_stages_on_project_id_and_name", unique: true
+ t.index ["project_id"], name: "index_analytics_ca_project_stages_on_project_id"
+ t.index ["relative_position"], name: "index_analytics_ca_project_stages_on_relative_position"
+ t.index ["start_event_label_id"], name: "index_analytics_ca_project_stages_on_start_event_label_id"
+ end
+
create_table "appearances", id: :serial, force: :cascade do |t|
t.string "title", null: false
t.text "description", null: false
@@ -183,7 +229,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "external_authorization_service_default_label"
t.boolean "pages_domain_verification_enabled", default: true, null: false
t.string "user_default_internal_regex"
- t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false
t.float "external_authorization_service_timeout", default: 0.5
t.text "external_auth_client_cert"
t.text "encrypted_external_auth_client_key"
@@ -196,7 +241,6 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "pseudonymizer_enabled", default: false, null: false
t.boolean "hide_third_party_offers", default: false, null: false
t.boolean "snowplow_enabled", default: false, null: false
- t.string "snowplow_collector_uri"
t.string "snowplow_site_id"
t.string "snowplow_cookie_domain"
t.boolean "instance_statistics_visibility_private", default: false, null: false
@@ -228,16 +272,23 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "lock_memberships_to_ldap", default: false, null: false
t.boolean "time_tracking_limit_to_hours", default: false, null: false
t.string "grafana_url", default: "/-/grafana", null: false
- t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id", using: :btree
- t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id", using: :btree
- t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
+ t.string "outbound_local_requests_whitelist", limit: 255, default: [], null: false, array: true
+ t.integer "raw_blob_request_limit", default: 300, null: false
+ t.boolean "allow_local_requests_from_web_hooks_and_services", default: false, null: false
+ t.boolean "allow_local_requests_from_system_hooks", default: true, null: false
+ t.bigint "instance_administration_project_id"
+ t.string "snowplow_collector_hostname"
+ t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
+ t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
+ t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"
+ t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id"
end
create_table "approval_merge_request_rule_sources", force: :cascade do |t|
t.bigint "approval_merge_request_rule_id", null: false
t.bigint "approval_project_rule_id", null: false
- t.index ["approval_merge_request_rule_id"], name: "index_approval_merge_request_rule_sources_1", unique: true, using: :btree
- t.index ["approval_project_rule_id"], name: "index_approval_merge_request_rule_sources_2", using: :btree
+ t.index ["approval_merge_request_rule_id"], name: "index_approval_merge_request_rule_sources_1", unique: true
+ t.index ["approval_project_rule_id"], name: "index_approval_merge_request_rule_sources_2"
end
create_table "approval_merge_request_rules", force: :cascade do |t|
@@ -249,31 +300,31 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.integer "rule_type", limit: 2, default: 1, null: false
t.integer "report_type", limit: 2
- t.index ["merge_request_id", "code_owner", "name"], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)", using: :btree
- t.index ["merge_request_id", "code_owner"], name: "index_approval_merge_request_rules_1", using: :btree
- t.index ["merge_request_id", "rule_type", "name"], name: "index_approval_rule_name_for_code_owners_rule_type", unique: true, where: "(rule_type = 2)", using: :btree
- t.index ["merge_request_id", "rule_type"], name: "index_approval_rules_code_owners_rule_type", where: "(rule_type = 2)", using: :btree
+ t.index ["merge_request_id", "code_owner", "name"], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)"
+ t.index ["merge_request_id", "code_owner"], name: "index_approval_merge_request_rules_1"
+ t.index ["merge_request_id", "rule_type", "name"], name: "index_approval_rule_name_for_code_owners_rule_type", unique: true, where: "(rule_type = 2)"
+ t.index ["merge_request_id", "rule_type"], name: "index_approval_rules_code_owners_rule_type", where: "(rule_type = 2)"
end
create_table "approval_merge_request_rules_approved_approvers", force: :cascade do |t|
t.bigint "approval_merge_request_rule_id", null: false
t.integer "user_id", null: false
- t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_approved_approvers_1", unique: true, using: :btree
- t.index ["user_id"], name: "index_approval_merge_request_rules_approved_approvers_2", using: :btree
+ t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_approved_approvers_1", unique: true
+ t.index ["user_id"], name: "index_approval_merge_request_rules_approved_approvers_2"
end
create_table "approval_merge_request_rules_groups", force: :cascade do |t|
t.bigint "approval_merge_request_rule_id", null: false
t.integer "group_id", null: false
- t.index ["approval_merge_request_rule_id", "group_id"], name: "index_approval_merge_request_rules_groups_1", unique: true, using: :btree
- t.index ["group_id"], name: "index_approval_merge_request_rules_groups_2", using: :btree
+ t.index ["approval_merge_request_rule_id", "group_id"], name: "index_approval_merge_request_rules_groups_1", unique: true
+ t.index ["group_id"], name: "index_approval_merge_request_rules_groups_2"
end
create_table "approval_merge_request_rules_users", force: :cascade do |t|
t.bigint "approval_merge_request_rule_id", null: false
t.integer "user_id", null: false
- t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_users_1", unique: true, using: :btree
- t.index ["user_id"], name: "index_approval_merge_request_rules_users_2", using: :btree
+ t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_users_1", unique: true
+ t.index ["user_id"], name: "index_approval_merge_request_rules_users_2"
end
create_table "approval_project_rules", force: :cascade do |t|
@@ -283,22 +334,22 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "approvals_required", limit: 2, default: 0, null: false
t.string "name", null: false
t.integer "rule_type", limit: 2, default: 0, null: false
- t.index ["project_id"], name: "index_approval_project_rules_on_project_id", using: :btree
- t.index ["rule_type"], name: "index_approval_project_rules_on_rule_type", using: :btree
+ t.index ["project_id"], name: "index_approval_project_rules_on_project_id"
+ t.index ["rule_type"], name: "index_approval_project_rules_on_rule_type"
end
create_table "approval_project_rules_groups", force: :cascade do |t|
t.bigint "approval_project_rule_id", null: false
t.integer "group_id", null: false
- t.index ["approval_project_rule_id", "group_id"], name: "index_approval_project_rules_groups_1", unique: true, using: :btree
- t.index ["group_id"], name: "index_approval_project_rules_groups_2", using: :btree
+ t.index ["approval_project_rule_id", "group_id"], name: "index_approval_project_rules_groups_1", unique: true
+ t.index ["group_id"], name: "index_approval_project_rules_groups_2"
end
create_table "approval_project_rules_users", force: :cascade do |t|
t.bigint "approval_project_rule_id", null: false
t.integer "user_id", null: false
- t.index ["approval_project_rule_id", "user_id"], name: "index_approval_project_rules_users_1", unique: true, using: :btree
- t.index ["user_id"], name: "index_approval_project_rules_users_2", using: :btree
+ t.index ["approval_project_rule_id", "user_id"], name: "index_approval_project_rules_users_1", unique: true
+ t.index ["user_id"], name: "index_approval_project_rules_users_2"
end
create_table "approvals", id: :serial, force: :cascade do |t|
@@ -306,8 +357,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["merge_request_id"], name: "index_approvals_on_merge_request_id", using: :btree
- t.index ["user_id", "merge_request_id"], name: "index_approvals_on_user_id_and_merge_request_id", unique: true, using: :btree
+ t.index ["merge_request_id"], name: "index_approvals_on_merge_request_id"
+ t.index ["user_id", "merge_request_id"], name: "index_approvals_on_user_id_and_merge_request_id", unique: true
end
create_table "approver_groups", id: :serial, force: :cascade do |t|
@@ -316,8 +367,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "group_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["group_id"], name: "index_approver_groups_on_group_id", using: :btree
- t.index ["target_id", "target_type"], name: "index_approver_groups_on_target_id_and_target_type", using: :btree
+ t.index ["group_id"], name: "index_approver_groups_on_group_id"
+ t.index ["target_id", "target_type"], name: "index_approver_groups_on_target_id_and_target_type"
end
create_table "approvers", id: :serial, force: :cascade do |t|
@@ -326,8 +377,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type", using: :btree
- t.index ["user_id"], name: "index_approvers_on_user_id", using: :btree
+ t.index ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type"
+ t.index ["user_id"], name: "index_approvers_on_user_id"
end
create_table "audit_events", id: :serial, force: :cascade do |t|
@@ -338,8 +389,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "details"
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["created_at", "author_id"], name: "analytics_index_audit_events_on_created_at_and_author_id", using: :btree
- t.index ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
+ t.index ["created_at", "author_id"], name: "analytics_index_audit_events_on_created_at_and_author_id"
+ t.index ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type"
end
create_table "award_emoji", id: :serial, force: :cascade do |t|
@@ -349,8 +400,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "awardable_type"
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id", using: :btree
- t.index ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name", using: :btree
+ t.index ["awardable_type", "awardable_id"], name: "index_award_emoji_on_awardable_type_and_awardable_id"
+ t.index ["user_id", "name"], name: "index_award_emoji_on_user_id_and_name"
end
create_table "badges", id: :serial, force: :cascade do |t|
@@ -361,15 +412,15 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "type", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["group_id"], name: "index_badges_on_group_id", using: :btree
- t.index ["project_id"], name: "index_badges_on_project_id", using: :btree
+ t.index ["group_id"], name: "index_badges_on_group_id"
+ t.index ["project_id"], name: "index_badges_on_project_id"
end
create_table "board_assignees", id: :serial, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "assignee_id", null: false
- t.index ["assignee_id"], name: "index_board_assignees_on_assignee_id", using: :btree
- t.index ["board_id", "assignee_id"], name: "index_board_assignees_on_board_id_and_assignee_id", unique: true, using: :btree
+ t.index ["assignee_id"], name: "index_board_assignees_on_assignee_id"
+ t.index ["board_id", "assignee_id"], name: "index_board_assignees_on_board_id_and_assignee_id", unique: true
end
create_table "board_group_recent_visits", force: :cascade do |t|
@@ -378,17 +429,17 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id"
t.integer "board_id"
t.integer "group_id"
- t.index ["board_id"], name: "index_board_group_recent_visits_on_board_id", using: :btree
- t.index ["group_id"], name: "index_board_group_recent_visits_on_group_id", using: :btree
- t.index ["user_id", "group_id", "board_id"], name: "index_board_group_recent_visits_on_user_group_and_board", unique: true, using: :btree
- t.index ["user_id"], name: "index_board_group_recent_visits_on_user_id", using: :btree
+ t.index ["board_id"], name: "index_board_group_recent_visits_on_board_id"
+ t.index ["group_id"], name: "index_board_group_recent_visits_on_group_id"
+ t.index ["user_id", "group_id", "board_id"], name: "index_board_group_recent_visits_on_user_group_and_board", unique: true
+ t.index ["user_id"], name: "index_board_group_recent_visits_on_user_id"
end
create_table "board_labels", id: :serial, force: :cascade do |t|
t.integer "board_id", null: false
t.integer "label_id", null: false
- t.index ["board_id", "label_id"], name: "index_board_labels_on_board_id_and_label_id", unique: true, using: :btree
- t.index ["label_id"], name: "index_board_labels_on_label_id", using: :btree
+ t.index ["board_id", "label_id"], name: "index_board_labels_on_board_id_and_label_id", unique: true
+ t.index ["label_id"], name: "index_board_labels_on_label_id"
end
create_table "board_project_recent_visits", force: :cascade do |t|
@@ -397,10 +448,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id"
t.integer "project_id"
t.integer "board_id"
- t.index ["board_id"], name: "index_board_project_recent_visits_on_board_id", using: :btree
- t.index ["project_id"], name: "index_board_project_recent_visits_on_project_id", using: :btree
- t.index ["user_id", "project_id", "board_id"], name: "index_board_project_recent_visits_on_user_project_and_board", unique: true, using: :btree
- t.index ["user_id"], name: "index_board_project_recent_visits_on_user_id", using: :btree
+ t.index ["board_id"], name: "index_board_project_recent_visits_on_board_id"
+ t.index ["project_id"], name: "index_board_project_recent_visits_on_project_id"
+ t.index ["user_id", "project_id", "board_id"], name: "index_board_project_recent_visits_on_user_project_and_board", unique: true
+ t.index ["user_id"], name: "index_board_project_recent_visits_on_user_id"
end
create_table "boards", id: :serial, force: :cascade do |t|
@@ -411,9 +462,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "milestone_id"
t.integer "group_id"
t.integer "weight"
- t.index ["group_id"], name: "index_boards_on_group_id", using: :btree
- t.index ["milestone_id"], name: "index_boards_on_milestone_id", using: :btree
- t.index ["project_id"], name: "index_boards_on_project_id", using: :btree
+ t.index ["group_id"], name: "index_boards_on_group_id"
+ t.index ["milestone_id"], name: "index_boards_on_milestone_id"
+ t.index ["project_id"], name: "index_boards_on_project_id"
end
create_table "broadcast_messages", id: :serial, force: :cascade do |t|
@@ -426,7 +477,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "font"
t.text "message_html", null: false
t.integer "cached_markdown_version"
- t.index ["starts_at", "ends_at", "id"], name: "index_broadcast_messages_on_starts_at_and_ends_at_and_id", using: :btree
+ t.index ["starts_at", "ends_at", "id"], name: "index_broadcast_messages_on_starts_at_and_ends_at_and_id"
end
create_table "chat_names", id: :serial, force: :cascade do |t|
@@ -439,8 +490,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "last_used_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["service_id", "team_id", "chat_id"], name: "index_chat_names_on_service_id_and_team_id_and_chat_id", unique: true, using: :btree
- t.index ["user_id", "service_id"], name: "index_chat_names_on_user_id_and_service_id", unique: true, using: :btree
+ t.index ["service_id", "team_id", "chat_id"], name: "index_chat_names_on_service_id_and_team_id_and_chat_id", unique: true
+ t.index ["user_id", "service_id"], name: "index_chat_names_on_user_id_and_service_id", unique: true
end
create_table "chat_teams", id: :serial, force: :cascade do |t|
@@ -449,7 +500,13 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["namespace_id"], name: "index_chat_teams_on_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_chat_teams_on_namespace_id", unique: true
+ end
+
+ create_table "ci_build_needs", id: :serial, force: :cascade do |t|
+ t.integer "build_id", null: false
+ t.text "name", null: false
+ t.index ["build_id", "name"], name: "index_ci_build_needs_on_build_id_and_name", unique: true
end
create_table "ci_build_trace_chunks", force: :cascade do |t|
@@ -457,13 +514,13 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "chunk_index", null: false
t.integer "data_store", null: false
t.binary "raw_data"
- t.index ["build_id", "chunk_index"], name: "index_ci_build_trace_chunks_on_build_id_and_chunk_index", unique: true, using: :btree
+ t.index ["build_id", "chunk_index"], name: "index_ci_build_trace_chunks_on_build_id_and_chunk_index", unique: true
end
create_table "ci_build_trace_section_names", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.string "name", null: false
- t.index ["project_id", "name"], name: "index_ci_build_trace_section_names_on_project_id_and_name", unique: true, using: :btree
+ t.index ["project_id", "name"], name: "index_ci_build_trace_section_names_on_project_id_and_name", unique: true
end
create_table "ci_build_trace_sections", id: :serial, force: :cascade do |t|
@@ -474,9 +531,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "byte_end", null: false
t.integer "build_id", null: false
t.integer "section_name_id", null: false
- t.index ["build_id", "section_name_id"], name: "index_ci_build_trace_sections_on_build_id_and_section_name_id", unique: true, using: :btree
- t.index ["project_id"], name: "index_ci_build_trace_sections_on_project_id", using: :btree
- t.index ["section_name_id"], name: "index_ci_build_trace_sections_on_section_name_id", using: :btree
+ t.index ["build_id", "section_name_id"], name: "index_ci_build_trace_sections_on_build_id_and_section_name_id", unique: true
+ t.index ["project_id"], name: "index_ci_build_trace_sections_on_project_id"
+ t.index ["section_name_id"], name: "index_ci_build_trace_sections_on_section_name_id"
end
create_table "ci_builds", id: :serial, force: :cascade do |t|
@@ -526,28 +583,28 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "scheduled_at"
t.string "token_encrypted"
t.integer "upstream_pipeline_id"
- t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree
- t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree
- t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))", using: :btree
- t.index ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree
- t.index ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree
- t.index ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree
- t.index ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree
- t.index ["name"], name: "index_ci_builds_on_name_for_security_products_values", where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text]))", using: :btree
- t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree
- t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))", using: :btree
- t.index ["protected"], name: "index_ci_builds_on_protected", using: :btree
- t.index ["queued_at"], name: "index_ci_builds_on_queued_at", using: :btree
- t.index ["runner_id"], name: "index_ci_builds_on_runner_id", using: :btree
- t.index ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))", using: :btree
- t.index ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)", using: :btree
- t.index ["stage_id"], name: "index_ci_builds_on_stage_id", using: :btree
- t.index ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id", using: :btree
- t.index ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree
- t.index ["token_encrypted"], name: "index_ci_builds_on_token_encrypted", unique: true, where: "(token_encrypted IS NOT NULL)", using: :btree
- t.index ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree
- t.index ["upstream_pipeline_id"], name: "index_ci_builds_on_upstream_pipeline_id", where: "(upstream_pipeline_id IS NOT NULL)", using: :btree
- t.index ["user_id"], name: "index_ci_builds_on_user_id", using: :btree
+ t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)"
+ t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id"
+ t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))"
+ t.index ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at"
+ t.index ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type"
+ t.index ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref"
+ t.index ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref"
+ t.index ["name"], name: "index_ci_builds_on_name_for_security_products_values", where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text]))"
+ t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id"
+ t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))"
+ t.index ["protected"], name: "index_ci_builds_on_protected"
+ t.index ["queued_at"], name: "index_ci_builds_on_queued_at"
+ t.index ["runner_id"], name: "index_ci_builds_on_runner_id"
+ t.index ["scheduled_at"], name: "partial_index_ci_builds_on_scheduled_at_with_scheduled_jobs", where: "((scheduled_at IS NOT NULL) AND ((type)::text = 'Ci::Build'::text) AND ((status)::text = 'scheduled'::text))"
+ t.index ["stage_id", "stage_idx"], name: "tmp_build_stage_position_index", where: "(stage_idx IS NOT NULL)"
+ t.index ["stage_id"], name: "index_ci_builds_on_stage_id"
+ t.index ["status", "type", "runner_id"], name: "index_ci_builds_on_status_and_type_and_runner_id"
+ t.index ["token"], name: "index_ci_builds_on_token", unique: true
+ t.index ["token_encrypted"], name: "index_ci_builds_on_token_encrypted", unique: true, where: "(token_encrypted IS NOT NULL)"
+ t.index ["updated_at"], name: "index_ci_builds_on_updated_at"
+ t.index ["upstream_pipeline_id"], name: "index_ci_builds_on_upstream_pipeline_id", where: "(upstream_pipeline_id IS NOT NULL)"
+ t.index ["user_id"], name: "index_ci_builds_on_user_id"
end
create_table "ci_builds_metadata", id: :serial, force: :cascade do |t|
@@ -557,8 +614,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "timeout_source", default: 1, null: false
t.jsonb "config_options"
t.jsonb "config_variables"
- t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree
- t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree
+ t.index ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true
+ t.index ["project_id"], name: "index_ci_builds_metadata_on_project_id"
end
create_table "ci_builds_runner_session", force: :cascade do |t|
@@ -566,7 +623,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "url", null: false
t.string "certificate"
t.string "authorization"
- t.index ["build_id"], name: "index_ci_builds_runner_session_on_build_id", unique: true, using: :btree
+ t.index ["build_id"], name: "index_ci_builds_runner_session_on_build_id", unique: true
end
create_table "ci_group_variables", id: :serial, force: :cascade do |t|
@@ -581,7 +638,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.boolean "masked", default: false, null: false
t.integer "variable_type", limit: 2, default: 1, null: false
- t.index ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true, using: :btree
+ t.index ["group_id", "key"], name: "index_ci_group_variables_on_group_id_and_key", unique: true
end
create_table "ci_job_artifacts", id: :serial, force: :cascade do |t|
@@ -597,18 +654,28 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.binary "file_sha256"
t.integer "file_format", limit: 2
t.integer "file_location", limit: 2
- t.index ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id", using: :btree
- t.index ["file_store"], name: "index_ci_job_artifacts_on_file_store", using: :btree
- t.index ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true, using: :btree
- t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id", using: :btree
+ t.index ["expire_at", "job_id"], name: "index_ci_job_artifacts_on_expire_at_and_job_id"
+ t.index ["file_store"], name: "index_ci_job_artifacts_on_file_store"
+ t.index ["job_id", "file_type"], name: "index_ci_job_artifacts_on_job_id_and_file_type", unique: true
+ t.index ["project_id"], name: "index_ci_job_artifacts_on_project_id"
+ end
+
+ create_table "ci_job_variables", force: :cascade do |t|
+ t.string "key", null: false
+ t.text "encrypted_value"
+ t.string "encrypted_value_iv"
+ t.bigint "job_id", null: false
+ t.integer "variable_type", limit: 2, default: 1, null: false
+ t.index ["job_id"], name: "index_ci_job_variables_on_job_id"
+ t.index ["key", "job_id"], name: "index_ci_job_variables_on_key_and_job_id", unique: true
end
create_table "ci_pipeline_chat_data", force: :cascade do |t|
t.integer "pipeline_id", null: false
t.integer "chat_name_id", null: false
t.text "response_url", null: false
- t.index ["chat_name_id"], name: "index_ci_pipeline_chat_data_on_chat_name_id", using: :btree
- t.index ["pipeline_id"], name: "index_ci_pipeline_chat_data_on_pipeline_id", unique: true, using: :btree
+ t.index ["chat_name_id"], name: "index_ci_pipeline_chat_data_on_chat_name_id"
+ t.index ["pipeline_id"], name: "index_ci_pipeline_chat_data_on_pipeline_id", unique: true
end
create_table "ci_pipeline_schedule_variables", force: :cascade do |t|
@@ -621,7 +688,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "created_at"
t.datetime_with_timezone "updated_at"
t.integer "variable_type", limit: 2, default: 1, null: false
- t.index ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true, using: :btree
+ t.index ["pipeline_schedule_id", "key"], name: "index_ci_pipeline_schedule_variables_on_schedule_id_and_key", unique: true
end
create_table "ci_pipeline_schedules", id: :serial, force: :cascade do |t|
@@ -635,9 +702,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "active", default: true
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["next_run_at", "active"], name: "index_ci_pipeline_schedules_on_next_run_at_and_active", using: :btree
- t.index ["owner_id"], name: "index_ci_pipeline_schedules_on_owner_id", using: :btree
- t.index ["project_id"], name: "index_ci_pipeline_schedules_on_project_id", using: :btree
+ t.index ["next_run_at", "active"], name: "index_ci_pipeline_schedules_on_next_run_at_and_active"
+ t.index ["owner_id"], name: "index_ci_pipeline_schedules_on_owner_id"
+ t.index ["project_id"], name: "index_ci_pipeline_schedules_on_project_id"
end
create_table "ci_pipeline_variables", id: :serial, force: :cascade do |t|
@@ -648,7 +715,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_value_iv"
t.integer "pipeline_id", null: false
t.integer "variable_type", limit: 2, default: 1, null: false
- t.index ["pipeline_id", "key"], name: "index_ci_pipeline_variables_on_pipeline_id_and_key", unique: true, using: :btree
+ t.index ["pipeline_id", "key"], name: "index_ci_pipeline_variables_on_pipeline_id_and_key", unique: true
end
create_table "ci_pipelines", id: :serial, force: :cascade do |t|
@@ -677,25 +744,25 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "merge_request_id"
t.binary "source_sha"
t.binary "target_sha"
- t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id", using: :btree
- t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)", using: :btree
- t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id", using: :btree
- t.index ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)", using: :btree
- t.index ["project_id", "ref", "id"], name: "index_ci_pipelines_on_project_idandrefandiddesc", order: { id: :desc }, using: :btree
- t.index ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id", using: :btree
- t.index ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha", using: :btree
- t.index ["project_id", "source"], name: "index_ci_pipelines_on_project_id_and_source", using: :btree
- t.index ["project_id", "status", "config_source"], name: "index_ci_pipelines_on_project_id_and_status_and_config_source", using: :btree
- t.index ["project_id"], name: "index_ci_pipelines_on_project_id", using: :btree
- t.index ["status"], name: "index_ci_pipelines_on_status", using: :btree
- t.index ["user_id"], name: "index_ci_pipelines_on_user_id", using: :btree
+ t.index ["auto_canceled_by_id"], name: "index_ci_pipelines_on_auto_canceled_by_id"
+ t.index ["merge_request_id"], name: "index_ci_pipelines_on_merge_request_id", where: "(merge_request_id IS NOT NULL)"
+ t.index ["pipeline_schedule_id"], name: "index_ci_pipelines_on_pipeline_schedule_id"
+ t.index ["project_id", "iid"], name: "index_ci_pipelines_on_project_id_and_iid", unique: true, where: "(iid IS NOT NULL)"
+ t.index ["project_id", "ref", "id"], name: "index_ci_pipelines_on_project_idandrefandiddesc", order: { id: :desc }
+ t.index ["project_id", "ref", "status", "id"], name: "index_ci_pipelines_on_project_id_and_ref_and_status_and_id"
+ t.index ["project_id", "sha"], name: "index_ci_pipelines_on_project_id_and_sha"
+ t.index ["project_id", "source"], name: "index_ci_pipelines_on_project_id_and_source"
+ t.index ["project_id", "status", "config_source"], name: "index_ci_pipelines_on_project_id_and_status_and_config_source"
+ t.index ["project_id"], name: "index_ci_pipelines_on_project_id"
+ t.index ["status"], name: "index_ci_pipelines_on_status"
+ t.index ["user_id"], name: "index_ci_pipelines_on_user_id"
end
create_table "ci_runner_namespaces", id: :serial, force: :cascade do |t|
t.integer "runner_id"
t.integer "namespace_id"
- t.index ["namespace_id"], name: "index_ci_runner_namespaces_on_namespace_id", using: :btree
- t.index ["runner_id", "namespace_id"], name: "index_ci_runner_namespaces_on_runner_id_and_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_ci_runner_namespaces_on_namespace_id"
+ t.index ["runner_id", "namespace_id"], name: "index_ci_runner_namespaces_on_runner_id_and_namespace_id", unique: true
end
create_table "ci_runner_projects", id: :serial, force: :cascade do |t|
@@ -703,8 +770,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "project_id"
- t.index ["project_id"], name: "index_ci_runner_projects_on_project_id", using: :btree
- t.index ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
+ t.index ["project_id"], name: "index_ci_runner_projects_on_project_id"
+ t.index ["runner_id"], name: "index_ci_runner_projects_on_runner_id"
end
create_table "ci_runners", id: :serial, force: :cascade do |t|
@@ -727,12 +794,12 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "maximum_timeout"
t.integer "runner_type", limit: 2, null: false
t.string "token_encrypted"
- t.index ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree
- t.index ["is_shared"], name: "index_ci_runners_on_is_shared", using: :btree
- t.index ["locked"], name: "index_ci_runners_on_locked", using: :btree
- t.index ["runner_type"], name: "index_ci_runners_on_runner_type", using: :btree
- t.index ["token"], name: "index_ci_runners_on_token", using: :btree
- t.index ["token_encrypted"], name: "index_ci_runners_on_token_encrypted", using: :btree
+ t.index ["contacted_at"], name: "index_ci_runners_on_contacted_at"
+ t.index ["is_shared"], name: "index_ci_runners_on_is_shared"
+ t.index ["locked"], name: "index_ci_runners_on_locked"
+ t.index ["runner_type"], name: "index_ci_runners_on_runner_type"
+ t.index ["token"], name: "index_ci_runners_on_token"
+ t.index ["token_encrypted"], name: "index_ci_runners_on_token_encrypted"
end
create_table "ci_sources_pipelines", id: :serial, force: :cascade do |t|
@@ -741,11 +808,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "source_project_id"
t.integer "source_job_id"
t.integer "source_pipeline_id"
- t.index ["pipeline_id"], name: "index_ci_sources_pipelines_on_pipeline_id", using: :btree
- t.index ["project_id"], name: "index_ci_sources_pipelines_on_project_id", using: :btree
- t.index ["source_job_id"], name: "index_ci_sources_pipelines_on_source_job_id", using: :btree
- t.index ["source_pipeline_id"], name: "index_ci_sources_pipelines_on_source_pipeline_id", using: :btree
- t.index ["source_project_id"], name: "index_ci_sources_pipelines_on_source_project_id", using: :btree
+ t.index ["pipeline_id"], name: "index_ci_sources_pipelines_on_pipeline_id"
+ t.index ["project_id"], name: "index_ci_sources_pipelines_on_project_id"
+ t.index ["source_job_id"], name: "index_ci_sources_pipelines_on_source_job_id"
+ t.index ["source_pipeline_id"], name: "index_ci_sources_pipelines_on_source_pipeline_id"
+ t.index ["source_project_id"], name: "index_ci_sources_pipelines_on_source_project_id"
end
create_table "ci_stages", id: :serial, force: :cascade do |t|
@@ -757,10 +824,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "status"
t.integer "lock_version"
t.integer "position"
- t.index ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true, using: :btree
- t.index ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position", using: :btree
- t.index ["pipeline_id"], name: "index_ci_stages_on_pipeline_id", using: :btree
- t.index ["project_id"], name: "index_ci_stages_on_project_id", using: :btree
+ t.index ["pipeline_id", "name"], name: "index_ci_stages_on_pipeline_id_and_name", unique: true
+ t.index ["pipeline_id", "position"], name: "index_ci_stages_on_pipeline_id_and_position"
+ t.index ["pipeline_id"], name: "index_ci_stages_on_pipeline_id"
+ t.index ["project_id"], name: "index_ci_stages_on_project_id"
end
create_table "ci_trigger_requests", id: :serial, force: :cascade do |t|
@@ -769,8 +836,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "commit_id"
- t.index ["commit_id"], name: "index_ci_trigger_requests_on_commit_id", using: :btree
- t.index ["trigger_id"], name: "index_ci_trigger_requests_on_trigger_id", using: :btree
+ t.index ["commit_id"], name: "index_ci_trigger_requests_on_commit_id"
+ t.index ["trigger_id"], name: "index_ci_trigger_requests_on_trigger_id"
end
create_table "ci_triggers", id: :serial, force: :cascade do |t|
@@ -781,8 +848,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "owner_id"
t.string "description"
t.string "ref"
- t.index ["owner_id"], name: "index_ci_triggers_on_owner_id", using: :btree
- t.index ["project_id"], name: "index_ci_triggers_on_project_id", using: :btree
+ t.index ["owner_id"], name: "index_ci_triggers_on_owner_id"
+ t.index ["project_id"], name: "index_ci_triggers_on_project_id"
end
create_table "ci_variables", id: :serial, force: :cascade do |t|
@@ -796,14 +863,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "environment_scope", default: "*", null: false
t.boolean "masked", default: false, null: false
t.integer "variable_type", limit: 2, default: 1, null: false
- t.index ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true, using: :btree
+ t.index ["project_id", "key", "environment_scope"], name: "index_ci_variables_on_project_id_and_key_and_environment_scope", unique: true
end
create_table "cluster_groups", id: :serial, force: :cascade do |t|
t.integer "cluster_id", null: false
t.integer "group_id", null: false
- t.index ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true, using: :btree
- t.index ["group_id"], name: "index_cluster_groups_on_group_id", using: :btree
+ t.index ["cluster_id", "group_id"], name: "index_cluster_groups_on_cluster_id_and_group_id", unique: true
+ t.index ["group_id"], name: "index_cluster_groups_on_group_id"
end
create_table "cluster_platforms_kubernetes", id: :serial, force: :cascade do |t|
@@ -819,7 +886,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "encrypted_token"
t.string "encrypted_token_iv"
t.integer "authorization_type", limit: 2
- t.index ["cluster_id"], name: "index_cluster_platforms_kubernetes_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_cluster_platforms_kubernetes_on_cluster_id", unique: true
end
create_table "cluster_projects", id: :serial, force: :cascade do |t|
@@ -827,8 +894,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "cluster_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["cluster_id"], name: "index_cluster_projects_on_cluster_id", using: :btree
- t.index ["project_id"], name: "index_cluster_projects_on_project_id", using: :btree
+ t.index ["cluster_id"], name: "index_cluster_projects_on_cluster_id"
+ t.index ["project_id"], name: "index_cluster_projects_on_project_id"
end
create_table "cluster_providers_gcp", id: :serial, force: :cascade do |t|
@@ -846,7 +913,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "encrypted_access_token"
t.string "encrypted_access_token_iv"
t.boolean "legacy_abac", default: false, null: false
- t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_cluster_providers_gcp_on_cluster_id", unique: true
end
create_table "clusters", id: :serial, force: :cascade do |t|
@@ -861,8 +928,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "cluster_type", limit: 2, default: 3, null: false
t.string "domain"
t.boolean "managed", default: true, null: false
- t.index ["enabled"], name: "index_clusters_on_enabled", using: :btree
- t.index ["user_id"], name: "index_clusters_on_user_id", using: :btree
+ t.boolean "namespace_per_environment", default: false, null: false
+ t.index ["enabled"], name: "index_clusters_on_enabled"
+ t.index ["user_id"], name: "index_clusters_on_user_id"
end
create_table "clusters_applications_cert_managers", id: :serial, force: :cascade do |t|
@@ -873,7 +941,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.text "status_reason"
- t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_cert_managers_on_cluster_id", unique: true
end
create_table "clusters_applications_helm", id: :serial, force: :cascade do |t|
@@ -886,7 +954,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "encrypted_ca_key"
t.text "encrypted_ca_key_iv"
t.text "ca_cert"
- t.index ["cluster_id"], name: "index_clusters_applications_helm_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_helm_on_cluster_id", unique: true
end
create_table "clusters_applications_ingress", id: :serial, force: :cascade do |t|
@@ -900,7 +968,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "status_reason"
t.string "external_ip"
t.string "external_hostname"
- t.index ["cluster_id"], name: "index_clusters_applications_ingress_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_ingress_on_cluster_id", unique: true
end
create_table "clusters_applications_jupyter", id: :serial, force: :cascade do |t|
@@ -912,8 +980,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.text "status_reason"
- t.index ["cluster_id"], name: "index_clusters_applications_jupyter_on_cluster_id", unique: true, using: :btree
- t.index ["oauth_application_id"], name: "index_clusters_applications_jupyter_on_oauth_application_id", using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_jupyter_on_cluster_id", unique: true
+ t.index ["oauth_application_id"], name: "index_clusters_applications_jupyter_on_oauth_application_id"
end
create_table "clusters_applications_knative", id: :serial, force: :cascade do |t|
@@ -926,7 +994,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "status_reason"
t.string "external_ip"
t.string "external_hostname"
- t.index ["cluster_id"], name: "index_clusters_applications_knative_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_knative_on_cluster_id", unique: true
end
create_table "clusters_applications_prometheus", id: :serial, force: :cascade do |t|
@@ -939,7 +1007,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_alert_manager_token"
t.string "encrypted_alert_manager_token_iv"
t.datetime_with_timezone "last_update_started_at"
- t.index ["cluster_id"], name: "index_clusters_applications_prometheus_on_cluster_id", unique: true, using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_prometheus_on_cluster_id", unique: true
end
create_table "clusters_applications_runners", id: :serial, force: :cascade do |t|
@@ -951,8 +1019,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "version", null: false
t.text "status_reason"
t.boolean "privileged", default: true, null: false
- t.index ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true, using: :btree
- t.index ["runner_id"], name: "index_clusters_applications_runners_on_runner_id", using: :btree
+ t.index ["cluster_id"], name: "index_clusters_applications_runners_on_cluster_id", unique: true
+ t.index ["runner_id"], name: "index_clusters_applications_runners_on_runner_id"
end
create_table "clusters_kubernetes_namespaces", force: :cascade do |t|
@@ -965,10 +1033,13 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_service_account_token_iv"
t.string "namespace", null: false
t.string "service_account_name"
- t.index ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true, using: :btree
- t.index ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id", using: :btree
- t.index ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id", using: :btree
- t.index ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id", using: :btree
+ t.bigint "environment_id"
+ t.index ["cluster_id", "namespace"], name: "kubernetes_namespaces_cluster_and_namespace", unique: true
+ t.index ["cluster_id", "project_id", "environment_id"], name: "index_kubernetes_namespaces_on_cluster_project_environment_id", unique: true
+ t.index ["cluster_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_id"
+ t.index ["cluster_project_id"], name: "index_clusters_kubernetes_namespaces_on_cluster_project_id"
+ t.index ["environment_id"], name: "index_clusters_kubernetes_namespaces_on_environment_id"
+ t.index ["project_id"], name: "index_clusters_kubernetes_namespaces_on_project_id"
end
create_table "container_repositories", id: :serial, force: :cascade do |t|
@@ -976,8 +1047,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["project_id", "name"], name: "index_container_repositories_on_project_id_and_name", unique: true, using: :btree
- t.index ["project_id"], name: "index_container_repositories_on_project_id", using: :btree
+ t.index ["project_id", "name"], name: "index_container_repositories_on_project_id_and_name", unique: true
+ t.index ["project_id"], name: "index_container_repositories_on_project_id"
end
create_table "conversational_development_index_metrics", id: :serial, force: :cascade do |t|
@@ -1023,7 +1094,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "group_id", null: false
t.bigint "size"
t.datetime_with_timezone "updated_at", null: false
- t.index ["group_id", "file_name"], name: "index_dependency_proxy_blobs_on_group_id_and_file_name", using: :btree
+ t.index ["group_id", "file_name"], name: "index_dependency_proxy_blobs_on_group_id_and_file_name"
end
create_table "dependency_proxy_group_settings", id: :serial, force: :cascade do |t|
@@ -1031,7 +1102,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "enabled", default: false, null: false
t.integer "group_id", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["group_id"], name: "index_dependency_proxy_group_settings_on_group_id", using: :btree
+ t.index ["group_id"], name: "index_dependency_proxy_group_settings_on_group_id"
end
create_table "deploy_keys_projects", id: :serial, force: :cascade do |t|
@@ -1040,7 +1111,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.boolean "can_push", default: false, null: false
- t.index ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_deploy_keys_projects_on_project_id"
end
create_table "deploy_tokens", id: :serial, force: :cascade do |t|
@@ -1052,8 +1123,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.string "token", null: false
t.string "username"
- t.index ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)", using: :btree
- t.index ["token"], name: "index_deploy_tokens_on_token", unique: true, using: :btree
+ t.index ["token", "expires_at", "id"], name: "index_deploy_tokens_on_token_and_expires_at_and_id", where: "(revoked IS FALSE)"
+ t.index ["token"], name: "index_deploy_tokens_on_token", unique: true
end
create_table "deployments", id: :serial, force: :cascade do |t|
@@ -1072,37 +1143,41 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "status", limit: 2, null: false
t.datetime_with_timezone "finished_at"
t.integer "cluster_id"
- t.index ["cluster_id"], name: "index_deployments_on_cluster_id", using: :btree
- t.index ["created_at"], name: "index_deployments_on_created_at", using: :btree
- t.index ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id", using: :btree
- t.index ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id", using: :btree
- t.index ["environment_id", "iid", "project_id"], name: "index_deployments_on_environment_id_and_iid_and_project_id", using: :btree
- t.index ["environment_id", "status"], name: "index_deployments_on_environment_id_and_status", using: :btree
- t.index ["id"], name: "partial_index_deployments_for_legacy_successful_deployments", where: "((finished_at IS NULL) AND (status = 2))", using: :btree
- t.index ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true, using: :btree
- t.index ["project_id", "status", "created_at"], name: "index_deployments_on_project_id_and_status_and_created_at", using: :btree
- t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status", using: :btree
+ t.index ["cluster_id"], name: "index_deployments_on_cluster_id"
+ t.index ["created_at"], name: "index_deployments_on_created_at"
+ t.index ["deployable_type", "deployable_id"], name: "index_deployments_on_deployable_type_and_deployable_id"
+ t.index ["environment_id", "id"], name: "index_deployments_on_environment_id_and_id"
+ t.index ["environment_id", "iid", "project_id"], name: "index_deployments_on_environment_id_and_iid_and_project_id"
+ t.index ["environment_id", "status"], name: "index_deployments_on_environment_id_and_status"
+ t.index ["id"], name: "partial_index_deployments_for_legacy_successful_deployments", where: "((finished_at IS NULL) AND (status = 2))"
+ t.index ["project_id", "iid"], name: "index_deployments_on_project_id_and_iid", unique: true
+ t.index ["project_id", "status", "created_at"], name: "index_deployments_on_project_id_and_status_and_created_at"
+ t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status"
end
create_table "design_management_designs", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "issue_id", null: false
t.string "filename", null: false
- t.index ["issue_id", "filename"], name: "index_design_management_designs_on_issue_id_and_filename", unique: true, using: :btree
- t.index ["project_id"], name: "index_design_management_designs_on_project_id", using: :btree
+ t.index ["issue_id", "filename"], name: "index_design_management_designs_on_issue_id_and_filename", unique: true
+ t.index ["project_id"], name: "index_design_management_designs_on_project_id"
end
create_table "design_management_designs_versions", id: false, force: :cascade do |t|
t.bigint "design_id", null: false
t.bigint "version_id", null: false
- t.index ["design_id", "version_id"], name: "design_management_designs_versions_uniqueness", unique: true, using: :btree
- t.index ["design_id"], name: "index_design_management_designs_versions_on_design_id", using: :btree
- t.index ["version_id"], name: "index_design_management_designs_versions_on_version_id", using: :btree
+ t.integer "event", limit: 2, default: 0, null: false
+ t.index ["design_id", "version_id"], name: "design_management_designs_versions_uniqueness", unique: true
+ t.index ["design_id"], name: "index_design_management_designs_versions_on_design_id"
+ t.index ["event"], name: "index_design_management_designs_versions_on_event"
+ t.index ["version_id"], name: "index_design_management_designs_versions_on_version_id"
end
create_table "design_management_versions", force: :cascade do |t|
t.binary "sha", null: false
- t.index ["sha"], name: "index_design_management_versions_on_sha", unique: true, using: :btree
+ t.bigint "issue_id"
+ t.index ["issue_id"], name: "index_design_management_versions_on_issue_id"
+ t.index ["sha", "issue_id"], name: "index_design_management_versions_on_sha_and_issue_id", unique: true
end
create_table "draft_notes", force: :cascade do |t|
@@ -1114,23 +1189,24 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "position"
t.text "original_position"
t.text "change_position"
- t.index ["author_id"], name: "index_draft_notes_on_author_id", using: :btree
- t.index ["discussion_id"], name: "index_draft_notes_on_discussion_id", using: :btree
- t.index ["merge_request_id"], name: "index_draft_notes_on_merge_request_id", using: :btree
+ t.binary "commit_id"
+ t.index ["author_id"], name: "index_draft_notes_on_author_id"
+ t.index ["discussion_id"], name: "index_draft_notes_on_discussion_id"
+ t.index ["merge_request_id"], name: "index_draft_notes_on_merge_request_id"
end
create_table "elasticsearch_indexed_namespaces", id: false, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "namespace_id"
- t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true
end
create_table "elasticsearch_indexed_projects", id: false, force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id"
- t.index ["project_id"], name: "index_elasticsearch_indexed_projects_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_elasticsearch_indexed_projects_on_project_id", unique: true
end
create_table "emails", id: :serial, force: :cascade do |t|
@@ -1141,9 +1217,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "confirmation_token"
t.datetime_with_timezone "confirmed_at"
t.datetime_with_timezone "confirmation_sent_at"
- t.index ["confirmation_token"], name: "index_emails_on_confirmation_token", unique: true, using: :btree
- t.index ["email"], name: "index_emails_on_email", unique: true, using: :btree
- t.index ["user_id"], name: "index_emails_on_user_id", using: :btree
+ t.index ["confirmation_token"], name: "index_emails_on_confirmation_token", unique: true
+ t.index ["email"], name: "index_emails_on_email", unique: true
+ t.index ["user_id"], name: "index_emails_on_user_id"
end
create_table "environments", id: :serial, force: :cascade do |t|
@@ -1155,24 +1231,25 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "environment_type"
t.string "state", default: "available", null: false
t.string "slug", null: false
- t.index ["name"], name: "index_environments_on_name_varchar_pattern_ops", using: :btree, opclasses: {"name"=>"varchar_pattern_ops"}
- t.index ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true, using: :btree
- t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true, using: :btree
+ t.index ["name"], name: "index_environments_on_name_varchar_pattern_ops", opclass: :varchar_pattern_ops
+ t.index ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true
+ t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true
+ t.index ["project_id", "state"], name: "index_environments_on_project_id_and_state"
end
create_table "epic_issues", id: :serial, force: :cascade do |t|
t.integer "epic_id", null: false
t.integer "issue_id", null: false
- t.integer "relative_position", default: 1073741823, null: false
- t.index ["epic_id"], name: "index_epic_issues_on_epic_id", using: :btree
- t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true, using: :btree
+ t.integer "relative_position"
+ t.index ["epic_id"], name: "index_epic_issues_on_epic_id"
+ t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true
end
create_table "epic_metrics", id: :serial, force: :cascade do |t|
t.integer "epic_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["epic_id"], name: "index_epic_metrics", using: :btree
+ t.index ["epic_id"], name: "index_epic_metrics"
end
create_table "epics", id: :serial, force: :cascade do |t|
@@ -1205,15 +1282,15 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "closed_at"
t.integer "parent_id"
t.integer "relative_position"
- t.index ["assignee_id"], name: "index_epics_on_assignee_id", using: :btree
- t.index ["author_id"], name: "index_epics_on_author_id", using: :btree
- t.index ["closed_by_id"], name: "index_epics_on_closed_by_id", using: :btree
- t.index ["end_date"], name: "index_epics_on_end_date", using: :btree
- t.index ["group_id"], name: "index_epics_on_group_id", using: :btree
- t.index ["iid"], name: "index_epics_on_iid", using: :btree
- t.index ["milestone_id"], name: "index_milestone", using: :btree
- t.index ["parent_id"], name: "index_epics_on_parent_id", using: :btree
- t.index ["start_date"], name: "index_epics_on_start_date", using: :btree
+ t.index ["assignee_id"], name: "index_epics_on_assignee_id"
+ t.index ["author_id"], name: "index_epics_on_author_id"
+ t.index ["closed_by_id"], name: "index_epics_on_closed_by_id"
+ t.index ["end_date"], name: "index_epics_on_end_date"
+ t.index ["group_id"], name: "index_epics_on_group_id"
+ t.index ["iid"], name: "index_epics_on_iid"
+ t.index ["milestone_id"], name: "index_milestone"
+ t.index ["parent_id"], name: "index_epics_on_parent_id"
+ t.index ["start_date"], name: "index_epics_on_start_date"
end
create_table "events", id: :serial, force: :cascade do |t|
@@ -1224,12 +1301,12 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.integer "action", limit: 2, null: false
t.string "target_type"
- t.index ["action"], name: "index_events_on_action", using: :btree
- t.index ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id", using: :btree
- t.index ["created_at", "author_id"], name: "analytics_index_events_on_created_at_and_author_id", using: :btree
- t.index ["project_id", "created_at"], name: "index_events_on_project_id_and_created_at", using: :btree
- t.index ["project_id", "id"], name: "index_events_on_project_id_and_id", using: :btree
- t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id", using: :btree
+ t.index ["action"], name: "index_events_on_action"
+ t.index ["author_id", "project_id"], name: "index_events_on_author_id_and_project_id"
+ t.index ["created_at", "author_id"], name: "analytics_index_events_on_created_at_and_author_id"
+ t.index ["project_id", "created_at"], name: "index_events_on_project_id_and_created_at"
+ t.index ["project_id", "id"], name: "index_events_on_project_id_and_id"
+ t.index ["target_type", "target_id"], name: "index_events_on_target_type_and_target_id"
end
create_table "feature_gates", id: :serial, force: :cascade do |t|
@@ -1238,29 +1315,29 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "value"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["feature_key", "key", "value"], name: "index_feature_gates_on_feature_key_and_key_and_value", unique: true, using: :btree
+ t.index ["feature_key", "key", "value"], name: "index_feature_gates_on_feature_key_and_key_and_value", unique: true
end
create_table "features", id: :serial, force: :cascade do |t|
t.string "key", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["key"], name: "index_features_on_key", unique: true, using: :btree
+ t.index ["key"], name: "index_features_on_key", unique: true
end
create_table "fork_network_members", id: :serial, force: :cascade do |t|
t.integer "fork_network_id", null: false
t.integer "project_id", null: false
t.integer "forked_from_project_id"
- t.index ["fork_network_id"], name: "index_fork_network_members_on_fork_network_id", using: :btree
- t.index ["forked_from_project_id"], name: "index_fork_network_members_on_forked_from_project_id", using: :btree
- t.index ["project_id"], name: "index_fork_network_members_on_project_id", unique: true, using: :btree
+ t.index ["fork_network_id"], name: "index_fork_network_members_on_fork_network_id"
+ t.index ["forked_from_project_id"], name: "index_fork_network_members_on_forked_from_project_id"
+ t.index ["project_id"], name: "index_fork_network_members_on_project_id", unique: true
end
create_table "fork_networks", id: :serial, force: :cascade do |t|
t.integer "root_project_id"
t.string "deleted_root_project_name"
- t.index ["root_project_id"], name: "index_fork_networks_on_root_project_id", unique: true, using: :btree
+ t.index ["root_project_id"], name: "index_fork_networks_on_root_project_id", unique: true
end
create_table "forked_project_links", id: :serial, force: :cascade do |t|
@@ -1268,13 +1345,18 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "forked_from_project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
+ t.index ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true
end
create_table "geo_cache_invalidation_events", force: :cascade do |t|
t.string "key", null: false
end
+ create_table "geo_container_repository_updated_events", force: :cascade do |t|
+ t.integer "container_repository_id", null: false
+ t.index ["container_repository_id"], name: "idx_geo_con_rep_updated_events_on_container_repository_id"
+ end
+
create_table "geo_event_log", force: :cascade do |t|
t.datetime "created_at", null: false
t.bigint "repository_updated_event_id"
@@ -1289,25 +1371,27 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "job_artifact_deleted_event_id"
t.bigint "reset_checksum_event_id"
t.bigint "cache_invalidation_event_id"
- t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)", using: :btree
- t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)", using: :btree
- t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)", using: :btree
- t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)", using: :btree
- t.index ["lfs_object_deleted_event_id"], name: "index_geo_event_log_on_lfs_object_deleted_event_id", where: "(lfs_object_deleted_event_id IS NOT NULL)", using: :btree
- t.index ["repositories_changed_event_id"], name: "index_geo_event_log_on_repositories_changed_event_id", where: "(repositories_changed_event_id IS NOT NULL)", using: :btree
- t.index ["repository_created_event_id"], name: "index_geo_event_log_on_repository_created_event_id", where: "(repository_created_event_id IS NOT NULL)", using: :btree
- t.index ["repository_deleted_event_id"], name: "index_geo_event_log_on_repository_deleted_event_id", where: "(repository_deleted_event_id IS NOT NULL)", using: :btree
- t.index ["repository_renamed_event_id"], name: "index_geo_event_log_on_repository_renamed_event_id", where: "(repository_renamed_event_id IS NOT NULL)", using: :btree
- t.index ["repository_updated_event_id"], name: "index_geo_event_log_on_repository_updated_event_id", where: "(repository_updated_event_id IS NOT NULL)", using: :btree
- t.index ["reset_checksum_event_id"], name: "index_geo_event_log_on_reset_checksum_event_id", where: "(reset_checksum_event_id IS NOT NULL)", using: :btree
- t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)", using: :btree
+ t.bigint "container_repository_updated_event_id"
+ t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)"
+ t.index ["container_repository_updated_event_id"], name: "index_geo_event_log_on_container_repository_updated_event_id"
+ t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)"
+ t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)"
+ t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)"
+ t.index ["lfs_object_deleted_event_id"], name: "index_geo_event_log_on_lfs_object_deleted_event_id", where: "(lfs_object_deleted_event_id IS NOT NULL)"
+ t.index ["repositories_changed_event_id"], name: "index_geo_event_log_on_repositories_changed_event_id", where: "(repositories_changed_event_id IS NOT NULL)"
+ t.index ["repository_created_event_id"], name: "index_geo_event_log_on_repository_created_event_id", where: "(repository_created_event_id IS NOT NULL)"
+ t.index ["repository_deleted_event_id"], name: "index_geo_event_log_on_repository_deleted_event_id", where: "(repository_deleted_event_id IS NOT NULL)"
+ t.index ["repository_renamed_event_id"], name: "index_geo_event_log_on_repository_renamed_event_id", where: "(repository_renamed_event_id IS NOT NULL)"
+ t.index ["repository_updated_event_id"], name: "index_geo_event_log_on_repository_updated_event_id", where: "(repository_updated_event_id IS NOT NULL)"
+ t.index ["reset_checksum_event_id"], name: "index_geo_event_log_on_reset_checksum_event_id", where: "(reset_checksum_event_id IS NOT NULL)"
+ t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)"
end
create_table "geo_hashed_storage_attachments_events", force: :cascade do |t|
t.integer "project_id", null: false
t.text "old_attachments_path", null: false
t.text "new_attachments_path", null: false
- t.index ["project_id"], name: "index_geo_hashed_storage_attachments_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_hashed_storage_attachments_events_on_project_id"
end
create_table "geo_hashed_storage_migrated_events", force: :cascade do |t|
@@ -1319,20 +1403,20 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "new_wiki_disk_path", null: false
t.integer "old_storage_version", limit: 2
t.integer "new_storage_version", limit: 2, null: false
- t.index ["project_id"], name: "index_geo_hashed_storage_migrated_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_hashed_storage_migrated_events_on_project_id"
end
create_table "geo_job_artifact_deleted_events", force: :cascade do |t|
t.integer "job_artifact_id", null: false
t.string "file_path", null: false
- t.index ["job_artifact_id"], name: "index_geo_job_artifact_deleted_events_on_job_artifact_id", using: :btree
+ t.index ["job_artifact_id"], name: "index_geo_job_artifact_deleted_events_on_job_artifact_id"
end
create_table "geo_lfs_object_deleted_events", force: :cascade do |t|
t.integer "lfs_object_id", null: false
t.string "oid", null: false
t.string "file_path", null: false
- t.index ["lfs_object_id"], name: "index_geo_lfs_object_deleted_events_on_lfs_object_id", using: :btree
+ t.index ["lfs_object_id"], name: "index_geo_lfs_object_deleted_events_on_lfs_object_id"
end
create_table "geo_node_namespace_links", id: :serial, force: :cascade do |t|
@@ -1340,9 +1424,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "namespace_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["geo_node_id", "namespace_id"], name: "index_geo_node_namespace_links_on_geo_node_id_and_namespace_id", unique: true, using: :btree
- t.index ["geo_node_id"], name: "index_geo_node_namespace_links_on_geo_node_id", using: :btree
- t.index ["namespace_id"], name: "index_geo_node_namespace_links_on_namespace_id", using: :btree
+ t.index ["geo_node_id", "namespace_id"], name: "index_geo_node_namespace_links_on_geo_node_id_and_namespace_id", unique: true
+ t.index ["geo_node_id"], name: "index_geo_node_namespace_links_on_geo_node_id"
+ t.index ["namespace_id"], name: "index_geo_node_namespace_links_on_namespace_id"
end
create_table "geo_node_statuses", id: :serial, force: :cascade do |t|
@@ -1391,7 +1475,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "repositories_retrying_verification_count"
t.integer "wikis_retrying_verification_count"
t.integer "projects_count"
- t.index ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree
+ t.integer "container_repositories_count"
+ t.integer "container_repositories_synced_count"
+ t.integer "container_repositories_failed_count"
+ t.integer "container_repositories_registry_count"
+ t.index ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true
end
create_table "geo_nodes", id: :serial, force: :cascade do |t|
@@ -1411,14 +1499,18 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "minimum_reverification_interval", default: 7, null: false
t.string "internal_url"
t.string "name", null: false
- t.index ["access_key"], name: "index_geo_nodes_on_access_key", using: :btree
- t.index ["name"], name: "index_geo_nodes_on_name", unique: true, using: :btree
- t.index ["primary"], name: "index_geo_nodes_on_primary", using: :btree
+ t.integer "container_repositories_max_capacity", default: 10, null: false
+ t.datetime_with_timezone "created_at"
+ t.datetime_with_timezone "updated_at"
+ t.boolean "sync_object_storage", default: false, null: false
+ t.index ["access_key"], name: "index_geo_nodes_on_access_key"
+ t.index ["name"], name: "index_geo_nodes_on_name", unique: true
+ t.index ["primary"], name: "index_geo_nodes_on_primary"
end
create_table "geo_repositories_changed_events", force: :cascade do |t|
t.integer "geo_node_id", null: false
- t.index ["geo_node_id"], name: "index_geo_repositories_changed_events_on_geo_node_id", using: :btree
+ t.index ["geo_node_id"], name: "index_geo_repositories_changed_events_on_geo_node_id"
end
create_table "geo_repository_created_events", force: :cascade do |t|
@@ -1427,7 +1519,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "repo_path", null: false
t.text "wiki_path"
t.text "project_name", null: false
- t.index ["project_id"], name: "index_geo_repository_created_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_repository_created_events_on_project_id"
end
create_table "geo_repository_deleted_events", force: :cascade do |t|
@@ -1436,7 +1528,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "deleted_path", null: false
t.text "deleted_wiki_path"
t.text "deleted_project_name", null: false
- t.index ["project_id"], name: "index_geo_repository_deleted_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_repository_deleted_events_on_project_id"
end
create_table "geo_repository_renamed_events", force: :cascade do |t|
@@ -1448,7 +1540,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "new_wiki_path_with_namespace", null: false
t.text "old_path", null: false
t.text "new_path", null: false
- t.index ["project_id"], name: "index_geo_repository_renamed_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_repository_renamed_events_on_project_id"
end
create_table "geo_repository_updated_events", force: :cascade do |t|
@@ -1459,13 +1551,13 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "new_branch", default: false, null: false
t.boolean "remove_branch", default: false, null: false
t.text "ref"
- t.index ["project_id"], name: "index_geo_repository_updated_events_on_project_id", using: :btree
- t.index ["source"], name: "index_geo_repository_updated_events_on_source", using: :btree
+ t.index ["project_id"], name: "index_geo_repository_updated_events_on_project_id"
+ t.index ["source"], name: "index_geo_repository_updated_events_on_source"
end
create_table "geo_reset_checksum_events", force: :cascade do |t|
t.integer "project_id", null: false
- t.index ["project_id"], name: "index_geo_reset_checksum_events_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_geo_reset_checksum_events_on_project_id"
end
create_table "geo_upload_deleted_events", force: :cascade do |t|
@@ -1474,7 +1566,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "model_id", null: false
t.string "model_type", null: false
t.string "uploader", null: false
- t.index ["upload_id"], name: "index_geo_upload_deleted_events_on_upload_id", using: :btree
+ t.index ["upload_id"], name: "index_geo_upload_deleted_events_on_upload_id"
end
create_table "gitlab_subscriptions", force: :cascade do |t|
@@ -1488,17 +1580,17 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "max_seats_used", default: 0
t.integer "seats", default: 0
t.boolean "trial", default: false
- t.index ["hosted_plan_id"], name: "index_gitlab_subscriptions_on_hosted_plan_id", using: :btree
- t.index ["namespace_id"], name: "index_gitlab_subscriptions_on_namespace_id", unique: true, using: :btree
+ t.index ["hosted_plan_id"], name: "index_gitlab_subscriptions_on_hosted_plan_id"
+ t.index ["namespace_id"], name: "index_gitlab_subscriptions_on_namespace_id", unique: true
end
create_table "gpg_key_subkeys", id: :serial, force: :cascade do |t|
t.integer "gpg_key_id", null: false
t.binary "keyid"
t.binary "fingerprint"
- t.index ["fingerprint"], name: "index_gpg_key_subkeys_on_fingerprint", unique: true, using: :btree
- t.index ["gpg_key_id"], name: "index_gpg_key_subkeys_on_gpg_key_id", using: :btree
- t.index ["keyid"], name: "index_gpg_key_subkeys_on_keyid", unique: true, using: :btree
+ t.index ["fingerprint"], name: "index_gpg_key_subkeys_on_fingerprint", unique: true
+ t.index ["gpg_key_id"], name: "index_gpg_key_subkeys_on_gpg_key_id"
+ t.index ["keyid"], name: "index_gpg_key_subkeys_on_keyid", unique: true
end
create_table "gpg_keys", id: :serial, force: :cascade do |t|
@@ -1508,9 +1600,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.binary "primary_keyid"
t.binary "fingerprint"
t.text "key"
- t.index ["fingerprint"], name: "index_gpg_keys_on_fingerprint", unique: true, using: :btree
- t.index ["primary_keyid"], name: "index_gpg_keys_on_primary_keyid", unique: true, using: :btree
- t.index ["user_id"], name: "index_gpg_keys_on_user_id", using: :btree
+ t.index ["fingerprint"], name: "index_gpg_keys_on_fingerprint", unique: true
+ t.index ["primary_keyid"], name: "index_gpg_keys_on_primary_keyid", unique: true
+ t.index ["user_id"], name: "index_gpg_keys_on_user_id"
end
create_table "gpg_signatures", id: :serial, force: :cascade do |t|
@@ -1524,11 +1616,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "gpg_key_user_email"
t.integer "verification_status", limit: 2, default: 0, null: false
t.integer "gpg_key_subkey_id"
- t.index ["commit_sha"], name: "index_gpg_signatures_on_commit_sha", unique: true, using: :btree
- t.index ["gpg_key_id"], name: "index_gpg_signatures_on_gpg_key_id", using: :btree
- t.index ["gpg_key_primary_keyid"], name: "index_gpg_signatures_on_gpg_key_primary_keyid", using: :btree
- t.index ["gpg_key_subkey_id"], name: "index_gpg_signatures_on_gpg_key_subkey_id", using: :btree
- t.index ["project_id"], name: "index_gpg_signatures_on_project_id", using: :btree
+ t.index ["commit_sha"], name: "index_gpg_signatures_on_commit_sha", unique: true
+ t.index ["gpg_key_id"], name: "index_gpg_signatures_on_gpg_key_id"
+ t.index ["gpg_key_primary_keyid"], name: "index_gpg_signatures_on_gpg_key_primary_keyid"
+ t.index ["gpg_key_subkey_id"], name: "index_gpg_signatures_on_gpg_key_subkey_id"
+ t.index ["project_id"], name: "index_gpg_signatures_on_project_id"
end
create_table "group_custom_attributes", id: :serial, force: :cascade do |t|
@@ -1537,8 +1629,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "group_id", null: false
t.string "key", null: false
t.string "value", null: false
- t.index ["group_id", "key"], name: "index_group_custom_attributes_on_group_id_and_key", unique: true, using: :btree
- t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value", using: :btree
+ t.index ["group_id", "key"], name: "index_group_custom_attributes_on_group_id_and_key", unique: true
+ t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value"
end
create_table "historical_data", id: :serial, force: :cascade do |t|
@@ -1556,8 +1648,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at"
t.integer "saml_provider_id"
t.string "secondary_extern_uid"
- t.index ["saml_provider_id"], name: "index_identities_on_saml_provider_id", where: "(saml_provider_id IS NOT NULL)", using: :btree
- t.index ["user_id"], name: "index_identities_on_user_id", using: :btree
+ t.index "lower((extern_uid)::text), provider", name: "index_on_identities_lower_extern_uid_and_provider"
+ t.index ["saml_provider_id"], name: "index_identities_on_saml_provider_id", where: "(saml_provider_id IS NOT NULL)"
+ t.index ["user_id"], name: "index_identities_on_user_id"
end
create_table "import_export_uploads", id: :serial, force: :cascade do |t|
@@ -1565,8 +1658,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "project_id"
t.text "import_file"
t.text "export_file"
- t.index ["project_id"], name: "index_import_export_uploads_on_project_id", using: :btree
- t.index ["updated_at"], name: "index_import_export_uploads_on_updated_at", using: :btree
+ t.index ["project_id"], name: "index_import_export_uploads_on_project_id"
+ t.index ["updated_at"], name: "index_import_export_uploads_on_updated_at"
end
create_table "index_statuses", id: :serial, force: :cascade do |t|
@@ -1578,14 +1671,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at", null: false
t.binary "last_wiki_commit"
t.datetime_with_timezone "wiki_indexed_at"
- t.index ["project_id"], name: "index_index_statuses_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_index_statuses_on_project_id", unique: true
end
create_table "insights", id: :serial, force: :cascade do |t|
t.integer "namespace_id", null: false
t.integer "project_id", null: false
- t.index ["namespace_id"], name: "index_insights_on_namespace_id", using: :btree
- t.index ["project_id"], name: "index_insights_on_project_id", using: :btree
+ t.index ["namespace_id"], name: "index_insights_on_namespace_id"
+ t.index ["project_id"], name: "index_insights_on_project_id"
end
create_table "internal_ids", force: :cascade do |t|
@@ -1593,23 +1686,23 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "usage", null: false
t.integer "last_value", null: false
t.integer "namespace_id"
- t.index ["namespace_id"], name: "index_internal_ids_on_namespace_id", using: :btree
- t.index ["project_id"], name: "index_internal_ids_on_project_id", using: :btree
- t.index ["usage", "namespace_id"], name: "index_internal_ids_on_usage_and_namespace_id", unique: true, where: "(namespace_id IS NOT NULL)", using: :btree
- t.index ["usage", "project_id"], name: "index_internal_ids_on_usage_and_project_id", unique: true, where: "(project_id IS NOT NULL)", using: :btree
+ t.index ["namespace_id"], name: "index_internal_ids_on_namespace_id"
+ t.index ["project_id"], name: "index_internal_ids_on_project_id"
+ t.index ["usage", "namespace_id"], name: "index_internal_ids_on_usage_and_namespace_id", unique: true, where: "(namespace_id IS NOT NULL)"
+ t.index ["usage", "project_id"], name: "index_internal_ids_on_usage_and_project_id", unique: true, where: "(project_id IS NOT NULL)"
end
create_table "ip_restrictions", force: :cascade do |t|
t.integer "group_id", null: false
t.string "range", null: false
- t.index ["group_id"], name: "index_ip_restrictions_on_group_id", using: :btree
+ t.index ["group_id"], name: "index_ip_restrictions_on_group_id"
end
create_table "issue_assignees", id: false, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "issue_id", null: false
- t.index ["issue_id", "user_id"], name: "index_issue_assignees_on_issue_id_and_user_id", unique: true, using: :btree
- t.index ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree
+ t.index ["issue_id", "user_id"], name: "index_issue_assignees_on_issue_id_and_user_id", unique: true
+ t.index ["user_id"], name: "index_issue_assignees_on_user_id"
end
create_table "issue_links", id: :serial, force: :cascade do |t|
@@ -1617,9 +1710,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "target_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["source_id", "target_id"], name: "index_issue_links_on_source_id_and_target_id", unique: true, using: :btree
- t.index ["source_id"], name: "index_issue_links_on_source_id", using: :btree
- t.index ["target_id"], name: "index_issue_links_on_target_id", using: :btree
+ t.index ["source_id", "target_id"], name: "index_issue_links_on_source_id_and_target_id", unique: true
+ t.index ["source_id"], name: "index_issue_links_on_source_id"
+ t.index ["target_id"], name: "index_issue_links_on_target_id"
end
create_table "issue_metrics", id: :serial, force: :cascade do |t|
@@ -1629,7 +1722,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "first_added_to_board_at"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["issue_id"], name: "index_issue_metrics", using: :btree
+ t.index ["issue_id"], name: "index_issue_metrics"
end
create_table "issue_tracker_data", force: :cascade do |t|
@@ -1642,7 +1735,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_issues_url_iv"
t.string "encrypted_new_issue_url"
t.string "encrypted_new_issue_url_iv"
- t.index ["service_id"], name: "index_issue_tracker_data_on_service_id", using: :btree
+ t.index ["service_id"], name: "index_issue_tracker_data_on_service_id"
end
create_table "issues", id: :serial, force: :cascade do |t|
@@ -1673,21 +1766,22 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "state_id", limit: 2
t.string "service_desk_reply_to"
t.integer "weight"
- t.index ["author_id"], name: "index_issues_on_author_id", using: :btree
- t.index ["closed_by_id"], name: "index_issues_on_closed_by_id", using: :btree
- t.index ["confidential"], name: "index_issues_on_confidential", using: :btree
- t.index ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
- t.index ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
- t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree
- t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state", using: :btree
- t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)", using: :btree
- t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
- t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state", using: :btree
- t.index ["relative_position"], name: "index_issues_on_relative_position", using: :btree
- t.index ["state"], name: "index_issues_on_state", using: :btree
- t.index ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
- t.index ["updated_at"], name: "index_issues_on_updated_at", using: :btree
- t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
+ t.index ["author_id"], name: "index_issues_on_author_id"
+ t.index ["closed_by_id"], name: "index_issues_on_closed_by_id"
+ t.index ["confidential"], name: "index_issues_on_confidential"
+ t.index ["description"], name: "index_issues_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["milestone_id"], name: "index_issues_on_milestone_id"
+ t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)"
+ t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state"
+ t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)"
+ t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true
+ t.index ["project_id", "relative_position", "state", "id"], name: "index_issues_on_project_id_and_rel_position_and_state_and_id", order: { id: :desc }
+ t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state"
+ t.index ["relative_position"], name: "index_issues_on_relative_position"
+ t.index ["state"], name: "index_issues_on_state"
+ t.index ["title"], name: "index_issues_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["updated_at"], name: "index_issues_on_updated_at"
+ t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
end
create_table "jira_connect_installations", force: :cascade do |t|
@@ -1695,7 +1789,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_shared_secret"
t.string "encrypted_shared_secret_iv"
t.string "base_url"
- t.index ["client_key"], name: "index_jira_connect_installations_on_client_key", unique: true, using: :btree
+ t.index ["client_key"], name: "index_jira_connect_installations_on_client_key", unique: true
end
create_table "jira_connect_subscriptions", force: :cascade do |t|
@@ -1703,9 +1797,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "jira_connect_installation_id", null: false
t.integer "namespace_id", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["jira_connect_installation_id", "namespace_id"], name: "idx_jira_connect_subscriptions_on_installation_id_namespace_id", unique: true, using: :btree
- t.index ["jira_connect_installation_id"], name: "idx_jira_connect_subscriptions_on_installation_id", using: :btree
- t.index ["namespace_id"], name: "index_jira_connect_subscriptions_on_namespace_id", using: :btree
+ t.index ["jira_connect_installation_id", "namespace_id"], name: "idx_jira_connect_subscriptions_on_installation_id_namespace_id", unique: true
+ t.index ["jira_connect_installation_id"], name: "idx_jira_connect_subscriptions_on_installation_id"
+ t.index ["namespace_id"], name: "index_jira_connect_subscriptions_on_namespace_id"
end
create_table "jira_tracker_data", force: :cascade do |t|
@@ -1721,7 +1815,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_password"
t.string "encrypted_password_iv"
t.string "jira_issue_transition_id"
- t.index ["service_id"], name: "index_jira_tracker_data_on_service_id", using: :btree
+ t.index ["service_id"], name: "index_jira_tracker_data_on_service_id"
end
create_table "keys", id: :serial, force: :cascade do |t|
@@ -1734,8 +1828,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "fingerprint"
t.boolean "public", default: false, null: false
t.datetime "last_used_at"
- t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree
- t.index ["user_id"], name: "index_keys_on_user_id", using: :btree
+ t.index ["fingerprint"], name: "index_keys_on_fingerprint", unique: true
+ t.index ["id", "type"], name: "index_on_deploy_keys_id_and_type_and_public", unique: true, where: "(public = true)"
+ t.index ["user_id"], name: "index_keys_on_user_id"
end
create_table "label_links", id: :serial, force: :cascade do |t|
@@ -1744,8 +1839,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "target_type"
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["label_id"], name: "index_label_links_on_label_id", using: :btree
- t.index ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
+ t.index ["label_id"], name: "index_label_links_on_label_id"
+ t.index ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type"
end
create_table "label_priorities", id: :serial, force: :cascade do |t|
@@ -1754,9 +1849,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "priority", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["label_id"], name: "index_label_priorities_on_label_id", using: :btree
- t.index ["priority"], name: "index_label_priorities_on_priority", using: :btree
- t.index ["project_id", "label_id"], name: "index_label_priorities_on_project_id_and_label_id", unique: true, using: :btree
+ t.index ["label_id"], name: "index_label_priorities_on_label_id"
+ t.index ["priority"], name: "index_label_priorities_on_priority"
+ t.index ["project_id", "label_id"], name: "index_label_priorities_on_project_id_and_label_id", unique: true
end
create_table "labels", id: :serial, force: :cascade do |t|
@@ -1771,11 +1866,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "type"
t.integer "group_id"
t.integer "cached_markdown_version"
- t.index ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true, using: :btree
- t.index ["project_id"], name: "index_labels_on_project_id", using: :btree
- t.index ["template"], name: "index_labels_on_template", where: "template", using: :btree
- t.index ["title"], name: "index_labels_on_title", using: :btree
- t.index ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree
+ t.index ["group_id", "project_id", "title"], name: "index_labels_on_group_id_and_project_id_and_title", unique: true
+ t.index ["project_id"], name: "index_labels_on_project_id"
+ t.index ["template"], name: "index_labels_on_template", where: "template"
+ t.index ["title"], name: "index_labels_on_title"
+ t.index ["type", "project_id"], name: "index_labels_on_type_and_project_id"
end
create_table "ldap_group_links", id: :serial, force: :cascade do |t|
@@ -1793,8 +1888,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id", null: false
t.datetime "created_at", null: false
t.string "path", limit: 511
- t.index ["project_id", "path"], name: "index_lfs_file_locks_on_project_id_and_path", unique: true, using: :btree
- t.index ["user_id"], name: "index_lfs_file_locks_on_user_id", using: :btree
+ t.index ["project_id", "path"], name: "index_lfs_file_locks_on_project_id_and_path", unique: true
+ t.index ["user_id"], name: "index_lfs_file_locks_on_user_id"
end
create_table "lfs_objects", id: :serial, force: :cascade do |t|
@@ -1804,8 +1899,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at"
t.string "file"
t.integer "file_store"
- t.index ["file_store"], name: "index_lfs_objects_on_file_store", using: :btree
- t.index ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
+ t.index ["file_store"], name: "index_lfs_objects_on_file_store"
+ t.index ["oid"], name: "index_lfs_objects_on_oid", unique: true
end
create_table "lfs_objects_projects", id: :serial, force: :cascade do |t|
@@ -1814,8 +1909,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_type", limit: 2
- t.index ["lfs_object_id"], name: "index_lfs_objects_projects_on_lfs_object_id", using: :btree
- t.index ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
+ t.index ["lfs_object_id"], name: "index_lfs_objects_projects_on_lfs_object_id"
+ t.index ["project_id"], name: "index_lfs_objects_projects_on_project_id"
end
create_table "licenses", id: :serial, force: :cascade do |t|
@@ -1833,11 +1928,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at", null: false
t.integer "milestone_id"
t.integer "user_id"
- t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree
- t.index ["label_id"], name: "index_lists_on_label_id", using: :btree
- t.index ["list_type"], name: "index_lists_on_list_type", using: :btree
- t.index ["milestone_id"], name: "index_lists_on_milestone_id", using: :btree
- t.index ["user_id"], name: "index_lists_on_user_id", using: :btree
+ t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true
+ t.index ["label_id"], name: "index_lists_on_label_id"
+ t.index ["list_type"], name: "index_lists_on_list_type"
+ t.index ["milestone_id"], name: "index_lists_on_milestone_id"
+ t.index ["user_id"], name: "index_lists_on_user_id"
end
create_table "members", id: :serial, force: :cascade do |t|
@@ -1857,20 +1952,20 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.date "expires_at"
t.boolean "ldap", default: false, null: false
t.boolean "override", default: false, null: false
- t.index ["access_level"], name: "index_members_on_access_level", using: :btree
- t.index ["invite_email"], name: "index_members_on_invite_email", using: :btree
- t.index ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree
- t.index ["requested_at"], name: "index_members_on_requested_at", using: :btree
- t.index ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree
- t.index ["user_id"], name: "index_members_on_user_id", using: :btree
+ t.index ["access_level"], name: "index_members_on_access_level"
+ t.index ["invite_email"], name: "index_members_on_invite_email"
+ t.index ["invite_token"], name: "index_members_on_invite_token", unique: true
+ t.index ["requested_at"], name: "index_members_on_requested_at"
+ t.index ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type"
+ t.index ["user_id"], name: "index_members_on_user_id"
end
create_table "merge_request_assignees", force: :cascade do |t|
t.integer "user_id", null: false
t.integer "merge_request_id", null: false
- t.index ["merge_request_id", "user_id"], name: "index_merge_request_assignees_on_merge_request_id_and_user_id", unique: true, using: :btree
- t.index ["merge_request_id"], name: "index_merge_request_assignees_on_merge_request_id", using: :btree
- t.index ["user_id"], name: "index_merge_request_assignees_on_user_id", using: :btree
+ t.index ["merge_request_id", "user_id"], name: "index_merge_request_assignees_on_merge_request_id_and_user_id", unique: true
+ t.index ["merge_request_id"], name: "index_merge_request_assignees_on_merge_request_id"
+ t.index ["user_id"], name: "index_merge_request_assignees_on_user_id"
end
create_table "merge_request_blocks", force: :cascade do |t|
@@ -1878,8 +1973,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "blocked_merge_request_id", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["blocked_merge_request_id"], name: "index_merge_request_blocks_on_blocked_merge_request_id", using: :btree
- t.index ["blocking_merge_request_id", "blocked_merge_request_id"], name: "index_mr_blocks_on_blocking_and_blocked_mr_ids", unique: true, using: :btree
+ t.index ["blocked_merge_request_id"], name: "index_merge_request_blocks_on_blocked_merge_request_id"
+ t.index ["blocking_merge_request_id", "blocked_merge_request_id"], name: "index_mr_blocks_on_blocking_and_blocked_mr_ids", unique: true
end
create_table "merge_request_diff_commits", id: false, force: :cascade do |t|
@@ -1893,8 +1988,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "committer_name"
t.text "committer_email"
t.text "message"
- t.index ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true, using: :btree
- t.index ["sha"], name: "index_merge_request_diff_commits_on_sha", using: :btree
+ t.index ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_commits_on_mr_diff_id_and_order", unique: true
+ t.index ["sha"], name: "index_merge_request_diff_commits_on_sha"
end
create_table "merge_request_diff_files", id: false, force: :cascade do |t|
@@ -1912,7 +2007,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "binary"
t.integer "external_diff_offset"
t.integer "external_diff_size"
- t.index ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true, using: :btree
+ t.index ["merge_request_diff_id", "relative_order"], name: "index_merge_request_diff_files_on_mr_diff_id_and_order", unique: true
end
create_table "merge_request_diffs", id: :serial, force: :cascade do |t|
@@ -1928,8 +2023,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "external_diff"
t.integer "external_diff_store"
t.boolean "stored_externally"
- t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id", using: :btree
- t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id_partial", where: "((NOT stored_externally) OR (stored_externally IS NULL))", using: :btree
+ t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id"
+ t.index ["merge_request_id", "id"], name: "index_merge_request_diffs_on_merge_request_id_and_id_partial", where: "((NOT stored_externally) OR (stored_externally IS NULL))"
end
create_table "merge_request_metrics", id: :serial, force: :cascade do |t|
@@ -1944,13 +2039,19 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "merged_by_id"
t.integer "latest_closed_by_id"
t.datetime_with_timezone "latest_closed_at"
- t.index ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree
- t.index ["latest_closed_at"], name: "index_merge_request_metrics_on_latest_closed_at", where: "(latest_closed_at IS NOT NULL)", using: :btree
- t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id", using: :btree
- t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)", using: :btree
- t.index ["merge_request_id"], name: "index_merge_request_metrics", using: :btree
- t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id", using: :btree
- t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id", using: :btree
+ t.datetime_with_timezone "first_comment_at"
+ t.datetime_with_timezone "first_commit_at"
+ t.datetime_with_timezone "last_commit_at"
+ t.integer "diff_size"
+ t.integer "modified_paths_size"
+ t.integer "commits_count"
+ t.index ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at"
+ t.index ["latest_closed_at"], name: "index_merge_request_metrics_on_latest_closed_at", where: "(latest_closed_at IS NOT NULL)"
+ t.index ["latest_closed_by_id"], name: "index_merge_request_metrics_on_latest_closed_by_id"
+ t.index ["merge_request_id", "merged_at"], name: "index_merge_request_metrics_on_merge_request_id_and_merged_at", where: "(merged_at IS NOT NULL)"
+ t.index ["merge_request_id"], name: "index_merge_request_metrics"
+ t.index ["merged_by_id"], name: "index_merge_request_metrics_on_merged_by_id"
+ t.index ["pipeline_id"], name: "index_merge_request_metrics_on_pipeline_id"
end
create_table "merge_requests", id: :serial, force: :cascade do |t|
@@ -1992,26 +2093,26 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "state_id", limit: 2
t.integer "approvals_before_merge"
t.string "rebase_jid"
- t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
- t.index ["author_id"], name: "index_merge_requests_on_author_id", using: :btree
- t.index ["created_at"], name: "index_merge_requests_on_created_at", using: :btree
- t.index ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
- t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree
- t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))", using: :btree
- t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree
- t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)", using: :btree
- t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree
- t.index ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree
- t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)", using: :btree
- t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch", using: :btree
- t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))", using: :btree
- t.index ["target_branch"], name: "index_merge_requests_on_target_branch", using: :btree
- t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true, using: :btree
- t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)", using: :btree
- t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id", using: :btree
- t.index ["title"], name: "index_merge_requests_on_title", using: :btree
- t.index ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
- t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
+ t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id"
+ t.index ["author_id"], name: "index_merge_requests_on_author_id"
+ t.index ["created_at"], name: "index_merge_requests_on_created_at"
+ t.index ["description"], name: "index_merge_requests_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id"
+ t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))"
+ t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id"
+ t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)"
+ t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id"
+ t.index ["source_branch"], name: "index_merge_requests_on_source_branch"
+ t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)"
+ t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch"
+ t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))"
+ t.index ["target_branch"], name: "index_merge_requests_on_target_branch"
+ t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
+ t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
+ t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"
+ t.index ["title"], name: "index_merge_requests_on_title"
+ t.index ["title"], name: "index_merge_requests_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["updated_by_id"], name: "index_merge_requests_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
end
create_table "merge_requests_closing_issues", id: :serial, force: :cascade do |t|
@@ -2019,8 +2120,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "issue_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id", using: :btree
- t.index ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree
+ t.index ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id"
+ t.index ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id"
end
create_table "merge_trains", force: :cascade do |t|
@@ -2031,10 +2132,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.integer "target_project_id", null: false
t.text "target_branch", null: false
- t.index ["merge_request_id"], name: "index_merge_trains_on_merge_request_id", unique: true, using: :btree
- t.index ["pipeline_id"], name: "index_merge_trains_on_pipeline_id", using: :btree
- t.index ["target_project_id"], name: "index_merge_trains_on_target_project_id", using: :btree
- t.index ["user_id"], name: "index_merge_trains_on_user_id", using: :btree
+ t.index ["merge_request_id"], name: "index_merge_trains_on_merge_request_id", unique: true
+ t.index ["pipeline_id"], name: "index_merge_trains_on_pipeline_id"
+ t.index ["target_project_id"], name: "index_merge_trains_on_target_project_id"
+ t.index ["user_id"], name: "index_merge_trains_on_user_id"
end
create_table "milestones", id: :serial, force: :cascade do |t|
@@ -2051,16 +2152,16 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.date "start_date"
t.integer "cached_markdown_version"
t.integer "group_id"
- t.index ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
- t.index ["due_date"], name: "index_milestones_on_due_date", using: :btree
- t.index ["group_id"], name: "index_milestones_on_group_id", using: :btree
- t.index ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree
- t.index ["title"], name: "index_milestones_on_title", using: :btree
- t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ t.index ["description"], name: "index_milestones_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["due_date"], name: "index_milestones_on_due_date"
+ t.index ["group_id"], name: "index_milestones_on_group_id"
+ t.index ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true
+ t.index ["title"], name: "index_milestones_on_title"
+ t.index ["title"], name: "index_milestones_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
end
create_table "namespace_aggregation_schedules", primary_key: "namespace_id", id: :integer, default: nil, force: :cascade do |t|
- t.index ["namespace_id"], name: "index_namespace_aggregation_schedules_on_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_namespace_aggregation_schedules_on_namespace_id", unique: true
end
create_table "namespace_root_storage_statistics", primary_key: "namespace_id", id: :integer, default: nil, force: :cascade do |t|
@@ -2071,14 +2172,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "build_artifacts_size", default: 0, null: false
t.bigint "storage_size", default: 0, null: false
t.bigint "packages_size", default: 0, null: false
- t.index ["namespace_id"], name: "index_namespace_root_storage_statistics_on_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_namespace_root_storage_statistics_on_namespace_id", unique: true
end
create_table "namespace_statistics", id: :serial, force: :cascade do |t|
t.integer "namespace_id", null: false
t.integer "shared_runners_seconds", default: 0, null: false
t.datetime "shared_runners_seconds_last_reset"
- t.index ["namespace_id"], name: "index_namespace_statistics_on_namespace_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_namespace_statistics_on_namespace_id", unique: true
end
create_table "namespaces", id: :serial, force: :cascade do |t|
@@ -2119,24 +2220,26 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "ldap_sync_status", default: "ready", null: false
t.boolean "membership_lock", default: false
t.integer "last_ci_minutes_usage_notification_level"
- t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree
- t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)", using: :btree
- t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id", using: :btree
- t.index ["ldap_sync_last_successful_update_at"], name: "index_namespaces_on_ldap_sync_last_successful_update_at", using: :btree
- t.index ["ldap_sync_last_update_at"], name: "index_namespaces_on_ldap_sync_last_update_at", using: :btree
- t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree
- t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
- t.index ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
- t.index ["parent_id", "id"], name: "index_namespaces_on_parent_id_and_id", unique: true, using: :btree
- t.index ["path"], name: "index_namespaces_on_path", using: :btree
- t.index ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
- t.index ["plan_id"], name: "index_namespaces_on_plan_id", using: :btree
- t.index ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication", using: :btree
- t.index ["runners_token"], name: "index_namespaces_on_runners_token", unique: true, using: :btree
- t.index ["runners_token_encrypted"], name: "index_namespaces_on_runners_token_encrypted", unique: true, using: :btree
- t.index ["shared_runners_minutes_limit", "extra_shared_runners_minutes_limit"], name: "index_namespaces_on_shared_and_extra_runners_minutes_limit", using: :btree
- t.index ["trial_ends_on"], name: "index_namespaces_on_trial_ends_on", where: "(trial_ends_on IS NOT NULL)", using: :btree
- t.index ["type"], name: "index_namespaces_on_type", using: :btree
+ t.integer "subgroup_creation_level", default: 1
+ t.boolean "emails_disabled"
+ t.index ["created_at"], name: "index_namespaces_on_created_at"
+ t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)"
+ t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id"
+ t.index ["ldap_sync_last_successful_update_at"], name: "index_namespaces_on_ldap_sync_last_successful_update_at"
+ t.index ["ldap_sync_last_update_at"], name: "index_namespaces_on_ldap_sync_last_update_at"
+ t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true
+ t.index ["name"], name: "index_namespaces_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["owner_id"], name: "index_namespaces_on_owner_id"
+ t.index ["parent_id", "id"], name: "index_namespaces_on_parent_id_and_id", unique: true
+ t.index ["path"], name: "index_namespaces_on_path"
+ t.index ["path"], name: "index_namespaces_on_path_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["plan_id"], name: "index_namespaces_on_plan_id"
+ t.index ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication"
+ t.index ["runners_token"], name: "index_namespaces_on_runners_token", unique: true
+ t.index ["runners_token_encrypted"], name: "index_namespaces_on_runners_token_encrypted", unique: true
+ t.index ["shared_runners_minutes_limit", "extra_shared_runners_minutes_limit"], name: "index_namespaces_on_shared_and_extra_runners_minutes_limit"
+ t.index ["trial_ends_on"], name: "index_namespaces_on_trial_ends_on", where: "(trial_ends_on IS NOT NULL)"
+ t.index ["type"], name: "index_namespaces_on_type"
end
create_table "note_diff_files", id: :serial, force: :cascade do |t|
@@ -2149,7 +2252,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "b_mode", null: false
t.text "new_path", null: false
t.text "old_path", null: false
- t.index ["diff_note_id"], name: "index_note_diff_files_on_diff_note_id", unique: true, using: :btree
+ t.index ["diff_note_id"], name: "index_note_diff_files_on_diff_note_id", unique: true
end
create_table "notes", id: :serial, force: :cascade do |t|
@@ -2177,16 +2280,16 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "change_position"
t.boolean "resolved_by_push"
t.bigint "review_id"
- t.index ["author_id"], name: "index_notes_on_author_id", using: :btree
- t.index ["commit_id"], name: "index_notes_on_commit_id", using: :btree
- t.index ["created_at"], name: "index_notes_on_created_at", using: :btree
- t.index ["discussion_id"], name: "index_notes_on_discussion_id", using: :btree
- t.index ["line_code"], name: "index_notes_on_line_code", using: :btree
- t.index ["note"], name: "index_notes_on_note_trigram", using: :gin, opclasses: {"note"=>"gin_trgm_ops"}
- t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree
- t.index ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree
- t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree
- t.index ["review_id"], name: "index_notes_on_review_id", using: :btree
+ t.index ["author_id"], name: "index_notes_on_author_id"
+ t.index ["commit_id"], name: "index_notes_on_commit_id"
+ t.index ["created_at"], name: "index_notes_on_created_at"
+ t.index ["discussion_id"], name: "index_notes_on_discussion_id"
+ t.index ["line_code"], name: "index_notes_on_line_code"
+ t.index ["note"], name: "index_notes_on_note_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type"
+ t.index ["project_id", "id"], name: "index_notes_on_project_id_and_id_and_system_false", where: "(NOT system)"
+ t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type"
+ t.index ["review_id"], name: "index_notes_on_review_id"
end
create_table "notification_settings", id: :serial, force: :cascade do |t|
@@ -2212,9 +2315,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "issue_due"
t.string "notification_email"
t.boolean "new_epic"
- t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree
- t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree
- t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree
+ t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type"
+ t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true
+ t.index ["user_id"], name: "index_notification_settings_on_user_id"
end
create_table "oauth_access_grants", id: :serial, force: :cascade do |t|
@@ -2226,7 +2329,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.string "scopes"
- t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
+ t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
create_table "oauth_access_tokens", id: :serial, force: :cascade do |t|
@@ -2238,9 +2341,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "revoked_at"
t.datetime "created_at", null: false
t.string "scopes"
- t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
- t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree
- t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
+ t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
+ t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
+ t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
create_table "oauth_applications", id: :serial, force: :cascade do |t|
@@ -2254,14 +2357,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "owner_id"
t.string "owner_type"
t.boolean "trusted", default: false, null: false
- t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
- t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
+ t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type"
+ t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
create_table "oauth_openid_requests", id: :serial, force: :cascade do |t|
t.integer "access_grant_id", null: false
t.string "nonce", null: false
- t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id", using: :btree
+ t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id"
end
create_table "operations_feature_flag_scopes", force: :cascade do |t|
@@ -2271,7 +2374,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "active", null: false
t.string "environment_scope", default: "*", null: false
t.jsonb "strategies", default: [{"name"=>"default", "parameters"=>{}}], null: false
- t.index ["feature_flag_id", "environment_scope"], name: "index_feature_flag_scopes_on_flag_id_and_environment_scope", unique: true, using: :btree
+ t.index ["feature_flag_id", "environment_scope"], name: "index_feature_flag_scopes_on_flag_id_and_environment_scope", unique: true
end
create_table "operations_feature_flags", force: :cascade do |t|
@@ -2281,15 +2384,15 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.string "name", null: false
t.text "description"
- t.index ["project_id", "name"], name: "index_operations_feature_flags_on_project_id_and_name", unique: true, using: :btree
+ t.index ["project_id", "name"], name: "index_operations_feature_flags_on_project_id_and_name", unique: true
end
create_table "operations_feature_flags_clients", force: :cascade do |t|
t.integer "project_id", null: false
t.string "token"
t.string "token_encrypted"
- t.index ["project_id", "token"], name: "index_operations_feature_flags_clients_on_project_id_and_token", unique: true, using: :btree
- t.index ["project_id", "token_encrypted"], name: "index_feature_flags_clients_on_project_id_and_token_encrypted", unique: true, using: :btree
+ t.index ["project_id", "token"], name: "index_operations_feature_flags_clients_on_project_id_and_token", unique: true
+ t.index ["project_id", "token_encrypted"], name: "index_feature_flags_clients_on_project_id_and_token_encrypted", unique: true
end
create_table "packages_maven_metadata", force: :cascade do |t|
@@ -2300,7 +2403,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "app_name", null: false
t.string "app_version"
t.string "path", limit: 512, null: false
- t.index ["package_id", "path"], name: "index_packages_maven_metadata_on_package_id_and_path", using: :btree
+ t.index ["package_id", "path"], name: "index_packages_maven_metadata_on_package_id_and_path"
end
create_table "packages_package_files", force: :cascade do |t|
@@ -2314,7 +2417,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.binary "file_sha1"
t.string "file_name", null: false
t.text "file", null: false
- t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name", using: :btree
+ t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name"
end
create_table "packages_packages", force: :cascade do |t|
@@ -2324,7 +2427,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.string "version"
t.integer "package_type", limit: 2, null: false
- t.index ["project_id"], name: "index_packages_packages_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_packages_packages_on_project_id"
end
create_table "pages_domain_acme_orders", force: :cascade do |t|
@@ -2337,8 +2440,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "challenge_file_content", null: false
t.text "encrypted_private_key", null: false
t.text "encrypted_private_key_iv", null: false
- t.index ["challenge_token"], name: "index_pages_domain_acme_orders_on_challenge_token", using: :btree
- t.index ["pages_domain_id"], name: "index_pages_domain_acme_orders_on_pages_domain_id", using: :btree
+ t.index ["challenge_token"], name: "index_pages_domain_acme_orders_on_challenge_token"
+ t.index ["pages_domain_id"], name: "index_pages_domain_acme_orders_on_pages_domain_id"
end
create_table "pages_domains", id: :serial, force: :cascade do |t|
@@ -2356,13 +2459,13 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "certificate_valid_not_before"
t.datetime_with_timezone "certificate_valid_not_after"
t.integer "certificate_source", limit: 2, default: 0, null: false
- t.index ["certificate_source", "certificate_valid_not_after"], name: "index_pages_domains_need_auto_ssl_renewal", where: "(auto_ssl_enabled = true)", using: :btree
- t.index ["domain"], name: "index_pages_domains_on_domain", unique: true, using: :btree
- t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until", using: :btree
- t.index ["project_id"], name: "index_pages_domains_on_project_id", using: :btree
- t.index ["remove_at"], name: "index_pages_domains_on_remove_at", using: :btree
- t.index ["verified_at", "enabled_until"], name: "index_pages_domains_on_verified_at_and_enabled_until", using: :btree
- t.index ["verified_at"], name: "index_pages_domains_on_verified_at", using: :btree
+ t.index ["certificate_source", "certificate_valid_not_after"], name: "index_pages_domains_need_auto_ssl_renewal", where: "(auto_ssl_enabled = true)"
+ t.index ["domain"], name: "index_pages_domains_on_domain", unique: true
+ t.index ["project_id", "enabled_until"], name: "index_pages_domains_on_project_id_and_enabled_until"
+ t.index ["project_id"], name: "index_pages_domains_on_project_id"
+ t.index ["remove_at"], name: "index_pages_domains_on_remove_at"
+ t.index ["verified_at", "enabled_until"], name: "index_pages_domains_on_verified_at_and_enabled_until"
+ t.index ["verified_at"], name: "index_pages_domains_on_verified_at"
end
create_table "path_locks", id: :serial, force: :cascade do |t|
@@ -2371,9 +2474,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["path"], name: "index_path_locks_on_path", using: :btree
- t.index ["project_id"], name: "index_path_locks_on_project_id", using: :btree
- t.index ["user_id"], name: "index_path_locks_on_user_id", using: :btree
+ t.index ["path"], name: "index_path_locks_on_path"
+ t.index ["project_id"], name: "index_path_locks_on_project_id"
+ t.index ["user_id"], name: "index_path_locks_on_user_id"
end
create_table "personal_access_tokens", id: :serial, force: :cascade do |t|
@@ -2386,8 +2489,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "scopes", default: "--- []\n", null: false
t.boolean "impersonation", default: false, null: false
t.string "token_digest"
- t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true, using: :btree
- t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree
+ t.index ["token_digest"], name: "index_personal_access_tokens_on_token_digest", unique: true
+ t.index ["user_id"], name: "index_personal_access_tokens_on_user_id"
end
create_table "plans", id: :serial, force: :cascade do |t|
@@ -2397,7 +2500,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "title"
t.integer "active_pipelines_limit"
t.integer "pipeline_size_limit"
- t.index ["name"], name: "index_plans_on_name", using: :btree
+ t.index ["name"], name: "index_plans_on_name"
end
create_table "pool_repositories", force: :cascade do |t|
@@ -2405,16 +2508,16 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "disk_path"
t.string "state"
t.integer "source_project_id"
- t.index ["disk_path"], name: "index_pool_repositories_on_disk_path", unique: true, using: :btree
- t.index ["shard_id"], name: "index_pool_repositories_on_shard_id", using: :btree
- t.index ["source_project_id"], name: "index_pool_repositories_on_source_project_id", unique: true, using: :btree
+ t.index ["disk_path"], name: "index_pool_repositories_on_disk_path", unique: true
+ t.index ["shard_id"], name: "index_pool_repositories_on_shard_id"
+ t.index ["source_project_id"], name: "index_pool_repositories_on_source_project_id", unique: true
end
create_table "programming_languages", id: :serial, force: :cascade do |t|
t.string "name", null: false
t.string "color", null: false
t.datetime_with_timezone "created_at", null: false
- t.index ["name"], name: "index_programming_languages_on_name", unique: true, using: :btree
+ t.index ["name"], name: "index_programming_languages_on_name", unique: true
end
create_table "project_alerting_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
@@ -2427,16 +2530,16 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["name"], name: "index_project_aliases_on_name", unique: true, using: :btree
- t.index ["project_id"], name: "index_project_aliases_on_project_id", using: :btree
+ t.index ["name"], name: "index_project_aliases_on_name", unique: true
+ t.index ["project_id"], name: "index_project_aliases_on_project_id"
end
create_table "project_authorizations", id: false, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "project_id", null: false
t.integer "access_level", null: false
- t.index ["project_id"], name: "index_project_authorizations_on_project_id", using: :btree
- t.index ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_authorizations_on_project_id"
+ t.index ["user_id", "project_id", "access_level"], name: "index_project_authorizations_on_user_id_project_id_access_level", unique: true
end
create_table "project_auto_devops", id: :serial, force: :cascade do |t|
@@ -2445,7 +2548,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.boolean "enabled"
t.integer "deploy_strategy", default: 0, null: false
- t.index ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_auto_devops_on_project_id", unique: true
end
create_table "project_ci_cd_settings", id: :serial, force: :cascade do |t|
@@ -2454,7 +2557,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "merge_pipelines_enabled"
t.boolean "merge_trains_enabled", default: false, null: false
t.integer "default_git_depth"
- t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_ci_cd_settings_on_project_id", unique: true
end
create_table "project_custom_attributes", id: :serial, force: :cascade do |t|
@@ -2463,23 +2566,23 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "project_id", null: false
t.string "key", null: false
t.string "value", null: false
- t.index ["key", "value"], name: "index_project_custom_attributes_on_key_and_value", using: :btree
- t.index ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
+ t.index ["key", "value"], name: "index_project_custom_attributes_on_key_and_value"
+ t.index ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true
end
create_table "project_daily_statistics", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "fetch_count", null: false
t.date "date"
- t.index ["project_id", "date"], name: "index_project_daily_statistics_on_project_id_and_date", unique: true, order: { date: :desc }, using: :btree
+ t.index ["project_id", "date"], name: "index_project_daily_statistics_on_project_id_and_date", unique: true, order: { date: :desc }
end
create_table "project_deploy_tokens", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "deploy_token_id", null: false
t.datetime_with_timezone "created_at", null: false
- t.index ["deploy_token_id"], name: "index_project_deploy_tokens_on_deploy_token_id", using: :btree
- t.index ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true, using: :btree
+ t.index ["deploy_token_id"], name: "index_project_deploy_tokens_on_deploy_token_id"
+ t.index ["project_id", "deploy_token_id"], name: "index_project_deploy_tokens_on_project_id_and_deploy_token_id", unique: true
end
create_table "project_error_tracking_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
@@ -2494,9 +2597,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
create_table "project_feature_usages", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
t.datetime "jira_dvcs_cloud_last_sync_at"
t.datetime "jira_dvcs_server_last_sync_at"
- t.index ["jira_dvcs_cloud_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)", using: :btree
- t.index ["jira_dvcs_server_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)", using: :btree
- t.index ["project_id"], name: "index_project_feature_usages_on_project_id", using: :btree
+ t.index ["jira_dvcs_cloud_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)"
+ t.index ["jira_dvcs_server_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)"
+ t.index ["project_id"], name: "index_project_feature_usages_on_project_id"
end
create_table "project_features", id: :serial, force: :cascade do |t|
@@ -2510,7 +2613,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
t.integer "pages_access_level", null: false
- t.index ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_features_on_project_id", unique: true
end
create_table "project_group_links", id: :serial, force: :cascade do |t|
@@ -2520,8 +2623,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at"
t.integer "group_access", default: 30, null: false
t.date "expires_at"
- t.index ["group_id"], name: "index_project_group_links_on_group_id", using: :btree
- t.index ["project_id"], name: "index_project_group_links_on_project_id", using: :btree
+ t.index ["group_id"], name: "index_project_group_links_on_group_id"
+ t.index ["project_id"], name: "index_project_group_links_on_project_id"
end
create_table "project_import_data", id: :serial, force: :cascade do |t|
@@ -2530,7 +2633,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "encrypted_credentials"
t.string "encrypted_credentials_iv"
t.string "encrypted_credentials_salt"
- t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_project_import_data_on_project_id"
end
create_table "project_incident_management_settings", primary_key: "project_id", id: :serial, force: :cascade do |t|
@@ -2554,21 +2657,21 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "last_update_started_at"
t.datetime "next_execution_timestamp"
t.integer "retry_count", default: 0, null: false
- t.index ["jid"], name: "index_project_mirror_data_on_jid", using: :btree
- t.index ["last_successful_update_at"], name: "index_project_mirror_data_on_last_successful_update_at", using: :btree
- t.index ["last_update_at", "retry_count"], name: "index_project_mirror_data_on_last_update_at_and_retry_count", using: :btree
- t.index ["next_execution_timestamp", "retry_count"], name: "index_mirror_data_on_next_execution_and_retry_count", using: :btree
- t.index ["project_id"], name: "index_project_mirror_data_on_project_id", unique: true, using: :btree
- t.index ["status"], name: "index_project_mirror_data_on_status", using: :btree
+ t.index ["jid"], name: "index_project_mirror_data_on_jid"
+ t.index ["last_successful_update_at"], name: "index_project_mirror_data_on_last_successful_update_at"
+ t.index ["last_update_at", "retry_count"], name: "index_project_mirror_data_on_last_update_at_and_retry_count"
+ t.index ["next_execution_timestamp", "retry_count"], name: "index_mirror_data_on_next_execution_and_retry_count"
+ t.index ["project_id"], name: "index_project_mirror_data_on_project_id", unique: true
+ t.index ["status"], name: "index_project_mirror_data_on_status"
end
create_table "project_repositories", force: :cascade do |t|
t.integer "shard_id", null: false
t.string "disk_path", null: false
t.integer "project_id", null: false
- t.index ["disk_path"], name: "index_project_repositories_on_disk_path", unique: true, using: :btree
- t.index ["project_id"], name: "index_project_repositories_on_project_id", unique: true, using: :btree
- t.index ["shard_id"], name: "index_project_repositories_on_shard_id", using: :btree
+ t.index ["disk_path"], name: "index_project_repositories_on_disk_path", unique: true
+ t.index ["project_id"], name: "index_project_repositories_on_project_id", unique: true
+ t.index ["shard_id"], name: "index_project_repositories_on_shard_id"
end
create_table "project_repository_states", id: :serial, force: :cascade do |t|
@@ -2583,12 +2686,12 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "wiki_retry_count"
t.datetime_with_timezone "last_repository_verification_ran_at"
t.datetime_with_timezone "last_wiki_verification_ran_at"
- t.index ["last_repository_verification_failure"], name: "idx_repository_states_on_repository_failure_partial", where: "(last_repository_verification_failure IS NOT NULL)", using: :btree
- t.index ["last_wiki_verification_failure"], name: "idx_repository_states_on_wiki_failure_partial", where: "(last_wiki_verification_failure IS NOT NULL)", using: :btree
- t.index ["project_id", "last_repository_verification_ran_at"], name: "idx_repository_states_on_last_repository_verification_ran_at", where: "((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL))", using: :btree
- t.index ["project_id", "last_wiki_verification_ran_at"], name: "idx_repository_states_on_last_wiki_verification_ran_at", where: "((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL))", using: :btree
- t.index ["project_id"], name: "idx_repository_states_outdated_checksums", where: "(((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL)))", using: :btree
- t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true, using: :btree
+ t.index ["last_repository_verification_failure"], name: "idx_repository_states_on_repository_failure_partial", where: "(last_repository_verification_failure IS NOT NULL)"
+ t.index ["last_wiki_verification_failure"], name: "idx_repository_states_on_wiki_failure_partial", where: "(last_wiki_verification_failure IS NOT NULL)"
+ t.index ["project_id", "last_repository_verification_ran_at"], name: "idx_repository_states_on_last_repository_verification_ran_at", where: "((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL))"
+ t.index ["project_id", "last_wiki_verification_ran_at"], name: "idx_repository_states_on_last_wiki_verification_ran_at", where: "((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL))"
+ t.index ["project_id"], name: "idx_repository_states_outdated_checksums", where: "(((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL)))"
+ t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true
end
create_table "project_statistics", id: :serial, force: :cascade do |t|
@@ -2603,8 +2706,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.bigint "wiki_size"
t.bigint "shared_runners_seconds", default: 0, null: false
t.datetime "shared_runners_seconds_last_reset"
- t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree
- t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree
+ t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id"
+ t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true
end
create_table "project_tracing_settings", force: :cascade do |t|
@@ -2612,7 +2715,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.integer "project_id", null: false
t.string "external_url", null: false
- t.index ["project_id"], name: "index_project_tracing_settings_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_project_tracing_settings_on_project_id", unique: true
end
create_table "projects", id: :serial, force: :cascade do |t|
@@ -2689,31 +2792,32 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "reset_approvals_on_push", default: true
t.boolean "service_desk_enabled", default: true
t.integer "approvals_before_merge", default: 0, null: false
- t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))", using: :btree
- t.index ["created_at"], name: "index_projects_on_created_at", using: :btree
- t.index ["creator_id"], name: "index_projects_on_creator_id", using: :btree
- t.index ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"}
- t.index ["id", "repository_storage", "last_repository_updated_at"], name: "idx_projects_on_repository_storage_last_repository_updated_at", using: :btree
- t.index ["id"], name: "index_projects_on_id_partial_for_visibility", unique: true, where: "(visibility_level = ANY (ARRAY[10, 20]))", using: :btree
- t.index ["id"], name: "index_projects_on_mirror_and_mirror_trigger_builds_both_true", where: "((mirror IS TRUE) AND (mirror_trigger_builds IS TRUE))", using: :btree
- t.index ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree
- t.index ["last_repository_check_at"], name: "index_projects_on_last_repository_check_at", where: "(last_repository_check_at IS NOT NULL)", using: :btree
- t.index ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree
- t.index ["last_repository_updated_at"], name: "index_projects_on_last_repository_updated_at", using: :btree
- t.index ["mirror_last_successful_update_at"], name: "index_projects_on_mirror_last_successful_update_at", using: :btree
- t.index ["mirror_user_id"], name: "index_projects_on_mirror_user_id", using: :btree
- t.index ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
- t.index ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
- t.index ["path"], name: "index_projects_on_path", using: :btree
- t.index ["path"], name: "index_projects_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"}
- t.index ["pending_delete"], name: "index_projects_on_pending_delete", using: :btree
- t.index ["pool_repository_id"], name: "index_projects_on_pool_repository_id", where: "(pool_repository_id IS NOT NULL)", using: :btree
- t.index ["repository_storage", "created_at"], name: "idx_project_repository_check_partial", where: "(last_repository_check_at IS NULL)", using: :btree
- t.index ["repository_storage"], name: "index_projects_on_repository_storage", using: :btree
- t.index ["runners_token"], name: "index_projects_on_runners_token", using: :btree
- t.index ["runners_token_encrypted"], name: "index_projects_on_runners_token_encrypted", using: :btree
- t.index ["star_count"], name: "index_projects_on_star_count", using: :btree
- t.index ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
+ t.boolean "emails_disabled"
+ t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))"
+ t.index ["created_at"], name: "index_projects_on_created_at"
+ t.index ["creator_id"], name: "index_projects_on_creator_id"
+ t.index ["description"], name: "index_projects_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["id", "repository_storage", "last_repository_updated_at"], name: "idx_projects_on_repository_storage_last_repository_updated_at"
+ t.index ["id"], name: "index_projects_on_id_partial_for_visibility", unique: true, where: "(visibility_level = ANY (ARRAY[10, 20]))"
+ t.index ["id"], name: "index_projects_on_mirror_and_mirror_trigger_builds_both_true", where: "((mirror IS TRUE) AND (mirror_trigger_builds IS TRUE))"
+ t.index ["last_activity_at"], name: "index_projects_on_last_activity_at"
+ t.index ["last_repository_check_at"], name: "index_projects_on_last_repository_check_at", where: "(last_repository_check_at IS NOT NULL)"
+ t.index ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed"
+ t.index ["last_repository_updated_at"], name: "index_projects_on_last_repository_updated_at"
+ t.index ["mirror_last_successful_update_at"], name: "index_projects_on_mirror_last_successful_update_at"
+ t.index ["mirror_user_id"], name: "index_projects_on_mirror_user_id"
+ t.index ["name"], name: "index_projects_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["namespace_id"], name: "index_projects_on_namespace_id"
+ t.index ["path"], name: "index_projects_on_path"
+ t.index ["path"], name: "index_projects_on_path_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["pending_delete"], name: "index_projects_on_pending_delete"
+ t.index ["pool_repository_id"], name: "index_projects_on_pool_repository_id", where: "(pool_repository_id IS NOT NULL)"
+ t.index ["repository_storage", "created_at"], name: "idx_project_repository_check_partial", where: "(last_repository_check_at IS NULL)"
+ t.index ["repository_storage"], name: "index_projects_on_repository_storage"
+ t.index ["runners_token"], name: "index_projects_on_runners_token"
+ t.index ["runners_token_encrypted"], name: "index_projects_on_runners_token_encrypted"
+ t.index ["star_count"], name: "index_projects_on_star_count"
+ t.index ["visibility_level"], name: "index_projects_on_visibility_level"
end
create_table "prometheus_alert_events", force: :cascade do |t|
@@ -2723,8 +2827,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "ended_at"
t.integer "status", limit: 2
t.string "payload_key"
- t.index ["project_id", "status"], name: "index_prometheus_alert_events_on_project_id_and_status", using: :btree
- t.index ["prometheus_alert_id", "payload_key"], name: "index_prometheus_alert_event_scoped_payload_key", unique: true, using: :btree
+ t.index ["project_id", "status"], name: "index_prometheus_alert_events_on_project_id_and_status"
+ t.index ["prometheus_alert_id", "payload_key"], name: "index_prometheus_alert_event_scoped_payload_key", unique: true
end
create_table "prometheus_alerts", id: :serial, force: :cascade do |t|
@@ -2735,9 +2839,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "environment_id", null: false
t.integer "project_id", null: false
t.integer "prometheus_metric_id", null: false
- t.index ["environment_id"], name: "index_prometheus_alerts_on_environment_id", using: :btree
- t.index ["project_id", "prometheus_metric_id", "environment_id"], name: "index_prometheus_alerts_metric_environment", unique: true, using: :btree
- t.index ["prometheus_metric_id"], name: "index_prometheus_alerts_on_prometheus_metric_id", using: :btree
+ t.index ["environment_id"], name: "index_prometheus_alerts_on_environment_id"
+ t.index ["project_id", "prometheus_metric_id", "environment_id"], name: "index_prometheus_alerts_metric_environment", unique: true
+ t.index ["prometheus_metric_id"], name: "index_prometheus_alerts_on_prometheus_metric_id"
end
create_table "prometheus_metrics", id: :serial, force: :cascade do |t|
@@ -2752,10 +2856,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.boolean "common", default: false, null: false
t.string "identifier"
- t.index ["common"], name: "index_prometheus_metrics_on_common", using: :btree
- t.index ["group"], name: "index_prometheus_metrics_on_group", using: :btree
- t.index ["identifier"], name: "index_prometheus_metrics_on_identifier", unique: true, using: :btree
- t.index ["project_id"], name: "index_prometheus_metrics_on_project_id", using: :btree
+ t.index ["common"], name: "index_prometheus_metrics_on_common"
+ t.index ["group"], name: "index_prometheus_metrics_on_group"
+ t.index ["identifier"], name: "index_prometheus_metrics_on_identifier", unique: true
+ t.index ["project_id"], name: "index_prometheus_metrics_on_project_id"
end
create_table "protected_branch_merge_access_levels", id: :serial, force: :cascade do |t|
@@ -2765,9 +2869,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at", null: false
t.integer "group_id"
t.integer "user_id"
- t.index ["group_id"], name: "index_protected_branch_merge_access_levels_on_group_id", using: :btree
- t.index ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree
- t.index ["user_id"], name: "index_protected_branch_merge_access_levels_on_user_id", using: :btree
+ t.index ["group_id"], name: "index_protected_branch_merge_access_levels_on_group_id"
+ t.index ["protected_branch_id"], name: "index_protected_branch_merge_access"
+ t.index ["user_id"], name: "index_protected_branch_merge_access_levels_on_user_id"
end
create_table "protected_branch_push_access_levels", id: :serial, force: :cascade do |t|
@@ -2777,9 +2881,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "updated_at", null: false
t.integer "group_id"
t.integer "user_id"
- t.index ["group_id"], name: "index_protected_branch_push_access_levels_on_group_id", using: :btree
- t.index ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree
- t.index ["user_id"], name: "index_protected_branch_push_access_levels_on_user_id", using: :btree
+ t.index ["group_id"], name: "index_protected_branch_push_access_levels_on_group_id"
+ t.index ["protected_branch_id"], name: "index_protected_branch_push_access"
+ t.index ["user_id"], name: "index_protected_branch_push_access_levels_on_user_id"
end
create_table "protected_branch_unprotect_access_levels", id: :serial, force: :cascade do |t|
@@ -2787,9 +2891,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "access_level", default: 40
t.integer "user_id"
t.integer "group_id"
- t.index ["group_id"], name: "index_protected_branch_unprotect_access_levels_on_group_id", using: :btree
- t.index ["protected_branch_id"], name: "index_protected_branch_unprotect_access", using: :btree
- t.index ["user_id"], name: "index_protected_branch_unprotect_access_levels_on_user_id", using: :btree
+ t.index ["group_id"], name: "index_protected_branch_unprotect_access_levels_on_group_id"
+ t.index ["protected_branch_id"], name: "index_protected_branch_unprotect_access"
+ t.index ["user_id"], name: "index_protected_branch_unprotect_access_levels_on_user_id"
end
create_table "protected_branches", id: :serial, force: :cascade do |t|
@@ -2797,7 +2901,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
+ t.index ["project_id"], name: "index_protected_branches_on_project_id"
end
create_table "protected_environment_deploy_access_levels", id: :serial, force: :cascade do |t|
@@ -2807,9 +2911,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "protected_environment_id", null: false
t.integer "user_id"
t.integer "group_id"
- t.index ["group_id"], name: "index_protected_environment_deploy_access_levels_on_group_id", using: :btree
- t.index ["protected_environment_id"], name: "index_protected_environment_deploy_access", using: :btree
- t.index ["user_id"], name: "index_protected_environment_deploy_access_levels_on_user_id", using: :btree
+ t.index ["group_id"], name: "index_protected_environment_deploy_access_levels_on_group_id"
+ t.index ["protected_environment_id"], name: "index_protected_environment_deploy_access"
+ t.index ["user_id"], name: "index_protected_environment_deploy_access_levels_on_user_id"
end
create_table "protected_environments", id: :serial, force: :cascade do |t|
@@ -2817,8 +2921,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
t.string "name", null: false
- t.index ["project_id", "name"], name: "index_protected_environments_on_project_id_and_name", unique: true, using: :btree
- t.index ["project_id"], name: "index_protected_environments_on_project_id", using: :btree
+ t.index ["project_id", "name"], name: "index_protected_environments_on_project_id_and_name", unique: true
+ t.index ["project_id"], name: "index_protected_environments_on_project_id"
end
create_table "protected_tag_create_access_levels", id: :serial, force: :cascade do |t|
@@ -2828,9 +2932,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "group_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["group_id"], name: "index_protected_tag_create_access_levels_on_group_id", using: :btree
- t.index ["protected_tag_id"], name: "index_protected_tag_create_access", using: :btree
- t.index ["user_id"], name: "index_protected_tag_create_access_levels_on_user_id", using: :btree
+ t.index ["group_id"], name: "index_protected_tag_create_access_levels_on_group_id"
+ t.index ["protected_tag_id"], name: "index_protected_tag_create_access"
+ t.index ["user_id"], name: "index_protected_tag_create_access_levels_on_user_id"
end
create_table "protected_tags", id: :serial, force: :cascade do |t|
@@ -2838,8 +2942,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["project_id", "name"], name: "index_protected_tags_on_project_id_and_name", unique: true, using: :btree
- t.index ["project_id"], name: "index_protected_tags_on_project_id", using: :btree
+ t.index ["project_id", "name"], name: "index_protected_tags_on_project_id_and_name", unique: true
+ t.index ["project_id"], name: "index_protected_tags_on_project_id"
end
create_table "push_event_payloads", id: false, force: :cascade do |t|
@@ -2851,7 +2955,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.binary "commit_to"
t.text "ref"
t.string "commit_title", limit: 70
- t.index ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true, using: :btree
+ t.index ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true
end
create_table "push_rules", id: :serial, force: :cascade do |t|
@@ -2873,8 +2977,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "commit_committer_check"
t.boolean "regexp_uses_re2", default: true
t.string "commit_message_negative_regex"
- t.index ["is_sample"], name: "index_push_rules_on_is_sample", where: "is_sample", using: :btree
- t.index ["project_id"], name: "index_push_rules_on_project_id", using: :btree
+ t.index ["is_sample"], name: "index_push_rules_on_is_sample", where: "is_sample"
+ t.index ["project_id"], name: "index_push_rules_on_project_id"
end
create_table "redirect_routes", id: :serial, force: :cascade do |t|
@@ -2883,8 +2987,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "path", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["path"], name: "index_redirect_routes_on_path", unique: true, using: :btree
- t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id", using: :btree
+ t.index ["path"], name: "index_redirect_routes_on_path", unique: true
+ t.index ["source_type", "source_id"], name: "index_redirect_routes_on_source_type_and_source_id"
end
create_table "release_links", force: :cascade do |t|
@@ -2893,8 +2997,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["release_id", "name"], name: "index_release_links_on_release_id_and_name", unique: true, using: :btree
- t.index ["release_id", "url"], name: "index_release_links_on_release_id_and_url", unique: true, using: :btree
+ t.index ["release_id", "name"], name: "index_release_links_on_release_id_and_name", unique: true
+ t.index ["release_id", "url"], name: "index_release_links_on_release_id_and_url", unique: true
end
create_table "releases", id: :serial, force: :cascade do |t|
@@ -2909,9 +3013,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name"
t.string "sha"
t.datetime_with_timezone "released_at", null: false
- t.index ["author_id"], name: "index_releases_on_author_id", using: :btree
- t.index ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree
- t.index ["project_id"], name: "index_releases_on_project_id", using: :btree
+ 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|
@@ -2931,15 +3035,15 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "error_notification_sent"
- t.index ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
- t.index ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
+ t.index ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at"
+ t.index ["project_id"], name: "index_remote_mirrors_on_project_id"
end
create_table "repository_languages", id: false, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "programming_language_id", null: false
t.float "share", null: false
- t.index ["project_id", "programming_language_id"], name: "index_repository_languages_on_project_and_languages_id", unique: true, using: :btree
+ t.index ["project_id", "programming_language_id"], name: "index_repository_languages_on_project_and_languages_id", unique: true
end
create_table "resource_label_events", force: :cascade do |t|
@@ -2953,11 +3057,11 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "reference"
t.text "reference_html"
t.integer "epic_id"
- t.index ["epic_id"], name: "index_resource_label_events_on_epic_id", using: :btree
- t.index ["issue_id"], name: "index_resource_label_events_on_issue_id", using: :btree
- t.index ["label_id"], name: "index_resource_label_events_on_label_id", using: :btree
- t.index ["merge_request_id"], name: "index_resource_label_events_on_merge_request_id", using: :btree
- t.index ["user_id"], name: "index_resource_label_events_on_user_id", using: :btree
+ t.index ["epic_id"], name: "index_resource_label_events_on_epic_id"
+ t.index ["issue_id"], name: "index_resource_label_events_on_issue_id"
+ t.index ["label_id"], name: "index_resource_label_events_on_label_id"
+ t.index ["merge_request_id"], name: "index_resource_label_events_on_merge_request_id"
+ t.index ["user_id"], name: "index_resource_label_events_on_user_id"
end
create_table "reviews", force: :cascade do |t|
@@ -2965,9 +3069,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "merge_request_id", null: false
t.integer "project_id", null: false
t.datetime_with_timezone "created_at", null: false
- t.index ["author_id"], name: "index_reviews_on_author_id", using: :btree
- t.index ["merge_request_id"], name: "index_reviews_on_merge_request_id", using: :btree
- t.index ["project_id"], name: "index_reviews_on_project_id", using: :btree
+ t.index ["author_id"], name: "index_reviews_on_author_id"
+ t.index ["merge_request_id"], name: "index_reviews_on_merge_request_id"
+ t.index ["project_id"], name: "index_reviews_on_project_id"
end
create_table "routes", id: :serial, force: :cascade do |t|
@@ -2977,9 +3081,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.string "name"
- t.index ["path"], name: "index_routes_on_path", unique: true, using: :btree
- t.index ["path"], name: "index_routes_on_path_text_pattern_ops", using: :btree, opclasses: {"path"=>"varchar_pattern_ops"}
- t.index ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree
+ t.index ["path"], name: "index_routes_on_path", unique: true
+ t.index ["path"], name: "index_routes_on_path_text_pattern_ops", opclass: :varchar_pattern_ops
+ t.index ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true
end
create_table "saml_providers", id: :serial, force: :cascade do |t|
@@ -2989,7 +3093,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "sso_url", null: false
t.boolean "enforced_sso", default: false, null: false
t.boolean "enforced_group_managed_accounts", default: false, null: false
- t.index ["group_id"], name: "index_saml_providers_on_group_id", using: :btree
+ t.index ["group_id"], name: "index_saml_providers_on_group_id"
end
create_table "scim_oauth_access_tokens", id: :serial, force: :cascade do |t|
@@ -2997,7 +3101,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.integer "group_id", null: false
t.string "token_encrypted", null: false
- t.index ["group_id", "token_encrypted"], name: "index_scim_oauth_access_tokens_on_group_id_and_token_encrypted", unique: true, using: :btree
+ t.index ["group_id", "token_encrypted"], name: "index_scim_oauth_access_tokens_on_group_id_and_token_encrypted", unique: true
end
create_table "sent_notifications", id: :serial, force: :cascade do |t|
@@ -3011,7 +3115,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "note_type"
t.text "position"
t.string "in_reply_to_discussion_id"
- t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
+ t.index ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true
end
create_table "services", id: :serial, force: :cascade do |t|
@@ -3038,14 +3142,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "confidential_note_events", default: true
t.boolean "deployment_events", default: false, null: false
t.string "description", limit: 500
- t.index ["project_id"], name: "index_services_on_project_id", using: :btree
- t.index ["template"], name: "index_services_on_template", using: :btree
- t.index ["type"], name: "index_services_on_type", using: :btree
+ t.index ["project_id"], name: "index_services_on_project_id"
+ t.index ["template"], name: "index_services_on_template"
+ t.index ["type"], name: "index_services_on_type"
end
create_table "shards", id: :serial, force: :cascade do |t|
t.string "name", null: false
- t.index ["name"], name: "index_shards_on_name", unique: true, using: :btree
+ t.index ["name"], name: "index_shards_on_name", unique: true
end
create_table "slack_integrations", id: :serial, force: :cascade do |t|
@@ -3056,16 +3160,16 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "user_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["service_id"], name: "index_slack_integrations_on_service_id", using: :btree
- t.index ["team_id", "alias"], name: "index_slack_integrations_on_team_id_and_alias", unique: true, using: :btree
+ t.index ["service_id"], name: "index_slack_integrations_on_service_id"
+ t.index ["team_id", "alias"], name: "index_slack_integrations_on_team_id_and_alias", unique: true
end
create_table "smartcard_identities", force: :cascade do |t|
t.integer "user_id", null: false
t.string "subject", null: false
t.string "issuer", null: false
- t.index ["subject", "issuer"], name: "index_smartcard_identities_on_subject_and_issuer", unique: true, using: :btree
- t.index ["user_id"], name: "index_smartcard_identities_on_user_id", using: :btree
+ t.index ["subject", "issuer"], name: "index_smartcard_identities_on_subject_and_issuer", unique: true
+ t.index ["user_id"], name: "index_smartcard_identities_on_user_id"
end
create_table "snippets", id: :serial, force: :cascade do |t|
@@ -3083,25 +3187,25 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "cached_markdown_version"
t.text "description"
t.text "description_html"
- t.index ["author_id"], name: "index_snippets_on_author_id", using: :btree
- t.index ["file_name"], name: "index_snippets_on_file_name_trigram", using: :gin, opclasses: {"file_name"=>"gin_trgm_ops"}
- t.index ["project_id"], name: "index_snippets_on_project_id", using: :btree
- t.index ["title"], name: "index_snippets_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
- t.index ["updated_at"], name: "index_snippets_on_updated_at", using: :btree
- t.index ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
+ t.index ["author_id"], name: "index_snippets_on_author_id"
+ t.index ["file_name"], name: "index_snippets_on_file_name_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["project_id"], name: "index_snippets_on_project_id"
+ t.index ["title"], name: "index_snippets_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["updated_at"], name: "index_snippets_on_updated_at"
+ t.index ["visibility_level"], name: "index_snippets_on_visibility_level"
end
create_table "software_license_policies", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "software_license_id", null: false
t.integer "approval_status", default: 0, null: false
- t.index ["project_id", "software_license_id"], name: "index_software_license_policies_unique_per_project", unique: true, using: :btree
- t.index ["software_license_id"], name: "index_software_license_policies_on_software_license_id", using: :btree
+ t.index ["project_id", "software_license_id"], name: "index_software_license_policies_unique_per_project", unique: true
+ t.index ["software_license_id"], name: "index_software_license_policies_on_software_license_id"
end
create_table "software_licenses", id: :serial, force: :cascade do |t|
t.string "name", null: false
- t.index ["name"], name: "index_software_licenses_on_name", using: :btree
+ t.index ["name"], name: "index_software_licenses_on_name"
end
create_table "spam_logs", id: :serial, force: :cascade do |t|
@@ -3126,8 +3230,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "project_id"
- t.index ["project_id"], name: "index_subscriptions_on_project_id", using: :btree
- t.index ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_subscriptions_on_project_id"
+ t.index ["subscribable_id", "subscribable_type", "user_id", "project_id"], name: "index_subscriptions_on_subscribable_and_user_id_and_project_id", unique: true
end
create_table "suggestions", force: :cascade do |t|
@@ -3140,7 +3244,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "lines_above", default: 0, null: false
t.integer "lines_below", default: 0, null: false
t.boolean "outdated", default: false, null: false
- t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true, using: :btree
+ t.index ["note_id", "relative_order"], name: "index_suggestions_on_note_id_and_relative_order", unique: true
end
create_table "system_note_metadata", id: :serial, force: :cascade do |t|
@@ -3149,7 +3253,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "action"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true, using: :btree
+ t.index ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true
end
create_table "taggings", id: :serial, force: :cascade do |t|
@@ -3160,17 +3264,17 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "tagger_type"
t.string "context"
t.datetime "created_at"
- t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true, using: :btree
- t.index ["tag_id"], name: "index_taggings_on_tag_id", using: :btree
- t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
- t.index ["taggable_id", "taggable_type"], name: "index_taggings_on_taggable_id_and_taggable_type", using: :btree
+ t.index ["tag_id", "taggable_id", "taggable_type", "context", "tagger_id", "tagger_type"], name: "taggings_idx", unique: true
+ t.index ["tag_id"], name: "index_taggings_on_tag_id"
+ t.index ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context"
+ t.index ["taggable_id", "taggable_type"], name: "index_taggings_on_taggable_id_and_taggable_type"
end
create_table "tags", id: :serial, force: :cascade do |t|
t.string "name"
t.integer "taggings_count", default: 0
- t.index ["name"], name: "index_tags_on_name", unique: true, using: :btree
- t.index ["name"], name: "index_tags_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
+ t.index ["name"], name: "index_tags_on_name", unique: true
+ t.index ["name"], name: "index_tags_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
end
create_table "term_agreements", id: :serial, force: :cascade do |t|
@@ -3179,9 +3283,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "accepted", default: false, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
- t.index ["term_id"], name: "index_term_agreements_on_term_id", using: :btree
- t.index ["user_id", "term_id"], name: "term_agreements_unique_index", unique: true, using: :btree
- t.index ["user_id"], name: "index_term_agreements_on_user_id", using: :btree
+ t.index ["term_id"], name: "index_term_agreements_on_term_id"
+ t.index ["user_id", "term_id"], name: "term_agreements_unique_index", unique: true
+ t.index ["user_id"], name: "index_term_agreements_on_user_id"
end
create_table "timelogs", id: :serial, force: :cascade do |t|
@@ -3192,9 +3296,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "issue_id"
t.integer "merge_request_id"
t.datetime_with_timezone "spent_at"
- t.index ["issue_id"], name: "index_timelogs_on_issue_id", using: :btree
- t.index ["merge_request_id"], name: "index_timelogs_on_merge_request_id", using: :btree
- t.index ["user_id"], name: "index_timelogs_on_user_id", using: :btree
+ t.index ["issue_id"], name: "index_timelogs_on_issue_id"
+ t.index ["merge_request_id"], name: "index_timelogs_on_merge_request_id"
+ t.index ["user_id"], name: "index_timelogs_on_user_id"
end
create_table "todos", id: :serial, force: :cascade do |t|
@@ -3210,20 +3314,20 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "note_id"
t.string "commit_id"
t.integer "group_id"
- t.index ["author_id"], name: "index_todos_on_author_id", using: :btree
- t.index ["commit_id"], name: "index_todos_on_commit_id", using: :btree
- t.index ["group_id"], name: "index_todos_on_group_id", using: :btree
- t.index ["note_id"], name: "index_todos_on_note_id", using: :btree
- t.index ["project_id"], name: "index_todos_on_project_id", using: :btree
- t.index ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree
- t.index ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)", using: :btree
- t.index ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)", using: :btree
- t.index ["user_id"], name: "index_todos_on_user_id", using: :btree
+ t.index ["author_id"], name: "index_todos_on_author_id"
+ t.index ["commit_id"], name: "index_todos_on_commit_id"
+ t.index ["group_id"], name: "index_todos_on_group_id"
+ t.index ["note_id"], name: "index_todos_on_note_id"
+ t.index ["project_id"], name: "index_todos_on_project_id"
+ t.index ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id"
+ t.index ["user_id", "id"], name: "index_todos_on_user_id_and_id_done", where: "((state)::text = 'done'::text)"
+ t.index ["user_id", "id"], name: "index_todos_on_user_id_and_id_pending", where: "((state)::text = 'pending'::text)"
+ t.index ["user_id"], name: "index_todos_on_user_id"
end
create_table "trending_projects", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false
- t.index ["project_id"], name: "index_trending_projects_on_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_trending_projects_on_project_id", unique: true
end
create_table "u2f_registrations", id: :serial, force: :cascade do |t|
@@ -3235,8 +3339,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name"
- t.index ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree
- t.index ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree
+ t.index ["key_handle"], name: "index_u2f_registrations_on_key_handle"
+ t.index ["user_id"], name: "index_u2f_registrations_on_user_id"
end
create_table "uploads", id: :serial, force: :cascade do |t|
@@ -3250,10 +3354,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "mount_point"
t.string "secret"
t.integer "store"
- t.index ["checksum"], name: "index_uploads_on_checksum", using: :btree
- t.index ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type", using: :btree
- t.index ["store"], name: "index_uploads_on_store", using: :btree
- t.index ["uploader", "path"], name: "index_uploads_on_uploader_and_path", using: :btree
+ t.index ["checksum"], name: "index_uploads_on_checksum"
+ t.index ["model_id", "model_type"], name: "index_uploads_on_model_id_and_model_type"
+ t.index ["store"], name: "index_uploads_on_store"
+ t.index ["uploader", "path"], name: "index_uploads_on_uploader_and_path"
end
create_table "user_agent_details", id: :serial, force: :cascade do |t|
@@ -3264,14 +3368,14 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "submitted", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
+ t.index ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type"
end
create_table "user_callouts", id: :serial, force: :cascade do |t|
t.integer "feature_name", null: false
t.integer "user_id", null: false
- t.index ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
- t.index ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
+ t.index ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true
+ t.index ["user_id"], name: "index_user_callouts_on_user_id"
end
create_table "user_custom_attributes", id: :serial, force: :cascade do |t|
@@ -3280,15 +3384,15 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id", null: false
t.string "key", null: false
t.string "value", null: false
- t.index ["key", "value"], name: "index_user_custom_attributes_on_key_and_value", using: :btree
- t.index ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true, using: :btree
+ t.index ["key", "value"], name: "index_user_custom_attributes_on_key_and_value"
+ t.index ["user_id", "key"], name: "index_user_custom_attributes_on_user_id_and_key", unique: true
end
create_table "user_interacted_projects", id: false, force: :cascade do |t|
t.integer "user_id", null: false
t.integer "project_id", null: false
- t.index ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true, using: :btree
- t.index ["user_id"], name: "index_user_interacted_projects_on_user_id", using: :btree
+ t.index ["project_id", "user_id"], name: "index_user_interacted_projects_on_project_id_and_user_id", unique: true
+ t.index ["user_id"], name: "index_user_interacted_projects_on_user_id"
end
create_table "user_preferences", id: :serial, force: :cascade do |t|
@@ -3307,7 +3411,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "epics_sort"
t.integer "roadmap_epics_state"
t.string "roadmaps_sort"
- t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
+ t.string "projects_sort", limit: 64
+ t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true
end
create_table "user_statuses", primary_key: "user_id", id: :serial, force: :cascade do |t|
@@ -3315,7 +3420,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "emoji", default: "speech_balloon", null: false
t.string "message", limit: 100
t.string "message_html"
- t.index ["user_id"], name: "index_user_statuses_on_user_id", using: :btree
+ t.index ["user_id"], name: "index_user_statuses_on_user_id"
end
create_table "user_synced_attributes_metadata", id: :serial, force: :cascade do |t|
@@ -3324,7 +3429,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.boolean "location_synced", default: false
t.integer "user_id", null: false
t.string "provider"
- t.index ["user_id"], name: "index_user_synced_attributes_metadata_on_user_id", unique: true, using: :btree
+ t.index ["user_id"], name: "index_user_synced_attributes_metadata_on_user_id", unique: true
end
create_table "users", id: :serial, force: :cascade do |t|
@@ -3407,26 +3512,26 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.text "note"
t.integer "roadmap_layout", limit: 2
t.integer "bot_type", limit: 2
- t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id", using: :btree
- t.index ["admin"], name: "index_users_on_admin", using: :btree
- t.index ["bot_type"], name: "index_users_on_bot_type", using: :btree
- t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
- t.index ["created_at"], name: "index_users_on_created_at", using: :btree
- t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
- t.index ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"}
- t.index ["feed_token"], name: "index_users_on_feed_token", using: :btree
- t.index ["ghost"], name: "index_users_on_ghost", using: :btree
- t.index ["group_view"], name: "index_users_on_group_view", using: :btree
- t.index ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree
- t.index ["managing_group_id"], name: "index_users_on_managing_group_id", using: :btree
- t.index ["name"], name: "index_users_on_name", using: :btree
- t.index ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"}
- t.index ["public_email"], name: "index_users_on_public_email", where: "((public_email)::text <> ''::text)", using: :btree
- t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
- t.index ["state"], name: "index_users_on_state", using: :btree
- t.index ["state"], name: "index_users_on_state_and_internal", where: "((ghost <> true) AND (bot_type IS NULL))", using: :btree
- t.index ["username"], name: "index_users_on_username", using: :btree
- t.index ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"}
+ t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id"
+ t.index ["admin"], name: "index_users_on_admin"
+ t.index ["bot_type"], name: "index_users_on_bot_type"
+ t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
+ t.index ["created_at"], name: "index_users_on_created_at"
+ t.index ["email"], name: "index_users_on_email", unique: true
+ t.index ["email"], name: "index_users_on_email_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["feed_token"], name: "index_users_on_feed_token"
+ t.index ["ghost"], name: "index_users_on_ghost"
+ t.index ["group_view"], name: "index_users_on_group_view"
+ t.index ["incoming_email_token"], name: "index_users_on_incoming_email_token"
+ t.index ["managing_group_id"], name: "index_users_on_managing_group_id"
+ t.index ["name"], name: "index_users_on_name"
+ t.index ["name"], name: "index_users_on_name_trigram", opclass: :gin_trgm_ops, using: :gin
+ t.index ["public_email"], name: "index_users_on_public_email", where: "((public_email)::text <> ''::text)"
+ t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
+ t.index ["state"], name: "index_users_on_state"
+ t.index ["state"], name: "index_users_on_state_and_internal", where: "((ghost <> true) AND (bot_type IS NULL))"
+ t.index ["username"], name: "index_users_on_username"
+ t.index ["username"], name: "index_users_on_username_trigram", opclass: :gin_trgm_ops, using: :gin
end
create_table "users_ops_dashboard_projects", force: :cascade do |t|
@@ -3434,8 +3539,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.integer "user_id", null: false
t.integer "project_id", null: false
- t.index ["project_id"], name: "index_users_ops_dashboard_projects_on_project_id", using: :btree
- t.index ["user_id", "project_id"], name: "index_users_ops_dashboard_projects_on_user_id_and_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_users_ops_dashboard_projects_on_project_id"
+ t.index ["user_id", "project_id"], name: "index_users_ops_dashboard_projects_on_user_id_and_project_id", unique: true
end
create_table "users_star_projects", id: :serial, force: :cascade do |t|
@@ -3443,8 +3548,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "user_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.index ["project_id"], name: "index_users_star_projects_on_project_id", using: :btree
- t.index ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree
+ t.index ["project_id"], name: "index_users_star_projects_on_project_id"
+ t.index ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true
end
create_table "vulnerability_feedback", id: :serial, force: :cascade do |t|
@@ -3461,12 +3566,12 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "comment_author_id"
t.text "comment"
t.datetime_with_timezone "comment_timestamp"
- t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id", using: :btree
- t.index ["comment_author_id"], name: "index_vulnerability_feedback_on_comment_author_id", using: :btree
- t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id", using: :btree
- t.index ["merge_request_id"], name: "index_vulnerability_feedback_on_merge_request_id", using: :btree
- t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id", using: :btree
- t.index ["project_id", "category", "feedback_type", "project_fingerprint"], name: "vulnerability_feedback_unique_idx", unique: true, using: :btree
+ t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id"
+ t.index ["comment_author_id"], name: "index_vulnerability_feedback_on_comment_author_id"
+ t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id"
+ t.index ["merge_request_id"], name: "index_vulnerability_feedback_on_merge_request_id"
+ t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id"
+ t.index ["project_id", "category", "feedback_type", "project_fingerprint"], name: "vulnerability_feedback_unique_idx", unique: true
end
create_table "vulnerability_identifiers", force: :cascade do |t|
@@ -3478,7 +3583,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "external_id", null: false
t.string "name", null: false
t.text "url"
- t.index ["project_id", "fingerprint"], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true, using: :btree
+ t.index ["project_id", "fingerprint"], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true
end
create_table "vulnerability_occurrence_identifiers", force: :cascade do |t|
@@ -3486,8 +3591,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.bigint "occurrence_id", null: false
t.bigint "identifier_id", null: false
- t.index ["identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_identifier_id", using: :btree
- t.index ["occurrence_id", "identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_unique_keys", unique: true, using: :btree
+ t.index ["identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_identifier_id"
+ t.index ["occurrence_id", "identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_unique_keys", unique: true
end
create_table "vulnerability_occurrence_pipelines", force: :cascade do |t|
@@ -3495,8 +3600,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.datetime_with_timezone "updated_at", null: false
t.bigint "occurrence_id", null: false
t.integer "pipeline_id", null: false
- t.index ["occurrence_id", "pipeline_id"], name: "vulnerability_occurrence_pipelines_on_unique_keys", unique: true, using: :btree
- t.index ["pipeline_id"], name: "index_vulnerability_occurrence_pipelines_on_pipeline_id", using: :btree
+ t.index ["occurrence_id", "pipeline_id"], name: "vulnerability_occurrence_pipelines_on_unique_keys", unique: true
+ t.index ["pipeline_id"], name: "index_vulnerability_occurrence_pipelines_on_pipeline_id"
end
create_table "vulnerability_occurrences", force: :cascade do |t|
@@ -3514,10 +3619,10 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "name", null: false
t.string "metadata_version", null: false
t.text "raw_metadata", null: false
- t.index ["primary_identifier_id"], name: "index_vulnerability_occurrences_on_primary_identifier_id", using: :btree
- t.index ["project_id", "primary_identifier_id", "location_fingerprint", "scanner_id"], name: "index_vulnerability_occurrences_on_unique_keys", unique: true, using: :btree
- t.index ["scanner_id"], name: "index_vulnerability_occurrences_on_scanner_id", using: :btree
- t.index ["uuid"], name: "index_vulnerability_occurrences_on_uuid", unique: true, using: :btree
+ t.index ["primary_identifier_id"], name: "index_vulnerability_occurrences_on_primary_identifier_id"
+ t.index ["project_id", "primary_identifier_id", "location_fingerprint", "scanner_id"], name: "index_vulnerability_occurrences_on_unique_keys", unique: true
+ t.index ["scanner_id"], name: "index_vulnerability_occurrences_on_scanner_id"
+ t.index ["uuid"], name: "index_vulnerability_occurrences_on_uuid", unique: true
end
create_table "vulnerability_scanners", force: :cascade do |t|
@@ -3526,7 +3631,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.integer "project_id", null: false
t.string "external_id", null: false
t.string "name", null: false
- t.index ["project_id", "external_id"], name: "index_vulnerability_scanners_on_project_id_and_external_id", unique: true, using: :btree
+ t.index ["project_id", "external_id"], name: "index_vulnerability_scanners_on_project_id_and_external_id", unique: true
end
create_table "web_hook_logs", id: :serial, force: :cascade do |t|
@@ -3542,8 +3647,8 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "internal_error_message"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
- t.index ["created_at", "web_hook_id"], name: "index_web_hook_logs_on_created_at_and_web_hook_id", using: :btree
- t.index ["web_hook_id"], name: "index_web_hook_logs_on_web_hook_id", using: :btree
+ t.index ["created_at", "web_hook_id"], name: "index_web_hook_logs_on_created_at_and_web_hook_id"
+ t.index ["web_hook_id"], name: "index_web_hook_logs_on_web_hook_id"
end
create_table "web_hooks", id: :serial, force: :cascade do |t|
@@ -3570,12 +3675,20 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
t.string "encrypted_url"
t.string "encrypted_url_iv"
t.integer "group_id"
- t.index ["project_id"], name: "index_web_hooks_on_project_id", using: :btree
- t.index ["type"], name: "index_web_hooks_on_type", using: :btree
+ t.index ["project_id"], name: "index_web_hooks_on_project_id"
+ t.index ["type"], name: "index_web_hooks_on_type"
end
+ add_foreign_key "allowed_email_domains", "namespaces", column: "group_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "end_event_label_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_group_stages", "labels", column: "start_event_label_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_group_stages", "namespaces", column: "group_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_project_stages", "labels", column: "end_event_label_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_project_stages", "labels", column: "start_event_label_id", on_delete: :cascade
+ add_foreign_key "analytics_cycle_analytics_project_stages", "projects", on_delete: :cascade
add_foreign_key "application_settings", "namespaces", column: "custom_project_templates_group_id", on_delete: :nullify
add_foreign_key "application_settings", "projects", column: "file_template_project_id", name: "fk_ec757bd087", on_delete: :nullify
+ add_foreign_key "application_settings", "projects", column: "instance_administration_project_id", on_delete: :nullify
add_foreign_key "application_settings", "users", column: "usage_stats_set_by_user_id", name: "fk_964370041d", on_delete: :nullify
add_foreign_key "approval_merge_request_rule_sources", "approval_merge_request_rules", on_delete: :cascade
add_foreign_key "approval_merge_request_rule_sources", "approval_project_rules", on_delete: :cascade
@@ -3608,6 +3721,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "boards", "namespaces", column: "group_id", on_delete: :cascade
add_foreign_key "boards", "projects", name: "fk_f15266b5f9", on_delete: :cascade
add_foreign_key "chat_teams", "namespaces", on_delete: :cascade
+ add_foreign_key "ci_build_needs", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_build_trace_chunks", "ci_builds", column: "build_id", on_delete: :cascade
add_foreign_key "ci_build_trace_section_names", "projects", on_delete: :cascade
add_foreign_key "ci_build_trace_sections", "ci_build_trace_section_names", column: "section_name_id", name: "fk_264e112c66", on_delete: :cascade
@@ -3624,6 +3738,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade
+ add_foreign_key "ci_job_variables", "ci_builds", column: "job_id", on_delete: :cascade
add_foreign_key "ci_pipeline_chat_data", "chat_names", on_delete: :cascade
add_foreign_key "ci_pipeline_chat_data", "ci_pipelines", column: "pipeline_id", on_delete: :cascade
add_foreign_key "ci_pipeline_schedule_variables", "ci_pipeline_schedules", column: "pipeline_schedule_id", name: "fk_41c35fda51", on_delete: :cascade
@@ -3666,6 +3781,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "clusters_applications_runners", "clusters", on_delete: :cascade
add_foreign_key "clusters_kubernetes_namespaces", "cluster_projects", on_delete: :nullify
add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade
+ add_foreign_key "clusters_kubernetes_namespaces", "environments", on_delete: :nullify
add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify
add_foreign_key "container_repositories", "projects"
add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", name: "fk_db58bbc5d7", on_delete: :cascade
@@ -3677,6 +3793,7 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "design_management_designs", "projects", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_versions", column: "version_id", name: "fk_f4d25ba00c", on_delete: :cascade
+ add_foreign_key "design_management_versions", "issues", on_delete: :cascade
add_foreign_key "draft_notes", "merge_requests", on_delete: :cascade
add_foreign_key "draft_notes", "users", column: "author_id", on_delete: :cascade
add_foreign_key "elasticsearch_indexed_namespaces", "namespaces", on_delete: :cascade
@@ -3698,7 +3815,9 @@ ActiveRecord::Schema.define(version: 2019_07_15_114644) do
add_foreign_key "fork_network_members", "projects", on_delete: :cascade
add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify
add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade
+ add_foreign_key "geo_container_repository_updated_events", "container_repositories", name: "fk_212c89c706", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade
+ add_foreign_key "geo_event_log", "geo_container_repository_updated_events", column: "container_repository_updated_event_id", name: "fk_6ada82d42a", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade
add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade
diff --git a/doc/README.md b/doc/README.md
index bf7017d8fb4..8ce5d2e240a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -9,7 +9,7 @@ description: 'Learn how to use and administer GitLab, the most scalable Git-base
</div>
<!-- the div above will not display on the docs site but will display on /help -->
-# GitLab Documentation
+# GitLab Docs
Welcome to [GitLab](https://about.gitlab.com/) Documentation.
@@ -25,7 +25,7 @@ No matter how you use GitLab, we have documentation for you.
| [**User Documentation**](user/index.md)<br/>Discover features and concepts for GitLab users. | [**Administrator documentation**](administration/index.md)<br/>Everything GitLab self-managed administrators need to know. |
| [**Contributing to GitLab**](#contributing-to-gitlab)<br/>At GitLab, everyone can contribute! | [**New to Git and GitLab?**](#new-to-git-and-gitlab)<br/>We have resources to get you started. |
| [**Building an integration with GitLab?**](#building-an-integration-with-gitlab)<br/>Consult our automation and integration documentation. | [**Coming to GitLab from another platform?**](#coming-to-gitlab-from-another-platform)<br/>Consult our handy guides. |
-| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Subscribe to GitLab**](#subscribe-to-gitlab)<br/>Get access to more features. |
+| [**Install GitLab**](https://about.gitlab.com/install/)<br/>Installation options for different platforms. | [**Customers**](subscriptions/index.md)<br/>Information for new and existing customers. |
| [**Update GitLab**](update/README.md)<br/>Update your GitLab self-managed instance to the latest version. | [**GitLab Releases**](https://about.gitlab.com/releases/)<br/>What's new in GitLab. |
## Popular Documentation
@@ -38,7 +38,7 @@ Have a look at some of our most popular documentation resources:
| [GitLab CI/CD examples](ci/examples/README.md) | Get up to speed quickly with common CI/CD scenarios. |
| [GitLab Container Registry](user/project/container_registry.md) | Host containers within GitLab. |
| [GitLab Pages](user/project/pages/index.md) | Host static websites for your projects with GitLab. |
-| [GitLab.com settings](user/gitlab_com/index.md) | Settings for [GitLab.com](#gitlabcom). |
+| [GitLab.com settings](user/gitlab_com/index.md) | Settings for GitLab.com. |
| [Kubernetes integration](user/project/clusters/index.md) | Use GitLab with Kubernetes. |
| [SSH authentication](ssh/README.md) | Secure your network communications. |
| [Using Docker images](ci/docker/using_docker_images.md) | Build and test your applications with Docker. |
@@ -279,7 +279,7 @@ The following documentation relates to the DevOps **Release** stage:
| [Canary Deployments](user/project/canary_deployments.md) **(PREMIUM)** | Employ a popular CI strategy where a small portion of the fleet is updated to the new version first. |
| [Deploy Boards](user/project/deploy_boards.md) **(PREMIUM)** | View the current health and status of each CI environment running on Kubernetes, displaying the status of the pods in the deployment. |
| [Environments and deployments](ci/environments.md) | With environments, you can control the continuous deployment of your software within GitLab. |
-| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) **(PREMIUM)** | Limit scope of variables to specific environments. |
+| [Environment-specific variables](ci/variables/README.md#limiting-environment-scopes-of-environment-variables) | Limit scope of variables to specific environments. |
| [GitLab CI/CD](ci/README.md) | Explore the features and capabilities of Continuous Deployment and Delivery with GitLab. |
| [GitLab Pages](user/project/pages/index.md) | Build, test, and deploy a static site directly from GitLab. |
| [Protected Runners](ci/runners/README.md#protected-runners) | Select Runners to only pick jobs for protected branches and tags. |
@@ -302,7 +302,7 @@ The following documentation relates to the DevOps **Configure** stage:
| Configure Topics | Description |
|:-----------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------------|
| [Auto DevOps](topics/autodevops/index.md) | Automatically employ a complete DevOps lifecycle. |
-| [Easy creation of Kubernetes<br/>clusters on GKE](user/project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab) | Use Google Kubernetes Engine and GitLab. |
+| [Create Kubernetes clusters on GKE](user/project/clusters/index.md#add-new-gke-cluster) | Use Google Kubernetes Engine and GitLab. |
| [Executable Runbooks](user/project/clusters/runbooks/index.md) | Documented procedures that explain how to carry out particular processes. |
| [GitLab ChatOps](ci/chatops/README.md) | Interact with CI/CD jobs through chat services. |
| [Installing Applications](user/project/clusters/index.md#installing-applications) | Deploy Helm, Ingress, and Prometheus on Kubernetes. |
@@ -354,6 +354,7 @@ The following documentation relates to the DevOps **Secure** stage:
| Secure Topics | Description |
|:------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](user/application_security/container_scanning/index.md) **(ULTIMATE)** | Use Clair to scan docker images for known vulnerabilities. |
+| [Dependency List](user/application_security/dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [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. |
@@ -361,90 +362,6 @@ The following documentation relates to the DevOps **Secure** stage:
| [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. |
-## Subscribe to GitLab
-
-There are two ways to use GitLab:
-
-- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain your own GitLab instance.
-- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install anything to use GitLab.com,
- you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
-
-For more information on managing your subscription and [Customers Portal](https://customers.gitlab.com) account, please see [Getting Started with Subscriptions](getting-started/subscription.md).
-
-The following sections outline tiers and features within GitLab self-managed and GitLab.com.
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#overview">
- Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
-### GitLab self-managed
-
-With GitLab self-managed, you deploy your own GitLab instance on-premises or on a cloud of your choice.
-GitLab self-managed is available for [free and with paid subscriptions](https://about.gitlab.com/pricing/#self-managed) in the following tiers:
-
-| Tier | Includes |
-|:---------|:-----------------------------------------------|
-| Core | Core features. |
-| Starter | Core and Starter features. |
-| Premium | Core, Starter, and Premium features. |
-| Ultimate | Core, Starter, Premium, and Ultimate features. |
-
-The following resources are available for more information on GitLab self-managed:
-
-- [Feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/), for information on what features are available at each tier.
-- [GitLab pricing page](https://about.gitlab.com/pricing/#self-managed), for subscription information and a free trial.
-- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
- - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
- - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#overview">
- Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
-### GitLab.com
-
-GitLab.com is hosted, managed, and administered by GitLab, Inc., with
-[free and paid subscriptions](https://about.gitlab.com/pricing/) for individuals
-and teams in the following tiers:
-
-| Tier | Includes same features available in |
-|:-------|:----------------------------------------------------|
-| Free | [Core](#gitlab-self-managed) self-managed tier. |
-| Bronze | [Starter](#gitlab-self-managed) self-managed tier. |
-| Silver | [Premium](#gitlab-self-managed) self-managed tier. |
-| Gold | [Ultimate](#gitlab-self-managed) self-managed tier. |
-
-GitLab.com subscriptions grant access
-to the same features available in GitLab self-managed, **except
-[administration](administration/index.md) tools and settings**.
-
-GitLab.com allows you to apply your subscription to a group or your personal user.
-
-When applied to a **group**, the group, all subgroups, and all projects under the selected group on GitLab.com will have the features of the associated plan. It is recommended to go with a group plan when managing projects and users of an organization.
-
-When associated with a **personal userspace** instead, all projects will have features with the subscription applied, but as it is not a group, group features will not be available.
-
-TIP: **Tip:**
-To support the open source community and encourage the development of open source projects, GitLab grants access to **Gold** features for all GitLab.com **public** projects, regardless of the subscription.
-
-The following resources are available for more information on GitLab.com:
-
-- [Feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/), for information on what features are available at each tier.
-- [GitLab pricing page](https://about.gitlab.com/pricing/), for subscription information and a free trial.
-- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
- - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
- - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
-
-<div align="right">
- <a type="button" class="btn btn-default" href="#overview">
- Back to Overview <i class="fa fa-angle-double-up" aria-hidden="true"></i>
- </a>
-</div>
-
## New to Git and GitLab?
Working with new systems can be daunting.
diff --git a/doc/administration/audit_events.md b/doc/administration/audit_events.md
index a80ff330e03..02de2caf558 100644
--- a/doc/administration/audit_events.md
+++ b/doc/administration/audit_events.md
@@ -73,6 +73,8 @@ From there, you can see the following actions:
- User was added to project and with which [permissions]
- Permission changes of a user assigned to a project
- User was removed from project
+- Project export was downloaded
+- Project repository was downloaded
### Instance events **(PREMIUM ONLY)**
@@ -94,6 +96,7 @@ recorded:
- Changed password
- Ask for password reset
- Grant OAuth access
+- Started/stopped user impersonation
It is possible to filter particular actions by choosing an audit data type from
the filter drop-down. You can further filter by specific group, project or user
diff --git a/doc/administration/auditor_users.md b/doc/administration/auditor_users.md
index 65d36612d85..18c415b5ff7 100644
--- a/doc/administration/auditor_users.md
+++ b/doc/administration/auditor_users.md
@@ -52,7 +52,7 @@ section.
**Admin Area > Users**. You will find the option of the access level under
the 'Access' section.
- ![Admin Area Form](img/auditor_access_form.png)
+ ![Admin Area Form](img/auditor_access_form.png)
1. Click **Save changes** or **Create user** for the changes to take effect.
diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_1.png b/doc/administration/auth/img/google_secure_ldap_add_step_1.png
index fd254443d75..bee9c602a14 100644
--- a/doc/administration/auth/img/google_secure_ldap_add_step_1.png
+++ b/doc/administration/auth/img/google_secure_ldap_add_step_1.png
Binary files differ
diff --git a/doc/administration/auth/img/google_secure_ldap_add_step_2.png b/doc/administration/auth/img/google_secure_ldap_add_step_2.png
index 611a21ae03c..b127410cb8c 100644
--- a/doc/administration/auth/img/google_secure_ldap_add_step_2.png
+++ b/doc/administration/auth/img/google_secure_ldap_add_step_2.png
Binary files differ
diff --git a/doc/administration/auth/img/google_secure_ldap_client_settings.png b/doc/administration/auth/img/google_secure_ldap_client_settings.png
index 3c0b3f3d4bd..868e6645f56 100644
--- a/doc/administration/auth/img/google_secure_ldap_client_settings.png
+++ b/doc/administration/auth/img/google_secure_ldap_client_settings.png
Binary files differ
diff --git a/doc/administration/auth/ldap-ee.md b/doc/administration/auth/ldap-ee.md
index 2f2ee8a27d3..34f3cfa353f 100644
--- a/doc/administration/auth/ldap-ee.md
+++ b/doc/administration/auth/ldap-ee.md
@@ -4,37 +4,27 @@ type: reference
# LDAP Additions in GitLab EE **(STARTER ONLY)**
-This is a continuation of the main [LDAP documentation](ldap.md), detailing LDAP
-features specific to GitLab Enterprise Edition Starter, Premium and Ultimate.
+This section documents LDAP features specific to to GitLab Enterprise Edition
+[Starter](https://about.gitlab.com/pricing/#self-managed) and above.
-## Overview
-
-[LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
-stands for **Lightweight Directory Access Protocol**, which
-is a standard application protocol for
-accessing and maintaining distributed directory information services
-over an Internet Protocol (IP) network.
-
-GitLab integrates with LDAP to support **user authentication**. This integration
-works with most LDAP-compliant directory servers, including Microsoft Active
-Directory, Apple Open Directory, Open LDAP, and 389 Server.
-**GitLab Enterprise Edition** includes enhanced integration, including group
-membership syncing.
+For documentation relevant to both Community Edition and Enterprise Edition,
+see the main [LDAP documentation](ldap.md).
## Use cases
-- User sync: Once a day, GitLab will update users against LDAP
+- User sync: Once a day, GitLab will update users against LDAP.
- Group sync: Once an hour, GitLab will update group membership
- based on LDAP group members
+ based on LDAP group members.
-## Multiple LDAP servers
+## Multiple LDAP servers **(STARTER ONLY)**
With GitLab Enterprise Edition Starter, you can configure multiple LDAP servers
that your GitLab instance will connect to.
-To add another LDAP server, you can start by duplicating the settings under
-[the main configuration](ldap.md#configuration) and edit them to match the
-additional LDAP server.
+To add another LDAP server:
+
+1. Duplicating the settings under [the main configuration](ldap.md#configuration).
+1. Edit them to match the additional LDAP server.
Be sure to choose a different provider ID made of letters a-z and numbers 0-9.
This ID will be stored in the database so that GitLab can remember which LDAP
@@ -47,19 +37,19 @@ users against LDAP.
The process will execute the following access checks:
-1. Ensure the user is still present in LDAP
-1. If the LDAP server is Active Directory, ensure the user is active (not
- blocked/disabled state). This will only be checked if
- `active_directory: true` is set in the LDAP configuration [^1]
+- Ensure the user is still present in LDAP.
+- If the LDAP server is Active Directory, ensure the user is active (not
+ blocked/disabled state). This will only be checked if
+ `active_directory: true` is set in the LDAP configuration. [^1]
The user will be set to `ldap_blocked` state in GitLab if the above conditions
fail. This means the user will not be able to login or push/pull code.
The process will also update the following user information:
-1. Email address
-1. If `sync_ssh_keys` is set, SSH public keys
-1. If Kerberos is enabled, Kerberos identity
+- Email address.
+- If `sync_ssh_keys` is set, SSH public keys.
+- If Kerberos is enabled, Kerberos identity.
NOTE: **Note:**
The LDAP sync process updates existing users while new users will
@@ -72,9 +62,6 @@ first time GitLab will trigger a sync for groups the user should be a member of.
That way they don't need to wait for the hourly sync to be granted
access to their groups and projects.
-In GitLab Premium, we can also add a GitLab group to sync with one or multiple LDAP groups or we can
-also add a filter. The filter must comply with the syntax defined in [RFC 2254](https://tools.ietf.org/search/rfc2254).
-
A group sync process will run every hour on the hour, and `group_base` must be set
in LDAP configuration for LDAP synchronizations based on group CN to work. This allows
GitLab group membership to be automatically updated based on LDAP group members.
@@ -120,13 +107,26 @@ following.
1. [Restart GitLab][restart] for the changes to take effect.
----
-
To take advantage of group sync, group owners or maintainers will need to create an
-LDAP group link in their group **Settings > LDAP Groups** page. Multiple LDAP
-groups and/or filters can be linked with a single GitLab group. When the link is
-created, an access level/role is specified (Guest, Reporter, Developer, Maintainer,
-or Owner).
+LDAP group link in their group **Settings > LDAP Groups** page.
+
+Multiple LDAP groups and [filters](#filters-premium-only) can be linked with
+a single GitLab group. When the link is created, an access level/role is
+specified (Guest, Reporter, Developer, Maintainer, or Owner).
+
+### Filters **(PREMIUM ONLY)**
+
+In GitLab Premium, you can add an LDAP user filter for group synchronization.
+Filters allow for complex logic without creating a special LDAP group.
+
+To sync GitLab group membership based on an LDAP filter:
+
+1. Open the **LDAP Synchronization** page for the GitLab group.
+1. Select **LDAP user filter** as the **Sync method**.
+1. Enter an LDAP user filter in the **LDAP user filter** field.
+
+The filter must comply with the
+syntax defined in [RFC 2254](https://tools.ietf.org/search/rfc2254).
## Administrator sync
@@ -190,10 +190,13 @@ group, as opposed to the full DN.
## Global group memberships lock
"Lock memberships to LDAP synchronization" setting allows instance administrators
-to lock down user abilities to invite new members to a group. When enabled following happens:
+to lock down user abilities to invite new members to a group.
+
+When enabled, the following applies:
-1. Only administrator can manage memberships of any group including access levels.
-2. Users are not allowed to share project with other groups or invite members to a project created in a group.
+- Only administrator can manage memberships of any group including access levels.
+- Users are not allowed to share project with other groups or invite members to
+ a project created in a group.
## Adjusting LDAP user sync schedule
@@ -333,10 +336,18 @@ administrative duties.
### Supported LDAP group types/attributes
-GitLab supports LDAP groups that use member attributes `member`, `submember`,
-`uniquemember`, `memberof` and `memberuid`. This means group sync supports, at
-least, LDAP groups with object class `groupOfNames`, `posixGroup`, and
-`groupOfUniqueName`. Other object classes should work fine as long as members
+GitLab supports LDAP groups that use member attributes:
+
+- `member`
+- `submember`
+- `uniquemember`
+- `memberof`
+- `memberuid`.
+
+This means group sync supports, at least, LDAP groups with object class:
+`groupOfNames`, `posixGroup`, and `groupOfUniqueName`.
+
+Other object classes should work fine as long as members
are defined as one of the mentioned attributes. This also means GitLab supports
Microsoft Active Directory, Apple Open Directory, Open LDAP, and 389 Server.
Other LDAP servers should work, too.
@@ -344,11 +355,12 @@ Other LDAP servers should work, too.
Active Directory also supports nested groups. Group sync will recursively
resolve membership if `active_directory: true` is set in the configuration file.
-> **Note:** Nested group membership will only be resolved if the nested group
- also falls within the configured `group_base`. For example, if GitLab sees a
- nested group with DN `cn=nested_group,ou=special_groups,dc=example,dc=com` but
- the configured `group_base` is `ou=groups,dc=example,dc=com`, `cn=nested_group`
- will be ignored.
+NOTE: **Note:**
+Nested group membership will only be resolved if the nested group
+also falls within the configured `group_base`. For example, if GitLab sees a
+nested group with DN `cn=nested_group,ou=special_groups,dc=example,dc=com` but
+the configured `group_base` is `ou=groups,dc=example,dc=com`, `cn=nested_group`
+will be ignored.
### Queries
@@ -403,7 +415,7 @@ main: # 'main' is the GitLab 'provider ID' of this LDAP server
[^1]: In Active Directory, a user is marked as disabled/blocked if the user
account control attribute (`userAccountControl:1.2.840.113556.1.4.803`)
- has bit 2 set. See https://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
+ has bit 2 set. See <https://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/>
for more information.
### User DN has changed
@@ -423,10 +435,10 @@ things to check to debug the situation.
- Ensure LDAP configuration has a `group_base` specified. This configuration is
required for group sync to work properly.
- Ensure the correct LDAP group link is added to the GitLab group. Check group
- links by visiting the GitLab group, then **Settings dropdown -> LDAP groups**.
-- Check that the user has an LDAP identity
+ links by visiting the GitLab group, then **Settings dropdown > LDAP groups**.
+- Check that the user has an LDAP identity:
1. Sign in to GitLab as an administrator user.
- 1. Navigate to **Admin area -> Users**.
+ 1. Navigate to **Admin area > Users**.
1. Search for the user
1. Open the user, by clicking on their name. Do not click 'Edit'.
1. Navigate to the **Identities** tab. There should be an LDAP identity with
@@ -437,7 +449,7 @@ Often, the best way to learn more about why group sync is behaving a certain
way is to enable debug logging. There is verbose output that details every
step of the sync.
-1. Start a Rails console
+1. Start a Rails console:
```bash
# For Omnibus installations
@@ -446,6 +458,7 @@ step of the sync.
# For installations from source
sudo -u git -H bundle exec rails console production
```
+
1. Set the log level to debug (only for this session):
```ruby
@@ -540,8 +553,9 @@ and more DNs may be added, or existing entries modified, based on additional
LDAP group lookups. The very last occurrence of this entry should indicate
exactly which users GitLab believes should be added to the group.
-> **Note:** 10 is 'Guest', 20 is 'Reporter', 30 is 'Developer', 40 is 'Maintainer'
- and 50 is 'Owner'
+NOTE: **Note:**
+10 is 'Guest', 20 is 'Reporter', 30 is 'Developer', 40 is 'Maintainer'
+and 50 is 'Owner'.
```bash
Resolved 'my_group' group member access: {"uid=john0,ou=people,dc=example,dc=com"=>30,
diff --git a/doc/administration/auth/ldap.md b/doc/administration/auth/ldap.md
index be05a4d63a7..186bf4c4825 100644
--- a/doc/administration/auth/ldap.md
+++ b/doc/administration/auth/ldap.md
@@ -7,28 +7,44 @@ type: reference
# LDAP
GitLab integrates with LDAP to support user authentication.
-This integration works with most LDAP-compliant directory
-servers, including Microsoft Active Directory, Apple Open Directory, Open LDAP,
-and 389 Server. GitLab Enterprise Editions include enhanced integration,
+
+This integration works with most LDAP-compliant directory servers, including:
+
+- Microsoft Active Directory
+- Apple Open Directory
+- Open LDAP
+- 389 Server.
+
+GitLab Enterprise Editions (EE) include enhanced integration,
including group membership syncing as well as multiple LDAP servers support.
-## GitLab EE
+For more details about EE-specific LDAP features, see the
+[LDAP Enterprise Edition documentation](ldap-ee.md).
-The information on this page is relevant for both GitLab CE and EE. For more
-details about EE-specific LDAP features, see the
-[LDAP Enterprise Edition documentation](ldap-ee.md). **(STARTER ONLY)**
+NOTE: **Note:**
+The information on this page is relevant for both GitLab CE and EE.
+
+## Overview
+
+[LDAP](https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol)
+stands for **Lightweight Directory Access Protocol**, which is a standard
+application protocol for accessing and maintaining distributed directory
+information services over an Internet Protocol (IP) network.
## Security
-GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email'
-or 'userPrincipalName' attribute. An LDAP user who is allowed to change their
-email on the LDAP server can potentially
-[take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
-on your GitLab server.
+GitLab assumes that LDAP users:
+
+- Are not able to change their LDAP `mail`, `email`, or `userPrincipalName` attribute.
+ An LDAP user who is allowed to change their email on the LDAP server can potentially
+ [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users)
+ on your GitLab server.
+- Have unique email addresses, otherwise it is possible for LDAP users with the same
+ email address to share the same GitLab account.
We recommend against using LDAP integration if your LDAP users are
-allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
-the LDAP server.
+allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on
+the LDAP server or share email addresses.
### User deletion
@@ -64,9 +80,12 @@ NOTE: **Note**:
In GitLab Enterprise Edition Starter, you can configure multiple LDAP servers
to connect to one GitLab server.
-For a complete guide on configuring LDAP with GitLab Community Edition, please check
-the admin guide [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md).
-For GitLab Enterprise Editions, see also [How to configure LDAP with GitLab EE](how_to_configure_ldap_gitlab_ee/index.md). **(STARTER ONLY)**
+For a complete guide on configuring LDAP with:
+
+- GitLab Community Edition, see
+ [How to configure LDAP with GitLab CE](how_to_configure_ldap_gitlab_ce/index.md).
+- Enterprise Editions, see
+ [How to configure LDAP with GitLab EE](how_to_configure_ldap_gitlab_ee/index.md). **(STARTER ONLY)**
To enable LDAP integration you need to add your LDAP server settings in
`/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml` for Omnibus
@@ -384,7 +403,7 @@ production:
Tip: If you want to limit access to the nested members of an Active Directory
group, you can use the following syntax:
-```
+```text
(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com)
```
@@ -402,13 +421,13 @@ The `user_filter` DN can contain special characters. For example:
- A comma:
- ```
+ ```text
OU=GitLab, Inc,DC=gitlab,DC=com
```
- Open and close brackets:
- ```
+ ```text
OU=Gitlab (Inc),DC=gitlab,DC=com
```
@@ -417,13 +436,13 @@ The `user_filter` DN can contain special characters. For example:
- Escape commas with `\2C`. For example:
- ```
+ ```text
OU=GitLab\2C Inc,DC=gitlab,DC=com
```
- Escape open and close brackets with `\28` and `\29`, respectively. For example:
- ```
+ ```text
OU=Gitlab \28Inc\29,DC=gitlab,DC=com
```
@@ -507,11 +526,11 @@ timeout), the login is rejected and a message will be logged to
### Debug LDAP user filter with ldapsearch
-This example uses ldapsearch and assumes you are using ActiveDirectory. The
+This example uses `ldapsearch` and assumes you are using ActiveDirectory. The
following query returns the login names of the users that will be allowed to
log in to GitLab if you configure your own user_filter.
-```
+```sh
ldapsearch -H ldaps://$host:$port -D "$bind_dn" -y bind_dn_password.txt -b "$base" "$user_filter" sAMAccountName
```
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index 78d040cda99..3445f916ef4 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -74,43 +74,38 @@ The OpenID Connect will provide you with a client details and secret for you to
}
```
- > **Note:**
- >
- > - For more information on each configuration option refer to
- the [OmniAuth OpenID Connect usage documentation](https://github.com/m0n9oose/omniauth_openid_connect#usage) and
- the [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html).
+ NOTE: **Note:**
+ For more information on each configuration option refer to the [OmniAuth OpenID Connect usage documentation](https://github.com/m0n9oose/omniauth_openid_connect#usage)
+ and the [OpenID Connect Core 1.0 specification](https://openid.net/specs/openid-connect-core-1_0.html).
1. For the configuration above, change the values for the provider to match your OpenID Connect client setup. Use the following as a guide:
- - `<your_oidc_label>` is the label that will be displayed on the login page.
- - `<your_oidc_url>` (optional) is the URL that points to the OpenID Connect provider. For example, `https://example.com/auth/realms/your-realm`.
- If this value is not provided, the URL is constructed from the `client_options` in the following format: `<client_options.scheme>://<client_options.host>:<client_options.port>`.
- - If `discovery` is set to `true`, the OpenID Connect provider will try to auto discover the client options using `<your_oidc_url>/.well-known/openid-configuration`. Defaults to `false`.
- - `client_auth_method` (optional) specifies the method used for authenticating the client with the OpenID Connect provider.
- - Supported values are:
- - `basic` - HTTP Basic Authentication
- - `jwt_bearer` - JWT based authentication (private key and client secret signing)
- - `mtls` - Mutual TLS or X.509 certificate validation
- - Any other value will POST the client id and secret in the request body
- - If not specified, defaults to `basic`.
- - `<uid_field>` (optional) is the field name from the `user_info` details that will be used as `uid` value. For example, `preferred_username`.
- If this value is not provided or the field with the configured value is missing from the `user_info` details, the `uid` will use the `sub` field.
- - `client_options` are the OpenID Connect client-specific options. Specifically:
-
- - `identifier` is the client identifier as configured in the OpenID Connect service provider.
- - `secret` is the client secret as configured in the OpenID Connect service provider.
- - `redirect_uri` is the GitLab URL to redirect the user to after successful login. For example, `http://example.com/users/auth/openid_connect/callback`.
- - `end_session_endpoint` (optional) is the URL to the endpoint that end the session (logout). Can be provided if auto-discovery disabled or unsuccessful.
-
- The following `client_options` are optional unless auto-discovery is disabled or unsuccessful:
-
- - `authorization_endpoint` is the URL to the endpoint that authorizes the end user.
- - `token_endpoint` is the URL to the endpoint that provides Access Token.
- - `userinfo_endpoint` is the URL to the endpoint that provides the user information.
- - `jwks_uri` is the URL to the endpoint where the Token signer publishes its keys.
-
-1. Save the configuration file.
-1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
- for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
+ - `<your_oidc_label>` is the label that will be displayed on the login page.
+ - `<your_oidc_url>` (optional) is the URL that points to the OpenID Connect provider. For example, `https://example.com/auth/realms/your-realm`.
+ If this value is not provided, the URL is constructed from the `client_options` in the following format: `<client_options.scheme>://<client_options.host>:<client_options.port>`.
+ - If `discovery` is set to `true`, the OpenID Connect provider will try to auto discover the client options using `<your_oidc_url>/.well-known/openid-configuration`. Defaults to `false`.
+ - `client_auth_method` (optional) specifies the method used for authenticating the client with the OpenID Connect provider.
+ - Supported values are:
+ - `basic` - HTTP Basic Authentication
+ - `jwt_bearer` - JWT based authentication (private key and client secret signing)
+ - `mtls` - Mutual TLS or X.509 certificate validation
+ - Any other value will POST the client id and secret in the request body
+ - If not specified, defaults to `basic`.
+ - `<uid_field>` (optional) is the field name from the `user_info` details that will be used as `uid` value. For example, `preferred_username`.
+ If this value is not provided or the field with the configured value is missing from the `user_info` details, the `uid` will use the `sub` field.
+ - `client_options` are the OpenID Connect client-specific options. Specifically:
+ - `identifier` is the client identifier as configured in the OpenID Connect service provider.
+ - `secret` is the client secret as configured in the OpenID Connect service provider.
+ - `redirect_uri` is the GitLab URL to redirect the user to after successful login. For example, `http://example.com/users/auth/openid_connect/callback`.
+ - `end_session_endpoint` (optional) is the URL to the endpoint that end the session (logout). Can be provided if auto-discovery disabled or unsuccessful.
+ - The following `client_options` are optional unless auto-discovery is disabled or unsuccessful:
+ - `authorization_endpoint` is the URL to the endpoint that authorizes the end user.
+ - `token_endpoint` is the URL to the endpoint that provides Access Token.
+ - `userinfo_endpoint` is the URL to the endpoint that provides the user information.
+ - `jwks_uri` is the URL to the endpoint where the Token signer publishes its keys.
+
+1. Save the configuration file.
+1. [Reconfigure](../restart_gitlab.md#omnibus-gitlab-reconfigure) or [restart GitLab](../restart_gitlab.md#installations-from-source)
+ for the changes to take effect if you installed GitLab via Omnibus or from source respectively.
On the sign in page, there should now be an OpenID Connect icon below the regular sign in form.
Click the icon to begin the authentication process. The OpenID Connect provider will ask the user to
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index 3e3054af509..d43b3718bf9 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -27,8 +27,6 @@ but not recommended and out of the scope of this document.
Read the [insecure Registry documentation][docker-insecure] if you want to
implement this.
----
-
**Installations from source**
If you have installed GitLab from source:
@@ -116,8 +114,6 @@ GitLab from source respectively.
Be careful to choose a port different than the one that Registry listens to (`5000` by default),
otherwise you will run into conflicts.
----
-
**Omnibus GitLab installations**
1. Your `/etc/gitlab/gitlab.rb` should contain the Registry URL as well as the
@@ -141,7 +137,14 @@ 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**
@@ -158,8 +161,6 @@ otherwise you will run into conflicts.
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
----
-
Users should now be able to login to the Container Registry with their GitLab
credentials using:
@@ -171,14 +172,16 @@ 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`.
----
-
**Omnibus GitLab installations**
1. Place your TLS certificate and key in
@@ -210,8 +213,6 @@ look like:
> registry_nginx['ssl_certificate_key'] = "/etc/gitlab/ssl/certificate.key"
> ```
----
-
**Installations from source**
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
@@ -226,8 +227,6 @@ look like:
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Make the relevant changes in NGINX as well (domain, port, TLS certificates path).
----
-
Users should now be able to login to the Container Registry using their GitLab
credentials:
@@ -252,8 +251,6 @@ Registry application itself.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `registry` entry and
@@ -272,8 +269,6 @@ If the Container Registry is enabled, then it will be available on all new
projects. To disable this function and let the owners of a project to enable
the Container Registry by themselves, follow the steps below.
----
-
**Omnibus GitLab installations**
1. Edit `/etc/gitlab/gitlab.rb` and add the following line:
@@ -284,8 +279,6 @@ the Container Registry by themselves, follow the steps below.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
1. Open `/home/git/gitlab/config/gitlab.yml`, find the `default_projects_features`
@@ -321,8 +314,6 @@ This path is accessible to:
> **Warning** You should confirm that all GitLab, Registry and web server users
have access to this directory.
----
-
**Omnibus GitLab installations**
The default location where images are stored in Omnibus, is
@@ -336,8 +327,6 @@ The default location where images are stored in Omnibus, is
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
The default location where images are stored in source installations, is
@@ -446,8 +435,6 @@ In the examples below we set the Registry's port to `5001`.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
1. Open the configuration file of your Registry server and edit the
@@ -565,8 +552,6 @@ To configure a notification endpoint in Omnibus:
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**Installations from source**
Configuring the notification endpoint is done in your registry config YML file created
@@ -640,8 +625,6 @@ Start with a value between `25000000` (25MB) and `50000000` (50MB).
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**For installations from source**
1. Edit `config/gitlab.yml`:
@@ -673,8 +656,6 @@ You can add a configuration option for backwards compatibility.
1. Save the file and [reconfigure GitLab][] for the changes to take effect.
----
-
**For installations from source**
1. Edit the YML configuration file you created when you [deployed the registry][registry-deploy]. Add the following snippet:
@@ -701,6 +682,39 @@ To get around this, you can [change the group path](../user/group/index.md#chang
branch name. Another option is to create a [push rule](../push_rules/push_rules.html) to prevent
this at the instance level.
+### Image push errors
+
+When getting errors or "retrying" loops in an attempt to push an image but `docker login` works fine,
+there is likely an issue with the headers forwarded to the registry by NGINX. The default recommended
+NGINX configurations should handle this, but it might occur in custom setups where the SSL is
+offloaded to a third party reverse proxy.
+
+This problem was discussed in a [docker project issue][docker-image-push-issue] and a simple solution
+would be to enable relative urls in the registry.
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['env'] = {
+ "REGISTRY_HTTP_RELATIVEURLS" => true
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**For installations from source**
+
+1. Edit the YML configuration file you created when you [deployed the registry][registry-deploy]. Add the following snippet:
+
+ ```yaml
+ http:
+ relativeurls: true
+ ```
+
+1. Restart the registry for the changes to take affect.
+
[ce-18239]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18239
[docker-insecure-self-signed]: https://docs.docker.com/registry/insecure/#use-self-signed-certificates
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
@@ -719,3 +733,4 @@ this at the instance level.
[new-domain]: #configure-container-registry-under-its-own-domain
[notifications-config]: https://docs.docker.com/registry/notifications/
[registry-notifications-config]: https://docs.docker.com/registry/configuration/#notifications
+[docker-image-push-issue]: https://github.com/docker/distribution/issues/970
diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md
index 113514e1ee8..32462a95a1a 100644
--- a/doc/administration/custom_hooks.md
+++ b/doc/administration/custom_hooks.md
@@ -21,7 +21,7 @@ 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 are symlinked to the gitlab-shell
+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
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 874b1f3c80d..37d7194af53 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -13,6 +13,7 @@ override certain values.
Variable | Type | Description
-------- | ---- | -----------
+`ENABLE_BOOTSNAP` | string | Enables Bootsnap for speeding up initial Rails boot (`1` to enable)
`GITLAB_CDN_HOST` | string | Sets the base URL for a CDN to serve static assets (e.g. `//mycdnsubdomain.fictional-cdn.com`)
`GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
`GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png
index fd51523104b..0a7b841897b 100644
--- a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png
+++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-project-page.png
Binary files differ
diff --git a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png
index b2a6da69d3d..85759d903a4 100644
--- a/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png
+++ b/doc/administration/geo/disaster_recovery/img/checksum-differences-admin-projects.png
Binary files differ
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/configuration.md b/doc/administration/geo/replication/configuration.md
index 0e11dffa0d6..fd076bb79d8 100644
--- a/doc/administration/geo/replication/configuration.md
+++ b/doc/administration/geo/replication/configuration.md
@@ -17,7 +17,7 @@ You are encouraged to first read through all the steps before executing them
in your testing/production environment.
NOTE: **Note:**
-**Do not** set up any custom authentication for the **secondary** nodes. This will be handled by the **primary** node.
+**Do not** set up any custom authentication for the **secondary** nodes. This will be handled by the **primary** node.
Any change that requires access to the **Admin Area** needs to be done in the
**primary** node because the **secondary** node is a read-only replica.
@@ -242,7 +242,7 @@ node's Geo Nodes dashboard in your browser.
![Geo dashboard](img/geo_node_dashboard.png)
If your installation isn't working properly, check the
-[troubleshooting document].
+[troubleshooting document](troubleshooting.md).
The two most obvious issues that can become apparent in the dashboard are:
diff --git a/doc/administration/geo/replication/img/geo_architecture.png b/doc/administration/geo/replication/img/geo_architecture.png
index d318cd5d0f4..aac63be41ff 100644
--- a/doc/administration/geo/replication/img/geo_architecture.png
+++ b/doc/administration/geo/replication/img/geo_architecture.png
Binary files differ
diff --git a/doc/administration/geo/replication/index.md b/doc/administration/geo/replication/index.md
index f0d329d5296..dbd466b906d 100644
--- a/doc/administration/geo/replication/index.md
+++ b/doc/administration/geo/replication/index.md
@@ -1,6 +1,12 @@
-# Geo Replication **(PREMIUM ONLY)**
+# Replication (Geo) **(PREMIUM ONLY)**
-Geo is the solution for widely distributed development teams.
+> - Introduced in GitLab Enterprise Edition 8.9.
+> - Using Geo in combination with
+> [High Availability](../../high_availability/README.md)
+> is considered **Generally Available** (GA) in
+> [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
+
+Replication with Geo is the solution for widely distributed development teams.
## Overview
@@ -8,14 +14,8 @@ Fetching large repositories can take a long time for teams located far from a si
Geo provides local, read-only instances of your GitLab instances, reducing the time it takes to clone and fetch large repositories and speeding up development.
-> - Geo is part of [GitLab Premium](https://about.gitlab.com/pricing/#self-managed).
-> - Introduced in GitLab Enterprise Edition 8.9.
-> - We recommend you use:
-> - At least GitLab Enterprise Edition 10.0 for basic Geo features.
-> - The latest version for a better experience.
-> - Make sure that all nodes run the same GitLab version.
-> - Geo requires PostgreSQL 9.6 and Git 2.9, in addition to GitLab's usual [minimum requirements](../../../install/requirements.md).
-> - Using Geo in combination with [High Availability](../../high_availability/README.md) is considered **Generally Available** (GA) in GitLab [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
+NOTE: **Note:**
+Check the [requirements](#requirements-for-running-geo) carefully before setting up Geo.
For a video introduction to Geo, see [Introduction to GitLab Geo - GitLab Features](https://www.youtube.com/watch?v=-HDLxSjEh6w).
@@ -111,6 +111,13 @@ The following are required to run Geo:
- [Ubuntu](https://www.ubuntu.com) 16.04+
- PostgreSQL 9.6+ with [FDW](https://www.postgresql.org/docs/9.6/postgres-fdw.html) support and [Streaming Replication](https://wiki.postgresql.org/wiki/Streaming_Replication)
- Git 2.9+
+- All nodes must run the same GitLab version.
+
+Additionally, check GitLab's [minimum requirements](../../../install/requirements.md),
+and we recommend you use:
+
+- At least GitLab Enterprise Edition 10.0 for basic Geo features.
+- The latest version for a better experience.
### Firewall rules
@@ -239,35 +246,48 @@ This list of limitations only reflects the latest version of GitLab. If you are
- Object pools for forked project deduplication work only on the **primary** node, and are duplicated on the **secondary** node.
- [External merge request diffs](../../merge_request_diffs.md) will not be replicated if they are on-disk, and viewing merge requests will fail. However, external MR diffs in object storage **are** supported. The default configuration (in-database) does work.
-### Limitations on replication
-
-Only the following items are replicated to the **secondary** node:
-
-- All database content. For example, snippets, epics, issues, merge requests, groups, and project metadata.
-- Project repositories.
-- Project wiki repositories.
-- User uploads. For example, attachments to issues, merge requests, epics, and avatars.
-- CI job artifacts and traces.
+### Limitations on replication/verification
+
+The following table lists the GitLab features along with their replication
+and verification status on a **secondary** node.
+
+You can keep track of the progress to include the missing items in:
+
+- [ee-893](https://gitlab.com/groups/gitlab-org/-/epics/893).
+- [ee-1430](https://gitlab.com/groups/gitlab-org/-/epics/1430).
+
+| Feature | Replicated | Verified |
+|-----------|------------|----------|
+| All database content (e.g. snippets, epics, issues, merge requests, groups, and project metadata) | Yes | Yes |
+| Project repository | Yes | Yes |
+| Project wiki repository | Yes | Yes |
+| Project designs repository | No | No |
+| Uploads (e.g. attachments to issues, merge requests, epics, and avatars) | Yes | Yes, only on transfer, or manually (1) |
+| LFS Objects | Yes | Yes, only on transfer, or manually (1) |
+| CI job artifacts (other than traces) | Yes | No, only manually (1) |
+| Archived traces | Yes | Yes, only on transfer, or manually (1) |
+| Personal snippets | Yes | Yes |
+| Version-controlled personal snippets ([unsupported](https://gitlab.com/gitlab-org/gitlab-ce/issues/13426)) | No | No |
+| Project snippets | Yes | Yes |
+| Version-controlled project snippets ([unsupported](https://gitlab.com/gitlab-org/gitlab-ce/issues/13426)) | No | No |
+| Object pools for forked project deduplication | No | No |
+| [Server-side Git Hooks](../../custom_hooks.md) | No | No |
+| [Elasticsearch integration](../../../integration/elasticsearch.md) | No | No |
+| [GitLab Pages](../../pages/index.md) | No | No |
+| [Container Registry](../../container_registry.md) ([track progress](https://gitlab.com/gitlab-org/gitlab-ee/issues/2870)) | No | No |
+| [NPM Registry](../../npm_registry.md) | No | No |
+| [Maven Packages](../../maven_packages.md) | No | No |
+| [External merge request diffs](../../merge_request_diffs.md) | No, if they are on-disk | No |
+| Content in object storage ([track progress](https://gitlab.com/groups/gitlab-org/-/epics/1526)) | No | No |
+
+1. The integrity can be verified manually using [Integrity Check Rake Task](../../raketasks/check.md) on both nodes and comparing the output between them.
DANGER: **DANGER**
-Data not on this list is unavailable on the **secondary** node. Failing over without manually replicating data not on this list will cause the data to be **lost**.
-
-### Examples of data not replicated
-
-Take special note that these examples of GitLab features are both:
-
-- Commonly used.
-- **Not** replicated by Geo at present.
-
-Examples include:
-
-- [Elasticsearch integration](../../../integration/elasticsearch.md).
-- [Container Registry](../../container_registry.md). [Object Storage](object_storage.md) can mitigate this.
-- [GitLab Pages](../../pages/index.md).
-- [Mattermost integration](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
-
-CAUTION: **Caution:**
-If you wish to use them on a **secondary** node, or to execute a failover successfully, you will need to replicate their data using some other means.
+Features not on this list, or with **No** in the **Replicated** column,
+are not replicated on the **secondary** node. Failing over without manually
+replicating data from those features will cause the data to be **lost**.
+If you wish to use those features on a **secondary** node, or to execute a failover
+successfully, you must replicate their data using some other means.
## Frequently Asked Questions
diff --git a/doc/administration/geo/replication/troubleshooting.md b/doc/administration/geo/replication/troubleshooting.md
index 28abfff973d..fe1557fd8b5 100644
--- a/doc/administration/geo/replication/troubleshooting.md
+++ b/doc/administration/geo/replication/troubleshooting.md
@@ -230,7 +230,7 @@ sudo gitlab-ctl reconfigure
This will increase the timeout to three hours (10800 seconds). Choose a time
long enough to accommodate a full clone of your largest repositories.
-### Reseting Geo **secondary** node replication
+### Resetting Geo **secondary** node replication
If you get a **secondary** node in a broken state and want to reset the replication state,
to start again from scratch, there are a few steps that can help you:
@@ -524,6 +524,20 @@ If it doesn't exist or inadvertent changes have been made to it, run `sudo gitla
If this path is mounted on a remote volume, please check your volume configuration and that it has correct permissions.
+### An existing tracking database cannot be reused
+
+Geo cannot reuse an existing tracking database.
+
+It is safest to use a fresh secondary, or reset the whole secondary by following
+[Resetting Geo secondary node replication](#resetting-geo-secondary-node-replication).
+
+If you are not concerned about possible orphaned directories and files, then you
+can simply reset the existing tracking database with:
+
+```sh
+sudo gitlab-rake geo:db:reset
+```
+
### Geo node has a database that is writable which is an indication it is not configured for replication with the primary node.
This error refers to a problem with the database replica on a **secondary** node,
diff --git a/doc/administration/geo/replication/updating_the_geo_nodes.md b/doc/administration/geo/replication/updating_the_geo_nodes.md
index 166ee94eca4..39174780e24 100644
--- a/doc/administration/geo/replication/updating_the_geo_nodes.md
+++ b/doc/administration/geo/replication/updating_the_geo_nodes.md
@@ -10,10 +10,23 @@ all you need to do is update GitLab itself:
1. Log into each node (**primary** and **secondary** nodes).
1. [Update GitLab][update].
-1. [Update tracking database on **secondary** node](#update-tracking-database-on-secondary-node) when
- the tracking database is enabled.
1. [Test](#check-status-after-updating) **primary** and **secondary** nodes, and check version in each.
+### Check status after updating
+
+Now that the update process is complete, you may want to check whether
+everything is working correctly:
+
+1. Run the Geo raketask on all nodes, everything should be green:
+
+ ```sh
+ sudo gitlab-rake gitlab:geo:check
+ ```
+
+1. Check the **primary** node's Geo dashboard for any errors.
+1. Test the data replication by pushing code to the **primary** node and see if it
+ is received by **secondary** nodes.
+
## Upgrading to GitLab 12.1
By default, GitLab 12.1 will attempt to automatically upgrade the embedded PostgreSQL server to 10.7 from 9.6. Please see [the omnibus documentation](https://docs.gitlab.com/omnibus/settings/database.html#upgrading-a-geo-instance) for the recommended procedure.
@@ -251,7 +264,7 @@ Omnibus is the following:
1. Check the steps about defining `postgresql['sql_user_password']`, `gitlab_rails['db_password']`.
1. Make sure `postgresql['max_replication_slots']` matches the number of **secondary** Geo nodes locations.
1. Install GitLab on the **secondary** server.
-1. Re-run the [database replication process][database-replication].
+1. Re-run the [database replication process](database.md#step-3-initiate-the-replication-process).
## Special update notes for 9.0.x
@@ -419,22 +432,7 @@ is prepended with the relevant node for better clarity:
sudo gitlab-ctl start
```
-## Check status after updating
-
-Now that the update process is complete, you may want to check whether
-everything is working correctly:
-
-1. Run the Geo raketask on all nodes, everything should be green:
-
- ```sh
- sudo gitlab-rake gitlab:geo:check
- ```
-
-1. Check the **primary** node's Geo dashboard for any errors.
-1. Test the data replication by pushing code to the **primary** node and see if it
- is received by **secondary** nodes.
-
-## Update tracking database on **secondary** node
+### Update tracking database on **secondary** node
After updating a **secondary** node, you might need to run migrations on
the tracking database. The tracking database was added in GitLab 9.1,
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 4407facfca9..878f0ef842d 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -2,127 +2,127 @@
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is the service that
provides high-level RPC access to Git repositories. Without it, no other
-components can read or write Git data.
+components can read or write Git data. GitLab components that access Git
+repositories (gitlab-rails, gitlab-shell, gitlab-workhorse, etc.) act as clients
+to Gitaly. End users do not have direct access to Gitaly.
-GitLab components that access Git repositories (gitlab-rails,
-gitlab-shell, gitlab-workhorse) act as clients to Gitaly. End users do
-not have direct access to Gitaly.
+In the rest of this page, Gitaly server is referred to the standalone node that
+only runs Gitaly, and Gitaly client to the GitLab Rails node that runs all other
+processes except Gitaly.
## Configuring Gitaly
The Gitaly service itself is configured via a TOML configuration file.
-This file is documented [in the gitaly
+This file is documented [in the Gitaly
repository](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/configuration/README.md).
-To change a Gitaly setting in Omnibus you can use
-`gitaly['my_setting']` in `/etc/gitlab/gitlab.rb`. Changes will be applied
-when you run `gitlab-ctl reconfigure`.
+In case you want to change some of its settings:
-```ruby
-gitaly['prometheus_listen_addr'] = 'localhost:9236'
-```
-
-To change a Gitaly setting in installations from source you can edit
-`/home/git/gitaly/config.toml`. Changes will be applied when you run
-`service gitlab restart`.
+**For Omnibus GitLab**
-```toml
-prometheus_listen_addr = "localhost:9236"
-```
+1. Edit `/etc/gitlab/gitlab.rb` and add or change the [Gitaly settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/1dd07197c7e5ae23626aad5a4a070a800b670380/files/gitlab-config-template/gitlab.rb.template#L1622-1676).
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-## Client-side GRPC logs
+**For installations from source**
-Gitaly uses the [gRPC](https://grpc.io/) RPC framework. The Ruby gRPC
-client has its own log file which may contain useful information when
-you are seeing Gitaly errors. You can control the log level of the
-gRPC client with the `GRPC_LOG_LEVEL` environment variable. The
-default level is `WARN`.
+1. Edit `/home/git/gitaly/config.toml` and add or change the [Gitaly settings](https://gitlab.com/gitlab-org/gitaly/blob/master/config.toml.example).
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
## Running Gitaly on its own server
-> This is an optional way to deploy Gitaly which can benefit GitLab
+This is an optional way to deploy Gitaly which can benefit GitLab
installations that are larger than a single machine. Most
installations will be better served with the default configuration
used by Omnibus and the GitLab source installation guide.
Starting with GitLab 11.4, Gitaly is able to serve all Git requests without
-needed a shared NFS mount for Git repository data.
+requiring a shared NFS mount for Git repository data.
Between 11.4 and 11.8 the exception was the
[Elasticsearch indexer](https://gitlab.com/gitlab-org/gitlab-elasticsearch-indexer).
But since 11.8 the indexer uses Gitaly for data access as well. NFS can still
be leveraged for redudancy on block level of the Git data. But only has to
be mounted on the Gitaly server.
-NOTE: **Note:** While Gitaly can be used as a replacement for NFS, we do not recommend
-using EFS as it may impact GitLab's performance. Please review the [relevant documentation](../high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
+Starting with GitLab 11.8, it is possible to use ElasticSearch in conjunction with
+a Gitaly setup that isn't utilising NFS. In order to use ElasticSearch in this
+scenario, the [new repository indexer](../../integration/elasticsearch.md#elasticsearch-repository-indexer-beta)
+needs to be enabled in your GitLab configuration.
+
+NOTE: **Note:** While Gitaly can be used as a replacement for NFS, it's not recommended
+to use EFS as it may impact GitLab's performance. Review the [relevant documentation](../high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs)
for more details.
### Network architecture
-- gitlab-rails shards repositories into "repository storages"
-- `gitlab-rails/config/gitlab.yml` contains a map from storage names to
- (Gitaly address, Gitaly token) pairs
-- the `storage name` -\> `(Gitaly address, Gitaly token)` map in
- `gitlab.yml` is the single source of truth for the Gitaly network
- topology
-- a (Gitaly address, Gitaly token) corresponds to a Gitaly server
-- a Gitaly server hosts one or more storages
-- Gitaly addresses must be specified in such a way that they resolve
- correctly for ALL Gitaly clients
-- Gitaly clients are: unicorn, sidekiq, gitlab-workhorse,
- gitlab-shell, Elasticsearch Indexer, and Gitaly itself
-- special case: a Gitaly server must be able to make RPC calls **to
- itself** via its own (Gitaly address, Gitaly token) pair as
- specified in `gitlab-rails/config/gitlab.yml`
-- Gitaly servers must not be exposed to the public internet
+The following list depicts what the network architecture of Gitaly is:
-Gitaly network traffic is unencrypted by default, but supports
-[TLS](#tls-support). Authentication is done through a static token.
+- GitLab Rails shards repositories into [repository storages](../repository_storage_paths.md).
+- `/config/gitlab.yml` contains a map from storage names to
+ `(Gitaly address, Gitaly token)` pairs.
+- the `storage name` -\> `(Gitaly address, Gitaly token)` map in
+ `/config/gitlab.yml` is the single source of truth for the Gitaly network
+ 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,
+ gitlab-shell, Elasticsearch Indexer, and Gitaly itself.
+- A Gitaly server must be able to make RPC calls **to itself** via its own
+ `(Gitaly address, Gitaly token)` pair as specified in `/config/gitlab.yml`.
+- Gitaly servers must not be exposed to the public internet as Gitaly's network
+ traffic is unencrypted by default. The use of firewall is highly recommended
+ to restrict access to the Gitaly server. Another option is to
+ [use TLS](#tls-support).
+- Authentication is done through a static token which is shared among the Gitaly
+ and GitLab Rails nodes.
-NOTE: **Note:** Gitaly network traffic is unencrypted so we recommend a firewall to
-restrict access to your Gitaly server.
+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`.
-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`.
+### 1. Installation
-### Installation
+First install Gitaly on each Gitaly server using either
+Omnibus GitLab or install it from source:
-First install Gitaly using either Omnibus or 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
+ **_do not_** provide the `EXTERNAL_URL=` value.
+- From source: [Install Gitaly](../../install/installation.md#install-gitaly).
-Omnibus: [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
-package you want using **steps 1 and 2** from the GitLab downloads page but
-**_do not_** provide the `EXTERNAL_URL=` value.
+### 2. Client side token configuration
-Source: [Install Gitaly](../../install/installation.md#install-gitaly)
+Configure a token on the instance that runs the GitLab Rails application.
-### Client side token configuration
+**For Omnibus GitLab**
-Configure a token on the client side.
+1. On the client node(s), edit `/etc/gitlab/gitlab.rb`:
-Omnibus installations:
+ ```ruby
+ gitlab_rails['gitaly_token'] = 'abc123secret'
+ ```
-```ruby
-# /etc/gitlab/gitlab.rb
-gitlab_rails['gitaly_token'] = 'abc123secret'
-```
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-Source installations:
+**For installations from source**
-```yaml
-# /home/git/gitlab/config/gitlab.yml
-gitlab:
- gitaly:
- token: 'abc123secret'
-```
+1. On the client node(s), edit `/home/git/gitlab/config/gitlab.yml`:
-You need to reconfigure (Omnibus) or restart (source) for these
-changes to be picked up.
+ ```yaml
+ gitlab:
+ gitaly:
+ token: 'abc123secret'
+ ```
-### Gitaly server configuration
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-Next, on the Gitaly server, we need to configure storage paths, enable
+### 3. Gitaly server configuration
+
+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
@@ -137,131 +137,228 @@ the Gitaly server. The easiest way to accomplish this is to copy `/etc/gitlab/gi
from an existing GitLab server to the Gitaly server. Without this shared secret,
Git operations in GitLab will result in an API error.
-NOTE: **Note:** In most or all cases the storage paths below end in `/repositories` which is
-different than `path` in `git_data_dirs` of Omnibus installations. Check the
-directory layout on your Gitaly server to be sure.
-
-Omnibus installations:
-
-<!--
-updates to following example must also be made at
-https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
--->
-
-```ruby
-# /etc/gitlab/gitlab.rb
-
-# Avoid running unnecessary services on the Gitaly server
-postgresql['enable'] = false
-redis['enable'] = false
-nginx['enable'] = false
-prometheus['enable'] = false
-unicorn['enable'] = false
-sidekiq['enable'] = false
-gitlab_workhorse['enable'] = false
-
-# Prevent database connections during 'gitlab-ctl reconfigure'
-gitlab_rails['rake_cache_clear'] = false
-gitlab_rails['auto_migrate'] = false
-
-# Configure the gitlab-shell API callback URL. Without this, `git push` will
-# fail. This can be your 'front door' GitLab URL or an internal load
-# balancer.
-# Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
-gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
-
-# Make Gitaly accept connections on all network interfaces. You must use
-# firewalls to restrict access to this address/port.
-gitaly['listen_addr'] = "0.0.0.0:8075"
-gitaly['auth_token'] = 'abc123secret'
-
-gitaly['storage'] = [
- { 'name' => 'default', 'path' => '/mnt/gitlab/default/repositories' },
- { 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' },
-]
-
-# 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"
-```
+NOTE: **Note:**
+In most or all cases, the storage paths below end in `/repositories` which is
+not that case with `path` in `git_data_dirs` of Omnibus GitLab installations.
+Check the directory layout on your Gitaly server to be sure.
+
+**For Omnibus GitLab**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ <!--
+ updates to following example must also be made at
+ https://gitlab.com/charts/gitlab/blob/master/doc/advanced/external-gitaly/external-omnibus-gitaly.md#configure-omnibus-gitlab
+ -->
+
+ ```ruby
+ # /etc/gitlab/gitlab.rb
+
+ # Avoid running unnecessary services on the Gitaly server
+ postgresql['enable'] = false
+ redis['enable'] = false
+ nginx['enable'] = false
+ prometheus['enable'] = false
+ unicorn['enable'] = false
+ sidekiq['enable'] = false
+ gitlab_workhorse['enable'] = false
+
+ # Prevent database connections during 'gitlab-ctl reconfigure'
+ gitlab_rails['rake_cache_clear'] = false
+ gitlab_rails['auto_migrate'] = false
+
+ # Configure the gitlab-shell API callback URL. Without this, `git push` will
+ # fail. This can be your 'front door' GitLab URL or an internal load
+ # balancer.
+ # Don't forget to copy `/etc/gitlab/gitlab-secrets.json` from web server to Gitaly server.
+ gitlab_rails['internal_api_url'] = 'https://gitlab.example.com'
+
+ # Make Gitaly accept connections on all network interfaces. You must use
+ # firewalls to restrict access to this address/port.
+ 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`:
+
+ ```
+ gitaly['storage'] = [
+ { 'name' => 'storage2' },
+ ]
+ ```
+
+ NOTE: **Note:**
+ In some cases, you'll have to set `path` for `gitaly['storage']` in the
+ format `'path' => '/mnt/gitlab/<storage name>/repositories'`.
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+**For installations from source**
+
+1. On the client node(s), edit `/home/git/gitaly/config.toml`:
+
+ ```toml
+ listen_addr = '0.0.0.0:8075'
+ tls_listen_addr = '0.0.0.0:9999'
+
+ [tls]
+ certificate_path = /path/to/cert.pem
+ key_path = /path/to/key.pem
+
+ [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
+ format `path = '/mnt/gitlab/<storage name>/repositories'`.
+
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+
+### 4. Converting clients to use the Gitaly server
+
+As the final step, you need to update the client machines to switch from using
+their local Gitaly service to the new Gitaly server you just configured. This
+is a risky step because if there is any sort of network, firewall, or name
+resolution problem preventing your GitLab server from reaching the Gitaly server,
+then all Gitaly requests will fail.
-Source installations:
+Additionally, you need to
+[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
-```toml
-# /home/git/gitaly/config.toml
-listen_addr = '0.0.0.0:8075'
-tls_listen_addr = '0.0.0.0:9999'
+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`.
-[tls]
-certificate_path = /path/to/cert.pem
-key_path = /path/to/key.pem
+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`.
-[auth]
-token = 'abc123secret'
+**For Omnibus GitLab**
-[[storage]]
-name = 'default'
-path = '/mnt/gitlab/default/repositories'
+1. Edit `/etc/gitlab/gitlab.rb`:
-[[storage]]
-name = 'storage1'
-path = '/mnt/gitlab/storage1/repositories'
-```
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage1' => { 'gitaly_address' => 'tcp://gitaly1.internal:8075' },
+ 'storage2' => { 'gitaly_address' => 'tcp://gitaly2.internal:8075' },
+ })
-Again, reconfigure (Omnibus) or restart (source).
+ gitlab_rails['gitaly_token'] = 'abc123secret'
+ ```
-### Converting clients to use the Gitaly server
+ NOTE: **Note:**
+ In some cases, you'll have to set `path` for each `git_data_dirs` in the
+ format `'path' => '/mnt/gitlab/<storage name>'`.
-Now as the final step update the client machines to switch from using
-their local Gitaly service to the new Gitaly server you just
-configured. This is a risky step because if there is any sort of
-network, firewall, or name resolution problem preventing your GitLab
-server from reaching the Gitaly server then all Gitaly requests will
-fail.
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. Tail the logs to see the requests:
-Additionally, you need to
-[disable Rugged if previously manually enabled](../high_availability/nfs.md#improving-nfs-performance-with-gitlab).
+ ```sh
+ sudo gitlab-ctl tail gitaly
+ ```
-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.
+**For installations from source**
-Omnibus installations:
+1. Edit `/home/git/gitlab/config/gitlab.yml`:
-```ruby
-# /etc/gitlab/gitlab.rb
-git_data_dirs({
- 'default' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
- 'storage1' => { 'gitaly_address' => 'tcp://gitaly.internal:8075' },
-})
+ ```yaml
+ gitlab:
+ repositories:
+ storages:
+ 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'
-```
+ gitaly:
+ token: 'abc123secret'
+ ```
-Source installations:
-
-```yaml
-# /home/git/gitlab/config/gitlab.yml
-gitlab:
- repositories:
- storages:
- default:
- path: /mnt/gitlab/default/repositories
- gitaly_address: tcp://gitaly.internal:8075
- storage1:
- path: /mnt/gitlab/storage1/repositories
- gitaly_address: tcp://gitaly.internal:8075
-
- gitaly:
- token: 'abc123secret'
-```
+ NOTE: **Note:**
+ In some cases, you'll have to set `path` for each of the `storages` in the
+ format `path: /mnt/gitlab/<storage name>/repositories`.
+
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+1. Tail the logs to see the requests:
-Now reconfigure (Omnibus) or restart (source). When you tail the
-Gitaly logs on your Gitaly server (`sudo gitlab-ctl tail gitaly` or
-`tail -f /home/git/gitlab/log/gitaly.log`) you should see requests
-coming in. One sure way to trigger a Gitaly request is to clone a
-repository from your GitLab server over HTTP.
+ ```sh
+ tail -f /home/git/gitlab/log/gitaly.log
+ ```
+
+When you tail the Gitaly logs on your Gitaly server you should see requests
+coming in. One sure way to trigger a Gitaly request is to clone a repository
+from your GitLab server over HTTP.
+
+### Disabling the Gitaly service in a cluster environment
+
+If you are running Gitaly [as a remote
+service](#running-gitaly-on-its-own-server) you may want to disable
+the local Gitaly service that runs on your GitLab server by default.
+Disabling Gitaly only makes sense when you run GitLab in a custom
+cluster configuration, where different services run on different
+machines. Disabling Gitaly on all machines in the cluster is not a
+valid configuration.
+
+To disable Gitaly on a client node:
+
+**For Omnibus GitLab**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ gitaly['enable'] = false
+ ```
+
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+
+**For installations from source**
+
+1. Edit `/etc/default/gitlab`:
+
+ ```shell
+ gitaly_enabled=false
+ ```
+
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
## TLS support
@@ -271,168 +368,131 @@ Gitaly supports TLS encryption. To be able to communicate
with a Gitaly instance that listens for secure connections you will need to use `tls://` url
scheme in the `gitaly_address` of the corresponding storage entry in the GitLab configuration.
-The admin needs to bring their own certificate as we do not provide that automatically.
-The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
+You will need to bring your own certificates as this isn't provided automatically.
+The certificate to be used needs to be installed on all Gitaly nodes and on all
+client nodes that communicate with it following the procedure described in
+[GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates).
-Note that it is possible to configure Gitaly servers with both an
+NOTE: **Note:**
+It is possible to configure Gitaly servers with both an
unencrypted listening address `listen_addr` and an encrypted listening
address `tls_listen_addr` at the same time. This allows you to do a
gradual transition from unencrypted to encrypted traffic, if necessary.
-To observe what type of connections are actually being used in a
-production environment you can use the following Prometheus query:
-
-```
-sum(rate(gitaly_connections_total[5m])) by (type)
-```
+To configure Gitaly with TLS:
-### Example TLS configuration
+**For Omnibus GitLab**
-### Omnibus installations:
+1. On the client nodes, edit `/etc/gitlab/gitlab.rb`:
-#### On client nodes:
+ ```ruby
+ git_data_dirs({
+ 'default' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage1' => { 'gitaly_address' => 'tls://gitaly1.internal:9999' },
+ 'storage2' => { 'gitaly_address' => 'tls://gitaly2.internal:9999' },
+ })
-```ruby
-# /etc/gitlab/gitlab.rb
-git_data_dirs({
- 'default' => { 'gitaly_address' => 'tls://gitaly.internal:9999' },
- 'storage1' => { 'gitaly_address' => 'tls://gitaly.internal:9999' },
-})
+ gitlab_rails['gitaly_token'] = 'abc123secret'
+ ```
-gitlab_rails['gitaly_token'] = 'abc123secret'
-```
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
+1. On the Gitaly server nodes, edit `/etc/gitlab/gitlab.rb`:
-#### On Gitaly server nodes:
+ ```ruby
+ gitaly['tls_listen_addr'] = "0.0.0.0:9999"
+ gitaly['certificate_path'] = "path/to/cert.pem"
+ gitaly['key_path'] = "path/to/key.pem"
+ ```
-```ruby
-gitaly['tls_listen_addr'] = "0.0.0.0:9999"
-gitaly['certificate_path'] = "path/to/cert.pem"
-gitaly['key_path'] = "path/to/key.pem"
-```
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-### Source installations:
+**For installations from source**
-#### On client nodes:
+1. On the client nodes, edit `/home/git/gitlab/config/gitlab.yml`:
-```yaml
-# /home/git/gitlab/config/gitlab.yml
-gitlab:
- repositories:
- storages:
- default:
- path: /mnt/gitlab/default/repositories
- gitaly_address: tls://gitaly.internal:9999
- storage1:
- path: /mnt/gitlab/storage1/repositories
- gitaly_address: tls://gitaly.internal:9999
+ ```yaml
+ gitlab:
+ repositories:
+ storages:
+ default:
+ gitaly_address: tls://gitaly1.internal:9999
+ storage1:
+ gitaly_address: tls://gitaly1.internal:9999
+ storage2:
+ gitaly_address: tls://gitaly2.internal:9999
- gitaly:
- token: 'abc123secret'
-```
-
-#### On Gitaly server nodes:
-
-```toml
-# /home/git/gitaly/config.toml
-tls_listen_addr = '0.0.0.0:9999'
+ gitaly:
+ token: 'abc123secret'
+ ```
-[tls]
-certificate_path = '/path/to/cert.pem'
-key_path = '/path/to/key.pem'
-```
-
-## Gitaly-ruby
+ NOTE: **Note:**
+ In some cases, you'll have to set `path` for each of the `storages` in the
+ format `path: /mnt/gitlab/<storage name>/repositories`.
-Gitaly was developed to replace Ruby application code in gitlab-ce/ee.
-In order to save time and/or avoid the risk of rewriting existing
-application logic, in some cases we chose to copy some application code
-from gitlab-ce into Gitaly almost as-is. To be able to run that code, we
-made gitaly-ruby, which is a sidecar process for the main Gitaly Go
-process. Some examples of things that are implemented in gitaly-ruby are
-RPC's that deal with wiki's, and RPC's that create commits on behalf of
-a user, such as merge commits.
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
+1. On the Gitaly server nodes, edit `/home/git/gitaly/config.toml`:
-### Number of gitaly-ruby workers
+ ```toml
+ tls_listen_addr = '0.0.0.0:9999'
-Gitaly-ruby has much less capacity than Gitaly itself. If your Gitaly
-server has to handle a lot of request, the default setting of having
-just 1 active gitaly-ruby sidecar might not be enough. If you see
-ResourceExhausted errors from Gitaly it's very likely that you have not
-enough gitaly-ruby capacity.
+ [tls]
+ certificate_path = '/path/to/cert.pem'
+ key_path = '/path/to/key.pem'
+ ```
-You can increase the number of gitaly-ruby processes on your Gitaly
-server with the following settings.
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
-Omnibus:
+To observe what type of connections are actually being used in a
+production environment you can use the following Prometheus query:
-```ruby
-# /etc/gitlab/gitlab.rb
-# Default is 2 workers. The minimum is 2; 1 worker is always reserved as
-# a passive stand-by.
-gitaly['ruby_num_workers'] = 4
```
-
-Source:
-
-```toml
-# /home/git/gitaly/config.toml
-[gitaly-ruby]
-num_workers = 4
+sum(rate(gitaly_connections_total[5m])) by (type)
```
-### Observing gitaly-ruby traffic
+## `gitaly-ruby`
-Gitaly-ruby is a somewhat hidden, internal implementation detail of
-Gitaly. There is not that much visibility into what goes on inside
-gitaly-ruby processes.
+Gitaly was developed to replace the Ruby application code in GitLab.
+In order to save time and/or avoid the risk of rewriting existing
+application logic, in some cases we chose to copy some application code
+from GitLab into Gitaly almost as-is. To be able to run that code,
+`gitaly-ruby` was created, which is a "sidecar" process for the main Gitaly Go
+process. Some examples of things that are implemented in `gitaly-ruby` are
+RPCs that deal with wikis, and RPCs that create commits on behalf of
+a user, such as merge commits.
-If you have Prometheus set up to scrape your Gitaly process, you can see
-request rates and error codes for individual RPC's in gitaly-ruby by
-querying `grpc_client_handled_total`. Strictly speaking this metric does
-not differentiate between gitaly-ruby and other RPC's, but in practice
-(as of GitLab 11.9), all gRPC calls made by Gitaly itself are internal
-calls from the main Gitaly process to one of its gitaly-ruby sidecars.
+### Number of `gitaly-ruby` workers
-Assuming your `grpc_client_handled_total` counter only observes Gitaly,
-the following query shows you RPC's are (most likely) internally
-implemented as calls to gitaly-ruby.
+`gitaly-ruby` has much less capacity than Gitaly itself. If your Gitaly
+server has to handle a lot of requests, the default setting of having
+just one active `gitaly-ruby` sidecar might not be enough. If you see
+`ResourceExhausted` errors from Gitaly, it's very likely that you have not
+enough `gitaly-ruby` capacity.
-```
-sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0
-```
+You can increase the number of `gitaly-ruby` processes on your Gitaly
+server with the following settings.
-## Disabling or enabling the Gitaly service in a cluster environment
+**For Omnibus GitLab**
-If you are running Gitaly [as a remote
-service](#running-gitaly-on-its-own-server) you may want to disable
-the local Gitaly service that runs on your GitLab server by default.
+1. Edit `/etc/gitlab/gitlab.rb`:
-> 'Disabling Gitaly' only makes sense when you run GitLab in a custom
-cluster configuration, where different services run on different
-machines. Disabling Gitaly on all machines in the cluster is not a
-valid configuration.
+ ```ruby
+ # Default is 2 workers. The minimum is 2; 1 worker is always reserved as
+ # a passive stand-by.
+ gitaly['ruby_num_workers'] = 4
+ ```
-If you are setting up a GitLab cluster where Gitaly does not need to
-run on all machines, you can disable the Gitaly service in your
-Omnibus installation, add the following line to `/etc/gitlab/gitlab.rb`:
+1. Save the file and [reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
-```ruby
-gitaly['enable'] = false
-```
-
-When you run `gitlab-ctl reconfigure` the Gitaly service will be
-disabled.
+**For installations from source**
-To disable the Gitaly service in a GitLab cluster where you installed
-GitLab from source, add the following to `/etc/default/gitlab` on the
-machine where you want to disable Gitaly.
+1. Edit `/home/git/gitaly/config.toml`:
-```shell
-gitaly_enabled=false
-```
+ ```toml
+ [gitaly-ruby]
+ num_workers = 4
+ ```
-When you run `service gitlab restart` Gitaly will be disabled on this
-particular machine.
+1. Save the file and [restart GitLab](../restart_gitlab.md#installations-from-source).
## Eliminating NFS altogether
@@ -440,19 +500,32 @@ If you are planning to use Gitaly without NFS for your storage needs
and want to eliminate NFS from your environment altogether, there are
a few things that you need to do:
- 1. Make sure the [`git` user home directory](https://docs.gitlab.com/omnibus/settings/configuration.html#moving-the-home-directory-for-a-user) is on local disk.
- 1. Configure [database lookup of SSH keys](../operations/fast_ssh_key_lookup.md)
- to eliminate the need for a shared authorized_keys file.
- 1. Configure [object storage for job artifacts](../job_artifacts.md#using-object-storage)
- including [live tracing](../job_traces.md#new-live-trace-architecture).
- 1. Configure [object storage for LFS objects](../../workflow/lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage).
- 1. Configure [object storage for uploads](../uploads.md#using-object-storage-core-only).
-
-NOTE: **Note:** One current feature of GitLab still requires a shared directory (NFS): [GitLab Pages](../../user/project/pages/index.md).
+1. Make sure the [`git` user home directory](https://docs.gitlab.com/omnibus/settings/configuration.html#moving-the-home-directory-for-a-user) is on local disk.
+1. Configure [database lookup of SSH keys](../operations/fast_ssh_key_lookup.md)
+ to eliminate the need for a shared authorized_keys file.
+1. Configure [object storage for job artifacts](../job_artifacts.md#using-object-storage)
+ including [live tracing](../job_traces.md#new-live-trace-architecture).
+1. Configure [object storage for LFS objects](../../workflow/lfs/lfs_administration.md#storing-lfs-objects-in-remote-object-storage).
+1. Configure [object storage for uploads](../uploads.md#using-object-storage-core-only).
+
+NOTE: **Note:**
+One current feature of GitLab that still requires a shared directory (NFS) is
+[GitLab Pages](../../user/project/pages/index.md).
There is [work in progress](https://gitlab.com/gitlab-org/gitlab-pages/issues/196)
to eliminate the need for NFS to support GitLab Pages.
-## Troubleshooting Gitaly in production
+## Troubleshooting Gitaly
+
+### Commits, pushes, and clones return a 401
+
+```
+remote: GitLab: 401 Unauthorized
+```
+
+You will need to sync your `gitlab-secrets.json` file with your GitLab
+app nodes.
+
+### `gitaly-debug`
Since GitLab 11.6, Gitaly comes with a command-line tool called
`gitaly-debug` that can be run on a Gitaly server to aid in
@@ -462,3 +535,123 @@ Git clone speed for a specific repository on the server.
For an up to date list of sub-commands see [the gitaly-debug
README](https://gitlab.com/gitlab-org/gitaly/blob/master/cmd/gitaly-debug/README.md).
+
+### Client side GRPC logs
+
+Gitaly uses the [gRPC](https://grpc.io/) RPC framework. The Ruby gRPC
+client has its own log file which may contain useful information when
+you are seeing Gitaly errors. You can control the log level of the
+gRPC client with the `GRPC_LOG_LEVEL` environment variable. The
+default level is `WARN`.
+
+### Observing `gitaly-ruby` traffic
+
+[`gitaly-ruby`](#gitaly-ruby) is an internal implementation detail of Gitaly,
+so, there's not that much visibility into what goes on inside
+`gitaly-ruby` processes.
+
+If you have Prometheus set up to scrape your Gitaly process, you can see
+request rates and error codes for individual RPCs in `gitaly-ruby` by
+querying `grpc_client_handled_total`. Strictly speaking, this metric does
+not differentiate between `gitaly-ruby` and other RPCs, but in practice
+(as of GitLab 11.9), all gRPC calls made by Gitaly itself are internal
+calls from the main Gitaly process to one of its `gitaly-ruby` sidecars.
+
+Assuming your `grpc_client_handled_total` counter only observes Gitaly,
+the following query shows you RPCs are (most likely) internally
+implemented as calls to `gitaly-ruby`:
+
+```
+sum(rate(grpc_client_handled_total[5m])) by (grpc_method) > 0
+```
+
+### Repository changes fail with a `401 Unauthorized` error
+
+If you're running Gitaly on its own server and notice that users can
+successfully clone and fetch repositories (via both SSH and HTTPS), but can't
+push to them or make changes to the repository in the web UI without getting a
+`401 Unauthorized` message, then it's possible Gitaly is failing to authenticate
+with the other nodes due to having the [wrong secrets file](#3-gitaly-server-configuration).
+
+Confirm the following are all true:
+
+- When any user performs a `git push` to any repository on this Gitaly node, it
+ fails with the following error (note the `401 Unauthorized`):
+
+ ```sh
+ remote: GitLab: 401 Unauthorized
+ To <REMOTE_URL>
+ ! [remote rejected] branch-name -> branch-name (pre-receive hook declined)
+ error: failed to push some refs to '<REMOTE_URL>'
+ ```
+
+- When any user adds or modifies a file from the repository using the GitLab
+ UI, it immediatley fails with a red `401 Unauthorized` banner.
+- Creating a new project and [initializing it with a README](../../gitlab-basics/create-project.md#blank-projects)
+ successfully creates the project but doesn't create the README.
+- When [tailing the logs](https://docs.gitlab.com/omnibus/settings/logs.md#tail-logs-in-a-console-on-the-server) on an app node and reproducing the error, you get `401` errors
+ when reaching the `/api/v4/internal/allowed` endpoint:
+
+ ```sh
+ # api_json.log
+ {
+ "time": "2019-07-18T00:30:14.967Z",
+ "severity": "INFO",
+ "duration": 0.57,
+ "db": 0,
+ "view": 0.57,
+ "status": 401,
+ "method": "POST",
+ "path": "\/api\/v4\/internal\/allowed",
+ "params": [
+ {
+ "key": "action",
+ "value": "git-receive-pack"
+ },
+ {
+ "key": "changes",
+ "value": "REDACTED"
+ },
+ {
+ "key": "gl_repository",
+ "value": "REDACTED"
+ },
+ {
+ "key": "project",
+ "value": "\/path\/to\/project.git"
+ },
+ {
+ "key": "protocol",
+ "value": "web"
+ },
+ {
+ "key": "env",
+ "value": "{\"GIT_ALTERNATE_OBJECT_DIRECTORIES\":[],\"GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE\":[],\"GIT_OBJECT_DIRECTORY\":null,\"GIT_OBJECT_DIRECTORY_RELATIVE\":null}"
+ },
+ {
+ "key": "user_id",
+ "value": "2"
+ },
+ {
+ "key": "secret_token",
+ "value": "[FILTERED]"
+ }
+ ],
+ "host": "gitlab.example.com",
+ "ip": "REDACTED",
+ "ua": "Ruby",
+ "route": "\/api\/:version\/internal\/allowed",
+ "queue_duration": 4.24,
+ "gitaly_calls": 0,
+ "gitaly_duration": 0,
+ "correlation_id": "XPUZqTukaP3"
+ }
+
+ # nginx_access.log
+ [IP] - - [18/Jul/2019:00:30:14 +0000] "POST /api/v4/internal/allowed HTTP/1.1" 401 30 "" "Ruby"
+ ```
+
+To fix this problem, confirm that your [`gitlab-secrets.json` file](#3-gitaly-server-configuration)
+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).
diff --git a/doc/administration/high_availability/README.md b/doc/administration/high_availability/README.md
index 41ef68f5b57..56665ba8b9a 100644
--- a/doc/administration/high_availability/README.md
+++ b/doc/administration/high_availability/README.md
@@ -164,25 +164,24 @@ contention due to certain workloads.
#### Reference Architecture
-- **Status:** Work-in-progress
- **Supported Users (approximate):** 10,000
-- **Related Issues:** [gitlab-com/support/support-team-meta#1513](https://gitlab.com/gitlab-com/support/support-team-meta/issues/1513),
- [gitlab-org/quality/team-tasks#110](https://gitlab.com/gitlab-org/quality/team-tasks/issues/110)
-
-The Support and Quality teams are in the process of building and performance testing
-an environment that will support about 10,000 users. The specifications below
-are a work-in-progress representation of the work so far. Quality will be
-certifying this environment in FY20-Q2. The specifications may be adjusted
-prior to certification based on performance testing.
-
-- 3 PostgreSQL - 4 CPU, 8GB RAM per node
-- 1 PgBouncer - 2 CPU, 4GB RAM
-- 2 Redis - 2 CPU, 8GB RAM per node
-- 3 Consul/Sentinel - 2 CPU, 2GB RAM per node
-- 4 Sidekiq - 4 CPU, 8GB RAM per node
-- 5 GitLab application nodes - 20 CPU, 64GB RAM per node
-- 1 Gitaly - 20 CPU, 64GB RAM
-- 1 Monitoring node - 4 CPU, 8GB RAM
+- **Known Issues:** While validating the reference architecture, slow endpoints were discovered and are being investigated. [gitlab-org/gitlab-ce/issues/64335](https://gitlab.com/gitlab-org/gitlab-ce/issues/64335)
+
+The Support and Quality teams built, performance tested, and validated an
+environment that supports about 10,000 users. The specifications below are a
+representation of the work so far. The specifications may be adjusted in the
+future based on additional testing and iteration.
+
+NOTE: **Note:** The specifications here were performance tested against a specific coded workload. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repo/change size.
+
+- 3 PostgreSQL - 4 CPU, 16GiB memory per node
+- 1 PgBouncer - 2 CPU, 4GiB memory
+- 2 Redis - 2 CPU, 8GiB memory per node
+- 3 Consul/Sentinel - 2 CPU, 2GiB memory per node
+- 4 Sidekiq - 4 CPU, 16GiB memory per node
+- 5 GitLab application nodes - 16 CPU, 64GiB memory per node
+- 1 Gitaly - 16 CPU, 64GiB memory
+- 1 Monitoring node - 2 CPU, 8GiB memory, 100GiB local storage
### Fully Distributed
diff --git a/doc/administration/high_availability/database.md b/doc/administration/high_availability/database.md
index f7a1f425b40..7c9e02d889e 100644
--- a/doc/administration/high_availability/database.md
+++ b/doc/administration/high_availability/database.md
@@ -327,6 +327,7 @@ When installing the GitLab package, do not supply `EXTERNAL_URL` value.
postgresql['sql_user_password'] = 'POSTGRESQL_PASSWORD_HASH'
# Replace X with value of number of db nodes + 1
postgresql['max_wal_senders'] = X
+ postgresql['max_replication_slots'] = X
# Replace XXX.XXX.XXX.XXX/YY with Network Address
postgresql['trust_auth_cidr_addresses'] = %w(XXX.XXX.XXX.XXX/YY)
diff --git a/doc/administration/high_availability/img/fully-distributed.png b/doc/administration/high_availability/img/fully-distributed.png
index ad23207134e..c3cd2bf24f0 100644
--- a/doc/administration/high_availability/img/fully-distributed.png
+++ b/doc/administration/high_availability/img/fully-distributed.png
Binary files differ
diff --git a/doc/administration/high_availability/img/horizontal.png b/doc/administration/high_availability/img/horizontal.png
index c3bd489d96f..75d08e1097a 100644
--- a/doc/administration/high_availability/img/horizontal.png
+++ b/doc/administration/high_availability/img/horizontal.png
Binary files differ
diff --git a/doc/administration/high_availability/img/hybrid.png b/doc/administration/high_availability/img/hybrid.png
index 7d4a56bf0ea..8dd9923e597 100644
--- a/doc/administration/high_availability/img/hybrid.png
+++ b/doc/administration/high_availability/img/hybrid.png
Binary files differ
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/high_availability/monitoring_node.md b/doc/administration/high_availability/monitoring_node.md
index b91a994d01e..b2750603c74 100644
--- a/doc/administration/high_availability/monitoring_node.md
+++ b/doc/administration/high_availability/monitoring_node.md
@@ -18,7 +18,7 @@ The steps below are the minimum necessary to configure a Monitoring node running
Omnibus:
1. SSH into the Monitoring node.
-1. [Download/install](https://about.gitlab.com/install) the Omnibus GitLab
+1. [Download/install](https://about.gitlab.com/install/) the Omnibus GitLab
package you want using **steps 1 and 2** from the GitLab downloads page.
- Do not complete any other steps on the download page.
diff --git a/doc/administration/high_availability/nfs.md b/doc/administration/high_availability/nfs.md
index 294f0e969d5..274bd32299b 100644
--- a/doc/administration/high_availability/nfs.md
+++ b/doc/administration/high_availability/nfs.md
@@ -71,6 +71,11 @@ bug](https://bugzilla.redhat.com/show_bug.cgi?id=1552203) that may be fixed in
[more recent kernels with this
commit](https://github.com/torvalds/linux/commit/95da1b3a5aded124dd1bda1e3cdb876184813140).
+NOTE: **Note** Red Hat Enterprise 7 [shipped a kernel
+update](https://access.redhat.com/errata/RHSA-2019:2029) on August 6,
+2019 that may have resolved this problem. The following instructions may
+not be needed if the latest kernel is updated properly.
+
GitLab recommends all NFS users disable the NFS server
delegation feature. To disable NFS server delegations
on an Linux NFS server, do the following:
diff --git a/doc/administration/high_availability/pgbouncer.md b/doc/administration/high_availability/pgbouncer.md
index 0b945bc6244..b99724d12a2 100644
--- a/doc/administration/high_availability/pgbouncer.md
+++ b/doc/administration/high_availability/pgbouncer.md
@@ -20,84 +20,85 @@ It is recommended to run pgbouncer alongside the `gitlab-rails` service, or on i
1. Edit `/etc/gitlab/gitlab.rb` replacing values noted in the `# START user configuration` section:
- ```ruby
- # Disable all components except Pgbouncer and Consul agent
- roles ['pgbouncer_role']
-
- # Configure Pgbouncer
- pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
-
- # Configure Consul agent
- consul['watchers'] = %w(postgresql)
-
- # START user configuration
- # Please set the real values as explained in Required Information section
- # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
- # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
- pgbouncer['users'] = {
- 'gitlab-consul': {
- password: 'CONSUL_PASSWORD_HASH'
- },
- 'pgbouncer': {
- password: 'PGBOUNCER_PASSWORD_HASH'
- }
- }
- # Replace placeholders:
- #
- # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
- # with the addresses gathered for CONSUL_SERVER_NODES
- consul['configuration'] = {
- retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
- }
- #
- # END user configuration
- ```
-
- > `pgbouncer_role` was introduced with GitLab 10.3
-
-1. Run `gitlab-ctl reconfigure`
+ ```ruby
+ # Disable all components except Pgbouncer and Consul agent
+ roles ['pgbouncer_role']
+
+ # Configure Pgbouncer
+ pgbouncer['admin_users'] = %w(pgbouncer gitlab-consul)
+
+ # Configure Consul agent
+ consul['watchers'] = %w(postgresql)
+
+ # START user configuration
+ # Please set the real values as explained in Required Information section
+ # Replace CONSUL_PASSWORD_HASH with with a generated md5 value
+ # Replace PGBOUNCER_PASSWORD_HASH with with a generated md5 value
+ pgbouncer['users'] = {
+ 'gitlab-consul': {
+ password: 'CONSUL_PASSWORD_HASH'
+ },
+ 'pgbouncer': {
+ password: 'PGBOUNCER_PASSWORD_HASH'
+ }
+ }
+ # Replace placeholders:
+ #
+ # Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z
+ # with the addresses gathered for CONSUL_SERVER_NODES
+ consul['configuration'] = {
+ retry_join: %w(Y.Y.Y.Y consul1.gitlab.example.com Z.Z.Z.Z)
+ }
+ #
+ # END user configuration
+ ```
+
+ NOTE: **Note:**
+ `pgbouncer_role` was introduced with GitLab 10.3.
+
+1. Run `gitlab-ctl reconfigure`
1. Create a `.pgpass` file so Consul is able to
reload pgbouncer. Enter the `PGBOUNCER_PASSWORD` twice when asked:
- ```sh
- gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
- ```
+ ```sh
+ gitlab-ctl write-pgpass --host 127.0.0.1 --database pgbouncer --user pgbouncer --hostuser gitlab-consul
+ ```
#### PGBouncer Checkpoint
1. Ensure the node is talking to the current master:
- ```sh
- gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
- ```
+ ```sh
+ gitlab-ctl pgb-console # You will be prompted for PGBOUNCER_PASSWORD
+ ```
- If there is an error `psql: ERROR: Auth failed` after typing in the
- password, ensure you previously generated the MD5 password hashes with the correct
- format. The correct format is to concatenate the password and the username:
- `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
- needed to generate an MD5 password hash for the `pgbouncer` user.
+ If there is an error `psql: ERROR: Auth failed` after typing in the
+ password, ensure you previously generated the MD5 password hashes with the correct
+ format. The correct format is to concatenate the password and the username:
+ `PASSWORDUSERNAME`. For example, `Sup3rS3cr3tpgbouncer` would be the text
+ needed to generate an MD5 password hash for the `pgbouncer` user.
1. Once the console prompt is available, run the following queries:
- ```sh
- show databases ; show clients ;
- ```
-
- The output should be similar to the following:
+ ```sh
+ show databases ; show clients ;
+ ```
- ```
- name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
- ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
- gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
- pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
- (2 rows)
+ The output should be similar to the following:
- type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
- ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
- C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
- (2 rows)
- ```
+ ```
+ name | host | port | database | force_user | pool_size | reserve_pool | pool_mode | max_connections | current_connections
+ ---------------------+-------------+------+---------------------+------------+-----------+--------------+-----------+-----------------+---------------------
+ gitlabhq_production | MASTER_HOST | 5432 | gitlabhq_production | | 20 | 0 | | 0 | 0
+ pgbouncer | | 6432 | pgbouncer | pgbouncer | 2 | 0 | statement | 0 | 0
+ (2 rows)
+
+ type | user | database | state | addr | port | local_addr | local_port | connect_time | request_time | ptr | link | remote_pid | tls
+ ------+-----------+---------------------+---------+----------------+-------+------------+------------+---------------------+---------------------+-----------+------+------------+-----
+ C | pgbouncer | pgbouncer | active | 127.0.0.1 | 56846 | 127.0.0.1 | 6432 | 2017-08-21 18:09:59 | 2017-08-21 18:10:48 | 0x22b3880 | | 0 |
+ (2 rows)
+ ```
### Running Pgbouncer as part of a non-HA GitLab installation
diff --git a/doc/administration/img/custom_hooks_error_msg.png b/doc/administration/img/custom_hooks_error_msg.png
index 4f25c471908..e7d5c157d08 100644
--- a/doc/administration/img/custom_hooks_error_msg.png
+++ b/doc/administration/img/custom_hooks_error_msg.png
Binary files differ
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 00c8863f200..2b25e8efd23 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -2,7 +2,7 @@
description: 'Learn how to install, configure, update, and maintain your GitLab instance.'
---
-# Administrator documentation **(CORE ONLY)**
+# Administrator Docs **(CORE ONLY)**
Learn how to administer your self-managed GitLab instance.
@@ -185,3 +185,13 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Debugging tips](troubleshooting/debug.md): Tips to debug problems when things go wrong
- [Log system](logs.md): Where to look for logs.
- [Sidekiq Troubleshooting](troubleshooting/sidekiq.md): Debug when Sidekiq appears hung and is not processing jobs.
+- Useful [diagnostics tools](troubleshooting/diagnostics_tools.md) that are sometimes used by the GitLab
+ Support team.
+- [Troubleshooting ElasticSearch](troubleshooting/elasticsearch.md): Tips to troubleshoot ElasticSearch.
+- [Kubernetes troubleshooting](troubleshooting/kubernetes_cheat_sheet.md): Commands and tips useful
+ 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).
+ - [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/integration/plantuml.md b/doc/administration/integration/plantuml.md
index c2ac063ce37..16a193550a1 100644
--- a/doc/administration/integration/plantuml.md
+++ b/doc/administration/integration/plantuml.md
@@ -72,12 +72,12 @@ our AsciiDoc snippets, wikis and repos using delimited blocks:
- **Markdown**
- ````markdown
+ ~~~markdown
```plantuml
Bob -> Alice : hello
Alice -> Bob : Go Away
```
- ````
+ ~~~
- **AsciiDoc**
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 5490a59ceac..2b624f8de77 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -115,6 +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
| `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 5a2f389d298..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
@@ -151,22 +151,29 @@ etc. For example:
{"severity":"ERROR","time":"2018-11-23T15:42:11.647Z","exception":"Kubeclient::HttpError","error_code":null,"service":"Clusters::Applications::InstallService","app_id":2,"project_ids":[19],"group_ids":[],"message":"SSL_connect returned=1 errno=0 state=error: certificate verify failed (unable to get local issuer certificate)"}
```
-## `githost.log`
+## `git_json.log`
-This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for
-Omnibus GitLab packages or in `/home/git/gitlab/log/githost.log` for
+This file lives in `/var/log/gitlab/gitlab-rails/git_json.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/git_json.log` for
installations from source.
+NOTE: **Note:**
+After 12.2, this file was renamed from `githost.log` to
+`git_json.log` and stored in JSON format.
+
GitLab has to interact with Git repositories but in some rare cases
something can go wrong and in this case you will know what exactly
happened. This log file contains all failed requests from GitLab to Git
repositories. In the majority of cases this file will be useful for developers
only. For example:
-```
-December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict
-
-error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'
+```json
+{
+ "severity":"ERROR",
+ "time":"2019-07-19T22:16:12.528Z",
+ "correlation_id":"FeGxww5Hj64",
+ "message":"Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict\n\nerror: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git'"
+}
```
## `audit_json.log`
@@ -236,7 +243,7 @@ I, [2015-02-13T06:17:00.679433 #9291] INFO -- : Moving existing hooks directory
User clone/fetch activity using ssh transport appears in this log as `executing git command <gitaly-upload-pack...`.
-## `unicorn\_stderr.log`
+## `unicorn_stderr.log`
This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for
Omnibus GitLab packages or in `/home/git/gitlab/log/unicorn_stderr.log` for
@@ -277,16 +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.
-Currently it logs the progress of project imports from the Bitbucket Server
-importer. Future importers may use this file.
-
-## `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.
@@ -305,6 +312,12 @@ GraphQL queries are recorded in that file. For example:
{"query_string":"query IntrospectionQuery{__schema {queryType { name },mutationType { name }}}...(etc)","variables":{"a":1,"b":2},"complexity":181,"depth":1,"duration":7}
```
+## `migrations.log`
+
+Introduced in GitLab 12.3. This file lives in `/var/log/gitlab/gitlab-rails/migrations.log` for
+Omnibus GitLab packages or in `/home/git/gitlab/log/migrations.log` for
+installations from source.
+
## Reconfigure Logs
Reconfigure log files live in `/var/log/gitlab/reconfigure` for Omnibus GitLab
@@ -324,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 99cd9051778..0b065082ded 100644
--- a/doc/administration/merge_request_diffs.md
+++ b/doc/administration/merge_request_diffs.md
@@ -90,6 +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
| `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
new file mode 100644
index 00000000000..d445b68721d
--- /dev/null
+++ b/doc/administration/monitoring/gitlab_instance_administration_project/index.md
@@ -0,0 +1,36 @@
+# GitLab instance administration project
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/56883) in GitLab 12.2.
+
+GitLab has been adding the ability for administrators to see insights into the health of
+their GitLab instance. In order to surface this experience in a native way, similar to how
+you would interact with an application deployed via GitLab, a base project called
+"GitLab Instance Administration" with
+[internal visibility](../../../public_access/public_access.md#internal-projects) will be
+added under a group called "GitLab Instance Administrators" specifically created for
+visualizing and configuring the monitoring of your GitLab instance.
+
+All administrators at the time of creation of the project and group will be added
+as maintainers of the group and project, and as an admin, you'll be able to add new
+members to the group in order to give them maintainer access to the project.
+
+This project will be used for self-monitoring your GitLab instance.
+
+## Connection to Prometheus
+
+The project will be automatically configured to connect to the
+[internal Prometheus](../prometheus/index.md) instance if the Prometheus
+instance is present (should be the case if GitLab was installed via Omnibus
+and you haven't disabled it).
+
+If that's not the case or if you have an external Prometheus instance or an HA setup,
+you should
+[configure it manually](../../../user/project/integrations/prometheus.md#manual-configuration-of-prometheus).
+
+## 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.
+
+Once the webhook is setup, you can
+[take action on incoming alerts](../../../user/project/integrations/prometheus.md#taking-action-on-incidents-ultimate).
diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md
index fa0459b24ff..2b3daec42bd 100644
--- a/doc/administration/monitoring/index.md
+++ b/doc/administration/monitoring/index.md
@@ -2,6 +2,9 @@
Explore our features to monitor your GitLab instance:
+- [GitLab self-monitoring](gitlab_instance_administration_project/index.md): The
+ GitLab instance administration project helps to monitor the GitLab instance and
+ take action on alerts.
- [Performance monitoring](performance/index.md): GitLab Performance Monitoring makes it possible to measure a wide variety of statistics of your instance.
- [Prometheus](prometheus/index.md): Prometheus is a powerful time-series monitoring service, providing a flexible platform for monitoring GitLab and other software products.
- [GitHub imports](github_imports.md): Monitor the health and progress of GitLab's GitHub importer with various Prometheus metrics.
diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md
index 4dd0bbbe937..95be0d5fd88 100644
--- a/doc/administration/monitoring/performance/grafana_configuration.md
+++ b/doc/administration/monitoring/performance/grafana_configuration.md
@@ -1,6 +1,6 @@
# Grafana Configuration
-[Grafana](https://grafana.org/) is a tool that allows you to visualize time
+[Grafana](https://grafana.com/) is a tool that allows you to visualize time
series metrics through graphs and dashboards. It supports several backend
data stores, including InfluxDB. GitLab writes performance data to InfluxDB
and Grafana will allow you to query to display useful graphs.
@@ -118,6 +118,36 @@ If you have set up Grafana, you can enable a link to access it easily from the s
1. Click **Save changes**.
1. The new link will be available in the admin area under **Monitoring > Metrics Dashboard**.
+## Security Update
+
+Users running GitLab version 12.0 or later should immediately upgrade to one of the following security releases due to a known vulnerability with the embedded Grafana dashboard:
+
+- 12.0.6
+- 12.1.6
+
+After upgrading, the Grafana dashboard will be disabled and the location of your existing Grafana data will be changed from `/var/opt/gitlab/grafana/data/` to `/var/opt/gitlab/grafana/data.bak.#{Date.today}/`.
+
+To prevent the data from being relocated, you can run the following command prior to upgrading:
+
+```sh
+echo "0" > /var/opt/gitlab/grafana/CVE_reset_status
+```
+
+To reinstate your old data, move it back into its original location:
+
+```
+sudo mv /var/opt/gitlab/grafana/data.bak.xxxx/ /var/opt/gitlab/grafana/data/
+```
+
+However, you should **not** reinstate your old data _except_ under one of the following conditions:
+
+1. If you are certain that you changed your default admin password when you enabled Grafana
+1. If you run GitLab in a private network, accessed only by trusted users, and your Grafana login page has not been exposed to the internet
+
+If you require access to your old Grafana data but do not meet one of these criteria, you may consider reinstating it temporarily, [exporting the dashboards](https://grafana.com/docs/reference/export_import/#exporting-a-dashboard) you need, then refreshing the data and [re-importing your dashboards](https://grafana.com/docs/reference/export_import/#importing-a-dashboard). Note that this poses a temporary vulnerability while your old Grafana data is in use, and the decision to do so should be weighed carefully with your need to access existing data and dashboards.
+
+For more information and further mitigation details, please refer to our [blog post on the security release](https://about.gitlab.com/2019/08/12/critical-security-release-gitlab-12-dot-1-dot-6-released/).
+
---
Read more on:
diff --git a/doc/administration/monitoring/performance/img/performance_bar.png b/doc/administration/monitoring/performance/img/performance_bar.png
index 2bf686f9017..d1187fd879a 100644
--- a/doc/administration/monitoring/performance/img/performance_bar.png
+++ b/doc/administration/monitoring/performance/img/performance_bar.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
index 7af6d401d1d..2c43201cbd0 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_gitaly_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png b/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
deleted file mode 100644
index a55ce753101..00000000000
--- a/doc/administration/monitoring/performance/img/performance_bar_line_profiling.png
+++ /dev/null
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
new file mode 100644
index 00000000000..ecb2dffbf8d
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_redis_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png
new file mode 100644
index 00000000000..210f80a713d
--- /dev/null
+++ b/doc/administration/monitoring/performance/img/performance_bar_rugged_calls.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
index b3219b4fa94..6b571f4e85c 100644
--- a/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
+++ b/doc/administration/monitoring/performance/img/performance_bar_sql_queries.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/img/request_profile_result.png b/doc/administration/monitoring/performance/img/request_profile_result.png
index 1b06e240fa0..9176a0b49fd 100644
--- a/doc/administration/monitoring/performance/img/request_profile_result.png
+++ b/doc/administration/monitoring/performance/img/request_profile_result.png
Binary files differ
diff --git a/doc/administration/monitoring/performance/performance_bar.md b/doc/administration/monitoring/performance/performance_bar.md
index 4ee156fdc11..02f4b78bd60 100644
--- a/doc/administration/monitoring/performance/performance_bar.md
+++ b/doc/administration/monitoring/performance/performance_bar.md
@@ -8,16 +8,14 @@ activated, it looks as follows:
It allows you to see (from left to right):
- the current host serving the page
-- the timing of the page (backend, frontend)
- time taken and number of DB queries, click through for details of these queries
![SQL profiling using the Performance Bar](img/performance_bar_sql_queries.png)
- time taken and number of [Gitaly] calls, click through for details of these calls
![Gitaly profiling using the Performance Bar](img/performance_bar_gitaly_calls.png)
-- profile of the code used to generate the page, line by line. In the profile view, the numbers in the left panel represent wall time, cpu time, and number of calls (based on [rblineprof](https://github.com/tmm1/rblineprof)).
- ![Line profiling using the Performance Bar](img/performance_bar_line_profiling.png)
-- time taken and number of calls to Redis
-- time taken and number of background jobs created by Sidekiq
-- time taken and number of Ruby GC calls
+- time taken and number of [Rugged] calls, click through for details of these calls
+ ![Rugged profiling using the Performance Bar](img/performance_bar_rugged_calls.png)
+- time taken and number of Redis calls, click through for details of these calls
+ ![Redis profiling using the Performance Bar](img/performance_bar_redis_calls.png)
On the far right is a request selector that allows you to view the same metrics
(excluding the page timing and line profiler) for any requests made while the
@@ -43,3 +41,4 @@ You can toggle the Bar using the same shortcut.
![GitLab Performance Bar Admin Settings](img/performance_bar_configuration_settings.png)
[Gitaly]: ../../gitaly/index.md
+[Rugged]: ../../high_availability/nfs.md#improving-nfs-performance-with-gitlab
diff --git a/doc/administration/monitoring/performance/request_profiling.md b/doc/administration/monitoring/performance/request_profiling.md
index 726882fbb87..c32edb60f9d 100644
--- a/doc/administration/monitoring/performance/request_profiling.md
+++ b/doc/administration/monitoring/performance/request_profiling.md
@@ -5,9 +5,9 @@
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>` 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>' https://gitlab.example.com/group/project`.
+ - `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
view the profiling output from **Monitoring > Requests Profiles** admin page.
![Profiling output](img/request_profile_result.png)
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 89501f20d99..ec26c0b2e7e 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -34,6 +34,9 @@ The following metrics are available:
| 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 |
@@ -117,7 +120,6 @@ When Puma is used instead of Unicorn, following metrics are available:
| puma_workers | Gauge | 12.0 | Total number of workers |
| puma_running_workers | Gauge | 12.0 | Number of booted workers |
| puma_stale_workers | Gauge | 12.0 | Number of old workers |
-| puma_phase | Gauge | 12.0 | Phase number (increased during phased restarts) |
| puma_running | Gauge | 12.0 | Number of running threads |
| puma_queued_connections | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
| puma_active_connections | Gauge | 12.0 | Number of threads processing a request |
diff --git a/doc/administration/operations/fast_ssh_key_lookup.md b/doc/administration/operations/fast_ssh_key_lookup.md
index ea69378b249..e787af798bc 100644
--- a/doc/administration/operations/fast_ssh_key_lookup.md
+++ b/doc/administration/operations/fast_ssh_key_lookup.md
@@ -71,10 +71,10 @@ sudo service sshd reload
Confirm that SSH is working by removing your user's SSH key in the UI, adding a
new one, and attempting to pull a repo.
-> **Note:** For Omnibus Docker, `AuthorizedKeysCommand` is setup by default in
+NOTE: **Note:** For Omnibus Docker, `AuthorizedKeysCommand` is setup by default in
GitLab 11.11 and later.
-> **Warning:** Do not disable writes until SSH is confirmed to be working
+CAUTION: **Caution:** Do not disable writes until SSH is confirmed to be working
perfectly, because the file will quickly become out-of-date.
In the case of lookup failures (which are common), the `authorized_keys`
diff --git a/doc/administration/operations/img/sidekiq-cluster.png b/doc/administration/operations/img/sidekiq-cluster.png
index 4eb1849010e..3899385eb8f 100644
--- a/doc/administration/operations/img/sidekiq-cluster.png
+++ b/doc/administration/operations/img/sidekiq-cluster.png
Binary files differ
diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md
index ae67d7f08d6..8178cb243f3 100644
--- a/doc/administration/operations/unicorn.md
+++ b/doc/administration/operations/unicorn.md
@@ -69,7 +69,7 @@ unicorn['worker_memory_limit_min'] = "400 * 1 << 20"
unicorn['worker_memory_limit_max'] = "650 * 1 << 20"
```
-Otherwise, you can set the `GITLAB_UNICORN_MEMORY_MIN` and `GITLAB_UNICORN_MEMORY_MIN`
+Otherwise, you can set the `GITLAB_UNICORN_MEMORY_MIN` and `GITLAB_UNICORN_MEMORY_MAX`
[environment variables](../environment_variables.md).
This is what a Unicorn worker memory restart looks like in unicorn_stderr.log.
diff --git a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
index 5ab63074e12..0f3ca25ce55 100644
--- a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
+++ b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
Binary files differ
diff --git a/doc/administration/pages/source.md b/doc/administration/pages/source.md
index b2cad6cf926..c77a1a9638f 100644
--- a/doc/administration/pages/source.md
+++ b/doc/administration/pages/source.md
@@ -24,8 +24,6 @@ SNI and exposes pages using HTTP2 by default.
You are encouraged to read its [README][pages-readme] to fully understand how
it works.
----
-
In the case of [custom domains](#custom-domains) (but not
[wildcard domains](#wildcard-domains)), the Pages daemon needs to listen on
ports `80` and/or `443`. For that reason, there is some flexibility in the way
@@ -92,8 +90,6 @@ since that is needed in all configurations.
- [Wildcard DNS setup](#dns-configuration)
----
-
URL scheme: `http://page.example.io`
This is the minimum setup that you can use Pages with. It is the base for all
@@ -152,13 +148,12 @@ The Pages daemon doesn't listen to the outside world.
### Wildcard domains with TLS support
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Wildcard TLS certificate
->
-> ---
->
-> URL scheme: `https://page.example.io`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+
+URL scheme: `https://page.example.io`
Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the
outside world.
@@ -217,13 +212,12 @@ that without TLS certificates.
### Custom domains
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Secondary IP
->
-> ---
->
-> URL scheme: `http://page.example.io` and `http://domain.com`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Secondary IP
+
+URL scheme: `http://page.example.io` and `http://domain.com`
In that case, the pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
@@ -282,14 +276,13 @@ world. Custom domains are supported, but no TLS.
### Custom domains with TLS support
-> **Requirements:**
-> - [Wildcard DNS setup](#dns-configuration)
-> - Wildcard TLS certificate
-> - Secondary IP
->
-> ---
->
-> URL scheme: `https://page.example.io` and `https://domain.com`
+**Requirements:**
+
+- [Wildcard DNS setup](#dns-configuration)
+- Wildcard TLS certificate
+- Secondary IP
+
+URL scheme: `https://page.example.io` and `https://domain.com`
In that case, the pages daemon is running, Nginx still proxies requests to
the daemon but the daemon is also able to receive requests from the outside
diff --git a/doc/administration/plugins.md b/doc/administration/plugins.md
index 4302667caf5..4cf3c607dae 100644
--- a/doc/administration/plugins.md
+++ b/doc/administration/plugins.md
@@ -52,7 +52,37 @@ as appropriate. The plugins file list is updated for each event, there is no
need to restart GitLab to apply a new plugin.
If a plugin executes with non-zero exit code or GitLab fails to execute it, a
-message will be logged to `plugin.log`.
+message will be logged to:
+
+- `gitlab-rails/plugin.log` in an Omnibus installation.
+- `log/plugin.log` in a source installation.
+
+## Creating plugins
+
+Below is an example that will only response on the event `project_create` and
+will inform the admins from the GitLab instance that a new project has been created.
+
+```ruby
+# By using the embedded ruby version we eliminate the possibility that our chosen language
+# would be unavailable from
+#!/opt/gitlab/embedded/bin/ruby
+require 'json'
+require 'mail'
+
+# The incoming variables are in JSON format so we need to parse it first.
+ARGS = JSON.parse(STDIN.read)
+
+# We only want to trigger this plugin on the event project_create
+return unless ARGS['event_name'] == 'project_create'
+
+# We will inform our admins of our gitlab instance that a new project is created
+Mail.deliver do
+ from 'info@gitlab_instance.com'
+ to 'admin@gitlab_instance.com'
+ subject "new project " + ARGS['name']
+ body ARGS['owner_name'] + 'created project ' + ARGS['name']
+end
+```
## Validation
diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md
index 91fc0133d56..e880d76e756 100644
--- a/doc/administration/raketasks/ldap.md
+++ b/doc/administration/raketasks/ldap.md
@@ -28,6 +28,31 @@ limit by passing a number to the check task:
rake gitlab:ldap:check[50]
```
+## Run a Group Sync
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/14735) in [GitLab Starter](https://about.gitlab.com/pricing/) 12.3.
+
+The following task will run a [group sync](../auth/ldap-ee.md#group-sync) immediately. This is valuable
+when you'd like to update all configured group memberships against LDAP without
+waiting for the next scheduled group sync to be run.
+
+NOTE: **NOTE:**
+If you'd like to change the frequency at which a group sync is performed,
+[adjust the cron schedule](../auth/ldap-ee.md#adjusting-ldap-group-sync-schedule)
+instead.
+
+**Omnibus Installation**
+
+```
+sudo gitlab-rake gitlab:ldap:group_sync
+```
+
+**Source Installation**
+
+```bash
+bundle exec rake gitlab:ldap:group_sync
+```
+
## Rename a provider
If you change the LDAP server ID in `gitlab.yml` or `gitlab.rb` you will need
diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md
index 2f83dd17d9f..1198f3414c5 100644
--- a/doc/administration/raketasks/storage.md
+++ b/doc/administration/raketasks/storage.md
@@ -105,8 +105,6 @@ sudo gitlab-rake gitlab:storage:legacy_projects
sudo -u git -H bundle exec rake gitlab:storage:legacy_projects RAILS_ENV=production
```
----
-
To list projects using **Legacy** storage:
**Omnibus Installation**
@@ -138,8 +136,6 @@ sudo gitlab-rake gitlab:storage:hashed_projects
sudo -u git -H bundle exec rake gitlab:storage:hashed_projects RAILS_ENV=production
```
----
-
To list projects using **Hashed** storage:
**Omnibus Installation**
@@ -170,8 +166,6 @@ sudo gitlab-rake gitlab:storage:legacy_attachments
sudo -u git -H bundle exec rake gitlab:storage:legacy_attachments RAILS_ENV=production
```
----
-
To list project attachments using **Legacy** storage:
**Omnibus Installation**
@@ -202,8 +196,6 @@ sudo gitlab-rake gitlab:storage:hashed_attachments
sudo -u git -H bundle exec rake gitlab:storage:hashed_attachments RAILS_ENV=production
```
----
-
To list project attachments using **Hashed** storage:
**Omnibus Installation**
diff --git a/doc/administration/raketasks/uploads/migrate.md b/doc/administration/raketasks/uploads/migrate.md
index fd8ea8d3162..86e8b763f51 100644
--- a/doc/administration/raketasks/uploads/migrate.md
+++ b/doc/administration/raketasks/uploads/migrate.md
@@ -103,3 +103,13 @@ sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, S
sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]"
```
+
+## Migrate legacy uploads out of deprecated paths
+
+> Introduced in GitLab 12.3.
+
+To migrate all uploads created by legacy uploaders, run:
+
+```shell
+bundle exec rake gitlab:uploads:legacy:migrate
+```
diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md
index 7cf8f20a9dc..05a7cb006a3 100644
--- a/doc/administration/repository_checks.md
+++ b/doc/administration/repository_checks.md
@@ -33,8 +33,8 @@ in `repocheck.log`:
- in the [admin panel](logs.md#repochecklog)
- or on disk, see:
- - `/var/log/gitlab/gitlab-rails` for Omnibus installations
- - `/home/git/gitlab/log` for installations from source
+ - `/var/log/gitlab/gitlab-rails` for Omnibus installations
+ - `/home/git/gitlab/log` for installations from source
If for some reason the periodic repository check caused a lot of false
alarms you can choose to clear *all* repository check states by
diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md
index ad3a9b19c3c..b1a870210a8 100644
--- a/doc/administration/repository_storage_paths.md
+++ b/doc/administration/repository_storage_paths.md
@@ -57,10 +57,10 @@ storage2:
Now that you've read that big fat warning above, let's edit the configuration
files and add the full paths of the alternative repository storage paths. In
-the example below, we add two more mountpoints that are named `nfs` and `cephfs`
+the example below, we add two more mountpoints that are named `nfs_1` and `nfs_2`
respectively.
-NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
+NOTE: **Note:** This example uses NFS. We do not recommend using EFS for storage as it may impact GitLab's performance. See the [relevant documentation](high_availability/nfs.md#avoid-using-awss-elastic-file-system-efs) for more details.
**For installations from source**
@@ -73,10 +73,10 @@ NOTE: **Note:** This example uses NFS and CephFS. We do not recommend using EFS
storages: # You must have at least a 'default' storage path.
default:
path: /home/git/repositories
- nfs:
- path: /mnt/nfs/repositories
- cephfs:
- path: /mnt/cephfs/repositories
+ nfs_1:
+ path: /mnt/nfs1/repositories
+ nfs_2:
+ path: /mnt/nfs2/repositories
```
1. [Restart GitLab][restart-gitlab] for the changes to take effect.
@@ -96,8 +96,8 @@ working, you can remove the `repos_path` line.
```ruby
git_data_dirs({
"default" => { "path" => "/var/opt/gitlab/git-data" },
- "nfs" => { "path" => "/mnt/nfs/git-data" },
- "cephfs" => { "path" => "/mnt/cephfs/git-data" }
+ "nfs_1" => { "path" => "/mnt/nfs1/git-data" },
+ "nfs_2" => { "path" => "/mnt/nfs2/git-data" }
})
```
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 9dea6074a3f..1669f8b128c 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -105,7 +105,7 @@ question.
To start a migration, enable Hashed Storage for new projects:
1. Go to **Admin > Settings > Repository** and expand the **Repository Storage** section.
-2. Select the **Use hashed storage paths for newly created and renamed projects** checkbox.
+1. Select the **Use hashed storage paths for newly created and renamed projects** checkbox.
Check if the change breaks any existing integration you may have that
either runs on the same machine as your repositories are located, or may login to that machine
@@ -133,7 +133,7 @@ Similar to the migration, to disable Hashed Storage for new
projects:
1. Go to **Admin > Settings > Repository** and expand the **Repository Storage** section.
-2. Uncheck the **Use hashed storage paths for newly created and renamed projects** checkbox.
+1. Uncheck the **Use hashed storage paths for newly created and renamed projects** checkbox.
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.
diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md
index 098d946a9fa..604dff5983d 100644
--- a/doc/administration/troubleshooting/debug.md
+++ b/doc/administration/troubleshooting/debug.md
@@ -209,7 +209,7 @@ ps auwx | grep unicorn | awk '{ print " -p " $2}' | xargs strace -tt -T -f -s 10
The output in `/tmp/unicorn.txt` may help diagnose the root cause.
-# More information
+## More information
- [Debugging Stuck Ruby Processes](https://blog.newrelic.com/2013/04/29/debugging-stuck-ruby-processes-what-to-do-before-you-kill-9/)
- [Cheatsheet of using gdb and ruby processes](gdb-stuck-ruby.txt)
diff --git a/doc/administration/troubleshooting/diagnostics_tools.md b/doc/administration/troubleshooting/diagnostics_tools.md
new file mode 100644
index 00000000000..ab3b25f0e97
--- /dev/null
+++ b/doc/administration/troubleshooting/diagnostics_tools.md
@@ -0,0 +1,27 @@
+---
+type: reference
+---
+
+# Diagnostics tools
+
+These are some of the diagnostics tools the GitLab Support team uses during troubleshooting.
+They are listed here for transparency, and they may be useful for users with experience
+with troubleshooting GitLab. If you are currently having an issue with GitLab, you
+may want to check your [support options](https://about.gitlab.com/support/) first,
+before attempting to use these tools.
+
+## gitlabsos
+
+The [gitlabsos](https://gitlab.com/gitlab-com/support/toolbox/gitlabsos/) utility
+provides a unified method of gathering info and logs from GitLab and the system it's
+running on.
+
+## strace-parser
+
+[strace-parser](https://gitlab.com/wchandler/strace-parser) is a small tool to analyze
+and summarize raw strace data.
+
+## Pritaly
+
+[Pritaly](https://gitlab.com/wchandler/pritaly) takes Gitaly logs and colorizes output
+or converts the logs to JSON.
diff --git a/doc/administration/troubleshooting/elasticsearch.md b/doc/administration/troubleshooting/elasticsearch.md
new file mode 100644
index 00000000000..c4a7ba01fae
--- /dev/null
+++ b/doc/administration/troubleshooting/elasticsearch.md
@@ -0,0 +1,345 @@
+# Troubleshooting ElasticSearch
+
+Troubleshooting ElasticSearch requires:
+
+- Knowledge of common terms.
+- Establishing within which category the problem fits.
+
+## Common terminology
+
+- **Lucene**: A full-text search library written in Java.
+- **Near Realtime (NRT)**: Refers to the slight latency from the time to index a
+ document to the time when it becomes searchable.
+- **Cluster**: A collection of one or more nodes that work together to hold all
+ the data, providing indexing and search capabilities.
+- **Node**: A single server that works as part of a cluster.
+- **Index**: A collection of documents that have somewhat similar characteristics.
+- **Document**: A basic unit of information that can be indexed.
+- **Shards**: Fully-functional and independent subdivisions of indices. Each shard is actually
+ a Lucene index.
+- **Replicas**: Failover mechanisms that duplicate indices.
+
+## Troubleshooting workflows
+
+The type of problem will determine what steps to take. The possible troubleshooting workflows are for:
+
+- Search results.
+- Indexing.
+- Integration.
+- Performance.
+
+### Search Results workflow
+
+The following workflow is for ElasticSearch search results issues:
+
+```mermaid
+graph TD;
+ B --> |No| B1
+ B --> |Yes| B4
+ B1 --> B2
+ B2 --> B3
+ B4 --> B5
+ B5 --> |Yes| B6
+ B5 --> |No| B7
+ B7 --> B8
+ B{Is GitLab using<br>ElasticSearch for<br>searching?}
+ B1[Check Admin Area > Integrations<br>to ensure the settings are correct]
+ B2[Perform a search via<br>the rails console]
+ B3[If all settings are correct<br>and it still doesn't show ElasticSearch<br>doing the searches, escalate<br>to GitLab support.]
+ B4[Perform<br>the same search via the<br>ElasticSearch API]
+ B5{Are the results<br>the same?}
+ B6[This means it is working as intended.<br>Speak with GitLab support<br>to confirm if the issue lies with<br>the filters.]
+ B7[Check the index status of the project<br>containing the missing search<br>results.]
+ B8(Indexing Troubleshooting)
+```
+
+### Indexing workflow
+
+The following workflow is for ElasticSearch indexing issues:
+
+```mermaid
+graph TD;
+ C --> |Yes| C1
+ C1 --> |Yes| C2
+ C1 --> |No| C3
+ C3 --> |Yes| C4
+ C3 --> |No| C5
+ C --> |No| C6
+ C6 --> |No| C10
+ C7 --> |GitLab| C8
+ C7 --> |ElasticSearch| C9
+ C6 --> |Yes| C7
+ C10 --> |No| C12
+ C10 --> |Yes| C11
+ C12 --> |Yes| C13
+ C12 --> |No| C14
+ C14 --> |Yes| C15
+ C14 --> |No| C16
+ C{Is the problem with<br>creating an empty<br>index?}
+ C1{Does the gitlab-production<br>index exist on the<br>ElasticSearch instance?}
+ C2(Try to manually<br>delete the index on the<br>ElasticSearch instance and<br>retry creating an empty index.)
+ C3{Can indices be made<br>manually on the ElasticSearch<br>instance?}
+ C4(Retry the creation of an empty index)
+ C5(It is best to speak with an<br>ElasticSearch admin concerning the<br>instance's inability to create indices.)
+ C6{Is the indexer presenting<br>errors during indexing?}
+ C7{Is the error a GitLab<br>error or an ElasticSearch<br>error?}
+ C8[Escalate to<br>GitLab support]
+ C9[You will want<br>to speak with an<br>ElasticSearch admin.]
+ C10{Does the index status<br>show 100%?}
+ C11[Escalate to<br>GitLab support]
+ C12{Does re-indexing the project<br> present any GitLab errors?}
+ C13[Rectify the GitLab errors and<br>restart troubleshooting, or<br>escalate to GitLab support.]
+ C14{Does re-indexing the project<br>present errors on the <br>ElasticSearch instance?}
+ C15[It would be best<br>to speak with an<br>ElasticSearch admin.]
+ C16[This is likely a bug/issue<br>in GitLab and will require<br>deeper investigation. Escalate<br>to GitLab support.]
+```
+
+### Integration workflow
+
+The following workflow is for ElasticSearch integration issues:
+
+```mermaid
+graph TD;
+ D --> |No| D1
+ D --> |Yes| D2
+ D2 --> |No| D3
+ D2 --> |Yes| D4
+ D4 --> |No| D5
+ D4 --> |Yes| D6
+ D{Is the error concerning<br>the beta indexer?}
+ D1[It would be best<br>to speak with an<br>ElasticSearch admin.]
+ D2{Is the ICU development<br>package installed?}
+ D3>This package is required.<br>Install the package<br>and retry.]
+ D4{Is the error stemming<br>from the indexer?}
+ D5[This would indicate an OS level<br> issue. It would be best to<br>contact your sysadmin.]
+ D6[This is likely a bug/issue<br>in GitLab and will require<br>deeper investigation. Escalate<br>to GitLab support.]
+```
+
+### Performance workflow
+
+The following workflow is for ElasticSearch performance issues:
+
+```mermaid
+graph TD;
+ F --> |Yes| F1
+ F --> |No| F2
+ F2 --> |No| F3
+ F2 --> |Yes| F4
+ F4 --> F5
+ F5 --> |No| F6
+ F5 --> |Yes| F7
+ F{Is the ElasticSearch instance<br>running on the same server<br>as the GitLab instance?}
+ F1(This is not advised and will cause issues.<br>We recommend moving the ElasticSearch<br>instance to a different server.)
+ F2{Does the ElasticSearch<br>server have at least 8<br>GB of RAM and 2 CPU<br>cores?}
+ F3(According to ElasticSearch, a non-prod<br>server needs these as a base requirement.<br>Production often requires more. We recommend<br>you increase the server specifications.)
+ F4(Obtain the <br>cluster health information)
+ F5(Does it show the<br>status as green?)
+ F6(We recommend you speak with<br>an ElasticSearch admin<br>about implementing sharding.)
+ F7(Escalate to<br>GitLab support.)
+```
+
+## Troubleshooting walkthrough
+
+Most ElasticSearch troubleshooting can be broken down into 4 categories:
+
+- [Troubleshooting search results](#troubleshooting-search-results)
+- [Troubleshooting indexing](#troubleshooting-indexing)
+- [Troubleshooting integration](#troubleshooting-integration)
+- [Troubleshooting performance](#troubleshooting-performance)
+
+Generally speaking, if it does not fall into those four categories, it is either:
+
+- Something GitLab support needs to look into.
+- Not a true ElasticSearch issue.
+
+Exercise caution. Issues that appear to be ElasticSearch problems can be OS-level issues.
+
+### Troubleshooting search results
+
+Troubleshooting search result issues is rather straight forward on ElasticSearch.
+
+The first step is to confirm GitLab is using ElasticSearch for the search function.
+To do this:
+
+1. Confirm the integration is enabled in **Admin Area > Settings > Integrations**.
+1. Confirm searches utilize ElasticSearch by accessing the rails console
+ (`sudo gitlab-rails console`) and running the following commands:
+
+ ```rails
+ u = User.find_by_email('email_of_user_doing_search')
+ s = SearchService.new(u, {:search => 'search_term'})
+ pp s.search_objects.class.name
+ ```
+
+The ouput from the last command is the key here. If it shows:
+
+- `ActiveRecord::Relation`, **it is not** using ElasticSearch.
+- `Kaminari::PaginatableArray`, **it is** using ElasticSearch.
+
+| Not using ElasticSearch | Using ElasticSearch |
+|--------------------------|------------------------------|
+| `ActiveRecord::Relation` | `Kaminari::PaginatableArray` |
+
+If all the settings look correct and it is still not using ElasticSearch for the search function, it is best to escalate to GitLab support. This could be a bug/issue.
+
+Moving past that, it is best to attempt the same search using the [ElasticSearch Search API](https://www.elastic.co/guide/en/elasticsearch/reference/current/search-search.html) and compare the results from what you see in GitLab.
+
+If the results:
+
+- Sync up, then there is not a technical "issue" per se. Instead, it might be a problem
+ with the ElasticSearch filters we are using. This can be complicated, so it is best to
+ escalate to GitLab support to check these and guide you on the potential on whether or
+ not a feature request is needed.
+- Do not match up, this indicates a problem with the documents generated from the
+ project. It is best to re-index that project and proceed with
+ [Troubleshooting indexing](#troubleshooting-indexing).
+
+### Troubleshooting indexing
+
+Troubleshooting indexing issues can be tricky. It can pretty quickly go to either GitLab
+support or your ElasticSearch admin.
+
+The best place to start is to determine if the issue is with creating an empty index.
+If it is, check on the ElasticSearch side to determine if the `gitlab-production` (the
+name for the GitLab index) exists. If it exists, manually delete it on the ElasticSearch
+side and attempt to recreate it from the
+[`create_empty_index`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks)
+rake task.
+
+If you still encounter issues, try creating an index manually on the ElasticSearch
+instance. The details of the index aren't important here, as we want to test if indices
+can be made. If the indices:
+
+- Cannot be made, speak with your ElasticSearch admin.
+- Can be made, Escalate this to GitLab support.
+
+If the issue is not with creating an empty index, the next step is to check for errors
+during the indexing of projects. If errors do occur, they will either stem from the indexing:
+
+- On the GitLab side. You need to rectify those. If they are not
+ something you are familiar with, contact GitLab support for guidance.
+- Within the ElasticSearch instance itself. See if the error is [documented and has a fix](../../integration/elasticsearch.md#troubleshooting). If not, speak with your ElasticSearch admin.
+
+If the indexing process does not present errors, you will want to check the status of the indexed projects. You can do this via the following rake tasks:
+
+- [`sudo gitlab-rake gitlab:elastic:index_projects_status`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows the overall status)
+- [`sudo gitlab-rake gitlab:elastic:projects_not_indexed`](../../integration/elasticsearch.md#gitlab-elasticsearch-rake-tasks) (shows specific projects that are not indexed)
+
+If:
+
+- Everything is showing at 100%, escalate to GitLab support. This could be a potential
+ bug/issue.
+- You do see something not at 100%, attempt to reindex that project. To do this,
+ run `sudo gitlab-rake gitlab:elastic:index_projects ID_FROM=<project ID> ID_TO=<project ID>`.
+
+If reindexing the project shows:
+
+- Errors on the GitLab side, escalate those to GitLab support.
+- ElasticSearch errors or doesn't present any errors at all, reach out to your
+ ElasticSearch admin to check the instance.
+
+### Troubleshooting integration
+
+Troubleshooting integration tends to be pretty straight forward, as there really isn't
+much to "integrate" here.
+
+If the issue is:
+
+- Not concerning the beta indexer, it is almost always an
+ ElasticSearch-side issue. This means you should reach out to your ElasticSearch admin
+ regarding the error(s) you are seeing. If you are unsure here, it never hurts to reach
+ out to GitLab support.
+- With the beta indexer, check if the ICU development package is installed.
+ This is a required package so make sure you install it.
+
+Beyond that, you will want to review the error. If it is:
+
+- Specifically from the indexer, this could be a bug/issue and should be escalated to
+ GitLab support.
+- An OS issue, you will want to reach out to your systems administrator.
+
+### Troubleshooting performance
+
+Troubleshooting performance can be difficult on ElasticSearch. There is a ton of tuning
+that *can* be done, but the majority of this falls on shoulders of a skilled
+ElasticSearch administrator.
+
+Generally speaking, ensure:
+
+* The ElasticSearch server **is not** running on the same node as GitLab.
+* The ElasticSearch server have enough RAM and CPU cores.
+* That sharding **is** being used.
+
+Going into some more detail here, if ElasticSearch is running on the same server as GitLab, resource contention is **very** likely to occur. Ideally, ElasticSearch, which requires ample resources, should be running on its own server (maybe coupled with logstash and kibana).
+
+When it comes to ElasticSearch, RAM is the key resource. ElasticSearch themselves recommend:
+
+- **At least** 8 GB of RAM for a non-production instance.
+- **At least** 16 GB of RAM for a production instance.
+- Ideally, 64 GB of RAM.
+
+For CPU, ElasticSearch recommends at least 2 CPU cores, but ElasticSearch states common
+setups use up to 8 cores. For more details on server specs, check out
+[ElasticSearch's hardware guide](https://www.elastic.co/guide/en/elasticsearch/guide/current/hardware.html).
+
+Beyond the obvious, sharding comes into play. Sharding is a core part of ElasticSearch.
+It allows for horizontal scaling of indices, which is helpful when you are dealing with
+a large amount of data.
+
+With the way GitLab does indexing, there is a **huge** amount of documents being
+indexed. By utilizing sharding, you can speed up ElasticSearch's ability to locate
+data, since each shard is a Lucene index.
+
+If you are not using sharding, you are likely to hit issues when you start using
+ElasticSearch in a production environment.
+
+Keep in mind that an index with only one shard has **no scale factor** and will
+likely encounter issues when called upon with some frequency.
+
+If you need to know how many shards, read
+[ElasticSearch's documentation on capacity planning](https://www.elastic.co/guide/en/elasticsearch/guide/2.x/capacity-planning.html),
+as the answer is not straight forward.
+
+The easiest way to determine if sharding is in use is to check the output of the
+[ElasticSearch Health API](https://www.elastic.co/guide/en/elasticsearch/reference/current/cluster-health.html):
+
+- Red means the cluster is down.
+- Yellow means it is up with no sharding/replication.
+- Green means it is healthy (up, sharding, replicating).
+
+For production use, it should always be green.
+
+Beyond these steps, you get into some of the more complicated things to check,
+such as merges and caching. These can get complicated and it takes some time to
+learn them, so it is best to escalate/pair with an ElasticSearch expert if you need to
+dig further into these.
+
+Feel free to reach out to GitLab support, but this is likely to be something a skilled
+ElasticSearch admin has more experience with.
+
+## Common issues
+
+All common issues [should be documented](../../integration/elasticsearch.md#troubleshooting). If not,
+feel free to update that page with issues you encounter and solutions.
+
+## Replication
+
+Setting up ElasticSearch isn't too bad, but it can be a bit finnicky and time consuming.
+
+The eastiest method is to spin up a docker container with the required version and
+bind ports 9200/9300 so it can be used.
+
+The following is an example of running a docker container of ElasticSearch v7.2.0:
+
+```bash
+docker pull docker.elastic.co/elasticsearch/elasticsearch:7.2.0
+docker run -p 9200:9200 -p 9300:9300 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.2.0
+```
+
+From here, you can:
+
+- Grab the IP of the docker container (use `docker inspect <container_id>`)
+- Use `<IP.add.re.ss:9200>` to communicate with it.
+
+This is a quick method to test out ElasticSearch, but by no means is this a
+production solution.
diff --git a/doc/administration/troubleshooting/kubernetes_cheat_sheet.md b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
new file mode 100644
index 00000000000..238c522a0ee
--- /dev/null
+++ b/doc/administration/troubleshooting/kubernetes_cheat_sheet.md
@@ -0,0 +1,251 @@
+---
+type: reference
+---
+
+# Kubernetes, GitLab and You
+
+This is a list of useful information regarding Kubernetes that the GitLab Support
+Team sometimes uses while troubleshooting. GitLab is making this public, so that anyone
+can make use of the Support team's collected knowledge
+
+CAUTION: **Caution:**
+These commands **can alter or break** your Kubernetes components so use these at your own risk.
+
+If you are on a [paid tier](https://about.gitlab.com/pricing/) and are not sure how
+to use these commands, it is best to [contact Support](https://about.gitlab.com/support/)
+and they will assist you with any issues you are having.
+
+## Generic kubernetes commands
+
+- How to authorize to your GCP project (can be especially useful if you have projects
+ under different GCP accounts):
+
+ ```bash
+ gcloud auth login
+ ```
+
+- How to access Kubernetes dashboard:
+
+ ```bash
+ # for minikube:
+ minikube dashboard —url
+ # for non-local installations if access via Kubectl is configured:
+ kubectl proxy
+ ```
+
+- How to ssh to a Kubernetes node and enter the container as root
+ <https://github.com/kubernetes/kubernetes/issues/30656>:
+
+ - For GCP, you may find the node name and run `gcloud compute ssh node-name`.
+ - List containers using `docker ps`.
+ - Enter container using `docker exec --user root -ti container-id bash`.
+
+- How to copy a file from local machine to a pod:
+
+ ```bash
+ kubectl cp file-name pod-name:./destination-path
+ ```
+
+- What to do with pods in `CrashLoopBackoff` status:
+
+ - Check logs via Kubernetes dashboard.
+ - Check logs via Kubectl:
+
+ ```bash
+ kubectl logs <unicorn pod> -c dependencies
+ ```
+
+- How to tail all Kubernetes cluster events in real time:
+
+ ```bash
+ kubectl get events -w --all-namespaces
+ ```
+
+- How to get logs of the previously terminated pod instance:
+
+ ```bash
+ kubectl logs <pod-name> --previous
+ ```
+
+ NOTE: **Note:**
+ No logs are kept in the containers/pods themselves, everything is written to stdout.
+ This is the principle of Kubernetes, read [Twelve-factor app](https://12factor.net/)
+ for details.
+
+## 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).
+
+- Tailing logs of a separate pod. An example for a unicorn pod:
+
+ ```bash
+ 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:
+
+ - [Kubetail](https://github.com/johanhaleby/kubetail)
+ - [kail: kubernetes tail](https://github.com/boz/kail)
+ - [stern](https://github.com/wercker/stern)
+
+- Check all events in the `gitlab` namespace (the namespace name can be different if you
+ specified a different one when deploying the helm chart):
+
+ ```bash
+ kubectl get events -w --namespace=gitlab
+ ```
+
+- Most of the useful GitLab tools (console, rake tasks, etc) are found in the task-runner
+ pod. You may enter it and run commands inside or run them from the outside:
+
+ ```bash
+ # find the pod
+ kubectl get pods | grep task-runner
+
+ # enter it
+ kubectl exec -it <task-runner-pod-name> bash
+
+ # open rails console
+ # rails console can be also called from other GitLab pods
+ /srv/gitlab/bin/rails console
+
+ # source-style commands should also work
+ /srv/gitlab && bundle exec rake gitlab:check RAILS_ENV=production
+
+ # run GitLab check. Note that the output can be confusing and invalid because of the specific structure of GitLab installed via helm chart
+ /usr/local/bin/gitlab-rake gitlab:check
+
+ # open console without entering pod
+ kubectl exec -it <task-runner-pod-name> /srv/gitlab/bin/rails console
+
+ # check the status of DB migrations
+ kubectl exec -it <task-runner-pod-name> /usr/local/bin/gitlab-rake db:migrate:status
+ ```
+
+ You can also use `gitlab-rake`, instead of `/usr/local/bin/gitlab-rake`.
+
+- Troubleshooting **Operations > Kubernetes** integration:
+
+ - 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.
+
+- How to get your initial admin password <https://docs.gitlab.com/charts/installation/deployment.html#initial-login>:
+
+ ```bash
+ # find the name of the secret containing the password
+ kubectl get secrets | grep initial-root
+ # decode it
+ kubectl get secret <secret-name> -ojsonpath={.data.password} | base64 --decode ; echo
+ ```
+
+- 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:
+
+ ```bash
+ helm status name-of-installation
+ ```
+
+- How to update GitLab installed using Helm Chart:
+
+ ```bash
+ helm repo upgrade
+
+ # get current values and redirect them to yaml file (analogue of gitlab.rb values)
+ helm get values <release name> > gitlab.yaml
+
+ # run upgrade itself
+ helm upgrade <release name> <chart path> -f gitlab.yaml
+ ```
+
+ After <https://canary.gitlab.com/charts/gitlab/issues/780> is fixed, it should
+ be possible to use [Updating GitLab using the Helm Chart](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#updating-gitlab-using-the-helm-chart)
+ for upgrades.
+
+- How to apply changes to GitLab config:
+
+ - Modify the `gitlab.yaml` file.
+ - Run the following command to apply changes:
+
+ ```bash
+ helm upgrade <release name> <chart path> -f gitlab.yaml
+ ```
+
+## 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:
+
+ ```bash
+ brew install kubernetes-cli
+ ```
+
+- Install Minikube via Homebrew:
+
+ ```bash
+ brew cask install minikube
+ ```
+
+- Start Minikube and configure it. If Minikube cannot start, try running `minikube delete && minikube start`
+ and repeat the steps:
+
+ ```bash
+ minikube start --cpus 3 --memory 8192 # minimum amount for GitLab to work
+ minikube addons enable ingress
+ minikube addons enable kube-dns
+ ```
+
+- Install Helm via Homebrew and initialize it:
+
+ ```bash
+ brew install kubernetes-helm
+ helm init --service-account tiller
+ ```
+
+- Copy the file <https://gitlab.com/charts/gitlab/raw/master/examples/values-minikube-minimum.yaml>
+ to your workstation.
+
+- Find the IP address in the output of `minikube ip` and update the yaml file with
+ this IP address.
+
+- Install the GitLab Helm Chart:
+
+ ```bash
+ helm repo add gitlab https://charts.gitlab.io
+ helm install --name gitlab -f <path-to-yaml-file> gitlab/gitlab
+ ```
+
+ If you want to modify some GitLab settings, you can use the above-mentioned config
+ as a base and create your own yaml file.
+
+- Monitor the installation progress via `helm status gitlab` and `minikube dashboard`.
+ The installation could take up to 20-30 minutes depending on the amount of resources
+ on your workstation.
+
+- When all the pods show either a `Running` or `Completed` status, get the GitLab password as
+ described in [Initial login](https://docs.gitlab.com/ee/install/kubernetes/gitlab_chart.html#initial-login),
+ and log in to GitLab via the UI. It will be accessible via `https://gitlab.domain`
+ where `domain` is the value provided in the yaml file.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 51e267c865e..99f1c386183 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -78,6 +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
| `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) |
@@ -140,6 +141,22 @@ _The uploads are stored by default in
1. Save the file and [restart GitLab][] for the changes to take effect.
1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md).
+### Oracle Cloud S3 connection settings
+
+Note that Oracle Cloud S3 must be sure to use the following settings:
+
+| Setting | Value |
+|---------|-------|
+| `enable_signature_v4_streaming` | false |
+| `path_style` | true |
+
+If `enable_signature_v4_streaming` is set to `true`, you may see the
+following error:
+
+```
+STREAMING-AWS4-HMAC-SHA256-PAYLOAD is not supported
+```
+
### OpenStack compatible connection settings
The connection settings match those provided by [Fog](https://github.com/fog), and are as follows:
diff --git a/doc/api/README.md b/doc/api/README.md
index 8e60d1c61df..9156d719e11 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -1,152 +1,13 @@
-# GitLab API
+# API Docs
Automate GitLab via a simple and powerful API.
The main GitLab API is a [REST](https://en.wikipedia.org/wiki/Representational_state_transfer) API. Therefore, documentation in this section assumes knowledge of REST concepts.
-## API resources
-
-Available API resources can be grouped in the following contexts:
-
-- [Projects](#project-resources).
-- [Groups](#group-resources).
-- [Standalone](#standalone-resources).
-
-See also:
-
-- [V3 to V4](v3_to_v4.md).
-- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md).
-
-### Project resources
-
-The following API resources are available in the project context:
-
-| Resource | Available endpoints |
-|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
-| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
-| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
-| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
-| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
-| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
-| [Dependencies](dependencies.md) **[ULTIMATE]** | `/projects/:id/dependencies`
-| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
-| [Deployments](deployments.md) | `/projects/:id/deployments` |
-| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
-| [Environments](environments.md) | `/projects/:id/environments` |
-| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
-| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
-| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) |
-| [Issue boards](boards.md) | `/projects/:id/boards` |
-| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` |
-| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
-| [Labels](labels.md) | `/projects/:id/labels` |
-| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` |
-| [Members](members.md) | `/projects/:id/members` (also available for groups) |
-| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` |
-| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
-| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) |
-| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
-| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` |
-| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
-| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
-| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
-| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
-| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
-| [Project badges](project_badges.md) | `/projects/:id/badges` |
-| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
-| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
-| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
-| [Project milestones](milestones.md) | `/projects/:id/milestones` |
-| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
-| [Project templates](project_templates.md) | `/projects/:id/templates` |
-| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
-| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
-| [Releases](releases/index.md) | `/projects/:id/releases` |
-| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
-| [Repositories](repositories.md) | `/projects/:id/repository` |
-| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
-| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
-| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
-| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
-| [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) |
-| [Wikis](wikis.md) | `/projects/:id/wikis` |
-
-### Group resources
-
-The following API resources are available in the group context:
-
-| Resource | Available endpoints |
-|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------|
-| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
-| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
-| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) |
-| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` |
-| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` |
-| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` |
-| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
-| [Group badges](group_badges.md) | `/groups/:id/badges` |
-| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
-| [Group labels](group_labels.md) | `/groups/:id/labels` |
-| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
-| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
-| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
-| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) |
-| [Members](members.md) | `/groups/:id/members` (also available for projects) |
-| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
-| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
-| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
-| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) |
-| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
-
-### Standalone resources
-
-The following API resources are available outside of project and group contexts (including `/users`):
-
-| Resource | Available endpoints |
-|:--------------------------------------------------|:------------------------------------------------------------------------|
-| [Applications](applications.md) | `/applications` |
-| [Avatar](avatar.md) | `/avatar` |
-| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` |
-| [Code snippets](snippets.md) | `/snippets` |
-| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) |
-| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
-| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
-| [Feature flags](features.md) | `/features` |
-| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` |
-| [Import repository from GitHub](import.md) | `/import/github` |
-| [Issues](issues.md) | `/issues` (also available for groups and projects) |
-| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
-| [Keys](keys.md) | `/keys` |
-| [License](license.md) **(CORE ONLY)** | `/license` |
-| [Markdown](markdown.md) | `/markdown` |
-| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
-| [Namespaces](namespaces.md) | `/namespaces` |
-| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
-| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
-| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
-| [Runners](runners.md) | `/runners` (also available for projects) |
-| [Search](search.md) | `/search` (also available for groups and projects) |
-| [Settings](settings.md) | `/application/settings` |
-| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` |
-| [Suggestions](suggestions.md) | `/suggestions` |
-| [System hooks](system_hooks.md) | `/hooks` |
-| [Todos](todos.md) | `/todos` |
-| [Users](users.md) | `/users` |
-| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` |
-| [Version](version.md) | `/version` |
-
-### Templates API resources
-
-Endpoints are available for:
-
-- [Dockerfile templates](templates/dockerfiles.md).
-- [`.gitignore` templates](templates/gitignores.md).
-- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
-- [Open source license templates](templates/licenses.md).
+## Available API resources
+
+For a list of the available resources and their endpoints, see
+[API resources](api_resources.md).
## SCIM **(SILVER ONLY)**
@@ -216,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:
@@ -272,6 +134,12 @@ Example of using the personal access token in a header:
curl --header "Private-Token: <your_access_token>" https://gitlab.example.com/api/v4/projects
```
+You can also use personal access tokens with OAuth-compliant headers:
+
+```shell
+curl --header "Authorization: Bearer <your_access_token>" https://gitlab.example.com/api/v4/projects
+```
+
Read more about [personal access tokens][pat].
### Session cookie
@@ -284,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.
@@ -509,7 +385,7 @@ more than 10,000, the `X-Total` and `X-Total-Pages` headers as well as the
## Namespaced path encoding
-If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_NAME` is
+If using namespaced API calls, make sure that the `NAMESPACE/PROJECT_PATH` is
URL-encoded.
For example, `/` is represented by `%2F`:
@@ -518,6 +394,11 @@ For example, `/` is represented by `%2F`:
GET /api/v4/projects/diaspora%2Fdiaspora
```
+NOTE: **Note:**
+A project's **path** is not necessarily the same as its **name**. A
+project's path can found in the project's URL or in the project's settings
+under **General > Advanced > Change path**.
+
## Branches and tags name encoding
If your branch or tag contains a `/`, make sure the branch/tag name is
@@ -684,6 +565,13 @@ The correct encoding for the query parameter would be:
There are many unofficial GitLab API Clients for most of the popular
programming languages. Visit the [GitLab website] for a complete list.
+## Rate limits
+
+For administrator documentation on rate limit settings, see
+[Rate limits](../security/rate_limits.md). To find the settings that are
+specifically used by GitLab.com, see
+[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
+
[GitLab website]: https://about.gitlab.com/applications/#api-clients "Clients using the GitLab API"
[lib-api-url]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/api/api.rb
[ce-3749]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3749
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
new file mode 100644
index 00000000000..9af5430f1c8
--- /dev/null
+++ b/doc/api/api_resources.md
@@ -0,0 +1,144 @@
+# API resources
+
+Available resources for the [GitLab API](README.md) can be grouped in the following contexts:
+
+- [Projects](#project-resources).
+- [Groups](#group-resources).
+- [Standalone](#standalone-resources).
+
+See also:
+
+- [V3 to V4](v3_to_v4.md).
+- Adding [deploy keys for multiple projects](deploy_key_multiple_projects.md).
+- [API Resources for various templates](#templates-api-resources).
+
+## Project resources
+
+The following API resources are available in the project context:
+
+| Resource | Available endpoints |
+|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
+| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
+| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
+| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
+| [Container Registry](container_registry.md) | `/projects/:id/registry/repositories` |
+| [Custom attributes](custom_attributes.md) | `/projects/:id/custom_attributes` (also available for groups and users) |
+| [Dependencies](dependencies.md) **(ULTIMATE)** | `/projects/:id/dependencies`
+| [Deploy keys](deploy_keys.md) | `/projects/:id/deploy_keys` (also available standalone) |
+| [Deployments](deployments.md) | `/projects/:id/deployments` |
+| [Discussions](discussions.md) (threaded comments) | `/projects/:id/issues/.../discussions`, `/projects/:id/snippets/.../discussions`, `/projects/:id/merge_requests/.../discussions`, `/projects/:id/commits/.../discussions` (also available for groups) |
+| [Environments](environments.md) | `/projects/:id/environments` |
+| [Events](events.md) | `/projects/:id/events` (also available for users and standalone) |
+| [Issues](issues.md) | `/projects/:id/issues` (also available for groups and standalone) |
+| [Issues Statistics](issues_statistics.md) | `/projects/:id/issues_statistics` (also available for groups and standalone) |
+| [Issue boards](boards.md) | `/projects/:id/boards` |
+| [Issue links](issue_links.md) **(STARTER)** | `/projects/:id/issues/.../links` |
+| [Jobs](jobs.md) | `/projects/:id/jobs`, `/projects/:id/pipelines/.../jobs` |
+| [Labels](labels.md) | `/projects/:id/labels` |
+| [Managed licenses](managed_licenses.md) **(ULTIMATE)** | `/projects/:id/managed_licenses` |
+| [Members](members.md) | `/projects/:id/members` (also available for groups) |
+| [Merge request approvals](merge_request_approvals.md) **(STARTER)** | `/projects/:id/approvals`, `/projects/:id/merge_requests/.../approvals` |
+| [Merge requests](merge_requests.md) | `/projects/:id/merge_requests` (also available for groups and standalone) |
+| [Notes](notes.md) (comments) | `/projects/:id/issues/.../notes`, `/projects/:id/snippets/.../notes`, `/projects/:id/merge_requests/.../notes` (also available for groups) |
+| [Notification settings](notification_settings.md) | `/projects/:id/notification_settings` (also available for groups and standalone) |
+| [Packages](packages.md) **(PREMIUM)** | `/projects/:id/packages` |
+| [Pages domains](pages_domains.md) | `/projects/:id/pages` (also available standalone) |
+| [Pipelines](pipelines.md) | `/projects/:id/pipelines` |
+| [Pipeline schedules](pipeline_schedules.md) | `/projects/:id/pipeline_schedules` |
+| [Pipeline triggers](pipeline_triggers.md) | `/projects/:id/triggers` |
+| [Projects](projects.md) including setting Webhooks | `/projects`, `/projects/:id/hooks` (also available for users) |
+| [Project badges](project_badges.md) | `/projects/:id/badges` |
+| [Project clusters](project_clusters.md) | `/projects/:id/clusters` |
+| [Project-level variables](project_level_variables.md) | `/projects/:id/variables` |
+| [Project import/export](project_import_export.md) | `/projects/:id/export`, `/projects/import`, `/projects/:id/import` |
+| [Project milestones](milestones.md) | `/projects/:id/milestones` |
+| [Project snippets](project_snippets.md) | `/projects/:id/snippets` |
+| [Project templates](project_templates.md) | `/projects/:id/templates` |
+| [Protected branches](protected_branches.md) | `/projects/:id/protected_branches` |
+| [Protected tags](protected_tags.md) | `/projects/:id/protected_tags` |
+| [Releases](releases/index.md) | `/projects/:id/releases` |
+| [Release links](releases/links.md) | `/projects/:id/releases/.../assets/links` |
+| [Repositories](repositories.md) | `/projects/:id/repository` |
+| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
+| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
+| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
+| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
+| [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`
+| [Wikis](wikis.md) | `/projects/:id/wikis` |
+
+## Group resources
+
+The following API resources are available in the group context:
+
+| Resource | Available endpoints |
+|:-----------------------------------------------------------------|:---------------------------------------------------------------------------------|
+| [Access requests](access_requests.md) | `/groups/:id/access_requests/` (also available for projects) |
+| [Custom attributes](custom_attributes.md) | `/groups/:id/custom_attributes` (also available for projects and users) |
+| [Discussions](discussions.md) (threaded comments) **(ULTIMATE)** | `/groups/:id/epics/.../discussions` (also available for projects) |
+| [Epic issues](epic_issues.md) **(ULTIMATE)** | `/groups/:id/epics/.../issues` |
+| [Epic links](epic_links.md) **(ULTIMATE)** | `/groups/:id/epics/.../epics` |
+| [Epics](epics.md) **(ULTIMATE)** | `/groups/:id/epics` |
+| [Groups](groups.md) | `/groups`, `/groups/.../subgroups` |
+| [Group badges](group_badges.md) | `/groups/:id/badges` |
+| [Group issue boards](group_boards.md) | `/groups/:id/boards` |
+| [Group labels](group_labels.md) | `/groups/:id/labels` |
+| [Group-level variables](group_level_variables.md) | `/groups/:id/variables` |
+| [Group milestones](group_milestones.md) | `/groups/:id/milestones` |
+| [Issues](issues.md) | `/groups/:id/issues` (also available for projects and standalone) |
+| [Issues Statistics](issues_statistics.md) | `/groups/:id/issues_statistics` (also available for projects and standalone) |
+| [Members](members.md) | `/groups/:id/members` (also available for projects) |
+| [Merge requests](merge_requests.md) | `/groups/:id/merge_requests` (also available for projects and standalone) |
+| [Notes](notes.md) (comments) | `/groups/:id/epics/.../notes` (also available for projects) |
+| [Notification settings](notification_settings.md) | `/groups/:id/notification_settings` (also available for projects and standalone) |
+| [Resource label events](resource_label_events.md) | `/groups/:id/epics/.../resource_label_events` (also available for projects) |
+| [Search](search.md) | `/groups/:id/search` (also available for projects and standalone) |
+
+## Standalone resources
+
+The following API resources are available outside of project and group contexts (including `/users`):
+
+| Resource | Available endpoints |
+|:--------------------------------------------------|:------------------------------------------------------------------------|
+| [Applications](applications.md) | `/applications` |
+| [Avatar](avatar.md) | `/avatar` |
+| [Broadcast messages](broadcast_messages.md) | `/broadcast_messages` |
+| [Code snippets](snippets.md) | `/snippets` |
+| [Custom attributes](custom_attributes.md) | `/users/:id/custom_attributes` (also available for groups and projects) |
+| [Deploy keys](deploy_keys.md) | `/deploy_keys` (also available for projects) |
+| [Events](events.md) | `/events`, `/users/:id/events` (also available for projects) |
+| [Feature flags](features.md) | `/features` |
+| [Geo Nodes](geo_nodes.md) **(PREMIUM ONLY)** | `/geo_nodes` |
+| [Import repository from GitHub](import.md) | `/import/github` |
+| [Issues](issues.md) | `/issues` (also available for groups and projects) |
+| [Issues Statistics](issues_statistics.md) | `/issues_statistics` (also available for groups and projects) |
+| [Keys](keys.md) | `/keys` |
+| [License](license.md) **(CORE ONLY)** | `/license` |
+| [Markdown](markdown.md) | `/markdown` |
+| [Merge requests](merge_requests.md) | `/merge_requests` (also available for groups and projects) |
+| [Namespaces](namespaces.md) | `/namespaces` |
+| [Notification settings](notification_settings.md) | `/notification_settings` (also available for groups and projects) |
+| [Pages domains](pages_domains.md) | `/pages/domains` (also available for projects) |
+| [Projects](projects.md) | `/users/:id/projects` (also available for projects) |
+| [Runners](runners.md) | `/runners` (also available for projects) |
+| [Search](search.md) | `/search` (also available for groups and projects) |
+| [Settings](settings.md) | `/application/settings` |
+| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` |
+| [Suggestions](suggestions.md) | `/suggestions` |
+| [System hooks](system_hooks.md) | `/hooks` |
+| [Todos](todos.md) | `/todos` |
+| [Users](users.md) | `/users` |
+| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` |
+| [Version](version.md) | `/version` |
+
+## Templates API resources
+
+Endpoints are available for:
+
+- [Dockerfile templates](templates/dockerfiles.md).
+- [`.gitignore` templates](templates/gitignores.md).
+- [GitLab CI YAML templates](templates/gitlab_ci_ymls.md).
+- [Open source license templates](templates/licenses.md).
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/commits.md b/doc/api/commits.md
index 6eb4c47415f..1f17eaea46d 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -72,15 +72,16 @@ POST /projects/:id/repository/commits
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
-| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide `start_branch`. |
+| `branch` | string | yes | Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`. |
| `commit_message` | string | yes | Commit message |
-| `start_branch` | string | no | Name of the branch to start the new commit from |
-| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the commit from. Defaults to the value of `id`. |
+| `start_branch` | string | no | Name of the branch to start the new branch from |
+| `start_sha` | string | no | SHA of the commit to start the new branch from |
+| `start_project` | integer/string | no | The project ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) to start the new branch from. Defaults to the value of `id`. |
| `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. |
| `author_email` | string | no | Specify the commit author's email address |
| `author_name` | string | no | Specify the commit author's name |
| `stats` | boolean | no | Include commit stats. Default is true |
-| `force` | boolean | no | When `true` overwrites the target branch with a new commit based on the `start_branch` |
+| `force` | boolean | no | When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha` |
| `actions[]` Attribute | Type | Required | Description |
| --------------------- | ---- | -------- | ----------- |
@@ -581,6 +582,7 @@ POST /projects/:id/statuses/:sha
| `target_url` | string | no | The target URL to associate with this status
| `description` | string | no | The short description of the status
| `coverage` | float | no | The total code coverage
+| `pipeline_id` | integer | no | The ID of the pipeline to set status. Use in case of several pipeline on same SHA.
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/17/statuses/18f3e63d05582537db6d183d9d557be09e1f90c8?state=success"
diff --git a/doc/api/container_registry.md b/doc/api/container_registry.md
index 64ea15bca93..bf544f64178 100644
--- a/doc/api/container_registry.md
+++ b/doc/api/container_registry.md
@@ -6,6 +6,8 @@ This is the API docs of the [GitLab Container Registry](../user/project/containe
## List registry repositories
+### Within a project
+
Get a list of registry repositories in a project.
```
@@ -14,7 +16,8 @@ GET /projects/:id/registry/repositories
| 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. |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
+| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. |
```bash
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories"
@@ -28,6 +31,7 @@ Example response:
"id": 1,
"name": "",
"path": "group/project",
+ "project_id": 9,
"location": "gitlab.example.com:5000/group/project",
"created_at": "2019-01-10T13:38:57.391Z"
},
@@ -35,12 +39,77 @@ Example response:
"id": 2,
"name": "releases",
"path": "group/project/releases",
+ "project_id": 9,
"location": "gitlab.example.com:5000/group/project/releases",
"created_at": "2019-01-10T13:39:08.229Z"
}
]
```
+### Within a group
+
+Get a list of registry repositories in a group.
+
+```
+GET /groups/:id/registry/repositories
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) accessible by the authenticated user. |
+| `tags` | boolean | no | If the param is included as true, each repository will include an array of `"tags"` in the response. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/2/registry/repositories?tags=1"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "",
+ "path": "group/project",
+ "project_id": 9,
+ "location": "gitlab.example.com:5000/group/project",
+ "created_at": "2019-01-10T13:38:57.391Z",
+ "tags": [
+ {
+ "name": "0.0.1",
+ "path": "group/project:0.0.1",
+ "location": "gitlab.example.com:5000/group/project:0.0.1"
+ }
+ ]
+ },
+ {
+ "id": 2,
+ "name": "",
+ "path": "group/other_project",
+ "project_id": 11,
+ "location": "gitlab.example.com:5000/group/other_project",
+ "created_at": "2019-01-10T13:39:08.229Z",
+ "tags": [
+ {
+ "name": "0.0.1",
+ "path": "group/other_project:0.0.1",
+ "location": "gitlab.example.com:5000/group/other_project:0.0.1"
+ },
+ {
+ "name": "0.0.2",
+ "path": "group/other_project:0.0.2",
+ "location": "gitlab.example.com:5000/group/other_project:0.0.2"
+ },
+ {
+ "name": "latest",
+ "path": "group/other_project:latest",
+ "location": "gitlab.example.com:5000/group/other_project:latest"
+ }
+ ]
+ }
+]
+```
+
## Delete registry repository
Delete a repository in registry.
@@ -62,6 +131,8 @@ curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://git
## List repository tags
+### Within a project
+
Get a list of tags for given registry repository.
```
@@ -70,7 +141,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags
| 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. |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `repository_id` | integer | yes | The ID of registry repository. |
```bash
@@ -104,7 +175,7 @@ GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name
| 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. |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) accessible by the authenticated user. |
| `repository_id` | integer | yes | The ID of registry repository. |
| `tag_name` | string | yes | The name of tag. |
@@ -193,13 +264,13 @@ Examples:
curl --request DELETE --data 'name_regex=[0-9a-z]{40}' --data 'keep_n=5' --data 'older_than=2d' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
```
-2. Remove all tags, but keep always the latest 5:
+1. Remove all tags, but keep always the latest 5:
```bash
curl --request DELETE --data 'name_regex=.*' --data 'keep_n=5' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
```
-3. Remove all tags that are older than 1 month:
+1. Remove all tags that are older than 1 month:
```bash
curl --request DELETE --data 'name_regex=.*' --data 'older_than=1month' --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/registry/repositories/2/tags"
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
index ed5ebdade19..015ffbe60f6 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -11,14 +11,14 @@ Every call to this endpoint requires authentication. To perform this call, user
## List project dependencies
Get a list of project dependencies. This API partially mirroring
-[Dependency List](../user/application_security/dependency_scanning/index.md#dependency-list) feature.
+[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.
```
GET /projects/:id/dependencies
-GET /projects/:id/vulnerabilities?package_manger=maven
-GET /projects/:id/vulnerabilities?package_manger=yarn,bundler
+GET /projects/:id/vulnerabilities?package_manager=maven
+GET /projects/:id/vulnerabilities?package_manager=yarn,bundler
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/deploy_key_multiple_projects.md b/doc/api/deploy_key_multiple_projects.md
index 0c9e3e66cae..85df972746e 100644
--- a/doc/api/deploy_key_multiple_projects.md
+++ b/doc/api/deploy_key_multiple_projects.md
@@ -1,29 +1,5 @@
-# Adding deploy keys to multiple projects via API
+---
+redirect_to: deploy_keys.md#adding-deploy-keys-to-multiple-projects
+---
-If you want to easily add the same deploy key to multiple projects in the same
-group, this can be achieved quite easily with the API.
-
-First, find the ID of the projects you're interested in, by either listing all
-projects:
-
-```
-curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects
-```
-
-Or finding the ID of a group and then listing all projects in that group:
-
-```
-curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups
-
-# For group 1234:
-curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234
-```
-
-With those IDs, add the same deploy key to all:
-
-```
-for project_id in 321 456 987; do
- curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
- --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys
-done
-```
+This document was moved to [another location](deploy_keys.md#adding-deploy-keys-to-multiple-projects).
diff --git a/doc/api/deploy_keys.md b/doc/api/deploy_keys.md
index 41f6ab436e8..94351e1a300 100644
--- a/doc/api/deploy_keys.md
+++ b/doc/api/deploy_keys.md
@@ -203,3 +203,32 @@ 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
+group, this can be achieved quite easily with the API.
+
+First, find the ID of the projects you're interested in, by either listing all
+projects:
+
+```
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/projects
+```
+
+Or finding the ID of a group and then listing all projects in that group:
+
+```
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups
+
+# For group 1234:
+curl --header 'PRIVATE-TOKEN: <your_access_token>' https://gitlab.example.com/api/v4/groups/1234
+```
+
+With those IDs, add the same deploy key to all:
+
+```
+for project_id in 321 456 987; do
+ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" \
+ --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v4/projects/${project_id}/deploy_keys
+done
+```
diff --git a/doc/api/epics.md b/doc/api/epics.md
index d05eb0a8804..3036b3c2364 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -10,7 +10,7 @@ If epics feature is not available a `403` status code will be returned.
The [epic issues API](epic_issues.md) allows you to interact with issues associated with an epic.
-# Milestone dates integration
+## Milestone dates integration
> [Introduced][ee-6448] in GitLab 11.3.
diff --git a/doc/api/geo_nodes.md b/doc/api/geo_nodes.md
index ac64cbedf7d..d0b33ab467f 100644
--- a/doc/api/geo_nodes.md
+++ b/doc/api/geo_nodes.md
@@ -10,7 +10,7 @@ GET /geo_nodes
```
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/geo_nodes
```
Example response:
@@ -27,8 +27,15 @@ Example response:
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
+ "container_repositories_max_capacity": 10,
"verification_max_capacity": 100,
- "clone_protocol": "http"
+ "clone_protocol": "http",
+ "web_edit_url": "https://primary.example.com/admin/geo/nodes/1/edit",
+ "_links": {
+ "self": "https://primary.example.com/api/v4/geo_nodes/1",
+ "status":"https://primary.example.com/api/v4/geo_nodes/1/status",
+ "repair":"https://primary.example.com/api/v4/geo_nodes/1/repair"
+ }
},
{
"id": 2,
@@ -40,8 +47,17 @@ Example response:
"current": false,
"files_max_capacity": 10,
"repos_max_capacity": 25,
+ "container_repositories_max_capacity": 10,
"verification_max_capacity": 100,
- "clone_protocol": "http"
+ "sync_object_storage": true,
+ "clone_protocol": "http",
+ "web_edit_url": "https://primary.example.com/admin/geo/nodes/2/edit",
+ "web_geo_projects_url": "https://secondary.example.com/admin/geo/projects",
+ "_links": {
+ "self":"https://primary.example.com/api/v4/geo_nodes/2",
+ "status":"https://primary.example.com/api/v4/geo_nodes/2/status",
+ "repair":"https://primary.example.com/api/v4/geo_nodes/2/repair"
+ }
}
]
```
@@ -53,7 +69,7 @@ GET /geo_nodes/:id
```
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/1
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/geo_nodes/1
```
Example response:
@@ -69,8 +85,15 @@ Example response:
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
+ "container_repositories_max_capacity": 10,
"verification_max_capacity": 100,
- "clone_protocol": "http"
+ "clone_protocol": "http",
+ "web_edit_url": "https://primary.example.com/admin/geo/nodes/1/edit",
+ "_links": {
+ "self": "https://primary.example.com/api/v4/geo_nodes/1",
+ "status":"https://primary.example.com/api/v4/geo_nodes/1/status",
+ "repair":"https://primary.example.com/api/v4/geo_nodes/1/repair"
+ }
}
```
@@ -84,16 +107,18 @@ _This can only be run against a primary Geo node._
PUT /geo_nodes/:id
```
-| Attribute | Type | Required | Description |
-|----------------------|---------|-----------|---------------------------------------------------------------------------|
-| `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`. |
-| `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. |
-| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this secondary node. |
-| `verification_max_capacity` | integer | no | Control the maximum concurrency of verification for this node. |
+| Attribute | Type | Required | Description |
+|-----------------------------|---------|-----------|---------------------------------------------------------------------------|
+| `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`. |
+| `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. |
+| `repos_max_capacity` | integer | no | Control the maximum concurrency of repository backfill for this secondary node. |
+| `verification_max_capacity` | integer | no | Control the maximum concurrency of verification for this node. |
+| `container_repositories_max_capacity` | integer | no | Control the maximum concurrency of container repository sync for this node. |
+| `sync_object_storage` | boolean | no | Flag indicating if the secondary Geo node will replicate blobs in Object Storage. |
Example response:
@@ -108,8 +133,17 @@ Example response:
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
+ "container_repositories_max_capacity": 10,
"verification_max_capacity": 100,
- "clone_protocol": "http"
+ "sync_object_storage": true,
+ "clone_protocol": "http",
+ "web_edit_url": "https://primary.example.com/admin/geo/nodes/2/edit",
+ "web_geo_projects_url": "https://secondary.example.com/admin/geo/projects",
+ "_links": {
+ "self":"https://primary.example.com/api/v4/geo_nodes/2",
+ "status":"https://primary.example.com/api/v4/geo_nodes/2/status",
+ "repair":"https://primary.example.com/api/v4/geo_nodes/2/repair"
+ }
}
```
@@ -151,8 +185,15 @@ Example response:
"current": true,
"files_max_capacity": 10,
"repos_max_capacity": 25,
+ "container_repositories_max_capacity": 10,
"verification_max_capacity": 100,
- "clone_protocol": "http"
+ "clone_protocol": "http",
+ "web_edit_url": "https://primary.example.com/admin/geo/nodes/1/edit",
+ "_links": {
+ "self": "https://primary.example.com/api/v4/geo_nodes/1",
+ "status":"https://primary.example.com/api/v4/geo_nodes/1/status",
+ "repair":"https://primary.example.com/api/v4/geo_nodes/1/repair"
+ }
}
```
@@ -163,7 +204,7 @@ GET /geo_nodes/status
```
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/status
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/geo_nodes/status
```
Example response:
@@ -192,6 +233,10 @@ Example response:
"job_artifacts_failed_count": nil,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "0.00%",
+ "container_repositories_count": 3,
+ "container_repositories_synced_count": nil,
+ "container_repositories_failed_count": nil,
+ "container_repositories_synced_in_percentage": "0.00%",
"projects_count": 41,
"repositories_failed_count": nil,
"repositories_synced_count": nil,
@@ -255,6 +300,10 @@ Example response:
"job_artifacts_failed_count": 1,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "50.00%",
+ "container_repositories_count": 3,
+ "container_repositories_synced_count": nil,
+ "container_repositories_failed_count": nil,
+ "container_repositories_synced_in_percentage": "0.00%",
"projects_count": 41,
"repositories_failed_count": 1,
"repositories_synced_count": 40,
@@ -306,7 +355,7 @@ GET /geo_nodes/:id/status
```
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/2/status
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/geo_nodes/2/status
```
Example response:
@@ -334,6 +383,10 @@ Example response:
"job_artifacts_failed_count": 1,
"job_artifacts_synced_missing_on_primary_count": 0,
"job_artifacts_synced_in_percentage": "50.00%",
+ "container_repositories_count": 3,
+ "container_repositories_synced_count": nil,
+ "container_repositories_failed_count": nil,
+ "container_repositories_synced_in_percentage": "0.00%",
"projects_count": 41,
"repositories_failed_count": 1,
"repositories_synced_count": 40,
@@ -376,7 +429,7 @@ GET /geo_nodes/current/failures
This endpoint uses [Pagination](README.md#pagination).
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/geo_nodes/current/failures
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://primary.example.com/api/v4/geo_nodes/current/failures
```
Example response:
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/group_clusters.md b/doc/api/group_clusters.md
index 31c0e6abead..29e58d9279a 100644
--- a/doc/api/group_clusters.md
+++ b/doc/api/group_clusters.md
@@ -210,7 +210,7 @@ Parameters:
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
-through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing Kubernetes cluster"](../user/project/clusters/index.md#add-existing-kubernetes-cluster) option or
through the ["Add existing cluster to group"](#add-existing-cluster-to-group) endpoint.
Example request:
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
index 3d4b099b49e..e2ba0dea642 100644
--- a/doc/api/group_labels.md
+++ b/doc/api/group_labels.md
@@ -12,12 +12,13 @@ Get all labels for a given group.
GET /groups/:id/labels
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31543))_ |
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?with_counts=true
```
Example response:
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 96a547551f1..4f2b4a966c9 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -136,7 +136,6 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/1/issues/76/award_emoji",
"project":"http://example.com/api/v4/projects/1"
},
- "subscribed": false,
"task_completion_status":{
"count":0,
"completed_count":0
@@ -441,7 +440,6 @@ Example response:
"award_emoji":"http://example.com/api/v4/projects/4/issues/41/award_emoji",
"project":"http://example.com/api/v4/projects/4"
},
- "subscribed": false,
"task_completion_status":{
"count":0,
"completed_count":0
@@ -790,7 +788,7 @@ the `weight` parameter:
## Delete an issue
-Only for admins and project owners. Soft deletes the issue in question.
+Only for admins and project owners. Deletes the issue in question.
```
DELETE /projects/:id/issues/:issue_iid
diff --git a/doc/api/jobs.md b/doc/api/jobs.md
index 0e45ee1a583..2a1c1b5f6f3 100644
--- a/doc/api/jobs.md
+++ b/doc/api/jobs.md
@@ -491,7 +491,7 @@ Parameters
Example request:
```sh
-curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
+curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/5/artifacts/some/release/file.pdf"
```
Possible response status codes:
@@ -526,7 +526,7 @@ Parameters:
Example request:
```sh
-curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
+curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/master/raw/some/release/file.pdf?job=pdf"
```
Possible response status codes:
@@ -551,7 +551,7 @@ GET /projects/:id/jobs/:job_id/trace
| job_id | integer | yes | ID of a job. |
```sh
-curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
+curl --location --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/jobs/8/trace"
```
Possible response status codes:
diff --git a/doc/api/labels.md b/doc/api/labels.md
index 9d10d383bf9..5db0edcf14d 100644
--- a/doc/api/labels.md
+++ b/doc/api/labels.md
@@ -8,12 +8,13 @@ Get all labels for a given project.
GET /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 |
+| 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 |
+| `with_counts` | boolean | no | Whether or not to include issue and merge request counts. Defaults to `false`. _([Introduced in GitLab 12.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31543))_ |
```bash
-curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/labels?with_counts=true
```
Example response:
diff --git a/doc/api/lint.md b/doc/api/lint.md
index b9b49f3df27..79f5e629c7f 100644
--- a/doc/api/lint.md
+++ b/doc/api/lint.md
@@ -5,7 +5,7 @@
Checks if your `.gitlab-ci.yml` file is valid.
```
-POST /lint
+POST /ci/lint
```
| Attribute | Type | Required | Description |
diff --git a/doc/api/merge_request_approvals.md b/doc/api/merge_request_approvals.md
index c211916464a..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
@@ -72,30 +42,79 @@ POST /projects/:id/approvals
**Parameters:**
-| 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 |
-| `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 be able to self approve merge requests |
+| 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. 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 |
+| `merge_requests_disable_committers_approval` | boolean | no | Allow/Disallow committers from self approving merge requests |
```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": "",
@@ -110,17 +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
+ "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
@@ -225,8 +414,6 @@ GET /projects/:id/merge_requests/:merge_request_iid/approvals
}
}
],
- "approvers": [],
- "approver_groups": []
}
```
@@ -247,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
{
@@ -262,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
@@ -399,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/oauth2.md b/doc/api/oauth2.md
index 76e3a0fa1a4..f9382361187 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -50,11 +50,14 @@ The web application flow is:
`/oauth/authorize` endpoint with the following GET parameters:
```
- https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
+ https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES
```
- This will ask the user to approve the applications access to their account and
- then redirect back to the `REDIRECT_URI` you provided. The redirect will
+ This will ask the user to approve the applications access to their account
+ based on the scopes specified in `REQUESTED_SCOPES` and then redirect back to
+ the `REDIRECT_URI` you provided. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
+ is a space separated list of scopes you want to have access to (e.g. `scope=read_user+profile`
+ would request `read_user` and `profile` scopes). The redirect will
include the GET `code` parameter, for example:
```
@@ -110,11 +113,14 @@ To request the access token, you should redirect the user to the
`/oauth/authorize` endpoint using `token` response type:
```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
+https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH&scope=REQUESTED_SCOPES
```
-This will ask the user to approve the application's access to their account and
-then redirect them back to the `REDIRECT_URI` you provided. The redirect
+This will ask the user to approve the applications access to their account
+based on the scopes specified in `REQUESTED_SCOPES` and then redirect back to
+the `REDIRECT_URI` you provided. The [scope parameter](https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes#requesting-particular-scopes)
+ is a space separated list of scopes you want to have access to (e.g. `scope=read_user+profile`
+would request `read_user` and `profile` scopes). The redirect
will include a fragment with `access_token` as well as token details in GET
parameters, for example:
diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md
index 736312df116..e207ff8e98a 100644
--- a/doc/api/pipeline_triggers.md
+++ b/doc/api/pipeline_triggers.md
@@ -120,35 +120,6 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --form descript
}
```
-## Take ownership of a project trigger
-
-Update an owner of a project trigger.
-
-```
-POST /projects/:id/triggers/:trigger_id/take_ownership
-```
-
-| 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 |
-| `trigger_id` | integer | yes | The trigger id |
-
-```
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/triggers/10/take_ownership"
-```
-
-```json
-{
- "id": 10,
- "description": "my trigger",
- "created_at": "2016-01-07T09:53:58.235Z",
- "last_used": null,
- "token": "6d056f63e50fe6f8c5f8f4aa10edb7",
- "updated_at": "2016-01-07T09:53:58.235Z",
- "owner": null
-}
-```
-
## Remove a project trigger
Remove a project's build trigger.
diff --git a/doc/api/project_clusters.md b/doc/api/project_clusters.md
index 614ea41d572..762a4ad95ab 100644
--- a/doc/api/project_clusters.md
+++ b/doc/api/project_clusters.md
@@ -261,7 +261,7 @@ Parameters:
NOTE: **Note:**
`name`, `api_url`, `ca_cert` and `token` can only be updated if the cluster was added
-through the ["Add an existing Kubernetes Cluster"](../user/project/clusters/index.md#adding-an-existing-kubernetes-cluster) option or
+through the ["Add existing Kubernetes cluster"](../user/project/clusters/index.md#add-existing-kubernetes-cluster) option or
through the ["Add existing cluster to project"](#add-existing-cluster-to-project) endpoint.
Example request:
diff --git a/doc/api/project_level_variables.md b/doc/api/project_level_variables.md
index eab905bbc5f..591911bb8ec 100644
--- a/doc/api/project_level_variables.md
+++ b/doc/api/project_level_variables.md
@@ -74,7 +74,7 @@ POST /projects/:id/variables
| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
| `protected` | boolean | no | Whether the variable is protected |
| `masked` | boolean | no | Whether the variable is masked |
-| `environment_scope` | string | no | The `environment_scope` of the variable **(PREMIUM)** |
+| `environment_scope` | string | no | The `environment_scope` of the variable |
```
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables" --form "key=NEW_VARIABLE" --form "value=new value"
@@ -108,7 +108,7 @@ PUT /projects/:id/variables/:key
| `variable_type` | string | no | The type of a variable. Available types are: `env_var` (default) and `file` |
| `protected` | boolean | no | Whether the variable is protected |
| `masked` | boolean | no | Whether the variable is masked |
-| `environment_scope` | string | no | The `environment_scope` of the variable **(PREMIUM)** |
+| `environment_scope` | string | no | The `environment_scope` of the variable |
```
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/1/variables/NEW_VARIABLE" --form "value=updated value"
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 781192fb92e..70df44ec0fd 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -465,6 +465,194 @@ GET /users/:user_id/projects
]
```
+## List projects starred by a user
+
+Get a list of visible projects owned by the given user. When accessed without authentication, only public projects are returned.
+
+```
+GET /users/:user_id/starred_projects
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `user_id` | string | yes | The ID or username of the user. |
+| `archived` | boolean | no | Limit by archived status. |
+| `visibility` | string | no | Limit by visibility `public`, `internal`, or `private`. |
+| `order_by` | string | no | Return projects ordered by `id`, `name`, `path`, `created_at`, `updated_at`, or `last_activity_at` fields. Default is `created_at`. |
+| `sort` | string | no | Return projects sorted in `asc` or `desc` order. Default is `desc`. |
+| `search` | string | no | Return list of projects matching the search criteria. |
+| `simple` | boolean | no | Return only limited fields for each project. This is a no-op without authentication as then _only_ simple fields are returned.. |
+| `owned` | boolean | no | Limit by projects explicitly owned by the current user. |
+| `membership` | boolean | no | Limit by projects that the current user is a member of. |
+| `starred` | boolean | no | Limit by projects starred by the current user. |
+| `statistics` | boolean | no | Include project statistics. |
+| `with_custom_attributes` | boolean | no | Include [custom attributes](custom_attributes.md) in response (admins only). |
+| `with_issues_enabled` | boolean | no | Limit by enabled issues feature. |
+| `with_merge_requests_enabled` | boolean | no | Limit by enabled merge requests feature. |
+| `min_access_level` | integer | no | Limit by current user minimal [access level](members.md). |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/users/5/starred_projects"
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 4,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
+ "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
+ "web_url": "http://example.com/diaspora/diaspora-client",
+ "readme_url": "http://example.com/diaspora/diaspora-client/blob/master/README.md",
+ "tag_list": [
+ "example",
+ "disapora client"
+ ],
+ "owner": {
+ "id": 3,
+ "name": "Diaspora",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Diaspora Client",
+ "name_with_namespace": "Diaspora / Diaspora Client",
+ "path": "diaspora-client",
+ "path_with_namespace": "diaspora/diaspora-client",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 3,
+ "name": "Diaspora",
+ "path": "diaspora",
+ "kind": "group",
+ "full_path": "diaspora"
+ },
+ "import_status": "none",
+ "archived": false,
+ "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png",
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "merge_method": "merge",
+ "statistics": {
+ "commit_count": 37,
+ "storage_size": 1038090,
+ "repository_size": 1038090,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
+ },
+ {
+ "id": 6,
+ "description": null,
+ "default_branch": "master",
+ "visibility": "private",
+ "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
+ "http_url_to_repo": "http://example.com/brightbox/puppet.git",
+ "web_url": "http://example.com/brightbox/puppet",
+ "readme_url": "http://example.com/brightbox/puppet/blob/master/README.md",
+ "tag_list": [
+ "example",
+ "puppet"
+ ],
+ "owner": {
+ "id": 4,
+ "name": "Brightbox",
+ "created_at": "2013-09-30T13:46:02Z"
+ },
+ "name": "Puppet",
+ "name_with_namespace": "Brightbox / Puppet",
+ "path": "puppet",
+ "path_with_namespace": "brightbox/puppet",
+ "issues_enabled": true,
+ "open_issues_count": 1,
+ "merge_requests_enabled": true,
+ "jobs_enabled": true,
+ "wiki_enabled": true,
+ "snippets_enabled": false,
+ "resolve_outdated_diff_discussions": false,
+ "container_registry_enabled": false,
+ "created_at": "2013-09-30T13:46:02Z",
+ "last_activity_at": "2013-09-30T13:46:02Z",
+ "creator_id": 3,
+ "namespace": {
+ "id": 4,
+ "name": "Brightbox",
+ "path": "brightbox",
+ "kind": "group",
+ "full_path": "brightbox"
+ },
+ "import_status": "none",
+ "import_error": null,
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ },
+ "archived": false,
+ "avatar_url": null,
+ "shared_runners_enabled": true,
+ "forks_count": 0,
+ "star_count": 0,
+ "runners_token": "b8547b1dc37721d05889db52fa2f02",
+ "public_jobs": true,
+ "shared_with_groups": [],
+ "only_allow_merge_if_pipeline_succeeds": false,
+ "only_allow_merge_if_all_discussions_are_resolved": false,
+ "request_access_enabled": false,
+ "merge_method": "merge",
+ "statistics": {
+ "commit_count": 12,
+ "storage_size": 2066080,
+ "repository_size": 2066080,
+ "lfs_objects_size": 0,
+ "job_artifacts_size": 0
+ },
+ "_links": {
+ "self": "http://example.com/api/v4/projects",
+ "issues": "http://example.com/api/v4/projects/1/issues",
+ "merge_requests": "http://example.com/api/v4/projects/1/merge_requests",
+ "repo_branches": "http://example.com/api/v4/projects/1/repository_branches",
+ "labels": "http://example.com/api/v4/projects/1/labels",
+ "events": "http://example.com/api/v4/projects/1/events",
+ "members": "http://example.com/api/v4/projects/1/members"
+ }
+ }
+]
+```
+
## Get single project
Get a specific project. This endpoint can be accessed without authentication if
@@ -799,7 +987,7 @@ POST /projects/user/:user_id
| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge requests by default |
-| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
+| `external_authorization_classification_label` | string | no | **(PREMIUM)** The classification label for the project |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
@@ -856,7 +1044,7 @@ PUT /projects/:id
| `auto_devops_deploy_strategy` | string | no | Auto Deploy strategy (`continuous`, `manual` or `timed_incremental`) |
| `repository_storage` | string | no | Which storage shard the repository is on. Available only to admins |
| `approvals_before_merge` | integer | no | **(STARTER)** How many approvers should approve merge request by default |
-| `external_authorization_classification_label` | string | no | **(CORE ONLY)** The classification label for the project |
+| `external_authorization_classification_label` | string | no | **(PREMIUM)** The classification label for the project |
| `mirror` | boolean | no | **(STARTER)** Enables pull mirroring in a project |
| `mirror_user_id` | integer | no | **(STARTER)** User responsible for all the activity surrounding a pull mirror event |
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
@@ -1155,6 +1343,51 @@ Example response:
}
```
+## List Starrers of a project
+
+List the users who starred the specified project.
+
+```
+GET /projects/:id/starrers
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `search` | string | no | Search for specific users. |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/starrers"
+```
+
+Example responses:
+
+```json
+[
+ {
+ "starred_since": "2019-01-28T14:47:30.642Z",
+ "user":
+ {
+ "id": 1,
+ "username": "jane_smith",
+ "name": "Jane Smith",
+ "state": "active",
+ "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "web_url": "http://localhost:3000/jane_smith"
+ }
+ },
+ "starred_since": "2018-01-02T11:40:26.570Z",
+ "user":
+ {
+ "id": 2,
+ "username": "janine_smith",
+ "name": "Janine Smith",
+ "state": "blocked",
+ "avatar_url": "http://gravatar.com/../e32131cd8.jpeg",
+ "web_url": "http://localhost:3000/janine_smith"
+ }
+]
+```
+
## Languages
Get languages used in a project with percentage value.
@@ -1676,18 +1909,20 @@ GET /projects/:id/push_rule
"author_email_regex": "",
"file_name_regex": "",
"max_file_size": 5,
- "commit_committer_check": false
+ "commit_committer_check": false,
+ "reject_unsigned_commits": false
}
```
Users on GitLab [Premium, Silver, or higher](https://about.gitlab.com/pricing/) will also see
-the `commit_committer_check` parameter:
+the `commit_committer_check` and `reject_unsigned_commits` parameters:
```json
{
"id": 1,
"project_id": 3,
- "commit_committer_check": false
+ "commit_committer_check": false,
+ "reject_unsigned_commits": false
...
}
```
@@ -1713,6 +1948,7 @@ POST /projects/:id/push_rule
| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commit when it is not signed through GPG. |
### Edit project push rule
@@ -1735,6 +1971,7 @@ PUT /projects/:id/push_rule
| `file_name_regex` **(STARTER)** | string | no | All commited filenames must **not** match this, e.g. `(jar|exe)$` |
| `max_file_size` **(STARTER)** | integer | no | Maximum file size (MB) |
| `commit_committer_check` **(PREMIUM)** | boolean | no | Users can only push commits to this repository that were committed with one of their own verified emails. |
+| `reject_unsigned_commits` **(PREMIUM)** | boolean | no | Reject commits when they are not GPG signed. |
### Delete project push rule
diff --git a/doc/api/releases/img/upcoming_release_v12_1.png b/doc/api/releases/img/upcoming_release_v12_1.png
index 8bd8573ce84..cc3070fd19d 100644
--- a/doc/api/releases/img/upcoming_release_v12_1.png
+++ b/doc/api/releases/img/upcoming_release_v12_1.png
Binary files differ
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index 9c91264ed65..b1b75abe32f 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -21,7 +21,7 @@ GET /projects/:id/releases/:tag_name/assets/links
Example request:
```sh
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links"
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links"
```
Example response:
@@ -60,7 +60,7 @@ GET /projects/:id/releases/:tag_name/assets/links/:link_id
Example request:
```sh
-curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
@@ -96,7 +96,7 @@ curl --request POST \
--header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" \
--data name="awesome-v0.2.dmg" \
--data url="http://192.168.10.15:3000" \
- "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links"
+ "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links"
```
Example response:
@@ -132,7 +132,7 @@ You have to specify at least one of `name` or `url`
Example request:
```sh
-curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --request PUT --data name="new name" --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
@@ -163,7 +163,7 @@ DELETE /projects/:id/releases/:tag_name/assets/links/:link_id
Example request:
```sh
-curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "http://localhost:3000/api/v4/projects/24/releases/v0.1/assets/links/1"
+curl --request DELETE --header "PRIVATE-TOKEN: gDybLx3yrUK_HLp3qPjS" "https://gitlab.example.com/api/v4/projects/24/releases/v0.1/assets/links/1"
```
Example response:
diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md
index 87c7f371de1..b292c9dd7de 100644
--- a/doc/api/repository_files.md
+++ b/doc/api/repository_files.md
@@ -80,6 +80,75 @@ X-Gitlab-Size: 1476
...
```
+## Get file blame from repository
+
+Allows you to receive blame information. Each blame range contains lines and corresponding commit info.
+
+```
+GET /projects/:id/repository/files/:file_path/blame
+```
+
+```bash
+curl --request GET --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/path%2Fto%2Ffile.rb/blame?ref=master'
+```
+
+Example response:
+
+```json
+[
+ {
+ "commit": {
+ "id": "d42409d56517157c48bf3bd97d3f75974dde19fb",
+ "message": "Add feature\n\nalso fix bug\n",
+ "parent_ids": [
+ "cc6e14f9328fa6d7b5a0d3c30dc2002a3f2a3822"
+ ],
+ "authored_date": "2015-12-18T08:12:22.000Z",
+ "author_name": "John Doe",
+ "author_email": "john.doe@example.com",
+ "committed_date": "2015-12-18T08:12:22.000Z",
+ "committer_name": "John Doe",
+ "committer_email": "john.doe@example.com"
+ },
+ "lines": [
+ "require 'fileutils'",
+ "require 'open3'",
+ ""
+ ]
+ },
+ ...
+]
+```
+
+Parameters:
+
+- `file_path` (required) - Url encoded full path to new file. Ex. lib%2Fclass%2Erb
+- `ref` (required) - The name of branch, tag or commit
+
+NOTE: **Note:**
+`HEAD` method return just file metadata as in [Get file from repository](repository_files.md#get-file-from-repository).
+
+```bash
+curl --head --header 'PRIVATE-TOKEN: <your_access_token>' 'https://gitlab.example.com/api/v4/projects/13083/repository/files/path%2Fto%2Ffile.rb/blame?ref=master'
+```
+
+Example response:
+
+```text
+HTTP/1.1 200 OK
+...
+X-Gitlab-Blob-Id: 79f7bbd25901e8334750839545a9bd021f0e4c83
+X-Gitlab-Commit-Id: d5a3ff139356ce33e37e73add446f16869741b50
+X-Gitlab-Content-Sha256: 4c294617b60715c1d218e61164a3abd4808a4284cbc30e6728a01ad9aada4481
+X-Gitlab-Encoding: base64
+X-Gitlab-File-Name: file.rb
+X-Gitlab-File-Path: path/to/file.rb
+X-Gitlab-Last-Commit-Id: 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+X-Gitlab-Ref: master
+X-Gitlab-Size: 1476
+...
+```
+
## Get raw file from repository
```
diff --git a/doc/api/services.md b/doc/api/services.md
index df15e6892b0..7d025cd3bdf 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -595,44 +595,6 @@ Remove all previously Jira settings from a project.
DELETE /projects/:id/services/jira
```
-## Kubernetes
-
-Kubernetes / OpenShift integration
-
-CAUTION: **Warning:**
-Kubernetes service integration has been deprecated in GitLab 10.3. API service endpoints will continue to work as long as the Kubernetes service is active, however if the service is inactive API endpoints will automatically return a `400 Bad Request`. Read [GitLab 10.3 release post](https://about.gitlab.com/2017/12/22/gitlab-10-3-released/#kubernetes-integration-service) for more information.
-
-### Create/Edit Kubernetes service
-
-Set Kubernetes service for a project.
-
-```
-PUT /projects/:id/services/kubernetes
-```
-
-Parameters:
-
-- `namespace` (**required**) - The Kubernetes namespace to use
-- `api_url` (**required**) - The URL to the Kubernetes cluster API. For example, `https://kubernetes.example.com`
-- `token` (**required**) - The service token to authenticate against the Kubernetes cluster with
-- `ca_pem` (optional) - A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)
-
-### Delete Kubernetes service
-
-Delete Kubernetes service for a project.
-
-```
-DELETE /projects/:id/services/kubernetes
-```
-
-### Get Kubernetes service settings
-
-Get Kubernetes service settings for a project.
-
-```
-GET /projects/:id/services/kubernetes
-```
-
## Slack slash commands
Ability to receive slash commands from a Slack chat instance.
@@ -972,22 +934,28 @@ Parameters:
| `channel` | string | false | Default channel to use if others are not configured |
| `notify_only_broken_pipelines` | boolean | false | Send notifications for broken pipelines |
| `notify_only_default_branch` | boolean | false | Send notifications only for the default branch |
-| `push_events` | boolean | false | Enable notifications for push events |
-| `issues_events` | boolean | false | Enable notifications for issue events |
+| `commit_events` | boolean | false | Enable notifications for commit events |
+| `confidential_issue_channel` | string | false | The name of the channel to receive confidential issues events notifications |
| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events |
+| `confidential_note_channel` | string | false | The name of the channel to receive confidential note events notifications |
+| `confidential_note_events` | boolean | false | Enable notifications for confidential note events |
+| `deployment_channel` | string | false | The name of the channel to receive deployment events notifications |
+| `deployment_events` | boolean | false | Enable notifications for deployment events |
+| `issue_channel` | string | false | The name of the channel to receive issues events notifications |
+| `issues_events` | boolean | false | Enable notifications for issue events |
+| `job_events` | boolean | false | Enable notifications for job events |
+| `merge_request_channel` | string | false | The name of the channel to receive merge request events notifications |
| `merge_requests_events` | boolean | false | Enable notifications for merge request events |
-| `tag_push_events` | boolean | false | Enable notifications for tag push events |
+| `note_channel` | string | false | The name of the channel to receive note events notifications |
| `note_events` | boolean | false | Enable notifications for note events |
+| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
| `pipeline_events` | boolean | false | Enable notifications for pipeline events |
-| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
| `push_channel` | string | false | The name of the channel to receive push events notifications |
-| `issue_channel` | string | false | The name of the channel to receive issues events notifications |
-| `confidential_issue_channel` | string | false | The name of the channel to receive confidential issues events notifications |
-| `merge_request_channel` | string | false | The name of the channel to receive merge request events notifications |
-| `note_channel` | string | false | The name of the channel to receive note events notifications |
+| `push_events` | boolean | false | Enable notifications for push events |
| `tag_push_channel` | string | false | The name of the channel to receive tag push events notifications |
-| `pipeline_channel` | string | false | The name of the channel to receive pipeline events notifications |
+| `tag_push_events` | boolean | false | Enable notifications for tag push events |
| `wiki_page_channel` | string | false | The name of the channel to receive wiki page events notifications |
+| `wiki_page_events` | boolean | false | Enable notifications for wiki page events |
### Delete Slack service
diff --git a/doc/api/settings.md b/doc/api/settings.md
index ff48cac1f47..248d19461f6 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -39,6 +39,7 @@ Example response:
"session_expire_delay" : 10080,
"home_page_url" : null,
"default_snippet_visibility" : "private",
+ "outbound_local_requests_whitelist": [],
"domain_whitelist" : [],
"domain_blacklist_enabled" : false,
"domain_blacklist" : [],
@@ -63,7 +64,10 @@ Example response:
"performance_bar_allowed_group_id": 42,
"instance_statistics_visibility_private": false,
"user_show_add_ssh_key_message": true,
- "local_markdown_version": 0
+ "local_markdown_version": 0,
+ "allow_local_requests_from_hooks_and_services": true,
+ "allow_local_requests_from_web_hooks_and_services": true,
+ "allow_local_requests_from_system_hooks": false
}
```
@@ -113,6 +117,7 @@ Example response:
"default_project_visibility": "internal",
"default_snippet_visibility": "private",
"default_group_visibility": "private",
+ "outbound_local_requests_whitelist": [],
"domain_whitelist": [],
"domain_blacklist_enabled" : false,
"domain_blacklist" : [],
@@ -136,7 +141,10 @@ Example response:
"user_show_add_ssh_key_message": true,
"file_template_project_id": 1,
"local_markdown_version": 0,
- "geo_node_allowed_ips": "0.0.0.0/0, ::/0"
+ "geo_node_allowed_ips": "0.0.0.0/0, ::/0",
+ "allow_local_requests_from_hooks_and_services": true,
+ "allow_local_requests_from_web_hooks_and_services": true,
+ "allow_local_requests_from_system_hooks": false
}
```
@@ -175,7 +183,9 @@ are listed in the descriptions of the relevant settings.
| `akismet_api_key` | string | required by: `akismet_enabled` | API key for akismet spam protection. |
| `akismet_enabled` | boolean | no | (**If enabled, requires:** `akismet_api_key`) Enable or disable akismet spam protection. |
| `allow_group_owners_to_manage_ldap` | boolean | no | **(PREMIUM)** Set to `true` to allow group owners to manage LDAP |
-| `allow_local_requests_from_hooks_and_services` | boolean | no | Allow requests to the local network from hooks and services. |
+| `allow_local_requests_from_hooks_and_services` | boolean | no | (Deprecated: Use `allow_local_requests_from_web_hooks_and_services` instead) Allow requests to the local network from hooks and services. |
+| `allow_local_requests_from_web_hooks_and_services` | boolean | no | Allow requests to the local network from web hooks and services. |
+| `allow_local_requests_from_system_hooks` | boolean | no | Allow requests to the local network from system hooks. |
| `authorized_keys_enabled` | boolean | no | By default, we write to the `authorized_keys` file to support Git over SSH without additional configuration. GitLab can be optimized to authenticate SSH keys via the database file. Only disable this if you have configured your OpenSSH server to use the AuthorizedKeysCommand. |
| `auto_devops_domain` | string | no | Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages. |
| `auto_devops_enabled` | boolean | no | Enable Auto DevOps for projects by default. It will automatically build, test, and deploy applications based on a predefined CI/CD configuration. |
@@ -193,6 +203,7 @@ are listed in the descriptions of the relevant settings.
| `domain_blacklist` | array of strings | required by: `domain_blacklist_enabled` | Users with e-mail addresses that match these domain(s) will NOT be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: `domain.com`, `*.domain.com`. |
| `domain_blacklist_enabled` | boolean | no | (**If enabled, requires:** `domain_blacklist`) Allows blocking sign-ups from emails from specific domains. |
| `domain_whitelist` | array of strings | no | Force people to use only corporate emails for sign-up. Default is `null`, meaning there is no restriction. |
+| `outbound_local_requests_whitelist` | array of strings | no | Define a list of trusted domains or ip addresses to which local requests are allowed when local requests for hooks and services are disabled.
| `dsa_key_restriction` | integer | no | The minimum allowed bit length of an uploaded DSA key. Default is `0` (no restriction). `-1` disables DSA keys. |
| `ecdsa_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ECDSA key. Default is `0` (no restriction). `-1` disables ECDSA keys. |
| `ed25519_key_restriction` | integer | no | The minimum allowed curve size (in bits) of an uploaded ED25519 key. Default is `0` (no restriction). `-1` disables ED25519 keys. |
@@ -227,7 +238,7 @@ are listed in the descriptions of the relevant settings.
| `gravatar_enabled` | boolean | no | Enable Gravatar. |
| `hashed_storage_enabled` | boolean | no | Create new projects using hashed storage paths: Enable immutable, hash-based paths and repository names to store repositories on disk. This prevents repositories from having to be moved or renamed when the Project URL changes and may improve disk I/O performance. (EXPERIMENTAL) |
| `help_page_hide_commercial_content` | boolean | no | Hide marketing-related entries from help. |
-| `help_page_support_url` | string | no | Alternate support URL for help page. |
+| `help_page_support_url` | string | no | Alternate support URL for help page and help dropdown. |
| `help_page_text` | string | no | Custom text displayed on the help page. |
| `help_text` | string | no | **(PREMIUM)** GitLab server administrator information |
| `hide_third_party_offers` | boolean | no | Do not display offers from third parties within GitLab. |
@@ -310,4 +321,8 @@ are listed in the descriptions of the relevant settings.
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
| `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_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/users.md b/doc/api/users.md
index fdc84826680..b41fd106fc5 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -349,6 +349,9 @@ Note that `force_random_password` and `reset_password` take priority
over `password`. In addition, `reset_password` and
`force_random_password` can be used together.
+NOTE: **Note:**
+From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29888/), `private_profile` will default to `false`.
+
```
POST /users
```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index 7048ceaac41..ca9d0aa61bd 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -13,6 +13,11 @@ through the [continuous methodologies](introduction/index.md#introduction-to-cic
- Continuous Delivery (CD)
- Continuous Deployment (CD)
+NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
+
## Overview
Continuous Integration works by pushing small code chunks to your
@@ -48,6 +53,9 @@ the following documents:
- [GitLab CI/CD basic workflow](introduction/index.md#basic-cicd-workflow).
- [Step-by-step guide for writing `.gitlab-ci.yml` for the first time](../user/project/pages/getting_started_part_four.md).
+If you're coming over from Jenkins, you can also check out our handy [reference](jenkins/index.md)
+for converting your pipelines.
+
You can also get started by using one of the
[`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates)
available through the UI. You can use them by creating a new file,
@@ -59,11 +67,11 @@ to your needs:
For a broader overview, see the [CI/CD getting started](quick_start/README.md) guide.
Once you're familiar with how GitLab CI/CD works, see the
-[`. gitlab-ci.yml` full reference](yaml/README.md)
+[`.gitlab-ci.yml` full reference](yaml/README.md)
for all the attributes you can set and use.
NOTE: **Note:**
-GitLab CI/CD and [shared runners](runners/README.md#shared-specific-and-group-runners) are enabled in GitLab.com and available for all users, limited only to the [user's pipelines quota](../user/admin_area/settings/continuous_integration.md#extra-shared-runners-pipeline-minutes-quota-free-only).
+GitLab CI/CD and [shared runners](runners/README.md#shared-specific-and-group-runners) are enabled in GitLab.com and available for all users, limited only to the [user's pipelines quota](../user/gitlab_com/index.md#shared-runners).
## Configuration
@@ -155,6 +163,7 @@ for your CI/CD infrastructure:
- [Why we chose GitLab CI for our CI/CD solution](https://about.gitlab.com/2016/10/17/gitlab-ci-oohlala/)
- [Building our web-app on GitLab CI](https://about.gitlab.com/2016/07/22/building-our-web-app-on-gitlab-ci/)
+- [5 Teams that made the switch to GitLab CI/CD](https://about.gitlab.com/2019/04/25/5-teams-that-made-the-switch-to-gitlab-ci-cd/)
See also the [Why CI/CD?](https://docs.google.com/presentation/d/1OGgk2Tcxbpl7DJaIOzCX4Vqg3dlwfELC3u2jEeCBbDk) presentation.
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index 9a5a3624c73..f8151e3e18c 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -8,7 +8,7 @@ GitLab CI/CD provides a caching mechanism that can be used to save time
when your jobs are running.
Caching is about speeding the time a job is executed by reusing the same
-content of a previous job. It can be particularly useful when your are
+content of a previous job. It can be particularly useful when you are
developing software that depends on other libraries which are fetched via the
internet during build time.
@@ -378,8 +378,8 @@ Here's what happens behind the scenes:
1. `script` is executed.
1. `after_script` is executed.
1. `cache` runs and the `vendor/` directory is zipped into `cache.zip`.
- This file is then saved in the directory based on the
- [Runner's setting](#where-the-caches-are-stored) and the `cache: key`.
+ This file is then saved in the directory based on the
+ [Runner's setting](#where-the-caches-are-stored) and the `cache: key`.
1. `job B` runs.
1. The cache is extracted (if found).
1. `before_script` is executed.
@@ -520,7 +520,7 @@ via GitLab's UI:
1. Navigate to your project's **CI/CD > Pipelines** page.
1. Click on the **Clear Runner caches** button to clean up the cache.
- ![Clear Runners cache](img/clear_runners_cache.png)
+ ![Clear Runners cache](img/clear_runners_cache.png)
1. On the next push, your CI/CD job will use a new cache.
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index 241134783da..29d4f93f02e 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -4,14 +4,14 @@ type: index, concepts, howto
# GitLab ChatOps
-> **Notes:**
->
-> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
->
-> - ChatOps is currently in alpha, with some important features missing like access control.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
+NOTE: **Note:**
+ChatOps is currently in alpha with some important features missing, like access control.
+
## How it works
GitLab ChatOps is built upon two existing features:
diff --git a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
index b3110b435db..54b21939116 100644
--- a/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/bitbucket_integration.md
@@ -14,9 +14,9 @@ To use GitLab CI/CD with a Bitbucket Cloud repository:
1. In GitLab create a **CI/CD for external repo**, select **Repo by URL** and
create the project.
- ![Create project](img/external_repository.png)
+ ![Create project](img/external_repository.png)
- GitLab will import the repository and enable [Pull Mirroring][pull-mirroring].
+ GitLab will import the repository and enable [Pull Mirroring][pull-mirroring].
1. In GitLab create a
[Personal Access Token](../../user/profile/personal_access_tokens.md)
@@ -26,127 +26,127 @@ To use GitLab CI/CD with a Bitbucket Cloud repository:
1. In Bitbucket, from **Settings > Webhooks**, create a new web hook to notify
GitLab of new commits.
- The web hook URL should be set to the GitLab API to trigger pull mirroring,
- using the Personal Access Token we just generated for authentication.
+ The web hook URL should be set to the GitLab API to trigger pull mirroring,
+ using the Personal Access Token we just generated for authentication.
- ```text
- https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
- ```
+ ```text
+ https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
+ ```
- The web hook Trigger should be set to 'Repository Push'.
+ The web hook Trigger should be set to 'Repository Push'.
- ![Bitbucket Cloud webhook](img/bitbucket_webhook.png)
+ ![Bitbucket Cloud webhook](img/bitbucket_webhook.png)
- After saving, test the web hook by pushing a change to your Bitbucket
- repository.
+ After saving, test the web hook by pushing a change to your Bitbucket
+ repository.
1. In Bitbucket, create an **App Password** from **Bitbucket Settings > App
Passwords** to authenticate the build status script setting commit build
statuses in Bitbucket. Repository write permissions are required.
- ![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
+ ![Bitbucket Cloud webhook](img/bitbucket_app_password.png)
1. In GitLab, from **Settings > CI/CD > Environment variables**, add variables to allow
communication with Bitbucket via the Bitbucket API:
- `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above.
+ `BITBUCKET_ACCESS_TOKEN`: the Bitbucket app password created above.
- `BITBUCKET_USERNAME`: the username of the Bitbucket account.
+ `BITBUCKET_USERNAME`: the username of the Bitbucket account.
- `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ.
+ `BITBUCKET_NAMESPACE`: set this if your GitLab and Bitbucket namespaces differ.
- `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ.
+ `BITBUCKET_REPOSITORY`: set this if your GitLab and Bitbucket project names differ.
1. In Bitbucket, add a script to push the pipeline status to Bitbucket.
- > Note: changes made in GitLab will be overwritten by any changes made
- > upstream in Bitbucket.
-
- Create a file `build_status` and insert the script below and run
- `chmod +x build_status` in your terminal to make the script executable.
-
- ```bash
- #!/usr/bin/env bash
-
- # Push GitLab CI/CD build status to Bitbucket Cloud
-
- if [ -z "$BITBUCKET_ACCESS_TOKEN" ]; then
- echo "ERROR: BITBUCKET_ACCESS_TOKEN is not set"
- exit 1
- fi
- if [ -z "$BITBUCKET_USERNAME" ]; then
- echo "ERROR: BITBUCKET_USERNAME is not set"
- exit 1
- fi
- if [ -z "$BITBUCKET_NAMESPACE" ]; then
- echo "Setting BITBUCKET_NAMESPACE to $CI_PROJECT_NAMESPACE"
- BITBUCKET_NAMESPACE=$CI_PROJECT_NAMESPACE
- fi
- if [ -z "$BITBUCKET_REPOSITORY" ]; then
- echo "Setting BITBUCKET_REPOSITORY to $CI_PROJECT_NAME"
- BITBUCKET_REPOSITORY=$CI_PROJECT_NAME
- fi
-
- BITBUCKET_API_ROOT="https://api.bitbucket.org/2.0"
- BITBUCKET_STATUS_API="$BITBUCKET_API_ROOT/repositories/$BITBUCKET_NAMESPACE/$BITBUCKET_REPOSITORY/commit/$CI_COMMIT_SHA/statuses/build"
- BITBUCKET_KEY="ci/gitlab-ci/$CI_JOB_NAME"
-
- case "$BUILD_STATUS" in
- running)
- BITBUCKET_STATE="INPROGRESS"
- BITBUCKET_DESCRIPTION="The build is running!"
- ;;
- passed)
- BITBUCKET_STATE="SUCCESSFUL"
- BITBUCKET_DESCRIPTION="The build passed!"
- ;;
- failed)
- BITBUCKET_STATE="FAILED"
- BITBUCKET_DESCRIPTION="The build failed."
- ;;
- esac
-
- echo "Pushing status to $BITBUCKET_STATUS_API..."
- curl --request POST $BITBUCKET_STATUS_API \
- --user $BITBUCKET_USERNAME:$BITBUCKET_ACCESS_TOKEN \
- --header "Content-Type:application/json" \
- --silent \
- --data "{ \"state\": \"$BITBUCKET_STATE\", \"key\": \"$BITBUCKET_KEY\", \"description\":
- \"$BITBUCKET_DESCRIPTION\",\"url\": \"$CI_PROJECT_URL/-/jobs/$CI_JOB_ID\" }"
- ```
+ > Note: changes made in GitLab will be overwritten by any changes made
+ > upstream in Bitbucket.
+
+ Create a file `build_status` and insert the script below and run
+ `chmod +x build_status` in your terminal to make the script executable.
+
+ ```bash
+ #!/usr/bin/env bash
+
+ # Push GitLab CI/CD build status to Bitbucket Cloud
+
+ if [ -z "$BITBUCKET_ACCESS_TOKEN" ]; then
+ echo "ERROR: BITBUCKET_ACCESS_TOKEN is not set"
+ exit 1
+ fi
+ if [ -z "$BITBUCKET_USERNAME" ]; then
+ echo "ERROR: BITBUCKET_USERNAME is not set"
+ exit 1
+ fi
+ if [ -z "$BITBUCKET_NAMESPACE" ]; then
+ echo "Setting BITBUCKET_NAMESPACE to $CI_PROJECT_NAMESPACE"
+ BITBUCKET_NAMESPACE=$CI_PROJECT_NAMESPACE
+ fi
+ if [ -z "$BITBUCKET_REPOSITORY" ]; then
+ echo "Setting BITBUCKET_REPOSITORY to $CI_PROJECT_NAME"
+ BITBUCKET_REPOSITORY=$CI_PROJECT_NAME
+ fi
+
+ BITBUCKET_API_ROOT="https://api.bitbucket.org/2.0"
+ BITBUCKET_STATUS_API="$BITBUCKET_API_ROOT/repositories/$BITBUCKET_NAMESPACE/$BITBUCKET_REPOSITORY/commit/$CI_COMMIT_SHA/statuses/build"
+ BITBUCKET_KEY="ci/gitlab-ci/$CI_JOB_NAME"
+
+ case "$BUILD_STATUS" in
+ running)
+ BITBUCKET_STATE="INPROGRESS"
+ BITBUCKET_DESCRIPTION="The build is running!"
+ ;;
+ passed)
+ BITBUCKET_STATE="SUCCESSFUL"
+ BITBUCKET_DESCRIPTION="The build passed!"
+ ;;
+ failed)
+ BITBUCKET_STATE="FAILED"
+ BITBUCKET_DESCRIPTION="The build failed."
+ ;;
+ esac
+
+ echo "Pushing status to $BITBUCKET_STATUS_API..."
+ curl --request POST $BITBUCKET_STATUS_API \
+ --user $BITBUCKET_USERNAME:$BITBUCKET_ACCESS_TOKEN \
+ --header "Content-Type:application/json" \
+ --silent \
+ --data "{ \"state\": \"$BITBUCKET_STATE\", \"key\": \"$BITBUCKET_KEY\", \"description\":
+ \"$BITBUCKET_DESCRIPTION\",\"url\": \"$CI_PROJECT_URL/-/jobs/$CI_JOB_ID\" }"
+ ```
1. Still in Bitbucket, create a `.gitlab-ci.yml` file to use the script to push
pipeline success and failures to Bitbucket.
- ```yaml
- stages:
- - test
- - ci_status
-
- unit-tests:
- script:
- - echo "Success. Add your tests!"
-
- success:
- stage: ci_status
- before_script:
- - ""
- after_script:
- - ""
- script:
- - BUILD_STATUS=passed BUILD_KEY=push ./build_status
- when: on_success
-
- failure:
- stage: ci_status
- before_script:
- - ""
- after_script:
- - ""
- script:
- - BUILD_STATUS=failed BUILD_KEY=push ./build_status
- when: on_failure
- ```
+ ```yaml
+ stages:
+ - test
+ - ci_status
+
+ unit-tests:
+ script:
+ - echo "Success. Add your tests!"
+
+ success:
+ stage: ci_status
+ before_script:
+ - ""
+ after_script:
+ - ""
+ script:
+ - BUILD_STATUS=passed BUILD_KEY=push ./build_status
+ when: on_success
+
+ failure:
+ stage: ci_status
+ before_script:
+ - ""
+ after_script:
+ - ""
+ script:
+ - BUILD_STATUS=failed BUILD_KEY=push ./build_status
+ when: on_failure
+ ```
GitLab is now configured to mirror changes from Bitbucket, run CI/CD pipelines
configured in `.gitlab-ci.yml` and push the status to Bitbucket.
diff --git a/doc/ci/ci_cd_for_external_repos/github_integration.md b/doc/ci/ci_cd_for_external_repos/github_integration.md
index 0bb3aa35ed0..f639e3dadee 100644
--- a/doc/ci/ci_cd_for_external_repos/github_integration.md
+++ b/doc/ci/ci_cd_for_external_repos/github_integration.md
@@ -19,12 +19,12 @@ administrator:
1. In GitLab create a **CI/CD for external repo** project and select
**GitHub**.
- ![Create project](img/github_omniauth.png)
+ ![Create project](img/github_omniauth.png)
1. Once authenticated, you will be redirected to a list of your repositories to
connect. Click **Connect** to select the repository.
- ![Create project](img/github_repo_list.png)
+ ![Create project](img/github_repo_list.png)
1. In GitHub, add a `.gitlab-ci.yml` to [configure GitLab CI/CD](../quick_start/README.md).
@@ -55,14 +55,14 @@ repositories:
Token**. This token with be used to access your repository and push commit
statuses to GitHub.
- The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
- your project, update commit statuses, and create a web hook to notify
- GitLab of new commits.
+ The `repo` and `admin:repo_hook` should be enable to allow GitLab access to
+ your project, update commit statuses, and create a web hook to notify
+ GitLab of new commits.
1. In GitLab create a **CI/CD for external repo** project and select
**GitHub**.
- ![Create project](img/github_omniauth.png)
+ ![Create project](img/github_omniauth.png)
1. Paste the token into the **Personal access token** field and click **List
Repositories**. Click **Connect** to select the repository.
@@ -86,21 +86,21 @@ your repository:
Access Token.** GitLab will use this token to access your repository and
push commit statuses.
- Enter a **Token description** and update the scope to allow:
+ Enter a **Token description** and update the scope to allow:
- `repo` so that GitLab can access your project and update commit statuses
+ `repo` so that GitLab can access your project and update commit statuses
1. In GitLab create a **CI/CD project** using the Git URL option and the HTTPS
URL for your GitHub repository. If your project is private, use the personal
access token you just created for authentication.
- GitLab will automatically configure polling-based pull mirroring.
+ GitLab will automatically configure polling-based pull mirroring.
1. Still in GitLab, enable the [GitHub project integration](../../user/project/integrations/github.md)
from **Settings > Integrations.**
- Check the **Active** checkbox to enable the integration, paste your
- personal access token and HTTPS repository URL into the form, and **Save.**
+ Check the **Active** checkbox to enable the integration, paste your
+ personal access token and HTTPS repository URL into the form, and **Save.**
1. Still in GitLab create a **Personal Access Token** with `API` scope to
authenticate the GitHub web hook notifying GitLab of new commits.
@@ -108,15 +108,15 @@ your repository:
1. In GitHub from **Settings > Webhooks** create a web hook to notify GitLab of
new commits.
- The web hook URL should be set to the GitLab API to
- [trigger pull mirroring](../../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
- using the GitLab personal access token we just created.
+ The web hook URL should be set to the GitLab API to
+ [trigger pull mirroring](../../api/projects.md#start-the-pull-mirroring-process-for-a-project-starter),
+ using the GitLab personal access token we just created.
- ```
- https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
- ```
+ ```
+ https://gitlab.com/api/v4/projects/<NAMESPACE>%2F<PROJECT>/mirror/pull?private_token=<PERSONAL_ACCESS_TOKEN>
+ ```
- ![Create web hook](img/github_push_webhook.png)
+ ![Create web hook](img/github_push_webhook.png)
1. In GitHub add a `.gitlab-ci.yml` to configure GitLab CI/CD.
diff --git a/doc/ci/directed_acyclic_graph/index.md b/doc/ci/directed_acyclic_graph/index.md
new file mode 100644
index 00000000000..1c38c08b7cb
--- /dev/null
+++ b/doc/ci/directed_acyclic_graph/index.md
@@ -0,0 +1,76 @@
+---
+type: reference
+---
+
+# Directed Acyclic Graph
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/47063) in GitLab 12.2 (enabled by `ci_dag_support` feature flag).
+
+A [directed acyclic graph](https://www.techopedia.com/definition/5739/directed-acyclic-graph-dag) can be
+used in the context of a CI/CD pipeline to build relationships between jobs such that
+execution is performed in the quickest possible manner, regardless how stages may
+be set up.
+
+For example, you may have a specific tool or separate website that is built
+as part of your main project. Using a DAG, you can specify the relationship between
+these jobs and GitLab will then execute the jobs as soon as possible instead of waiting
+for each stage to complete.
+
+Unlike other DAG solutions for CI/CD, GitLab does not require you to choose one or the
+other. You can implement a hybrid combination of DAG and traditional
+stage-based operation within a single pipeline. Configuration is kept very simple,
+requiring a single keyword to enable the feature for any job.
+
+Consider a monorepo as follows:
+
+```
+./service_a
+./service_b
+./service_c
+./service_d
+```
+
+It has a pipeline that looks like the following:
+
+| build | test | deploy |
+| ----- | ---- | ------ |
+| build_a | test_a | deploy_a |
+| build_b | test_b | deploy_b |
+| build_c | test_c | deploy_c |
+| build_d | test_d | deploy_d |
+
+Using a DAG, you can relate the `_a` jobs to each other separately from the `_b` jobs,
+and even if service `a` takes a very long time to build, service `b` will not
+wait for it and will finish as quickly as it can. In this very same pipeline, `_c` and
+`_d` can be left alone and will run together in staged sequence just like any normal
+GitLab pipeline.
+
+## Use cases
+
+A DAG can help solve several different kinds of relationships between jobs within
+a CI/CD pipeline. Most typically this would cover when jobs need to fan in or out,
+and/or merge back together (diamond dependencies). This can happen when you're
+handling multi-platform builds or complex webs of dependencies as in something like
+an operating system build or a complex deployment graph of independently deployable
+but related microservices.
+
+Additionally, a DAG can help with general speediness of pipelines and helping
+to deliver fast feedback. By creating dependency relationships that don't unnecessarily
+block each other, your pipelines will run as quickly as possible regardless of
+pipeline stages, ensuring output (including errors) is available to developers
+as quickly as possible.
+
+## Usage
+
+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 you powerful options for parallelization within your pipeline.
+
+## Limitations
+
+A directed acyclic graph is a complicated feature, and as of the initial MVC there
+are certain use cases that you may need to work around. For more information:
+
+- [`needs` requirements and limitations](../yaml/README.md#requirements-and-limitations).
+- Related epic [gitlab-org#1716](https://gitlab.com/groups/gitlab-org/-/epics/1716).
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index efdcaf5a6f5..2cbad5f101c 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -6,7 +6,6 @@ type: concepts, howto
GitLab CI/CD allows you to use Docker Engine to build and test docker-based projects.
-
One of the new trends in Continuous Integration/Deployment is to:
1. Create an application image.
@@ -29,7 +28,16 @@ during jobs.
## Runner Configuration
-There are three methods to enable the use of `docker build` and `docker run` during jobs; each with their own tradeoffs.
+There are three methods to enable the use of `docker build` and `docker run`
+during jobs; each with their own tradeoffs.
+
+An alternative to using `docker build` is to [use kaniko](using_kaniko.md).
+This avoids having to execute Runner in privileged mode.
+
+TIP: **Tip:**
+To see how Docker and Runner are configured for shared Runners on
+GitLab.com, see [GitLab.com Shared
+Runners](../../user/gitlab_com/index.md#shared-runners).
### Use shell executor
@@ -40,42 +48,42 @@ GitLab Runner then executes job scripts as the `gitlab-runner` user.
1. During GitLab Runner installation select `shell` as method of executing job scripts or use command:
- ```bash
- sudo gitlab-runner register -n \
- --url https://gitlab.com/ \
- --registration-token REGISTRATION_TOKEN \
- --executor shell \
- --description "My Runner"
- ```
+ ```bash
+ sudo gitlab-runner register -n \
+ --url https://gitlab.com/ \
+ --registration-token REGISTRATION_TOKEN \
+ --executor shell \
+ --description "My Runner"
+ ```
1. Install Docker Engine on server.
- For more information how to install Docker Engine on different systems
- checkout the [Supported installations](https://docs.docker.com/engine/installation/).
+ For more information how to install Docker Engine on different systems
+ checkout the [Supported installations](https://docs.docker.com/engine/installation/).
1. Add `gitlab-runner` user to `docker` group:
- ```bash
- sudo usermod -aG docker gitlab-runner
- ```
+ ```bash
+ sudo usermod -aG docker gitlab-runner
+ ```
1. Verify that `gitlab-runner` has access to Docker:
- ```bash
- sudo -u gitlab-runner -H docker info
- ```
+ ```bash
+ sudo -u gitlab-runner -H docker info
+ ```
- You can now verify that everything works by adding `docker info` to `.gitlab-ci.yml`:
+ You can now verify that everything works by adding `docker info` to `.gitlab-ci.yml`:
- ```yaml
- before_script:
- - docker info
+ ```yaml
+ before_script:
+ - docker info
- build_image:
- script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
- ```
+ build_image:
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+ ```
1. You can now use `docker` command (and **install** `docker-compose` if needed).
@@ -83,101 +91,28 @@ NOTE: **Note:**
By adding `gitlab-runner` to the `docker` group you are effectively granting `gitlab-runner` full root permissions.
For more information please read [On Docker security: `docker` group considered harmful](https://www.andreas-jung.com/contents/on-docker-security-docker-group-considered-harmful).
-### Use docker-in-docker executor
+### Use docker-in-docker workflow with Docker executor
The second approach is to use the special docker-in-docker (dind)
[Docker image](https://hub.docker.com/_/docker/) with all tools installed
(`docker`) and run the job script in context of that
-image in privileged mode.
-
-NOTE: **Note:** `docker-compose` is not part of docker-in-docker (dind). In case you'd like to use `docker-compose` in your CI builds, please follow the [installation instructions for docker-compose](https://docs.docker.com/compose/install/) provided by docker.
-
-In order to do that, follow the steps:
+image in privileged mode.
-1. Install [GitLab Runner](https://docs.gitlab.com/runner/install).
-
-1. Register GitLab Runner from the command line to use `docker` and `privileged`
- mode:
-
- ```bash
- sudo gitlab-runner register -n \
- --url https://gitlab.com/ \
- --registration-token REGISTRATION_TOKEN \
- --executor docker \
- --description "My Docker Runner" \
- --docker-image "docker:stable" \
- --docker-privileged
- ```
-
- The above command will register a new Runner to use the special
- `docker:stable` image which is provided by Docker. **Notice that it's using
- the `privileged` mode to start the build and service containers.** If you
- want to use [docker-in-docker] mode, you always have to use `privileged = true`
- in your Docker containers.
-
- The above command will create a `config.toml` entry similar to this:
-
- ```toml
- [[runners]]
- url = "https://gitlab.com/"
- token = TOKEN
- executor = "docker"
- [runners.docker]
- tls_verify = false
- image = "docker:stable"
- privileged = true
- disable_cache = false
- volumes = ["/cache"]
- [runners.cache]
- Insecure = false
- ```
-
-1. You can now use `docker` in the build script (note the inclusion of the
- `docker:dind` service):
-
- ```yaml
- image: docker:stable
+NOTE: **Note:**
+`docker-compose` is not part of docker-in-docker (dind). To use `docker-compose` in your
+CI builds, follow the `docker-compose`
+[installation instructions](https://docs.docker.com/compose/install/).
- variables:
- # When using dind service we need to instruct docker, to talk with the
- # daemon started inside of the service. The daemon is available with
- # a network connection instead of the default /var/run/docker.sock socket.
- #
- # The 'docker' hostname is the alias of the service container as described at
- # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
- #
- # Note that if you're using the Kubernetes executor, the variable should be set to
- # tcp://localhost:2375/ because of how the Kubernetes executor connects services
- # to the job container
- # DOCKER_HOST: tcp://localhost:2375/
- #
- # For non-Kubernetes executors, we use tcp://docker:2375/
- DOCKER_HOST: tcp://docker:2375/
- # When using dind, it's wise to use the overlayfs driver for
- # improved performance.
- DOCKER_DRIVER: overlay2
-
- services:
- - docker:dind
-
- before_script:
- - docker info
-
- build:
- stage: build
- script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
- ```
+DANGER: **Danger:**
+By enabling `--docker-privileged`, you are effectively disabling all of
+the security mechanisms of containers and exposing your host to privilege
+escalation which can lead to container breakout. For more information, check
+out the official Docker documentation on
+[Runtime privilege and Linux capabilities][docker-cap].
Docker-in-Docker works well, and is the recommended configuration, but it is
not without its own challenges:
-- By enabling `--docker-privileged`, you are effectively disabling all of
- the security mechanisms of containers and exposing your host to privilege
- escalation which can lead to container breakout. For more information, check
- out the official Docker documentation on
- [Runtime privilege and Linux capabilities][docker-cap].
- When using docker-in-docker, each job is in a clean environment without the past
history. Concurrent jobs work fine because every build gets it's own
instance of Docker engine so they won't conflict with each other. But this
@@ -187,7 +122,7 @@ not without its own challenges:
[Using the overlayfs driver](#using-the-overlayfs-driver).
- Since the `docker:dind` container and the runner container don't share their
root filesystem, the job's working directory can be used as a mount point for
- children containers. For example, if you have files you want to share with a
+ child containers. For example, if you have files you want to share with a
child container, you may create a subdirectory under `/builds/$CI_PROJECT_PATH`
and use it as your mount point (for a more thorough explanation, check [issue
#41227](https://gitlab.com/gitlab-org/gitlab-ce/issues/41227)):
@@ -203,6 +138,177 @@ not without its own challenges:
An example project using this approach can be found here: <https://gitlab.com/gitlab-examples/docker>.
+In the examples below, we are using Docker images tags to specify a
+specific version, such as `docker:19.03.1`. If tags like `docker:stable`
+are used, you have no control over what version is going to be used and this
+can lead to unpredictable behavior, especially when new versions are
+released.
+
+#### TLS enabled
+
+NOTE: **Note**
+This requires GitLab Runner 11.11 or higher.
+
+The Docker daemon supports connection over TLS and it's done by default
+for Docker 19.03.1 or higher. This is the **suggested** way to use the
+docker-in-docker service and
+[GitLab.com Shared Runners](../../user/gitlab_com/index.html#shared-runners)
+support this.
+
+1. Install [GitLab Runner](https://docs.gitlab.com/runner/install).
+
+1. Register GitLab Runner from the command line to use `docker` and `privileged`
+ mode:
+
+ ```bash
+ sudo gitlab-runner register -n \
+ --url https://gitlab.com/ \
+ --registration-token REGISTRATION_TOKEN \
+ --executor docker \
+ --description "My Docker Runner" \
+ --docker-image "docker:19.03.1" \
+ --docker-privileged \
+ --docker-volumes "/certs/client"
+ ```
+
+ The above command will register a new Runner to use the special
+ `docker:19.03.1` image, which is provided by Docker. **Notice that it's
+ using the `privileged` mode to start the build and service
+ containers.** If you want to use [docker-in-docker] mode, you always
+ have to use `privileged = true` in your Docker containers.
+
+ This will also mount `/certs/client` for the service and build
+ container, which is needed for the docker client to use the
+ certificates inside of that directory. For more information how
+ Docker with TLS works check <https://hub.docker.com/_/docker/#tls>.
+
+ The above command will create a `config.toml` entry similar to this:
+
+ ```toml
+ [[runners]]
+ url = "https://gitlab.com/"
+ token = TOKEN
+ executor = "docker"
+ [runners.docker]
+ tls_verify = false
+ image = "docker:19.03.1"
+ privileged = true
+ disable_cache = false
+ volumes = ["/certs/client", "/cache"]
+ [runners.cache]
+ [runners.cache.s3]
+ [runners.cache.gcs]
+ ```
+
+1. You can now use `docker` in the build script (note the inclusion of the
+ `docker:19.03.1-dind` service):
+
+ ```yaml
+ image: docker:19.03.1
+
+ variables:
+ # When using dind service, we need to instruct docker, to talk with
+ # the daemon started inside of the service. The daemon is available
+ # with a network connection instead of the default
+ # /var/run/docker.sock socket. docker:19.03.1 does this automatically
+ # by setting the DOCKER_HOST in
+ # https://github.com/docker-library/docker/blob/d45051476babc297257df490d22cbd806f1b11e4/19.03.1/docker-entrypoint.sh#L23-L29
+ #
+ # The 'docker' hostname is the alias of the service container as described at
+ # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services.
+ #
+ # Note that if you're using the Kubernetes executor, the variable
+ # should be set to tcp://localhost:2376/ because of how the
+ # Kubernetes executor connects services to the job container
+ # DOCKER_HOST: tcp://localhost:2376/
+ #
+ # When using dind, it's wise to use the overlayfs driver for
+ # improved performance.
+ DOCKER_DRIVER: overlay2
+ # Specify to Docker where to create the certificates, Docker will
+ # create them automatically on boot, and will create
+ # `/certs/client` that will be shared between the service and job
+ # container, thanks to volume mount from config.toml
+ DOCKER_TLS_CERTDIR: "/certs"
+
+ services:
+ - docker:19.03.1-dind
+
+ before_script:
+ - docker info
+
+ build:
+ stage: build
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+ ```
+
+#### TLS disabled
+
+Sometimes there are legitimate reasons why you might want to disable TLS.
+For example, you have no control over the GitLab Runner configuration
+that you are using.
+
+Assuming that the Runner `config.toml` is similar to:
+
+```toml
+[[runners]]
+ url = "https://gitlab.com/"
+ token = TOKEN
+ executor = "docker"
+ [runners.docker]
+ tls_verify = false
+ image = "docker:19.03.1"
+ privileged = true
+ disable_cache = false
+ volumes = ["/cache"]
+ [runners.cache]
+ [runners.cache.s3]
+ [runners.cache.gcs]
+```
+
+You can now use `docker` in the build script (note the inclusion of the
+`docker:19.03.1-dind` service):
+
+```yaml
+image: docker:19.03.1
+
+variables:
+ # When using dind service we need to instruct docker, to talk with the
+ # daemon started inside of the service. The daemon is available with
+ # a network connection instead of the default /var/run/docker.sock socket.
+ #
+ # The 'docker' hostname is the alias of the service container as described at
+ # https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#accessing-the-services
+ #
+ # Note that if you're using the Kubernetes executor, the variable should be set to
+ # tcp://localhost:2375/ because of how the Kubernetes executor connects services
+ # to the job container
+ # DOCKER_HOST: tcp://localhost:2375/
+ #
+ # For non-Kubernetes executors, we use tcp://docker:2375/
+ DOCKER_HOST: tcp://docker:2375/
+ # When using dind, it's wise to use the overlayfs driver for
+ # improved performance.
+ DOCKER_DRIVER: overlay2
+ #
+ # This will instruct Docker not to start over TLS.
+ DOCKER_TLS_CERTDIR: ""
+
+services:
+ - docker:19.03.1-dind
+
+before_script:
+ - docker info
+
+build:
+ stage: build
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+```
+
### Use Docker socket binding
The third approach is to bind-mount `/var/run/docker.sock` into the
@@ -220,54 +326,54 @@ In order to do that, follow the steps:
1. Register GitLab Runner from the command line to use `docker` and share `/var/run/docker.sock`:
- ```bash
- sudo gitlab-runner register -n \
- --url https://gitlab.com/ \
- --registration-token REGISTRATION_TOKEN \
- --executor docker \
- --description "My Docker Runner" \
- --docker-image "docker:stable" \
- --docker-volumes /var/run/docker.sock:/var/run/docker.sock
- ```
-
- The above command will register a new Runner to use the special
- `docker:stable` image which is provided by Docker. **Notice that it's using
- the Docker daemon of the Runner itself, and any containers spawned by docker
- commands will be siblings of the Runner rather than children of the runner.**
- This may have complications and limitations that are unsuitable for your workflow.
-
- The above command will create a `config.toml` entry similar to this:
-
- ```toml
- [[runners]]
- url = "https://gitlab.com/"
- token = REGISTRATION_TOKEN
- executor = "docker"
- [runners.docker]
- tls_verify = false
- image = "docker:stable"
- privileged = false
- disable_cache = false
- volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
- [runners.cache]
- Insecure = false
- ```
+ ```bash
+ sudo gitlab-runner register -n \
+ --url https://gitlab.com/ \
+ --registration-token REGISTRATION_TOKEN \
+ --executor docker \
+ --description "My Docker Runner" \
+ --docker-image "docker:stable" \
+ --docker-volumes /var/run/docker.sock:/var/run/docker.sock
+ ```
+
+ The above command will register a new Runner to use the special
+ `docker:stable` image which is provided by Docker. **Notice that it's using
+ the Docker daemon of the Runner itself, and any containers spawned by docker
+ commands will be siblings of the Runner rather than children of the runner.**
+ This may have complications and limitations that are unsuitable for your workflow.
+
+ The above command will create a `config.toml` entry similar to this:
+
+ ```toml
+ [[runners]]
+ url = "https://gitlab.com/"
+ token = REGISTRATION_TOKEN
+ executor = "docker"
+ [runners.docker]
+ tls_verify = false
+ image = "docker:stable"
+ privileged = false
+ disable_cache = false
+ volumes = ["/var/run/docker.sock:/var/run/docker.sock", "/cache"]
+ [runners.cache]
+ Insecure = false
+ ```
1. You can now use `docker` in the build script (note that you don't need to
include the `docker:dind` service as when using the Docker in Docker executor):
- ```yaml
- image: docker:stable
+ ```yaml
+ image: docker:stable
- before_script:
- - docker info
+ before_script:
+ - docker info
- build:
- stage: build
- script:
- - docker build -t my-docker-image .
- - docker run my-docker-image /script/to/run/tests
- ```
+ build:
+ stage: build
+ script:
+ - docker build -t my-docker-image .
+ - docker run my-docker-image /script/to/run/tests
+ ```
While the above method avoids using Docker in privileged mode, you should be
aware of the following implications:
@@ -283,9 +389,9 @@ aware of the following implications:
work as expected since volume mounting is done in the context of the host
machine, not the build container. For example:
- ```sh
- docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
- ```
+ ```sh
+ docker run --rm -t -i -v $(pwd)/src:/home/app/src test-image:latest run_app_tests
+ ```
## Making docker-in-docker builds faster with Docker layer caching
@@ -356,23 +462,23 @@ which can be avoided if a different driver is used, for example `overlay2`.
1. Make sure a recent kernel is used, preferably `>= 4.2`.
1. Check whether the `overlay` module is loaded:
- ```sh
- sudo lsmod | grep overlay
- ```
+ ```sh
+ sudo lsmod | grep overlay
+ ```
- If you see no result, then it isn't loaded. To load it use:
+ If you see no result, then it isn't loaded. To load it use:
- ```sh
- sudo modprobe overlay
- ```
+ ```sh
+ sudo modprobe overlay
+ ```
- If everything went fine, you need to make sure module is loaded on reboot.
- On Ubuntu systems, this is done by editing `/etc/modules`. Just add the
- following line into it:
+ If everything went fine, you need to make sure module is loaded on reboot.
+ On Ubuntu systems, this is done by editing `/etc/modules`. Just add the
+ following line into it:
- ```text
- overlay
- ```
+ ```text
+ overlay
+ ```
### Use driver per project
@@ -440,9 +546,9 @@ For all projects, mostly suitable for public ones:
your Docker images and has read/write access to the Registry. This is ephemeral,
so it's only valid for one job. You can use the following example as-is:
- ```sh
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- ```
+ ```sh
+ docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
+ ```
For private and internal projects:
@@ -455,9 +561,9 @@ For private and internal projects:
Replace the `<username>` and `<access_token>` in the following example:
- ```sh
- docker login -u <username> -p <access_token> $CI_REGISTRY
- ```
+ ```sh
+ docker login -u <username> -p <access_token> $CI_REGISTRY
+ ```
- **Using the GitLab Deploy Token**: You can create and use a
[special deploy token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
@@ -465,9 +571,9 @@ For private and internal projects:
Once created, you can use the special environment variables, and GitLab CI/CD
will fill them in for you. You can use the following example as-is:
- ```sh
- docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
- ```
+ ```sh
+ docker login -u $CI_DEPLOY_USER -p $CI_DEPLOY_PASSWORD $CI_REGISTRY
+ ```
### Container Registry examples
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index f3896c5232c..489791141ed 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -305,25 +305,25 @@ For example, the following two definitions are equal:
1. Using a string as an option to `image` and `services`:
- ```yaml
- image: "registry.example.com/my/image:latest"
+ ```yaml
+ image: "registry.example.com/my/image:latest"
- services:
- - postgresql:9.4
- - redis:latest
- ```
+ services:
+ - postgresql:9.4
+ - redis:latest
+ ```
1. Using a map as an option to `image` and `services`. The use of `image:name` is
required:
- ```yaml
- image:
- name: "registry.example.com/my/image:latest"
+ ```yaml
+ image:
+ name: "registry.example.com/my/image:latest"
- services:
- - name: postgresql:9.4
- - name: redis:latest
- ```
+ services:
+ - name: postgresql:9.4
+ - name: redis:latest
+ ```
### Available settings for `image`
@@ -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,7 +517,19 @@ 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
private registry. Both require setting the environment variable
`DOCKER_AUTH_CONFIG` with appropriate authentication info.
@@ -555,18 +559,18 @@ There are two ways to determine the value of `DOCKER_AUTH_CONFIG`:
- **First way -** Do a `docker login` on your local machine:
- ```bash
- docker login registry.example.com:5000 --username my_username --password my_password
- ```
+ ```bash
+ docker login registry.example.com:5000 --username my_username --password my_password
+ ```
- Then copy the content of `~/.docker/config.json`.
+ Then copy the content of `~/.docker/config.json`.
- If you don't need access to the registry from your computer, you
- can do a `docker logout`:
+ If you don't need access to the registry from your computer, you
+ can do a `docker logout`:
- ```bash
- docker logout registry.example.com:5000
- ```
+ ```bash
+ docker logout registry.example.com:5000
+ ```
- **Second way -** In some setups, it's possible that Docker client
will use the available system keystore to store the result of `docker
@@ -575,12 +579,12 @@ There are two ways to determine the value of `DOCKER_AUTH_CONFIG`:
`${username}:${password}` manually. Open a terminal and execute the
following command:
- ```bash
- echo -n "my_username:my_password" | base64
+ ```bash
+ echo -n "my_username:my_password" | base64
- # Example output to copy
- bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
- ```
+ # Example output to copy
+ bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=
+ ```
#### Configuring a job
@@ -590,25 +594,25 @@ follow these steps:
1. Create a [variable](../variables/README.md#gitlab-cicd-environment-variables) `DOCKER_AUTH_CONFIG` with the content of the
Docker configuration file as the value:
- ```json
- {
- "auths": {
- "registry.example.com:5000": {
- "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
- }
- }
- }
- ```
+ ```json
+ {
+ "auths": {
+ "registry.example.com:5000": {
+ "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ="
+ }
+ }
+ }
+ ```
1. You can now use any private image from `registry.example.com:5000` defined in
`image` and/or `services` in your `.gitlab-ci.yml` file:
- ```yaml
- image: registry.example.com:5000/namespace/image:tag
- ```
+ ```yaml
+ image: registry.example.com:5000/namespace/image:tag
+ ```
- In the example above, GitLab Runner will look at `registry.example.com:5000` for the
- image `namespace/image:tag`.
+ In the example above, GitLab Runner will look at `registry.example.com:5000` for the
+ image `namespace/image:tag`.
You can add configuration for as many registries as you want, adding more
registries to the `"auths"` hash as described above.
@@ -637,10 +641,10 @@ To add `DOCKER_AUTH_CONFIG` to a Runner:
1. Modify the Runner's `config.toml` file as follows:
- ```toml
- [[runners]]
- environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"registry.example.com:5000\":{\"auth\":\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\"}}}"]
- ```
+ ```toml
+ [[runners]]
+ environment = ["DOCKER_AUTH_CONFIG={\"auths\":{\"registry.example.com:5000\":{\"auth\":\"bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=\"}}}"]
+ ```
1. Restart the Runner service.
@@ -662,16 +666,17 @@ To configure credentials store, follow these steps:
Make sure helper program is available in GitLab Runner `$PATH`.
1. Make GitLab Runner use it. There are two ways to accomplish this. Either:
+
- Create a
[variable](../variables/README.md#gitlab-cicd-environment-variables)
`DOCKER_AUTH_CONFIG` with the content of the
- Docker configuration file as the value:
+ Docker configuration file as the value:
- ```json
- {
- "credsStore": "osxkeychain"
- }
- ```
+ ```json
+ {
+ "credsStore": "osxkeychain"
+ }
+ ```
- Or, if you are running self-hosted Runners, add the above JSON to
`${GITLAB_RUNNER_HOME}/.docker/config.json`. GitLab Runner will read this config file
@@ -693,17 +698,18 @@ To configure access for `aws_account_id.dkr.ecr.region.amazonaws.com`, follow th
1. Make sure `docker-credential-ecr-login` is available in GitLab Runner's `$PATH`.
1. Make GitLab Runner use it. There are two ways to accomplish this. Either:
+
- Create a [variable](../variables/README.md#gitlab-cicd-environment-variables)
`DOCKER_AUTH_CONFIG` with the content of the
- Docker configuration file as the value:
+ Docker configuration file as the value:
- ```json
- {
- "credHelpers": {
- "aws_account_id.dkr.ecr.region.amazonaws.com": "ecr-login"
- }
- }
- ```
+ ```json
+ {
+ "credHelpers": {
+ "aws_account_id.dkr.ecr.region.amazonaws.com": "ecr-login"
+ }
+ }
+ ```
- Or, if you are running self-hosted Runners,
add the above JSON to `${GITLAB_RUNNER_HOME}/.docker/config.json`.
@@ -713,12 +719,12 @@ To configure access for `aws_account_id.dkr.ecr.region.amazonaws.com`, follow th
1. You can now use any private image from `aws_account_id.dkr.ecr.region.amazonaws.com` defined in
`image` and/or `services` in your `.gitlab-ci.yml` file:
- ```yaml
- image: aws_account_id.dkr.ecr.region.amazonaws.com/private/image:latest
- ```
+ ```yaml
+ image: aws_account_id.dkr.ecr.region.amazonaws.com/private/image:latest
+ ```
- In the example above, GitLab Runner will look at `aws_account_id.dkr.ecr.region.amazonaws.com` for the
- image `private/image:latest`.
+ In the example above, GitLab Runner will look at `aws_account_id.dkr.ecr.region.amazonaws.com` for the
+ image `private/image:latest`.
You can add configuration for as many registries as you want, adding more
registries to the `"credHelpers"` hash as described above.
diff --git a/doc/ci/docker/using_kaniko.md b/doc/ci/docker/using_kaniko.md
index 50f1ac3d54a..925653f9fdf 100644
--- a/doc/ci/docker/using_kaniko.md
+++ b/doc/ci/docker/using_kaniko.md
@@ -11,7 +11,8 @@ Requires GitLab Runner 11.2 and above.
container images from a Dockerfile, inside a container or Kubernetes cluster.
kaniko solves two problems with using the
-[docker-in-docker build](using_docker_build.md#use-docker-in-docker-executor) method:
+[docker-in-docker
+build](using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor) method:
- Docker-in-docker requires [privileged mode](https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities)
in order to function, which is a significant security concern.
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index f86ca8f74f2..f6c47a99712 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -591,15 +591,13 @@ exist, you should see something like:
### Monitoring environments
-> **Notes:**
->
-> - For the monitoring dashboard to appear, you need to:
-> - Enable the [Prometheus integration](../user/project/integrations/prometheus.md).
-> - Configure Prometheus to collect at least one [supported metric](../user/project/integrations/prometheus_library/index.md).
-> - With GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard.
-
If you have enabled [Prometheus for monitoring system and response metrics](../user/project/integrations/prometheus.md),
-you can monitor the behavior of your app running in each environment.
+you can monitor the behavior of your app running in each environment. For the monitoring
+dashboard to appear, you need to Configure Prometheus to collect at least one
+[supported metric](../user/project/integrations/prometheus_library/index.md).
+
+NOTE: **Note:**
+Since GitLab 9.2, all deployments to an environment are shown directly on the monitoring dashboard.
Once configured, GitLab will attempt to retrieve [supported performance metrics](../user/project/integrations/prometheus_library/index.md)
for any environment that has had a successful deployment. If monitoring data was
@@ -621,6 +619,10 @@ versions of the app, all without leaving GitLab.
Add a [button to the Monitoring dashboard](../user/project/operations/linking_to_an_external_dashboard.md) linking directly to your existing external dashboards.
+#### Embedding metrics in GitLab Flavored Markdown
+
+Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab Flavored Markdown](../user/project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown) for more details.
+
### Web terminals
> Web terminals were added in GitLab 8.15 and are only available to project Maintainers and Owners.
@@ -692,7 +694,7 @@ with `review/` would have that particular variable.
Some GitLab features can behave differently for each environment.
For example, you can
-[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables-premium). **(PREMIUM)**
+[create a secret variable to be injected only into a production environment](variables/README.md#limiting-environment-scopes-of-environment-variables).
In most cases, these features use the _environment specs_ mechanism, which offers
an efficient way to implement scoping within each environment group.
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 9295dcfd4e0..00995f881da 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -33,7 +33,7 @@ The following table lists examples with step-by-step tutorials that are containe
| Java with Maven | [How to deploy Maven projects to Artifactory with GitLab CI/CD](artifactory_and_gitlab/index.md). |
| PHP with PHPunit, atoum | [Testing PHP projects](php.md). |
| PHP with NPM, SCP | [Running Composer and NPM scripts with deployment via SCP in GitLab CI/CD](deployment/composer-npm-deploy.md). |
-| PHP with Laravel, Ennvoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
+| PHP with Laravel, Envoy | [Test and deploy Laravel applications with GitLab CI/CD and Envoy](laravel_with_gitlab_and_envoy/index.md). |
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
diff --git a/doc/ci/examples/artifactory_and_gitlab/index.md b/doc/ci/examples/artifactory_and_gitlab/index.md
index c9f700ed190..e5f307e570d 100644
--- a/doc/ci/examples/artifactory_and_gitlab/index.md
+++ b/doc/ci/examples/artifactory_and_gitlab/index.md
@@ -1,5 +1,5 @@
---
-redirect_from: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
+disqus_identifier: 'https://docs.gitlab.com/ee/articles/artifactory_and_gitlab/index.html'
author: Fabio Busatto
author_gitlab: bikebilly
level: intermediate
@@ -39,9 +39,10 @@ project:
1. Create a new project by selecting **Import project from ➔ Repo by URL**
1. Add the following URL:
- ```
- https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
- ```
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-dep.git
+ ```
+
1. Click **Create project**
This application is nothing more than a basic class with a stub for a JUnit based test suite.
@@ -63,15 +64,15 @@ The application is ready to use, but you need some additional steps to deploy it
1. Copy the snippet in the `pom.xml` file for your project, just after the
`dependencies` section. The snippet should look like this:
- ```xml
- <distributionManagement>
- <repository>
- <id>central</id>
- <name>83d43b5afeb5-releases</name>
- <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
- </repository>
- </distributionManagement>
- ```
+ ```xml
+ <distributionManagement>
+ <repository>
+ <id>central</id>
+ <name>83d43b5afeb5-releases</name>
+ <url>${env.MAVEN_REPO_URL}/libs-release-local</url>
+ </repository>
+ </distributionManagement>
+ ```
Another step you need to do before you can deploy the dependency to Artifactory
is to configure the authentication data. It is a simple task, but Maven requires
@@ -86,18 +87,18 @@ parameter in `.gitlab-ci.yml` to use the custom location instead of the default
1. Create a file called `settings.xml` in the `.m2` folder
1. Copy the following content into a `settings.xml` file:
- ```xml
- <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
- xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
- <servers>
- <server>
- <id>central</id>
- <username>${env.MAVEN_REPO_USER}</username>
- <password>${env.MAVEN_REPO_PASS}</password>
- </server>
- </servers>
- </settings>
- ```
+ ```xml
+ <settings xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.1.0 http://maven.apache.org/xsd/settings-1.1.0.xsd"
+ xmlns="http://maven.apache.org/SETTINGS/1.1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <servers>
+ <server>
+ <id>central</id>
+ <username>${env.MAVEN_REPO_USER}</username>
+ <password>${env.MAVEN_REPO_PASS}</password>
+ </server>
+ </servers>
+ </settings>
+ ```
Username and password will be replaced by the correct values using variables.
@@ -187,9 +188,10 @@ We'll use again a Maven app that can be cloned from our example project:
1. Create a new project by selecting **Import project from ➔ Repo by URL**
1. Add the following URL:
- ```
- https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
- ```
+ ```
+ https://gitlab.com/gitlab-examples/maven/simple-maven-app.git
+ ```
+
1. Click **Create project**
This one is a simple app as well. If you look at the `src/main/java/com/example/app/App.java`
@@ -204,13 +206,13 @@ Since Maven doesn't know how to resolve the dependency, you need to modify the c
1. Copy the snippet in the `dependencies` section of the `pom.xml` file.
The snippet should look like this:
- ```xml
- <dependency>
- <groupId>com.example.dep</groupId>
- <artifactId>simple-maven-dep</artifactId>
- <version>1.0</version>
- </dependency>
- ```
+ ```xml
+ <dependency>
+ <groupId>com.example.dep</groupId>
+ <artifactId>simple-maven-dep</artifactId>
+ <version>1.0</version>
+ </dependency>
+ ```
### Configure the Artifactory repository location
diff --git a/doc/ci/examples/browser_performance.md b/doc/ci/examples/browser_performance.md
index 8ecac4a5a4f..3266e5dc62e 100644
--- a/doc/ci/examples/browser_performance.md
+++ b/doc/ci/examples/browser_performance.md
@@ -15,7 +15,7 @@ your code by using GitLab CI/CD and [sitespeed.io](https://www.sitespeed.io)
using Docker-in-Docker.
First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+[docker-in-docker build](../docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
Once you set up the Runner, add a new job to `.gitlab-ci.yml` that
generates the expected report:
@@ -155,4 +155,4 @@ performance:
paths:
- performance.json
- sitespeed-results/
-``` \ No newline at end of file
+```
diff --git a/doc/ci/examples/code_quality.md b/doc/ci/examples/code_quality.md
index 43f773dab7c..9c65de115b4 100644
--- a/doc/ci/examples/code_quality.md
+++ b/doc/ci/examples/code_quality.md
@@ -1,5 +1,5 @@
---
-redirect_from: 'https://docs.gitlab.com/ee/ci/examples/code_climate.html'
+disqus_identifier: 'https://docs.gitlab.com/ee/ci/examples/code_climate.html'
type: reference, howto
---
@@ -14,7 +14,7 @@ This example shows how to run Code Quality on your code by using GitLab CI/CD
and Docker.
First, you need GitLab Runner with
-[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-executor).
+[docker-in-docker executor](../docker/using_docker_build.md#use-docker-in-docker-workflow-with-docker-executor).
Once you set up the Runner, include the CodeQuality template in your CI config:
@@ -34,6 +34,12 @@ For [GitLab Starter][ee] users, this information will be automatically
extracted and shown right in the merge request widget.
[Learn more on Code Quality in merge requests](../../user/project/merge_requests/code_quality.md).
+CAUTION: **Caution:**
+On self-managed instances, if a malicious actor compromises the Code Quality job
+definition they will be able to execute privileged docker commands on the Runner
+host. Having proper access control policies mitigates this attack vector by
+allowing access only to trusted actors.
+
## Previous job definitions
CAUTION: **Caution:**
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 50e61cafeb9..44d3ec8046c 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -418,10 +418,14 @@ fully understand [IAM Best Practices in AWS](https://docs.aws.amazon.com/IAM/lat
1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
- ![AWS Access Key Config](img/aws_config_window.png)
+
+ ![AWS Access Key Config](img/aws_config_window.png)
+
1. Go to your GitLab project, click **Settings > CI/CD** on the left sidebar
1. Expand the **Variables** section
- ![GitLab Secret Config](img/gitlab_config.png)
+
+ ![GitLab Secret Config](img/gitlab_config.png)
+
1. Add a key named `AWS_KEY_ID` and copy the key id from Step 2 into the **Value** textbox
1. Add a key named `AWS_KEY_SECRET` and copy the key secret from Step 2 into the **Value** textbox
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png
index c45d70d7f7a..9fe1739f37e 100644
--- a/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/img/deployed_dependency_update.png
Binary files differ
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
index 38fcf05f519..38d0d86ffa2 100644
--- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
@@ -143,7 +143,7 @@ Which brings us to the exciting part: how do we run this in GitLab CI/CD? There
need to do for this:
1. Set up [CI/CD jobs](../../yaml/README.md#introduction) that actually have a browser available.
-2. Update our WebdriverIO configuration to use those browsers to visit the review apps.
+1. Update our WebdriverIO configuration to use those browsers to visit the review apps.
For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
`confidence-check` that is executed _after_ the stage that deploys the review app. It uses the `node:latest` [Docker
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index 1576efd5a7d..808e4285f2f 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -1,5 +1,5 @@
---
-redirect_from: 'https://docs.gitlab.com/ee/articles/laravel_with_gitlab_and_envoy/index.html'
+disqus_identifier: 'https://docs.gitlab.com/ee/articles/laravel_with_gitlab_and_envoy/index.html'
author: Mehran Rasulian
author_gitlab: mehranrasulian
level: intermediate
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index a5fed00972f..0e595e1a0be 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -188,28 +188,27 @@ when running our Phoenix in our `localhost`.
- Open `hello_gitlab_ci/config/test.exs` on your favorite code editor
- Go to **Configure your database** session and edit the block to include `System.get_env`:
- ```elixir
- # Configure your database
- config :hello_gitlab_ci, HelloGitlabCi.Repo,
- adapter: Ecto.Adapters.Postgres,
- username: System.get_env("POSTGRES_USER") || "postgres",
- password: System.get_env("POSTGRES_PASSWORD") || "postgres",
- database: System.get_env("POSTGRES_DB") || "hello_gitlab_ci_test",
- hostname: System.get_env("POSTGRES_HOST") || "localhost",
- pool: Ecto.Adapters.SQL.Sandbox
- ```
-
- We'll need these system variables later on.
+ ```elixir
+ # Configure your database
+ config :hello_gitlab_ci, HelloGitlabCi.Repo,
+ adapter: Ecto.Adapters.Postgres,
+ username: System.get_env("POSTGRES_USER") || "postgres",
+ password: System.get_env("POSTGRES_PASSWORD") || "postgres",
+ database: System.get_env("POSTGRES_DB") || "hello_gitlab_ci_test",
+ hostname: System.get_env("POSTGRES_HOST") || "localhost",
+ pool: Ecto.Adapters.SQL.Sandbox
+ ```
+
+ We'll need these system variables later on.
- Create an empty file named `.gitkeep` into `hello_gitlab_ci/priv/repo/migrations`
- As our project is still fresh, we don't have any data on our database, so, the `migrations`
-directory will be empty.
- Without `.gitkeep`, git will not upload this empty directory and we'll got an error when running our
-test on GitLab.
+ As our project is still fresh, we don't have any data on our database, so, the `migrations`
+ directory will be empty.
+ Without `.gitkeep`, git will not upload this empty directory and we'll got an error when running our
+ test on GitLab.
- > **Note:**
- If we add a folder via the GitLab UI, GitLab itself will add the `.gitkeep` to that new dir.
+ > **Note:** If we add a folder via the GitLab UI, GitLab itself will add the `.gitkeep` to that new dir.
Now, let's run a local test and see if everything we did didn't break anything.
@@ -248,64 +247,64 @@ project.
- The fastest and easiest way to do this, is to click on **Set up CI** on project's main page:
- ![Set up CI](img/setup-ci.png)
+ ![Set up CI](img/setup-ci.png)
- On next screen, we can select a template ready to go. Click on **Apply a GitLab CI/CD Yaml
template** and select **Elixir**:
- ![Select template](img/select-template.png)
+ ![Select template](img/select-template.png)
- This template file tells GitLab CI/CD about what we wish to do every time a new commit is made.
- However, we have to adapt it to run a Phoenix app.
+ This template file tells GitLab CI/CD about what we wish to do every time a new commit is made.
+ However, we have to adapt it to run a Phoenix app.
- The first line tells GitLab what Docker image will be used.
- Remember when we learn about Runners, the isolated virtual machine where GitLab CI/CD build and test
- our application? This virtual machine must have all dependencies to run our application. This is
- where a Docker image is needed. The correct image will provide the entire system for us.
+ Remember when we learn about Runners, the isolated virtual machine where GitLab CI/CD build and test
+ our application? This virtual machine must have all dependencies to run our application. This is
+ where a Docker image is needed. The correct image will provide the entire system for us.
- As a suggestion, you can use [trenpixster's elixir image][docker-image], which already has all
- dependencies for Phoenix installed, such as Elixir, Erlang, NodeJS and PostgreSQL:
+ As a suggestion, you can use [trenpixster's elixir image][docker-image], which already has all
+ dependencies for Phoenix installed, such as Elixir, Erlang, NodeJS and PostgreSQL:
- ```yml
- image: trenpixster/elixir:latest
- ```
+ ```yml
+ image: trenpixster/elixir:latest
+ ```
- At `services` session, we'll only use `postgres`, so we'll delete `mysql` and `redis` lines:
- ```yml
- services:
- - postgres:latest
- ```
+ ```yml
+ services:
+ - postgres:latest
+ ```
- Now, we'll create a new entry called `variables`, before `before_script` session:
- ```yml
- variables:
- POSTGRES_DB: hello_gitlab_ci_test
- POSTGRES_HOST: postgres
- POSTGRES_USER: postgres
- POSTGRES_PASSWORD: "postgres"
- MIX_ENV: "test"
- ```
+ ```yml
+ variables:
+ POSTGRES_DB: hello_gitlab_ci_test
+ POSTGRES_HOST: postgres
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: "postgres"
+ MIX_ENV: "test"
+ ```
- Here, we are setting up the values for GitLab CI/CD authenticate into PostgreSQL, as we did on
- `config/test.exs` earlier.
+ Here, we are setting up the values for GitLab CI/CD authenticate into PostgreSQL, as we did on
+ `config/test.exs` earlier.
- In `before_script` session, we'll add some commands to prepare everything to the test:
- ```yml
- before_script:
- - apt-get update && apt-get -y install postgresql-client
- - mix local.hex --force
- - mix deps.get --only test
- - mix ecto.create
- - mix ecto.migrate
- ```
-
- It's important to install `postgresql-client` to let GitLab CI/CD access PostgreSQL and create our
- database with the login information provided earlier. More important is to respect the indentation,
- to avoid syntax errors when running the build.
+ ```yml
+ before_script:
+ - apt-get update && apt-get -y install postgresql-client
+ - mix local.hex --force
+ - mix deps.get --only test
+ - mix ecto.create
+ - mix ecto.migrate
+ ```
+
+ It's important to install `postgresql-client` to let GitLab CI/CD access PostgreSQL and create our
+ database with the login information provided earlier. More important is to respect the indentation,
+ to avoid syntax errors when running the build.
- And finally, we'll let `mix` session intact.
diff --git a/doc/ci/img/collapsible_log.png b/doc/ci/img/collapsible_log.png
index 2785033b349..d2a570e246e 100644
--- a/doc/ci/img/collapsible_log.png
+++ b/doc/ci/img/collapsible_log.png
Binary files differ
diff --git a/doc/ci/img/deployments_view.png b/doc/ci/img/deployments_view.png
index 12090434bef..9e2b7e89577 100644
--- a/doc/ci/img/deployments_view.png
+++ b/doc/ci/img/deployments_view.png
Binary files differ
diff --git a/doc/ci/img/environments_available.png b/doc/ci/img/environments_available.png
index 48fc6effc2d..6c64e9398f7 100644
--- a/doc/ci/img/environments_available.png
+++ b/doc/ci/img/environments_available.png
Binary files differ
diff --git a/doc/ci/img/environments_mr_review_app.png b/doc/ci/img/environments_mr_review_app.png
index 6a7b7ce5679..86c20d8d3b6 100644
--- a/doc/ci/img/environments_mr_review_app.png
+++ b/doc/ci/img/environments_mr_review_app.png
Binary files differ
diff --git a/doc/ci/img/manual_job_variables.png b/doc/ci/img/manual_job_variables.png
new file mode 100644
index 00000000000..a5ed351fdcd
--- /dev/null
+++ b/doc/ci/img/manual_job_variables.png
Binary files differ
diff --git a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
index 204e9c462e5..f3fb9444b55 100644
--- a/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
+++ b/doc/ci/introduction/img/gitlab_workflow_example_11_9.png
Binary files differ
diff --git a/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png
index 5089a1088c5..a0874f66eaa 100644
--- a/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png
+++ b/doc/ci/introduction/img/gitlab_workflow_example_extended_11_11.png
Binary files differ
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index fc89f0fc94f..366aca3442e 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -9,6 +9,11 @@ In this document we'll present an overview of the concepts of Continuous Integra
Continuous Delivery, and Continuous Deployment, as well as an introduction to
GitLab CI/CD.
+NOTE: **Out-of-the-box management systems can decrease hours spent on maintaining toolchains by 10% or more.**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to learn about continuous methods and how GitLab’s built-in CI can help you simplify and scale software development.
+
## Introduction to CI/CD methodologies
The continuous methodologies of software development are based on
@@ -146,14 +151,14 @@ so, GitLab CI/CD:
- Runs automated scripts (sequential or parallel) to:
- Build and test your app.
- Preview the changes per merge request with Review Apps, as you
- would see in your `localhost`.
+ would see in your `localhost`.
Once you're happy with your implementation:
- Get your code reviewed and approved.
- Merge the feature branch into the default branch.
- GitLab CI/CD deploys your changes automatically to a production environment.
-- And finally, you and your team can easily roll it back if something goes wrong.
+- And finally, you and your team can easily roll it back if something goes wrong.
![GitLab workflow example](img/gitlab_workflow_example_11_9.png)
@@ -176,24 +181,24 @@ you'll see some of the features available in GitLab
according to each stage (Verify, Package, Release).
1. **Verify**:
- - Automatically build and test your application with Continuous Integration.
- - Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md). **(STARTER)**
- - Determine the performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)**
- - Perform a series of tests, such as [Container Scanning](../../user/application_security/container_scanning/index.md) **(ULTIMATE)**, [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)**, and [JUnit tests](../junit_test_reports.md).
- - Deploy your changes with [Review Apps](../review_apps/index.md) to preview the app changes on every branch.
+ - Automatically build and test your application with Continuous Integration.
+ - Analyze your source code quality with [GitLab Code Quality](../../user/project/merge_requests/code_quality.md). **(STARTER)**
+ - Determine the performance impact of code changes with [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md). **(PREMIUM)**
+ - Perform a series of tests, such as [Container Scanning](../../user/application_security/container_scanning/index.md) **(ULTIMATE)**, [Dependency Scanning](../../user/application_security/dependency_scanning/index.md) **(ULTIMATE)**, and [JUnit tests](../junit_test_reports.md).
+ - Deploy your changes with [Review Apps](../review_apps/index.md) to preview the app changes on every branch.
1. **Package**:
- - Store Docker images with [Container Registry](../../user/project/container_registry.md).
- - Store NPM packages with [NPM Registry](../../user/project/packages/npm_registry.md). **(PREMIUM)**
- - Store Maven artifacts with [Maven Repository](../../user/project/packages/maven_repository.md). **(PREMIUM)**
+ - Store Docker images with [Container Registry](../../user/project/container_registry.md).
+ - Store NPM packages with [NPM Registry](../../user/project/packages/npm_registry.md). **(PREMIUM)**
+ - Store Maven artifacts with [Maven Repository](../../user/project/packages/maven_repository.md). **(PREMIUM)**
1. **Release**:
- - Continuous Deployment, automatically deploying your app to production.
- - Continuous Delivery, manually click to deploy your app to production.
- - Deploy static websites with [GitLab Pages](../../user/project/pages/index.md).
- - Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature with [Canary Deployments](../../user/project/canary_deployments.md). **(PREMIUM)**
- - Deploy your features behind [Feature Flags](../../user/project/operations/feature_flags.md). **(PREMIUM)**
- - Add release notes to any Git tag with [GitLab Releases](../../user/project/releases/index.md).
- - View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](../../user/project/deploy_boards.md). **(PREMIUM)**
- - Deploy your application to a production environment in a Kubernetes cluster with [Auto Deploy](../../topics/autodevops/index.md#auto-deploy).
+ - Continuous Deployment, automatically deploying your app to production.
+ - Continuous Delivery, manually click to deploy your app to production.
+ - Deploy static websites with [GitLab Pages](../../user/project/pages/index.md).
+ - Ship features to only a portion of your pods and let a percentage of your user base to visit the temporarily deployed feature with [Canary Deployments](../../user/project/canary_deployments.md). **(PREMIUM)**
+ - Deploy your features behind [Feature Flags](../../user/project/operations/feature_flags.md). **(PREMIUM)**
+ - Add release notes to any Git tag with [GitLab Releases](../../user/project/releases/index.md).
+ - View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](../../user/project/deploy_boards.md). **(PREMIUM)**
+ - Deploy your application to a production environment in a Kubernetes cluster with [Auto Deploy](../../topics/autodevops/index.md#auto-deploy).
With GitLab CI/CD you can also:
diff --git a/doc/ci/jenkins/index.md b/doc/ci/jenkins/index.md
new file mode 100644
index 00000000000..f8a3fab88e3
--- /dev/null
+++ b/doc/ci/jenkins/index.md
@@ -0,0 +1,232 @@
+---
+comments: false
+type: index, howto
+---
+
+# Migrating from Jenkins
+
+A lot of GitLab users have successfully migrated to GitLab CI/CD from Jenkins. To make this
+easier if you're just getting started, we've collected several resources here that you might find useful
+before diving in.
+
+First of all, our [Quick Start Guide](../quick_start/README.md) contains a good overview of how GitLab CI/CD works.
+You may also be interested in [Auto DevOps](../../topics/autodevops/index.md) which can potentially be used to build, test,
+and deploy your applications with little to no configuration needed at all.
+
+Otherwise, read on for important information that will help you get the ball rolling. Welcome
+to GitLab!
+
+## Important differences
+
+There are some high level differences between the products worth mentioning:
+
+- With GitLab you don't need a root `pipeline` keyword to wrap everything.
+- All jobs within a single stage always run in parallel, and all stages run in sequence. We are planning
+ to allow certain jobs to break this sequencing as needed with our [directed acyclic graph](https://gitlab.com/gitlab-org/gitlab-ce/issues/47063)
+ feature.
+- The `.gitlab-ci.yml` file is checked in to the root of your repository, much like a Jenkinsfile, but
+ is in the YAML format (see [complete reference](../yaml/README.md)) instead of a Groovy DSL. It's most
+ analagous to the declarative Jenkinsfile format.
+- GitLab comes with a [container registry](../../user/project/container_registry.md), and we recommend using
+ container images to set up your build environment.
+
+## Groovy vs. YAML
+
+Jenkins Pipelines are based on [Groovy](https://groovy-lang.org/), so the pipeline specification is written as code.
+GitLab works a bit differently, we use the more highly structured [YAML](https://yaml.org/) format, which
+places scripting elements inside of `script:` blocks separate from the pipeline specification itself.
+
+This is a strength of GitLab, in that it helps keep the learning curve much simpler to get up and running
+and avoids some of the problem of unconstrained complexity which can make your Jenkinsfiles hard to understand
+and manage.
+
+That said, we do of course still value DRY (don't repeat yourself) principles and want to ensure that
+behaviors of your jobs can be codified once and applied as needed. You can use the `extends:` syntax to
+[templatize your jobs](../yaml/README.md#extends), and `include:` can be used to [bring in entire sets of behaviors](../yaml/README.md#include)
+to pipelines in different projects.
+
+```yaml
+.in-docker:
+ tags:
+ - docker
+ image: alpine
+
+rspec:
+ extends:
+ - .in-docker
+ script:
+ - rake rspec
+```
+
+## Artifact publishing
+
+Artifacts may work a bit differently than you've used them with Jenkins. In GitLab, any job can define
+a set of artifacts to be saved by using the `artifacts:` keyword. This can be configured to point to a file
+or set of files that can then be persisted from job to job. Read more on our detailed [artifacts documentation](../../user/project/pipelines/job_artifacts.html)
+
+```yaml
+pdf:
+ script: xelatex mycv.tex
+ artifacts:
+ paths:
+ - ./mycv.pdf
+ - ./output/
+ expire_in: 1 week
+```
+
+Additionally, we have package management features like a built-in container, NPM, and Maven registry that you
+can leverage. You can see the complete list of packaging features (which includes links to documentation)
+in the [Packaging section of our documentation](../../README.md#package).
+
+## Integrated features
+
+Where you may have used plugins to get things like code quality, unit tests, security scanning, and so on working in Jenkins,
+GitLab takes advantage of our connected ecosystem to automatically pull these kinds of results into
+your Merge Requests, pipeline details pages, and other locations. You may find that you actually don't
+need to configure anything to have these appear.
+
+If they aren't working as expected, or if you'd like to see what's available, our [CI feature index](../README.md#feature-set) has the full list
+of bundled features and links to the documentation for each.
+
+## Converting Declarative Jenkinsfiles
+
+Declarative Jenkinsfiles contain "Sections" and "Directives" which are used to control the behavior of your
+pipelines. There are equivalents for all of these in GitLab, which we've documented below.
+
+This section is based on the [Jenkinsfile syntax documentation](https://jenkins.io/doc/book/pipeline/syntax/)
+and is meant to be a mapping of concepts there to concepts in GitLab.
+
+### Sections
+
+#### `agent`
+
+The agent section is used to define how a pipeline will be executed. For GitLab, we use the [GitLab Runner](../runners/README.md)
+to provide this capability. You can configure your own runners in Kubernetes or on any host, or take advantage
+of our shared runner fleet (note that the shared runner fleet is only available for GitLab.com users.) The link above will bring you to the documenation which will describe how to get
+up and running quickly. We also support using [tags](../runners/README.md#using-tags) to direct different jobs
+to different Runners (execution agents).
+
+The `agent` section also allows you to define which Docker images should be used for execution, for which we use
+the [`image`](../yaml/README.md#image) keyword. The `image` can be set on a single job or at the top level, in which
+case it will apply to all jobs in the pipeline.
+
+```yaml
+my_job:
+ image: alpine
+ ...
+```
+
+#### `post`
+
+The `post` section defines the actions that should be performed at the end of the pipeline. GitLab also supports
+this through the use of stages. You can define your stages as follows, and any jobs assigned to the `before_pipeline`
+or `after_pipeline` stages will run as expected. You can call these stages anything you like.
+
+```yaml
+stages:
+ - before_pipeline
+ - build
+ - test
+ - deploy
+ - after_pipeline
+```
+
+Setting a step to be performed before and after any job can be done via the [`before_script` and `after_script` keywords](../yaml/README.md#before_script-and-after_script).
+
+```yaml
+default:
+ before_script:
+ - echo "I run before any jobs starts in the entire pipeline, and can be responsible for setting up the environment."
+```
+
+#### `stages`
+
+GitLab CI also lets you define stages, but is a little bit more free-form to configure. The GitLab [`stages` keyword](../yaml/README.md#stages)
+is a top level setting that enumerates the list of stages, but you are not required to nest individual jobs underneath
+the `stages` section. Any job defined in the `.gitlab-ci.yml` can be made a part of any stage through use of the
+[`stage:` keyword](../yaml/README.md#stage).
+
+Note that, unless otherwise specified, every pipeline is instantiated with a `build`, `test`, and `deploy` stage
+which are run in that order. Jobs that have no `stage` defined are placed by default in the `test` stage.
+Of course, each job that refers to a stage must refer to a stage that exists in the pipeline configuration.
+
+```yaml
+stages:
+ - build
+ - test
+ - deploy
+
+my_job:
+ stage: build
+ ...
+```
+
+#### `steps`
+
+The `steps` section is equivalent to the [`script` section](../yaml/README.md#script) of an individual job. This is
+a simple YAML array with each line representing an individual command to be run.
+
+```yaml
+my_job:
+ script:
+ - echo "hello! the current time is:"
+ - time
+ ...
+```
+
+### Directives
+
+#### `environment`
+
+In GitLab, we use the [`variables` keyword](../yaml/README.md#variables) to define different variables at runtime.
+These can also be set up through the GitLab UI, under CI/CD settings. See also our [general documentation on variables](../variables/README.md),
+including the section on [protected variables](../variables/README.md#protected-environment-variables) which can be used
+to limit access to certain variables to certain environments or runners.
+
+```yaml
+variables:
+ POSTGRES_USER: user
+ POSTGRES_PASSWORD: testing_password
+```
+
+#### `options`
+
+Here, options for different things exist associated with the object in question itself. For example, options related
+to jobs are defined in relation to the job itself. If you're looking for a certain option, you should be able to find
+where it's located by searching our [complete configuration reference](../yaml/README.md) page.
+
+#### `parameters`
+
+GitLab does not require you to define which variables you want to be available when starting a manual job. A user
+can provide any variables they like.
+
+#### `triggers` / `cron`
+
+Because GitLab is integrated tightly with git, SCM polling options for triggers are not needed. We support an easy to use
+[syntax for scheduling pipelines](../../user/project/pipelines/schedules.md).
+
+#### `tools`
+
+GitLab does not support a separate `tools` directive. Our best-practice reccomendation is to use pre-built
+container images, which can be cached, and can be built to already contain the tools you need for your pipelines. Pipelines can
+be set up to automatically build these images as needed and deploy them to the [container registry](../../user/project/container_registry.md).
+
+If you're not using container images with Docker/Kubernetes, for example on Mac or FreeBSD, then the `shell` executor does require you to
+set up your environment either in advance or as part of the jobs. You could create a `before_script`
+action that handles this for you.
+
+#### `input`
+
+Similar to the `parameters` keyword, this is not needed because a manual job can always be provided runtime
+variable entry.
+
+#### `when`
+
+GitLab does support a [`when` keyword](../yaml/README.md#when) which is used to indicate when a job should be
+run in case of (or despite) failure, but most of the logic for controlling pipelines can be found in
+our very powerful [`only/except` rules system](../yaml/README.md#onlyexcept-basic) (see also our [advanced syntax](../yaml/README.md#onlyexcept-basic))
+
+```yaml
+my_job:
+ only: branches
+``` \ No newline at end of file
diff --git a/doc/ci/merge_request_pipelines/img/merge_request.png b/doc/ci/merge_request_pipelines/img/merge_request.png
index d03fdc6a885..bb64e17cc91 100644
--- a/doc/ci/merge_request_pipelines/img/merge_request.png
+++ b/doc/ci/merge_request_pipelines/img/merge_request.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png
index 58d5581f628..6d4b66824e1 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png
index 0a84e61d284..3ee9d8ec93b 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/img/merge_request_pipeline_config.png
Binary files differ
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 a13857bee25..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
@@ -6,7 +6,6 @@ last_update: 2019-07-03
# Pipelines for Merged Results **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
-> This feature is disabled by default until we resolve issues with [contention handling](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222), but [can be enabled manually](#enabling-pipelines-for-merged-results).
It's possible for your source and target branches to diverge, which can result
in the scenario that source branch's pipeline was green, the target's pipeline was green,
@@ -25,8 +24,10 @@ new ref and a pipeline for this ref validates the result prior to merging.
There are some cases where creating a combined ref is not possible or not wanted.
For example, a source branch that has conflicts with the target branch
-or a merge request that is still in WIP status. In this case, the merge request pipeline falls back to a "detached" state
-and runs on the source branch ref as if it was a regular pipeline.
+or a merge request that is still in WIP status. In this case,
+GitLab doesn't create a merge commit and the pipeline runs on source branch, instead,
+which is a default behavior of [Pipelines for merge requests](../index.md)
+ i.e. `detached` label is shown to the pipelines.
The detached state serves to warn you that you are working in a situation
subjected to merge problems, and helps to highlight that you should
@@ -34,15 +35,14 @@ 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:
- Forking/cross-repo workflows are not currently supported. To follow progress,
- see [#9713](https://gitlab.com/gitlab-org/gitlab-ee/issues/9713).
+ see [#11934](https://gitlab.com/gitlab-org/gitlab-ee/issues/11934).
- This feature is not available for
[fast forward merges](../../../user/project/merge_requests/fast_forward_merge.md) yet.
To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-ce/issues/58226).
@@ -61,18 +61,32 @@ CAUTION: **Warning:**
Make sure your `gitlab-ci.yml` file is [configured properly for pipelines for merge requests](../index.md#configuring-pipelines-for-merge-requests),
otherwise pipelines for merged results won't run and your merge requests will be stuck in an unresolved state.
-## Merge Trains **(PREMIUM)**
+## Troubleshooting
-Read the [documentation on Merge Trains](merge_trains/index.md).
+### Pipelines for merged results not created even with new change pushed to merge request
-<!-- ## Troubleshooting
+Can be caused by some disabled feature flags. Please make sure that
+the following feature flags are enabled on your GitLab instance:
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+- `:ci_use_merge_request_ref`
+- `:merge_ref_auto_sync`
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+To check these feature flag values, please ask administrator to execute the following commands:
+
+```shell
+> sudo gitlab-rails console # Login to Rails console of GitLab instance.
+> Feature.enabled?(:ci_use_merge_request_ref) # Check if it's enabled or not.
+> Feature.enable(:ci_use_merge_request_ref) # Enable the feature flag.
+```
+
+## Using Merge Trains **(PREMIUM)**
+
+By enabling [Pipelines for merged results](#pipelines-for-merged-results-premium),
+GitLab will [automatically display](merge_trains/index.md#how-to-add-a-merge-request-to-a-merge-train)
+a **Start/Add Merge Train button** as the most recommended merge strategy.
+
+Generally, this is a safer option than merging merge requests immediately as your
+merge request will be evaluated with an expected post-merge result before the actual
+merge happens.
+
+For more information, read the [documentation on Merge Trains](merge_trains/index.md).
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png
index 1561fdcc7a5..d7720ac1143 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_cancel_v12_0.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png
index fb0af43556e..9da959ad440 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_config_v12_0.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png
new file mode 100644
index 00000000000..8a795fff432
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_failure.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png
new file mode 100644
index 00000000000..03bc61129ba
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_immediate_merge.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png
new file mode 100644
index 00000000000..ec4b157d428
--- /dev/null
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_position_v12_0.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png
index f20108157d2..a4d0c8cf0e6 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_v12_0.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png
index 62c2f2f5ff5..45762b8e85e 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/img/merge_train_start_when_pipeline_succeeds_v12_0.png
Binary files differ
diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
index 57358434c02..80a1c264bc4 100644
--- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
+++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md
@@ -6,7 +6,6 @@ last_update: 2019-07-03
# Merge Trains **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.0.
-> This feature is disabled by default, but [can be enabled manually](#enabling-merge-trains).
[Pipelines for merged results](../index.md#pipelines-for-merged-results-premium) introduces
running a build on the result of the merged code prior to merging, as a way to keep master green.
@@ -33,35 +32,40 @@ Merge trains have the following requirements and limitations:
- This feature requires that
[pipelines for merged results](../index.md#pipelines-for-merged-results-premium) are
**configured properly**.
-- Each merge train can generate a merge ref and run a pipeline **one at a time**.
- We plan to make the pipelines for merged results
- [run in parallel](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222) in a
- future release.
+- Each merge train can run a maximum of **four** pipelines in parallel.
+ If more than four merge requests are added to the merge train, the merge requests
+ will be queued until a slot in the merge train is free. There is no limit to the
+ number of merge requests that can be queued.
+- This feature does not support [squash and merge](../../../../user/project/merge_requests/squash_and_merge.md).
-## Enabling Merge Trains
-
-To enable merge trains at the project level:
-
-1. Visit your project's **Settings > General** and expand **Merge requests**.
-1. Check **Allow merge trains**.
-1. Click **Save changes** button.
-
-![Merge request pipeline config](img/merge_train_config_v12_0.png)
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+Watch this video for a demonstration on [how parallel execution
+of Merge Trains can prevent commits from breaking the default
+branch](https://www.youtube.com/watch?v=D4qCqXgZkHQ).
## How to add a merge request to a merge train
To add a merge request to a merge train:
-1. Click "Start/Add merge train" button in a merge request
+1. Visit a merge request.
+1. Click the **Start/Add to merge train** button.
![Start merge train](img/merge_train_start_v12_0.png)
## How to remove a merge request from a merge train
-1. Click "Remove from merge train" button in the merge request widget.
+1. Visit a merge request.
+1. Click the **Remove from merge train** button.
![Cancel merge train](img/merge_train_cancel_v12_0.png)
+## How to view a merge request's current position on the merge train
+
+After a merge request has been added to the merge train, the merge request's
+current position will be displayed under the pipeline widget:
+
+![Merge train position indicator](img/merge_train_position_v12_0.png)
+
## Start/Add to merge train when pipeline succeeds
You can add a merge request to a merge train only when the latest pipeline in the
@@ -70,19 +74,71 @@ the merge request to a train because the current change of the merge request may
be broken thus it could affect the following merge requests.
In this case, you can schedule to add the merge request to a merge train **when the latest
-pipeline succeeds**. You can see the following button instead of the regular "Start/Add merge train"
+pipeline succeeds** (This pipeline is [Pipelines for merged results](../index.md), not Pipelines for merge train).
+You can see the following button instead of the regular **Start/Add to merge train**
button while the latest pipeline is running.
![Add to merge train when pipeline succeeds](img/merge_train_start_when_pipeline_succeeds_v12_0.png)
-<!-- ## Troubleshooting
+## Immediately merge a merge request with a merge train
+
+In case, you have a high-priority merge request (e.g. critical patch) to be merged urgently,
+you can use **Merge Immediately** option for bypassing the merge train.
+This is the fastest option to get the change merged into the target branch.
+
+![Merge Immediately](img/merge_train_immediate_merge.png)
+
+However, every time you merge a merge request immediately, it could affect the
+existing merge train to be reconstructed, specifically, it regenerates expected
+merge commits and pipelines. This means, merging immediately essentially wastes
+CI resources.
+
+## Troubleshooting
+
+### Merge request dropped from the merge train immediately
+
+If a merge request is not mergeable (for example, it's WIP, there is a merge
+conflict, etc), your merge request will be dropped from the merge train automatically.
+
+In these cases, the reason for dropping the merge request is in the **system notes**.
+
+To check the reason:
+
+1. Open the merge request that was dropped from the merge train.
+1. Open the **Discussion** tab.
+1. Find a system note that includes either:
+ - The text **... removed this merge request from the merge train because ...**
+ - **... aborted this merge request from the merge train because ...**
+ The reason is given in the text after the **because ...** phrase.
+
+![Merge Train Failure](img/merge_train_failure.png)
+
+### Merge When Pipeline Succeeds cannot be chosen
+
+[Merge When Pipeline Succeeds](../../../../user/project/merge_requests/merge_when_pipeline_succeeds.md)
+is unavailable when
+[Pipelines for Merged Results is enabled](../index.md#enabling-pipelines-for-merged-results).
+
+Follow [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/12267) to
+track progress on this issue.
+
+### Merge Train disturbs your workflow
+
+First of all, please check if [merge immediately](#immediately-merge-a-merge-request-with-a-merge-train)
+is available as a workaround in your workflow. This is the most recommended
+workaround you'd be able to take immediately. If it's not available or acceptable,
+please read through this section.
+
+Merge train is enabled by default when you enable [Pipelines for merged results](../index.md),
+however, you can forcibly disable this feature by disabling the feature flag `:merge_trains_enabled`.
+After you disabled this feature, all the existing merge trains will be aborted and
+you will no longer see the **Start/Add Merge Train** button in merge requests.
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+To check if the feature flag is enabled on your GitLab instance,
+please ask administrator to execute the following commands:
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+```shell
+> sudo gitlab-rails console # Login to Rails console of GitLab instance.
+> Feature.enabled?(:merge_trains_enabled) # Check if it's enabled or not.
+> Feature.disable(:merge_trains_enabled) # Disable the feature flag.
+``` \ No newline at end of file
diff --git a/doc/ci/multi_project_pipelines.md b/doc/ci/multi_project_pipelines.md
index 463b9194c58..cb8d383f7d9 100644
--- a/doc/ci/multi_project_pipelines.md
+++ b/doc/ci/multi_project_pipelines.md
@@ -171,6 +171,26 @@ In this scenario, the `UPSTREAM_BRANCH` variable with a value related to the
upstream pipeline will be passed to the `downstream-job` job, and will be available
within the context of all downstream builds.
+NOTE: **Tip:**
+Upstream pipelines take precedence over downstream ones. If there are two
+variables with the same name defined in both upstream and downstream projects,
+the ones defined in the upstream project will take precedence.
+
+### Mirroring status from upstream pipeline
+
+You can mirror the pipeline status from an upstream pipeline to a bridge job by
+using the `needs:pipeline` keyword. The latest pipeline status from master is
+replicated to the bridge job.
+
+Example:
+
+```yaml
+upstream_bridge:
+ stage: test
+ needs:
+ pipeline: other/project
+```
+
### Limitations
Because bridge jobs are a little different to regular jobs, it is not
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 06a81c3d0c7..ed8d0e3bc35 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -6,6 +6,11 @@ type: reference
> Introduced in GitLab 8.8.
+NOTE: **Tip:**
+Watch our
+["Mastering continuous software development"](https://about.gitlab.com/webcast/mastering-ci-cd/)
+webcast to see a comprehensive demo of GitLab CI/CD pipeline.
+
## Introduction
Pipelines are the top-level component of continuous integration, delivery, and deployment.
@@ -318,6 +323,20 @@ stage has a job with a manual action.
![Pipelines example](img/pipelines.png)
+### Specifying variables when running manual jobs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30485) in GitLab 12.2.
+
+When running manual jobs you can supply additional job specific variables.
+
+You can do this from the job page of the manual job you want to run with
+additional variables.
+
+This is useful when you want to alter the execution of a job by using
+environment variables.
+
+![Manual job variables](img/manual_job_variables.png)
+
### Delay a job in a pipeline graph
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
@@ -345,7 +364,7 @@ GitLab provides API endpoints to:
- [Triggering pipelines through the API](triggers/README.md).
- [Pipeline triggers API](../api/pipeline_triggers.md).
-### Start multiple manual actions in a stage
+### Start multiple manual actions in a stage
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27188) in GitLab 11.11.
diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md
index 0480b83d183..5626428bfc3 100644
--- a/doc/ci/quick_start/README.md
+++ b/doc/ci/quick_start/README.md
@@ -13,6 +13,10 @@ NOTE: **Note:**
Please keep in mind that only project Maintainers and Admin users have
the permissions to access a project's settings.
+NOTE: **Note:**
+Coming over to GitLab from Jenkins? Check out our [reference](../jenkins/index.md)
+for converting your pre-existing pipelines over to our format.
+
GitLab offers a [continuous integration][ci] service. If you
[add a `.gitlab-ci.yml` file][yaml] to the root directory of your repository,
and configure your GitLab project to use a [Runner], then each commit or
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/review_apps/img/review_button.png b/doc/ci/review_apps/img/review_button.png
index 0b231c50858..4f1cf4d7cfd 100644
--- a/doc/ci/review_apps/img/review_button.png
+++ b/doc/ci/review_apps/img/review_button.png
Binary files differ
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index 9b89988bf42..8ab7982fd65 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -147,11 +147,11 @@ Once you have the route mapping set up, it will take effect in the following loc
- In the diff for a merge request, comparison, or commit.
- !["View on env" button in merge request diff](img/view_on_env_mr.png)
+ !["View on env" button in merge request diff](img/view_on_env_mr.png)
- In the blob file view.
- !["View on env" button in file view](img/view_on_env_blob.png)
+ !["View on env" button in file view](img/view_on_env_blob.png)
## Visual Reviews **(STARTER)**
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 03a219e03ca..269bd5c3428 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -62,7 +62,7 @@ You can only register a shared Runner if you are an admin of the GitLab instance
1. Grab the shared-Runner token on the `admin/runners` page
- ![Shared Runners admin area](img/shared_runners_admin.png)
+ ![Shared Runners admin area](img/shared_runners_admin.png)
1. [Register the Runner][register]
@@ -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:
@@ -319,21 +319,21 @@ How this feature will work:
1. You set the _maximum job timeout_ for a Runner to 24 hours
1. You set the _CI/CD Timeout_ for a project to **2 hours**
1. You start a job
-1. The job, if running longer, will be timeouted after **2 hours**
+1. The job, if running longer, will be timed out after **2 hours**
**Example 2 - Runner timeout not configured**
1. You remove the _maximum job timeout_ configuration from a Runner
1. You set the _CI/CD Timeout_ for a project to **2 hours**
1. You start a job
-1. The job, if running longer, will be timeouted after **2 hours**
+1. The job, if running longer, will be timed out after **2 hours**
**Example 3 - Runner timeout smaller than project timeout**
1. You set the _maximum job timeout_ for a Runner to **30 minutes**
1. You set the _CI/CD Timeout_ for a project to 2 hours
1. You start a job
-1. The job, if running longer, will be timeouted after **30 minutes**
+1. The job, if running longer, will be timed out after **30 minutes**
### Be careful with sensitive information
@@ -373,12 +373,12 @@ attacker.
To reset the token:
-1. Go to **Settings > CI/CD** for a specified Project
-1. Expand the **General pipelines settings** section
-1. Find the **Runner token** form field and click the **Reveal value** button
-1. Delete the value and save the form
+1. Go to **Settings > CI/CD** for a specified Project.
+1. Expand the **General pipelines settings** section.
+1. Find the **Runner token** form field and click the **Reveal value** button.
+1. Delete the value and save the form.
1. After the page is refreshed, expand the **Runners settings** section
- and check the registration token - it should be changed
+ and check the registration token - it should be changed.
From now on the old token is not valid anymore and will not allow to register
a new Runner to the project. If you are using any tools to provision and
diff --git a/doc/ci/services/mysql.md b/doc/ci/services/mysql.md
index 697452cee83..9ea113969c8 100644
--- a/doc/ci/services/mysql.md
+++ b/doc/ci/services/mysql.md
@@ -25,6 +25,12 @@ variables:
MYSQL_ROOT_PASSWORD: "<your_mysql_password>"
```
+NOTE: **Note:**
+The `MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables can't be set in the GitLab UI.
+To set them, assign them to a variable [in the UI](../variables/README.md#via-the-ui),
+and then assign that variable to the
+`MYSQL_DATABASE` and `MYSQL_ROOT_PASSWORD` variables in your `.gitlab-ci.yml`.
+
And then configure your application to use the database, for example:
```yaml
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index b72dd6e920a..142f4f262e5 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -25,6 +25,13 @@ variables:
POSTGRES_PASSWORD: ""
```
+NOTE: **Note:**
+The `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` variables can't be set in
+the GitLab UI. To set them, assign them to a variable
+[in the UI](../variables/README.md#via-the-ui), and then assign that
+variable to the `POSTGRES_DB`, `POSTGRES_USER`, and `POSTGRES_PASSWORD` variables in
+your `.gitlab-ci.yml`.
+
And then configure your application to use the database, for example:
```yaml
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index 69591ed605c..b6aebd3bd78 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -57,44 +57,44 @@ to access it. This is where an SSH key pair comes in handy.
1. Modify your `.gitlab-ci.yml` with a `before_script` action. In the following
example, a Debian based image is assumed. Edit to your needs:
- ```yaml
- before_script:
- ##
- ## Install ssh-agent if not already installed, it is required by Docker.
- ## (change apt-get to yum if you use an RPM-based image)
- ##
- - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
-
- ##
- ## Run ssh-agent (inside the build environment)
- ##
- - eval $(ssh-agent -s)
-
- ##
- ## Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
- ## We're using tr to fix line endings which makes ed25519 keys work
- ## 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
-
- ##
- ## Create the SSH directory and give it the right permissions
- ##
- - mkdir -p ~/.ssh
- - chmod 700 ~/.ssh
-
- ##
- ## Optionally, if you will be using any Git commands, set the user name and
- ## and email.
- ##
- #- git config --global user.email "user@example.com"
- #- git config --global user.name "User name"
- ```
-
- NOTE: **Note:**
- The [`before_script`](../yaml/README.md#before_script-and-after_script) can be set globally
- or per-job.
+ ```yaml
+ before_script:
+ ##
+ ## Install ssh-agent if not already installed, it is required by Docker.
+ ## (change apt-get to yum if you use an RPM-based image)
+ ##
+ - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
+
+ ##
+ ## Run ssh-agent (inside the build environment)
+ ##
+ - eval $(ssh-agent -s)
+
+ ##
+ ## Add the SSH key stored in SSH_PRIVATE_KEY variable to the agent store
+ ## We're using tr to fix line endings which makes ed25519 keys work
+ ## 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 -
+
+ ##
+ ## Create the SSH directory and give it the right permissions
+ ##
+ - mkdir -p ~/.ssh
+ - chmod 700 ~/.ssh
+
+ ##
+ ## Optionally, if you will be using any Git commands, set the user name and
+ ## and email.
+ ##
+ #- git config --global user.email "user@example.com"
+ #- git config --global user.name "User name"
+ ```
+
+ NOTE: **Note:**
+ The [`before_script`](../yaml/README.md#before_script-and-after_script) can be set globally
+ or per-job.
1. Make sure the private server's [SSH host keys are verified](#verifying-the-ssh-host-keys).
@@ -118,9 +118,9 @@ on, and use that key for all projects that are run on this machine.
1. Then from the terminal login as the `gitlab-runner` user:
- ```
- sudo su - gitlab-runner
- ```
+ ```
+ sudo su - gitlab-runner
+ ```
1. Generate the SSH key pair as described in the instructions to
[generate an SSH key](../../ssh/README.md#generating-a-new-ssh-key-pair).
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index d1f9aa03b6b..2a382f18038 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -97,17 +97,6 @@ overview of the time the triggers were last used.
![Triggers page overview](img/triggers_page.png)
-## Taking ownership of a trigger
-
-> **Note**:
-GitLab 9.0 introduced a trigger ownership to solve permission problems.
-
-Each created trigger when run will impersonate their associated user including
-their access to projects and their project permissions.
-
-You can take ownership of existing triggers by clicking *Take ownership*.
-From now on the trigger will be run as you.
-
## Revoking a trigger
You can revoke a trigger any time by going at your project's
@@ -282,8 +271,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
-removed with one of the future versions of GitLab. You are advised to
-[take ownership](#taking-ownership-of-a-trigger) of any legacy triggers.
+removed with one of the future versions of GitLab.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
[ee-2346]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2346
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 4d6ca8cff6d..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, defined in [RFC4648](https://tools.ietf.org/html/rfc4648).
+- 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.
@@ -370,8 +373,11 @@ variables take precedence over those defined in `.gitlab-ci.yml`.
## Unsupported variables
There are cases where some variables cannot be used in the context of a
-`.gitlab-ci.yml` definition (for example under `script`). Read more
-about which variables are [not supported](where_variables_can_be_used.md).
+`.gitlab-ci.yml` definition (for example under `script`). Read more about which variables are [not supported](where_variables_can_be_used.md).
+
+## Where variables can be used
+
+Click [here](where_variables_can_be_used.md) for a section that describes where and how the different types of variables can be used.
## Advanced use
@@ -390,7 +396,7 @@ Protected variables can be added by going to your project's
Once you set them, they will be available for all subsequent pipelines.
-### Limiting environment scopes of environment variables **(PREMIUM)**
+### Limiting environment scopes of environment variables
You can limit the environment scope of a variable by
[defining which environments][envs] it can be available for.
@@ -481,81 +487,86 @@ Below you can find supported syntax reference:
1. Equality matching using a string
- > Example: `$VARIABLE == "some value"`
+ Examples:
- > Example: `$VARIABLE != "some value"` (introduced in GitLab 11.11)
+ - `$VARIABLE == "some value"`
+ - `$VARIABLE != "some value"` (introduced in GitLab 11.11)
- You can use equality operator `==` or `!=` to compare a variable content to a
- string. We support both, double quotes and single quotes to define a string
- value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
- are supported. `"some value" == $VARIABLE` is correct too.
+ You can use equality operator `==` or `!=` to compare a variable content to a
+ string. We support both, double quotes and single quotes to define a string
+ value, so both `$VARIABLE == "some value"` and `$VARIABLE == 'some value'`
+ are supported. `"some value" == $VARIABLE` is correct too.
1. Checking for an undefined value
- > Example: `$VARIABLE == null`
+ Examples:
- > Example: `$VARIABLE != null` (introduced in GitLab 11.11)
+ - `$VARIABLE == null`
+ - `$VARIABLE != null` (introduced in GitLab 11.11)
- It sometimes happens that you want to check whether a variable is defined
- or not. To do that, you can compare a variable to `null` keyword, like
- `$VARIABLE == null`. This expression is going to evaluate to truth if
- variable is not defined when `==` is used, or to falsey if `!=` is used.
+ It sometimes happens that you want to check whether a variable is defined
+ or not. To do that, you can compare a variable to `null` keyword, like
+ `$VARIABLE == null`. This expression is going to evaluate to truth if
+ variable is not defined when `==` is used, or to falsey if `!=` is used.
1. Checking for an empty variable
- > Example: `$VARIABLE == ""`
+ Examples:
- > Example: `$VARIABLE != ""` (introduced in GitLab 11.11)
+ - `$VARIABLE == ""`
+ - `$VARIABLE != ""` (introduced in GitLab 11.11)
- If you want to check whether a variable is defined, but is empty, you can
- simply compare it against an empty string, like `$VAR == ''` or non-empty
- string `$VARIABLE != ""`.
+ If you want to check whether a variable is defined, but is empty, you can
+ simply compare it against an empty string, like `$VAR == ''` or non-empty
+ string `$VARIABLE != ""`.
1. Comparing two variables
- > Example: `$VARIABLE_1 == $VARIABLE_2`
+ Examples:
- > Example: `$VARIABLE_1 != $VARIABLE_2` (introduced in GitLab 11.11)
+ - `$VARIABLE_1 == $VARIABLE_2`
+ - `$VARIABLE_1 != $VARIABLE_2` (introduced in GitLab 11.11)
- It is possible to compare two variables. This is going to compare values
- of these variables.
+ It is possible to compare two variables. This is going to compare values
+ of these variables.
1. Variable presence check
- > Example: `$STAGING`
+ Example: `$STAGING`
- If you only want to create a job when there is some variable present,
- which means that it is defined and non-empty, you can simply use
- variable name as an expression, like `$STAGING`. If `$STAGING` variable
- is defined, and is non empty, expression will evaluate to truth.
- `$STAGING` value needs to a string, with length higher than zero.
- Variable that contains only whitespace characters is not an empty variable.
+ If you only want to create a job when there is some variable present,
+ which means that it is defined and non-empty, you can simply use
+ variable name as an expression, like `$STAGING`. If `$STAGING` variable
+ is defined, and is non empty, expression will evaluate to truth.
+ `$STAGING` value needs to a string, with length higher than zero.
+ Variable that contains only whitespace characters is not an empty variable.
1. Pattern matching (introduced in GitLab 11.0)
- > Example: `$VARIABLE =~ /^content.*/`
+ Examples:
- > Example: `$VARIABLE_1 !~ /^content.*/` (introduced in GitLab 11.11)
+ - `$VARIABLE =~ /^content.*/`
+ - `$VARIABLE_1 !~ /^content.*/` (introduced in GitLab 11.11)
- It is possible perform pattern matching against a variable and regular
- expression. Expression like this evaluates to truth if matches are found
- when using `=~`. It evaluates to truth if matches are not found when `!~` is used.
+ It is possible perform pattern matching against a variable and regular
+ expression. Expression like this evaluates to truth if matches are found
+ when using `=~`. It evaluates to truth if matches are not found when `!~` is used.
- Pattern matching is case-sensitive by default. Use `i` flag modifier, like
- `/pattern/i` to make a pattern case-insensitive.
+ Pattern matching is case-sensitive by default. Use `i` flag modifier, like
+ `/pattern/i` to make a pattern case-insensitive.
1. Conjunction / Disjunction ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27925) in GitLab 12.0)
- > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
-
- > Example: `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+ Examples:
- > Example: `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+ - `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 == "something"`
+ - `$VARIABLE1 =~ /^content.*/ && $VARIABLE2 =~ /thing$/ && $VARIABLE3`
+ - `$VARIABLE1 =~ /^content.*/ || $VARIABLE2 =~ /thing$/ && $VARIABLE3`
- It is possible to join multiple conditions using `&&` or `||`. Any of the otherwise
- supported syntax may be used in a conjunctive or disjunctive statement.
- Precedence of operators follows standard Ruby 2.5 operation
- [precedence](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html).
+ It is possible to join multiple conditions using `&&` or `||`. Any of the otherwise
+ supported syntax may be used in a conjunctive or disjunctive statement.
+ Precedence of operators follows standard Ruby 2.5 operation
+ [precedence](https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html).
## Debug tracing
diff --git a/doc/ci/variables/img/custom_variables_output.png b/doc/ci/variables/img/custom_variables_output.png
index 29f5c63b3d9..797e9ec07b9 100644
--- a/doc/ci/variables/img/custom_variables_output.png
+++ b/doc/ci/variables/img/custom_variables_output.png
Binary files differ
diff --git a/doc/ci/variables/img/new_custom_variables_example.png b/doc/ci/variables/img/new_custom_variables_example.png
index efe104efe4c..bb60e6bab21 100644
--- a/doc/ci/variables/img/new_custom_variables_example.png
+++ b/doc/ci/variables/img/new_custom_variables_example.png
Binary files differ
diff --git a/doc/ci/variables/img/override_variable_manual_pipeline.png b/doc/ci/variables/img/override_variable_manual_pipeline.png
index 3c8c59720cf..c77c5cb7764 100644
--- a/doc/ci/variables/img/override_variable_manual_pipeline.png
+++ b/doc/ci/variables/img/override_variable_manual_pipeline.png
Binary files differ
diff --git a/doc/ci/variables/img/variable_types_usage_example.png b/doc/ci/variables/img/variable_types_usage_example.png
index 0e8bde891fe..c2ae32fd048 100644
--- a/doc/ci/variables/img/variable_types_usage_example.png
+++ b/doc/ci/variables/img/variable_types_usage_example.png
Binary files differ
diff --git a/doc/ci/variables/predefined_variables.md b/doc/ci/variables/predefined_variables.md
index 49543c57886..409f7d62538 100644
--- a/doc/ci/variables/predefined_variables.md
+++ b/doc/ci/variables/predefined_variables.md
@@ -30,7 +30,7 @@ future GitLab releases.**
| `CI_BUILDS_DIR` | all | 11.10 | Top-level directory where builds are executed. |
| `CI_CONCURRENT_ID` | all | 11.10 | Unique ID of build execution within a single executor. |
| `CI_CONCURRENT_PROJECT_ID` | all | 11.10 | Unique ID of build execution within a single executor and project. |
-| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a push request. Only populated when there is a merge request associated with the pipeline. |
+| `CI_COMMIT_BEFORE_SHA` | 11.2 | all | The previous latest commit present on a branch before a merge request. Only populated when there is a merge request associated with the pipeline. |
| `CI_COMMIT_DESCRIPTION` | 10.8 | all | The description of the commit: the message without first line, if the title is shorter than 100 characters; full message in other case. |
| `CI_COMMIT_MESSAGE` | 10.8 | all | The full commit message. |
| `CI_COMMIT_REF_NAME` | 9.0 | all | The branch or tag name for which project is built |
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 001f951ebb8..f7a67931793 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -505,7 +505,7 @@ Feature.enable(:allow_unsafe_ruby_regexp)
### `only`/`except` (advanced)
CAUTION: **Warning:**
-This an _alpha_ feature, and it is subject to change at any time without
+This is an _alpha_ feature, and it is subject to change at any time without
prior notice!
GitLab supports both simple and complex strategies, so it's possible to use an
@@ -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.
@@ -1665,6 +1679,84 @@ You can ask your administrator to
[flip this switch](../../administration/job_artifacts.md#validation-for-dependencies)
and bring back the old behavior.
+### `needs`
+
+> Introduced in GitLab 12.2.
+
+The `needs:` keyword enables executing jobs out-of-order, allowing you to implement
+a [directed acyclic graph](../directed_acyclic_graph/index.md) in your `.gitlab-ci.yml`.
+
+This lets you run some jobs without waiting for other ones, disregarding stage ordering
+so you can have multiple stages running concurrently.
+
+Let's consider the following example:
+
+```yaml
+linux:build:
+ stage: build
+
+mac:build:
+ stage: build
+
+linux:rspec:
+ stage: test
+ needs: [linux:build]
+
+linux:rubocop:
+ stage: test
+ needs: [linux:build]
+
+mac:rspec:
+ stage: test
+ needs: [mac:build]
+
+mac:rubocop:
+ stage: test
+ needs: [mac:build]
+
+production:
+ stage: deploy
+```
+
+This example creates three paths of execution:
+
+- Linux path: the `linux:rspec` and `linux:rubocop` jobs will be run as soon
+ as the `linux:build` job finishes without waiting for `mac:build` to finish.
+
+- macOS path: the `mac:rspec` and `mac:rubocop` jobs will be run as soon
+ as the `mac:build` job finishes, without waiting for `linux:build` to finish.
+
+- The `production` job will be executed as soon as all previous jobs
+ finish; in this case: `linux:build`, `linux:rspec`, `linux:rubocop`,
+ `mac:build`, `mac:rspec`, `mac:rubocop`.
+
+#### Requirements and limitations
+
+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 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.
+1. If you use `dependencies:` with `needs:`, it's important that you
+ do not mark a job as having a dependency on something that won't
+ have been run at the time it needs it. It's better to use both
+ keywords in this case so that GitLab handles the ordering appropriately.
+1. It is impossible for now to have `needs: []` (empty needs),
+ the job always needs to depend on something, unless this is the job
+ 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 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.
+1. For self-managed users, the feature must be turned on using the `ci_dag_support`
+ feature flag. The `ci_dag_limit_needs` option, if set, will limit the number of
+ jobs that a single job can need to `50`. If unset, the limit is `5`.
+
### `coverage`
> [Introduced][ce-7447] in GitLab 8.17.
@@ -1771,18 +1863,41 @@ sequentially from `job_name 1/N` to `job_name N/N`.
For every job, `CI_NODE_INDEX` and `CI_NODE_TOTAL` [environment variables](../variables/README.md#predefined-environment-variables) are set.
-A simple example:
+Marking a job to be run in parallel requires only a simple addition to your configuration file:
-```yaml
-test:
- script: rspec
- parallel: 5
+```diff
+ test:
+ script: rspec
++ parallel: 5
```
-
TIP: **Tip:**
Parallelize tests suites across parallel jobs.
Different languages have different tools to facilitate this.
+A simple example using [Sempahore Test Boosters](https://github.com/renderedtext/test-boosters) and RSpec to run some Ruby tests:
+
+```ruby
+# Gemfile
+source 'https://rubygems.org'
+
+gem 'rspec'
+gem 'semaphore_test_boosters'
+```
+
+```yaml
+test:
+ parallel: 3
+ script:
+ - bundle
+ - bundle exec rspec_booster --job $CI_NODE_INDEX/$CI_NODE_TOTAL
+```
+
+CAUTION: **Caution:**
+Please be aware that semaphore_test_boosters reports usages statistics to the author.
+
+You can then navigate to the **Jobs** tab of a new pipeline build and see your RSpec
+job split into three separate jobs.
+
### `trigger` **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
@@ -2445,20 +2560,20 @@ There are three possible values: `none`, `normal`, and `recursive`:
- `normal` means that only the top-level submodules will be included. It is
equivalent to:
- ```
- git submodule sync
- git submodule update --init
- ```
+ ```
+ git submodule sync
+ git submodule update --init
+ ```
- `recursive` means that all submodules (including submodules of submodules)
will be included. This feature needs Git v1.8.1 and later. When using a
GitLab Runner with an executor not based on Docker, make sure the Git version
meets that requirement. It is equivalent to:
- ```
- git submodule sync --recursive
- git submodule update --init --recursive
- ```
+ ```
+ git submodule sync --recursive
+ git submodule update --init --recursive
+ ```
Note that for this feature to work correctly, the submodules must be configured
(in `.gitmodules`) with either:
diff --git a/doc/customization/branded_login_page.md b/doc/customization/branded_login_page.md
index b892f59d777..afcc2b71284 100644
--- a/doc/customization/branded_login_page.md
+++ b/doc/customization/branded_login_page.md
@@ -1,19 +1,38 @@
-# Changing the appearance of the login page
+---
+type: howto
+---
-GitLab offers a way to put your company's identity on the login page of your GitLab server and make it a branded login page.
+# Changing the logo and description on the login page
-By default, the page shows the GitLab logo and description.
+You can customize the login page of your GitLab server to show the logo and
+description of your organization.
+
+By default, the page shows the GitLab logo and description:
![default_login_page](branded_login_page/default_login_page.png)
-## Changing the appearance of the login page
+To customize the login page:
-Navigate to the **Admin** area and go to the **Appearance** page.
+1. Navigate to the **Admin** area and go to the **Appearance** page.
+1. Fill in your desired Title and Description. You can also choose an image file
+ of the logo for your organization.
-Fill in the required details like Title, Description and upload the company logo.
+ ![appearance](branded_login_page/appearance.png)
-![appearance](branded_login_page/appearance.png)
+1. Save your changes.
-After saving the page, your GitLab login page will have the details you filled in:
+Your GitLab login page will display the details you provided:
![company_login_page](branded_login_page/custom_sign_in.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/branded_page_and_email_header.md b/doc/customization/branded_page_and_email_header.md
index 9a0f0b382fa..370c1461d30 100644
--- a/doc/customization/branded_page_and_email_header.md
+++ b/doc/customization/branded_page_and_email_header.md
@@ -1,15 +1,37 @@
-# Changing the logo on the overall page and email header
+---
+type: howto
+---
-Navigate to the **Admin** area and go to the **Appearance** page.
+# Changing the navigation bar and email header logo
-Upload the custom logo (**Header logo**) in the section **Navigation bar**.
+You can customize the logo that appears in email headers and in the navigation
+bar on pages that are displayed by your GitLab server.
-![appearance](branded_page_and_email_header/appearance.png)
+1. Navigate to the **Admin** area and go to the **Appearance** page, then locate
+ the **Navigation bar** section.
+1. For the **Header Logo**, choose an image file of the logo for your
+ organization.
-After saving the page, your GitLab navigation bar will contain the custom logo:
+ ![appearance](branded_page_and_email_header/appearance.png)
+
+1. Save your changes.
+
+Your GitLab navigation bar will display the custom logo:
![custom_brand_header](branded_page_and_email_header/custom_brand_header.png)
-The GitLab pipeline emails will also have the custom logo:
+The GitLab pipeline emails will also display the custom logo:
![custom_email_header](branded_page_and_email_header/custom_email_header.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/favicon.md b/doc/customization/favicon.md
index 45a18159b5e..dbde6e8c03b 100644
--- a/doc/customization/favicon.md
+++ b/doc/customization/favicon.md
@@ -1,16 +1,37 @@
+---
+type: howto
+---
+
# Changing the favicon
> [Introduced][ce-14497] in GitLab 11.0.
[ce-14497]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14497
-Navigate to the **Admin** area and go to the **Appearance** page.
+You can customize the favicon (the icon displayed in your web browser's
+address bar and web page tabs) for your GitLab server.
+
+1. Navigate to the **Admin** area and go to the **Appearance** page, then
+ locate the **Favicon** section.
+1. Upload an image file of your favicon.
-Upload the custom favicon (**Favicon**) in the section **Favicon**.
+ ![appearance](favicon/appearance.png)
-![appearance](favicon/appearance.png)
+1. Save your changes.
-After saving the page, the new favicon will be shown in the browser. The main
-favicon as well as the CI status icons will show the custom icon:
+Your new favicon will display in the browser. The main favicon and the CI
+status icons will show the custom icon:
![custom_favicon](favicon/custom_favicon.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/help_message.md b/doc/customization/help_message.md
index c2e592d03bf..a4d8f295750 100644
--- a/doc/customization/help_message.md
+++ b/doc/customization/help_message.md
@@ -1,13 +1,36 @@
-# GitLab Help custom text
+---
+type: howto
+---
-In larger organizations it is useful to have information about who has the responsibility of maintaining the company GitLab server.
+# Customizing the 'Help' and login page messages
-1. Navigate to the admin area, click on **Preferences** and expand **Help page**.
+In large organizations, it is useful to have information about who maintains
+the company GitLab server. You can customize and display this information on
+the GitLab login page and on the GitLab server's `/help` page.
-1. Under **Help text** fill in the required information about the person(s) administering GitLab or any other information relevant to your needs.
+1. Navigate to the **Admin** area, then click on **Preferences** and expand
+ **Help page**.
+1. Under **Help page text**, fill in the required information about the
+ person(s) administering GitLab. This text can also contain any other
+ information that you wish to display to users.
- ![help message](help_message/help_text.png)
+ ![help message](help_message/help_text.png)
-1. After saving the page this information will be shown on the GitLab login page and on the GitLab `/help` page (e.g., <https://gitlab.com/help>).
+1. Save your changes.
- ![help text on help page](help_message/help_text_on_help_page.png)
+The information you entered will be shown on the GitLab login page and on the
+GitLab `/help` page (e.g., <https://gitlab.com/help>).
+
+![help text on help page](help_message/help_text_on_help_page.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/help_message/help_text.png b/doc/customization/help_message/help_text.png
index 99697a106bf..5fcabcdb757 100644
--- a/doc/customization/help_message/help_text.png
+++ b/doc/customization/help_message/help_text.png
Binary files differ
diff --git a/doc/customization/index.md b/doc/customization/index.md
index 0198059297f..f17a2d80e2c 100644
--- a/doc/customization/index.md
+++ b/doc/customization/index.md
@@ -1,18 +1,18 @@
---
+type: index
description: Learn how to customize GitLab's appearance for self-managed installations.
---
# Customizing GitLab's appearance **(CORE ONLY)**
-For GitLab self-managed instances, it's possible to customize
-a few pages.
+For GitLab self-managed instances, you can customize the page logo,
+email headers, favicon, and several other aspects of GitLab's appearance.
-Read through the following documents to adjust GitLab's
-look and feel to meet your needs:
+The following pages explain how to customize the appearance of your instance:
-- [Custom login page](branded_login_page.md)
-- [Custom header and email logo](branded_page_and_email_header.md)
-- [Custom favicon](favicon.md)
-- [Libravatar](libravatar.md)
-- [New project page](new_project_page.md)
-- [Custom `/help` message](help_message.md) \ No newline at end of file
+- [Changing the logo and description on the login page](branded_login_page.md)
+- [Changing the navigation bar and email header logo](branded_page_and_email_header.md)
+- [Changing the favicon](favicon.md)
+- [Customizing the new project page](new_project_page.md)
+- [Customizing the `/help` and login page messages](help_message.md)
+- [Using the Libravatar service with GitLab](libravatar.md)
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
index adaa120a37e..ebf711e105b 100644
--- a/doc/customization/issue_and_merge_request_template.md
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter'
+redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter'
---
-This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter).
+This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter).
diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md
index e618f3be2fe..1c3bf877fa1 100644
--- a/doc/customization/libravatar.md
+++ b/doc/customization/libravatar.md
@@ -1,14 +1,20 @@
-# Use Libravatar service with GitLab
+---
+type: howto
+---
-GitLab by default supports [Gravatar](https://gravatar.com) avatar service.
-Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is
-[heavily based on gravatar](https://wiki.libravatar.org/api/).
+# Using the Libravatar service with GitLab
-This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server.
+GitLab by default supports the [Gravatar](https://gravatar.com) avatar service.
+
+Libravatar is another service that delivers your avatar (profile picture) to
+other websites. The Libravatar API is
+[heavily based on gravatar](https://wiki.libravatar.org/api/), so you can
+easily switch to the Libravatar avatar service or even a self-hosted Libravatar
+server.
## Configuration
-In [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
@@ -29,12 +35,14 @@ the configuration options as follows:
ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
-### Self-hosted
+### Self-hosted Libravatar server
-If you are [running your own libravatar service](https://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration
-but the important part is to provide the same placeholders so GitLab can parse the URL correctly.
+If you are [running your own libravatar service](https://wiki.libravatar.org/running_your_own/),
+the URL will be different in the configuration, but you must provide the same
+placeholders so GitLab can parse the URL correctly.
-For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is
+For example, you host a service on `http://libravatar.example.com` and the
+`plain_url` you need to supply in `gitlab.yml` is
`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon`
@@ -42,37 +50,52 @@ For example, you host a service on `http://libravatar.example.com` the `plain_ur
In `/etc/gitlab/gitlab.rb`:
-#### For http
+#### For HTTP
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
-#### For https
+#### For HTTPS
```ruby
gitlab_rails['gravatar_enabled'] = true
gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon"
```
-Run `sudo gitlab-ctl reconfigure` for changes to take effect.
+Then run `sudo gitlab-ctl reconfigure` for the changes to take effect.
## Default URL for missing images
-[Libravatar supports different sets](https://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service.
-
-In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set.
-For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
+[Libravatar supports different sets](https://wiki.libravatar.org/api/) of
+missing images for user email addresses that are not found on the Libravatar
+service.
-## Usage examples
+In order to use a set other than `identicon`, replace the `&d=identicon`
+portion of the URL with another supported set.
+For example, you can use the `retro` set, in which case the URL would look like:
+`plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"`
-### For Microsoft Office 365
+## Usage examples for Microsoft Office 365
-If your users are Office 365-users, the "GetPersonaPhoto" service can be used. Note that this service requires login, so this use case is
-most useful in a corporate installation, where all users have access to Office 365.
+If your users are Office 365 users, the `GetPersonaPhoto` service can be used.
+Note that this service requires a login, so this use case is most useful in a
+corporate installation where all users have access to Office 365.
```ruby
gitlab_rails['gravatar_plain_url'] = 'http://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=%{email}&size=HR120x120'
gitlab_rails['gravatar_ssl_url'] = 'https://outlook.office365.com/owa/service.svc/s/GetPersonaPhoto?email=%{email}&size=HR120x120'
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/new_project_page.md b/doc/customization/new_project_page.md
index 148bf9512c6..43b95a76d08 100644
--- a/doc/customization/new_project_page.md
+++ b/doc/customization/new_project_page.md
@@ -1,20 +1,38 @@
+---
+type: howto
+---
+
# Customizing the new project page
-It is possible to add a markdown-formatted message to your GitLab
-new project page.
+You can add a markdown-formatted message to your GitLab new project page.
By default, the new project page shows a sidebar with general information:
-![](new_project_page/default_new_project_page.png)
+![default_new_project_page](new_project_page/default_new_project_page.png)
+
+To customize the information in the sidebar:
+
+1. Navigate to the **Admin** area and go to the **Appearance** page, then
+ locate the **New project pages** section.
+1. Fill in your new project project guidelines:
+
+ ![appearance_settings](new_project_page/appearance_settings.png)
-## Changing the appearance of the new project page
+1. Save the page.
-Navigate to the **Admin** area and go to the **Appearance** page.
+Your new project page will show the customized guidelines in the sidebar, below
+the general information:
-Fill in your project guidelines:
+![custom_new_project_page](new_project_page/custom_new_project_page.png)
-![](new_project_page/appearance_settings.png)
+<!-- ## Troubleshooting
-After saving the page, your new project page will show the guidelines in the sidebar, below the general information:
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-![](new_project_page/custom_new_project_page.png)
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/customization/system_header_and_footer_messages/appearance.png b/doc/customization/system_header_and_footer_messages/appearance.png
index fd315bb6c07..d5a66bcb9f1 100644
--- a/doc/customization/system_header_and_footer_messages/appearance.png
+++ b/doc/customization/system_header_and_footer_messages/appearance.png
Binary files differ
diff --git a/doc/development/README.md b/doc/development/README.md
index a74770ae383..6281bb809ff 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -3,7 +3,7 @@ comments: false
description: 'Learn how to contribute to GitLab.'
---
-# GitLab development guides
+# Contributor and Development Docs
## Get started!
@@ -17,6 +17,7 @@ description: 'Learn how to contribute to GitLab.'
- [GitLab core team & GitLab Inc. contribution process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md)
- [Generate a changelog entry with `bin/changelog`](changelog.md)
- [Code review guidelines](code_review.md) for reviewing code and having code reviewed
+- [Database review guidelines](database_review.md) for reviewing database-related changes and complex SQL queries
- [Automatic CE->EE merge](automatic_ce_ee_merge.md)
- [Guidelines for implementing Enterprise Edition features](ee_features.md)
- [Security process for developers](https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md#security-releases-critical-non-critical-as-a-developer)
@@ -63,6 +64,7 @@ description: 'Learn how to contribute to GitLab.'
- [Routing](routing.md)
- [Repository mirroring](repository_mirroring.md)
- [Git LFS](lfs.md)
+- [Developing against interacting components or features](interacting_components.md)
## Performance guides
@@ -111,6 +113,10 @@ description: 'Learn how to contribute to GitLab.'
- [Database helper modules](database_helpers.md)
- [Code comments](code_comments.md)
+## Case studies
+
+- [Database case study: Filtering by label](filtering_by_label.md)
+
## Integration guides
- [Jira Connect app](integrations/jira_connect.md)
@@ -144,6 +150,10 @@ description: 'Learn how to contribute to GitLab.'
- [Go Guidelines](go_guide/index.md)
+## Shell Scripting guides
+
+- [Shell scripting standards and style guidelines](shell_scripting_guide/index.md)
+
## Other GitLab Development Kit (GDK) guides
- [Run full Auto DevOps cycle in a GDK instance](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/auto_devops.md)
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index 091edb6ac40..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 ..]
```
@@ -568,7 +573,7 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v
gitlabhq (includes Unicorn and Sidekiq logs)
-- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally.
+- `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `git_json.log` and `unicorn.stderr.log` normally.
gitlab-shell
@@ -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 423b35a9e3a..001a92790e1 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -171,6 +171,19 @@ Now, every time you create an MR for CE and EE:
job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
before merging your changes into CE.
+## 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
+[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
+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.
+
## FAQ
### How does automatic merging work?
@@ -187,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/chaos_endpoints.md b/doc/development/chaos_endpoints.md
index b3406275937..961520db7d8 100644
--- a/doc/development/chaos_endpoints.md
+++ b/doc/development/chaos_endpoints.md
@@ -36,6 +36,10 @@ Replace `secret` with your own secret token.
Once you have enabled the chaos endpoints and restarted the application, you can start testing using the endpoints.
+By default, when invoking a chaos endpoint, the web worker process which receives the request will handle it. This means, for example, that if the Kill
+operation is invoked, the Puma or Unicorn worker process handling the request will be killed. To test these operations in Sidekiq, the `async` parameter on
+each endpoint can be set to `true`. This will run the chaos process in a Sidekiq worker.
+
## Memory leaks
To simulate a memory leak in your application, use the `/-/chaos/leakmem` endpoint.
@@ -47,12 +51,14 @@ The memory is not retained after the request finishes. Once the request has comp
GET /-/chaos/leakmem
GET /-/chaos/leakmem?memory_mb=1024
GET /-/chaos/leakmem?memory_mb=1024&duration_s=50
+GET /-/chaos/leakmem?memory_mb=1024&duration_s=50&async=true
```
-| Attribute | Type | Required | Description |
-| ------------ | ------- | -------- | ---------------------------------------------------------------------------------- |
-| `memory_mb` | integer | no | How much memory, in MB, should be leaked. Defaults to 100MB. |
+| Attribute | Type | Required | Description |
+| ------------ | ------- | -------- | ------------------------------------------------------------------------------------ |
+| `memory_mb` | integer | no | How much memory, in MB, should be leaked. Defaults to 100MB. |
| `duration_s` | integer | no | Minimum duration_s, in seconds, that the memory should be retained. Defaults to 30s. |
+| `async` | boolean | no | Set to true to leak memory in a Sidekiq background worker process |
```bash
curl http://localhost:3000/-/chaos/leakmem?memory_mb=1024&duration_s=10 --header 'X-Chaos-Secret: secret'
@@ -63,17 +69,19 @@ curl http://localhost:3000/-/chaos/leakmem?memory_mb=1024&duration_s=10&token=se
This endpoint attempts to fully utilise a single core, at 100%, for the given period.
-Depending on your rack server setup, your request may timeout after a predermined period (normally 60 seconds).
+Depending on your rack server setup, your request may timeout after a predetermined period (normally 60 seconds).
If you're using Unicorn, this is done by killing the worker process.
```
GET /-/chaos/cpu_spin
GET /-/chaos/cpu_spin?duration_s=50
+GET /-/chaos/cpu_spin?duration_s=50&async=true
```
| Attribute | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------------------------------------------- |
-| `duration_s` | integer | no | Duration, in seconds, that the core will be utilised. Defaults to 30s |
+| `duration_s` | integer | no | Duration, in seconds, that the core will be utilized. Defaults to 30s |
+| `async` | boolean | no | Set to true to consume CPU in a Sidekiq background worker process |
```bash
curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60 --header 'X-Chaos-Secret: secret'
@@ -85,18 +93,20 @@ curl http://localhost:3000/-/chaos/cpu_spin?duration_s=60&token=secret
This endpoint attempts to fully utilise a single core, and interleave it with DB request, for the given period.
This endpoint can be used to model yielding execution to another threads when running concurrently.
-Depending on your rack server setup, your request may timeout after a predermined period (normally 60 seconds).
+Depending on your rack server setup, your request may timeout after a predetermined period (normally 60 seconds).
If you're using Unicorn, this is done by killing the worker process.
```
GET /-/chaos/db_spin
GET /-/chaos/db_spin?duration_s=50
+GET /-/chaos/db_spin?duration_s=50&async=true
```
-| Attribute | Type | Required | Description |
-| ------------ | ------- | -------- | --------------------------------------------------------------------- |
-| `interval_s` | float | no | Interval, in seconds, for every DB request. Defaults to 1s |
-| `duration_s` | integer | no | Duration, in seconds, that the core will be utilised. Defaults to 30s |
+| Attribute | Type | Required | Description |
+| ------------ | ------- | -------- | --------------------------------------------------------------------------- |
+| `interval_s` | float | no | Interval, in seconds, for every DB request. Defaults to 1s |
+| `duration_s` | integer | no | Duration, in seconds, that the core will be utilized. Defaults to 30s |
+| `async` | boolean | no | Set to true to perform the operation in a Sidekiq background worker process |
```bash
curl http://localhost:3000/-/chaos/db_spin?interval_s=1&duration_s=60 --header 'X-Chaos-Secret: secret'
@@ -112,11 +122,13 @@ As with the CPU Spin endpoint, this may lead to your request timing out if durat
```
GET /-/chaos/sleep
GET /-/chaos/sleep?duration_s=50
+GET /-/chaos/sleep?duration_s=50&async=true
```
| Attribute | Type | Required | Description |
| ------------ | ------- | -------- | ---------------------------------------------------------------------- |
| `duration_s` | integer | no | Duration, in seconds, that the request will sleep for. Defaults to 30s |
+| `async` | boolean | no | Set to true to sleep in a Sidekiq background worker process |
```bash
curl http://localhost:3000/-/chaos/sleep?duration_s=60 --header 'X-Chaos-Secret: secret'
@@ -132,8 +144,13 @@ Since this endpoint uses the `KILL` signal, the worker is not given a chance to
```
GET /-/chaos/kill
+GET /-/chaos/kill?async=true
```
+| Attribute | Type | Required | Description |
+| ------------ | ------- | -------- | ---------------------------------------------------------------------- |
+| `async` | boolean | no | Set to true to kill a Sidekiq background worker process |
+
```bash
curl http://localhost:3000/-/chaos/kill --header 'X-Chaos-Secret: secret'
curl http://localhost:3000/-/chaos/kill?token=secret
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 35ce6b8cbe4..b7d74b17eb3 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -45,9 +45,9 @@ page, with these behaviours:
1. It will not pick people whose [GitLab status](../user/profile/index.md#current-status)
contains the string 'OOO'.
-2. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
+1. [Trainee maintainers](https://about.gitlab.com/handbook/engineering/workflow/code-review/#trainee-maintainer)
are three times as likely to be picked as other reviewers.
-3. It always picks the same reviewers and maintainers for the same
+1. It always picks the same reviewers and maintainers for the same
branch name (unless their OOO status changes, as in point 1). It
removes leading `ce-` and `ee-`, and trailing `-ce` and `-ee`, so
that it can be stable for backport branches.
@@ -58,20 +58,21 @@ As described in the section on the responsibility of the maintainer below, you
are recommended to get your merge request approved and merged by maintainer(s)
from teams other than your own.
- 1. If your merge request includes backend changes [^1], it must be
- **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
- 1. If your merge request includes database migrations or changes to expensive queries [^2], it must be
- **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_database)**.
- 1. If your merge request includes frontend changes [^1], it must be
- **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
- 1. If your merge request includes UX changes [^1], it must be
- **approved by a [UX team member][team]**.
- 1. If your merge request includes adding a new JavaScript library [^1], it must be
- **approved by a [frontend lead][team]**.
- 1. If your merge request includes adding a new UI/UX paradigm [^1], it must be
- **approved by a [UX lead][team]**.
- 1. If your merge request includes a new dependency or a filesystem change, it must be
- **approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details.
+1. If your merge request includes backend changes [^1], it must be
+ **approved by a [backend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_backend)**.
+1. If your merge request includes database migrations or changes to expensive queries [^2], it must be
+ **approved by a [database maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_database)**.
+ Read the [database review guidelines](database_review.md) for more details.
+1. If your merge request includes frontend changes [^1], it must be
+ **approved by a [frontend maintainer](https://about.gitlab.com/handbook/engineering/projects/#gitlab-ce_maintainers_frontend)**.
+1. If your merge request includes UX changes [^1], it must be
+ **approved by a [UX team member][team]**.
+1. If your merge request includes adding a new JavaScript library [^1], it must be
+ **approved by a [frontend lead][team]**.
+1. If your merge request includes adding a new UI/UX paradigm [^1], it must be
+ **approved by a [UX lead][team]**.
+1. If your merge request includes a new dependency or a filesystem change, it must be
+ **approved by a [Distribution team member][team]**. See how to work with the [Distribution team](https://about.gitlab.com/handbook/engineering/dev-backend/distribution/) for more details.
#### Security requirements
@@ -341,8 +342,7 @@ Enterprise Edition instance. This has some implications:
- [Background migrations](background_migrations.md) run in Sidekiq, and
should only be done for migrations that would take an extreme amount of
time at GitLab.com scale.
-1. **Sidekiq workers**
- [cannot change in a backwards-incompatible way](sidekiq_style_guide.md#removing-or-renaming-queues):
+1. **Sidekiq workers** [cannot change in a backwards-incompatible way](sidekiq_style_guide.md#removing-or-renaming-queues):
1. Sidekiq queues are not drained before a deploy happens, so there will be
workers in the queue from the previous version of GitLab.
1. If you need to change a method signature, try to do so across two releases,
@@ -365,6 +365,31 @@ Enterprise Edition instance. This has some implications:
1. **Filesystem access** can be slow, so try to avoid
[shared files](shared_files.md) when an alternative solution is available.
+## Examples
+
+How code reviews are conducted can surprise new contributors. Here are some examples of code reviews that should help to orient you as to what to expect.
+
+**["Modify `DiffNote` to reuse it for Designs"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/13703):**
+It contained everything from nitpicks around newlines to reasoning
+about what versions for designs are, how we should compare them
+if there was no previous version of a certain file (parent vs.
+blank `sha` vs empty tree).
+
+**["Support multi-line suggestions"](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25211)**:
+The MR itself consists of a collaboration between FE and BE,
+and documenting comments from the author for the reviewer.
+There's some nitpicks, some questions for information, and
+towards the end, a security vulnerability.
+
+**["Allow multiple repositories per project"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10251)**:
+ZJ referred to the other projects (workhorse) this might impact,
+suggested some improvements for consistency. And James' comments
+helped us with overall code quality (using delegation, `&.` those
+types of things), and making the code more robust.
+
+**["Support multiple assignees for merge requests"](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/10161)**:
+A good example of collaboration on an MR touching multiple parts of the codebase. Nick pointed out interesting edge cases, James Lopes also joined in raising concerns on import/export feature.
+
### Credits
Largely based on the [thoughtbot code review guide].
diff --git a/doc/development/contributing/community_roles.md b/doc/development/contributing/community_roles.md
index 3296cb173d7..7d2d1b77a0e 100644
--- a/doc/development/contributing/community_roles.md
+++ b/doc/development/contributing/community_roles.md
@@ -1,4 +1,4 @@
-### Community members & roles
+# Community members & roles
GitLab community members and their privileges/responsibilities.
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index abe11b8d1a8..b2e3ef7bf63 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -60,40 +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.
-
-Within those team labels, we also have the ~backend and ~frontend labels to
-indicate if an issue needs backend work, frontend work, or both.
+- ~Quality
Team labels are always capitalized so that they show up as the first label for
any issue.
@@ -102,33 +80,11 @@ any issue.
Stage labels specify which [DevOps stage][devops-stages] the issue belongs to.
-The current stage labels are:
-
-- ~"devops::manage"
-- ~"devops::plan"
-- ~"devops::create"
-- ~"devops::verify"
-- ~"devops::package"
-- ~"devops::release"
-- ~"devops::configure"
-- ~"devops::monitor"
-- ~"devops::secure"
-- ~"devops::defend"
-- ~"devops::growth"
-- ~"devops::enablement"
+The current stage labels can be found by [searching the labels list for `devops::`](https://gitlab.com/groups/gitlab-org/-/labels?search=devops%3A%3A).
These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
and thus are mutually exclusive.
-They differ from the [Team labels](#team-labels) because teams may work on
-issues outside their stage.
-
-Normally there is a 1:1 relationship between Stage labels and Team labels, but
-any issue can be picked up by any team, depending on current priorities.
-So, an issue labeled ~"devops:create" may be scheduled by the ~Plan team, for
-example. In such cases, it's usual to include both team labels so each team can
-be aware of the progress.
-
The Stage labels are used to generate the [direction pages][direction-pages] automatically.
[devops-stages]: https://about.gitlab.com/direction/#devops-stages
@@ -138,50 +94,21 @@ The Stage labels are used to generate the [direction pages][direction-pages] aut
Group labels specify which [groups][structure-groups] the issue belongs to.
-The current group labels are:
-
-- ~"group::access"
-- ~"group::measure"
-- ~"group::source code"
-- ~"group::knowledge"
-- ~"group::editor"
-- ~"group::gitaly"
-- ~"group::gitter"
-- ~"group::team planning"
-- ~"group::enterprise planning"
-- ~"group::certify"
-- ~"group::ci and runner"
-- ~"group::testing"
-- ~"group::package"
-- ~"group::progressive delivery"
-- ~"group::release management"
-- ~"group::autodevops and kubernetes"
-- ~"group::serverless and paas"
-- ~"group::apm"
-- ~"group::health"
-- ~"group::static analysis"
-- ~"group::dynamic analysis"
-- ~"group::software composition analysis"
-- ~"group::runtime application security"
-- ~"group::threat management"
-- ~"group::application infrastructure security"
-- ~"group::activation"
-- ~"group::adoption"
-- ~"group::upsell"
-- ~"group::retention"
-- ~"group::fulfillment"
-- ~"group::telemetry"
-- ~"group::distribution"
-- ~"group::geo"
-- ~"group::memory"
-- ~"group::ecosystem"
-
+The current group labels can be found by [searching the labels list for `group::`](https://gitlab.com/groups/gitlab-org/-/labels?search=group%3A%3A).
+
These labels are [scoped labels](../../user/project/labels.md#scoped-labels-premium)
and thus are mutually exclusive.
-Groups are nested beneath a particular stage, so only one stage label and one group label
-can be applied to a single issue. You can find the groups listed in the
-[Product Categories pages][product-categories].
+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.
+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).
+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
[product-categories]: https://about.gitlab.com/handbook/product/categories/
@@ -248,7 +175,7 @@ If a bug seems to fall between two severity labels, assign it to the higher-seve
- Example(s) of ~S1
- Data corruption/loss.
- Security breach.
- - Unable to create an issue or merge request.
+ - Unable to create an issue or merge request.
- Unable to add a comment or thread to the issue or merge request.
- Example(s) of ~S2
- Cannot submit changes through the web IDE but the commandline works.
@@ -259,7 +186,7 @@ If a bug seems to fall between two severity labels, assign it to the higher-seve
- Example(s) of ~S4
- Label colors are incorrect.
- UI elements are not fully aligned.
-
+
## Label for community contributors
Issues that are beneficial to our users, 'nice to haves', that we currently do
@@ -294,7 +221,7 @@ know how difficult the issue is. Additionally:
as suitable for people that have never contributed to GitLab before on the
[Up For Grabs campaign](http://up-for-grabs.net)
- We encourage people that have never contributed to any open source project to
- look for [`Accepting merge requests` issues with a weight of 1][firt-timers]
+ look for [`Accepting merge requests` issues with a weight of 1][first-timers]
If you've decided that you would like to work on an issue, please @-mention
the [appropriate product manager](https://about.gitlab.com/handbook/product/#who-to-talk-to-for-what)
@@ -307,8 +234,8 @@ GitLab team members who apply the ~"Accepting merge requests" label to an issue
should update the issue description with a responsible product manager, inviting
any potential community contributor to @-mention per above.
-[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight
-[firt-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=0&sort=weight&weight=1
+[up-for-grabs]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight
+[first-timers]: https://gitlab.com/groups/gitlab-org/-/issues?state=opened&label_name[]=Accepting+merge+requests&assignee_id=None&sort=weight&weight=1
## Issue triaging
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 3325d3e074e..4e9c5c81379 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -103,7 +103,8 @@ If you would like quick feedback on your merge request feel free to mention some
from the [core team](https://about.gitlab.com/community/core-team/) or one of the
[merge request coaches](https://about.gitlab.com/team/). When having your code reviewed
and when reviewing merge requests, please keep the [code review guidelines](../code_review.md)
-in mind.
+in mind. And if your code also makes changes to the database, or does expensive queries,
+check the [database review guidelines](../database_review.md).
### Keep it simple
@@ -142,6 +143,37 @@ If the guidelines are not met, the MR will not pass the
[Danger checks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/danger/commit_messages/Dangerfile).
For more information see [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
+Example commit message template that can be used on your machine that embodies the above (guide for [how to apply template](https://codeinthehole.com/tips/a-useful-template-for-commit-messages/)):
+
+```text
+# (If applied, this commit will...) <subject> (Max 50 char)
+# |<---- Using a Maximum Of 50 Characters ---->|
+
+
+# Explain why this change is being made
+# |<---- Try To Limit Each Line to a Maximum Of 72 Characters ---->|
+
+# Provide links or keys to any relevant tickets, articles or other resources
+# Use issues and merge requests' full URLs instead of short references,
+# as they are displayed as plain text outside of GitLab
+
+# --- COMMIT END ---
+# --------------------
+# Remember to
+# Capitalize the subject line
+# Use the imperative mood in the subject line
+# Do not end the subject line with a period
+# Subject must contain at least 3 words
+# Separate subject from body with a blank line
+# Commits that change 30 or more lines across at least 3 files must
+# describe these changes in the commit body
+# Do not use Emojis
+# Use the body to explain what and why vs. how
+# Can use multiple lines with "-" for bullet points in body
+# For more information: https://chris.beams.io/posts/git-commit/
+# --------------------
+```
+
## Contribution acceptance criteria
To make sure that your merge request can be approved, please ensure that it meets
diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md
index 5c6ea1f469d..7832850a9f0 100644
--- a/doc/development/contributing/style_guides.md
+++ b/doc/development/contributing/style_guides.md
@@ -23,6 +23,7 @@
1. Code should be written in [US English][us-english]
1. [Go](../go_guide/index.md)
1. [Python](../python_guide/index.md)
+1. [Shell scripting](../shell_scripting_guide/index.md)
This is also the style used by linting tools such as
[RuboCop](https://github.com/rubocop-hq/rubocop) and [Hound CI](https://houndci.com).
diff --git a/doc/development/database_debugging.md b/doc/development/database_debugging.md
index 0311eda1ff1..eb3b227473b 100644
--- a/doc/development/database_debugging.md
+++ b/doc/development/database_debugging.md
@@ -9,31 +9,31 @@ An easy first step is to search for your error in Slack or google "GitLab (my er
Available `RAILS_ENV`
- - `production` (generally not for your main GDK db, but you may need this for e.g. omnibus)
- - `development` (this is your main GDK db)
- - `test` (used for tests like rspec)
+- `production` (generally not for your main GDK db, but you may need this for e.g. omnibus)
+- `development` (this is your main GDK db)
+- `test` (used for tests like rspec)
## Nuke everything and start over
If you just want to delete everything and start over with an empty DB (~1 minute):
- - `bundle exec rake db:reset RAILS_ENV=development`
+- `bundle exec rake db:reset RAILS_ENV=development`
If you just want to delete everything and start over with dummy data (~40 minutes). This also does `db:reset` and runs DB-specific migrations:
- - `bundle exec rake dev:setup RAILS_ENV=development`
+- `bundle exec rake dev:setup RAILS_ENV=development`
If your test DB is giving you problems, it is safe to nuke it because it doesn't contain important data:
- - `bundle exec rake db:reset RAILS_ENV=test`
+- `bundle exec rake db:reset RAILS_ENV=test`
## Migration wrangling
- - `bundle exec rake db:migrate RAILS_ENV=development`: Execute any pending migrations that you may have picked up from a MR
- - `bundle exec rake db:migrate:status RAILS_ENV=development`: Check if all migrations are `up` or `down`
- - `bundle exec rake db:migrate:down VERSION=20170926203418 RAILS_ENV=development`: Tear down a migration
- - `bundle exec rake db:migrate:up VERSION=20170926203418 RAILS_ENV=development`: Set up a migration
- - `bundle exec rake db:migrate:redo VERSION=20170926203418 RAILS_ENV=development`: Re-run a specific migration
+- `bundle exec rake db:migrate RAILS_ENV=development`: Execute any pending migrations that you may have picked up from a MR
+- `bundle exec rake db:migrate:status RAILS_ENV=development`: Check if all migrations are `up` or `down`
+- `bundle exec rake db:migrate:down VERSION=20170926203418 RAILS_ENV=development`: Tear down a migration
+- `bundle exec rake db:migrate:up VERSION=20170926203418 RAILS_ENV=development`: Set up a migration
+- `bundle exec rake db:migrate:redo VERSION=20170926203418 RAILS_ENV=development`: Re-run a specific migration
## Manually access the database
@@ -45,12 +45,12 @@ bundle exec rails dbconsole RAILS_ENV=development
bundle exec rails db RAILS_ENV=development
```
- - `\q`: Quit/exit
- - `\dt`: List all tables
- - `\d+ issues`: List columns for `issues` table
- - `CREATE TABLE board_labels();`: Create a table called `board_labels`
- - `SELECT * FROM schema_migrations WHERE version = '20170926203418';`: Check if a migration was run
- - `DELETE FROM schema_migrations WHERE version = '20170926203418';`: Manually remove a migration
+- `\q`: Quit/exit
+- `\dt`: List all tables
+- `\d+ issues`: List columns for `issues` table
+- `CREATE TABLE board_labels();`: Create a table called `board_labels`
+- `SELECT * FROM schema_migrations WHERE version = '20170926203418';`: Check if a migration was run
+- `DELETE FROM schema_migrations WHERE version = '20170926203418';`: Manually remove a migration
## FAQ
diff --git a/doc/development/database_review.md b/doc/development/database_review.md
new file mode 100644
index 00000000000..3f1b359cb0b
--- /dev/null
+++ b/doc/development/database_review.md
@@ -0,0 +1,134 @@
+# Database Review Guidelines
+
+This page is specific to database reviews. Please refer to our
+[code review guide](code_review.md) for broader advice and best
+practices for code review in general.
+
+## General process
+
+A database review is required for:
+
+- Changes that touch the database schema or perform data migrations,
+ including files in:
+ - `db/`
+ - `lib/gitlab/background_migration/`
+- Changes to the database tooling, e.g.:
+ - migration or ActiveRecord helpers in `lib/gitlab/database/`
+ - load balancing
+- Changes that produce SQL queries that are beyond the obvious. It is
+ generally up to the author of a merge request to decide whether or
+ not complex queries are being introduced and if they require a
+ database review.
+
+A database reviewer is expected to look out for obviously complex
+queries in the change and review those closer. If the author does not
+point out specific queries for review and there are no obviously
+complex queries, it is enough to concentrate on reviewing the
+migration only.
+
+It is preferable to review queries in SQL form and generally accepted
+to ask the author to translate any ActiveRecord queries in SQL form
+for review.
+
+### Roles and process
+
+A Merge Request author's role is to:
+
+- Decide whether a database review is needed.
+- If database review is needed, add the ~database label.
+- Use the [database changes](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/merge_request_templates/Database%20changes.md)
+ merge request template, or include the appropriate items in the MR description.
+
+A database **reviewer**'s role is to:
+
+- Perform a first-pass review on the MR and suggest improvements to the author.
+- Once satisfied, relabel the MR with ~"database::reviewed", approve it, and
+ reassign MR to the database **maintainer** suggested by Reviewer
+ Roulette.
+
+A database **maintainer**'s role is to:
+
+- Perform the final database review on the MR.
+- Discuss further improvements or other relevant changes with the
+ database reviewer and the MR author.
+- Finally approve the MR and relabel the MR with ~"database::approved"
+- Merge the MR if no other approvals are pending or pass it on to
+ other maintainers as required (frontend, backend, docs).
+
+### Distributing review workload
+
+Review workload is distributed using [reviewer roulette](code_review.md#reviewer-roulette)
+([example](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25181#note_147551725)).
+The MR author should then co-assign the suggested database
+**reviewer**. When they give their sign-off, they will hand over to
+the suggested database **maintainer**.
+
+If reviewer roulette didn't suggest a database reviewer & maintainer,
+make sure you have applied the ~database label and rerun the
+`danger-review` CI job, or pick someone from the
+[`@gl-database` team](https://gitlab.com/groups/gl-database/-/group_members).
+
+### How to prepare for speedy database reviews
+
+In order to make reviewing easier and therefore faster, please consider preparing a comment
+and details for a database reviewer:
+
+- Provide queries in SQL form rather than ActiveRecord.
+- Format any queries with a SQL query formatter, for example with [sqlformat.darold.net](http://sqlformat.darold.net).
+- Consider providing query plans via a link to [explain.depesz.com](https://explain.depesz.com) or another tool instead of textual form.
+- For query changes, it is best to provide the SQL query along with a plan *before* and *after* the change. This helps to spot differences quickly.
+- When providing query plans, make sure to use good parameter values, so that the query executed is a good example and also hits enough data. Usually, the `gitlab-org` namespace (`namespace_id = 9970`) and the `gitlab-org/gitlab-ce` project (`project_id = 13083`) provides enough data to serve as a good example.
+
+### How to review for database
+
+- Check migrations
+ - Review relational modeling and design choices
+ - Review migrations follow [database migration style guide](migration_style_guide.md),
+ for example
+ - [Check ordering of columns](ordering_table_columns.md)
+ - [Check indexes are present for foreign keys](migration_style_guide.md#adding-foreign-key-constraints)
+ - Ensure that migrations execute in a transaction or only contain
+ concurrent index/foreign key helpers (with transactions disabled)
+ - Check consistency with `db/schema.rb` and that migrations are [reversible](migration_style_guide.md#reversibility)
+ - Check queries timing (If any): Queries executed in a migration
+ need to fit comfortable within `15s` - preferably much less than that - on GitLab.com.
+- Check [background migrations](background_migrations.md):
+ - For data migrations, establish a time estimate for execution
+ - They should only be used when migrating data in larger tables.
+ - If a single `update` is below than `1s` the query can be placed
+ directly in a regular migration (inside `db/migrate`).
+ - Review queries (for example, make sure batch sizes are fine)
+ - Establish a time estimate for execution
+ - Because execution time can be longer than for a regular migration,
+ it's suggested to treat background migrations as post migrations:
+ place them in `db/post_migrate` instead of `db/migrate`. Keep in mind
+ that post migrations are executed post-deployment in production.
+- Check [timing guidelines for migrations](#timing-guidelines-for-migrations)
+- Query performance
+ - Check for any obviously complex queries and queries the author specifically
+ points out for review (if any)
+ - If not present yet, ask the author to provide SQL queries and query plans
+ (e.g. by using [chatops](understanding_explain_plans.md#chatops) or direct
+ database access)
+ - For given queries, review parameters regarding data distribution
+ - [Check query plans](understanding_explain_plans.md) and suggest improvements
+ to queries (changing the query, schema or adding indexes and similar)
+ - General guideline is for queries to come in below 100ms execution time
+ - If queries rely on prior migrations that are not present yet on production
+ (eg indexes, columns), you can use a [one-off instance from the restore
+ pipeline](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd)
+ in order to establish a proper testing environment.
+
+### Timing guidelines for migrations
+
+In general, migrations for a single deploy shouldn't take longer than
+1 hour for GitLab.com. The following guidelines are not hard rules, they were
+estimated to keep migration timing to a minimum.
+
+NOTE: **Note:** Keep in mind that all runtimes should be measured against GitLab.com.
+
+| Migration Type | Execution Time Recommended | Notes |
+|----|----|---|
+| Regular migrations on `db/migrate` | `3 minutes` | A valid exception are index creation as this can take a long time. |
+| Post migrations on `db/post_migrate` | `10 minutes` | |
+| Background migrations | --- | Since these are suitable for larger tables, it's not possible to set a precise timing guideline, however, any query must stay well below `10s` of execution time. |
diff --git a/doc/development/diffs.md b/doc/development/diffs.md
index 5655398c886..ac0b8555360 100644
--- a/doc/development/diffs.md
+++ b/doc/development/diffs.md
@@ -133,4 +133,4 @@ File diff will be suppressed (technically different from collapsed, but behaves
Diff Viewers, which can be found on `models/diff_viewer/*` are classes used to map metadata about each type of Diff File. It has information
whether it's a binary, which partial should be used to render it or which File extensions this class accounts for.
-`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type in order to check if it can be rendered. \ No newline at end of file
+`DiffViewer::Base` validates _blobs_ (old and new versions) content, extension and file type in order to check if it can be rendered.
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index ca29353ecbe..ac93ada5a4b 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -121,27 +121,27 @@ All reviewers can help ensure accuracy, clarity, completeness, and adherence to
- **Prior to merging**, documentation changes committed by the developer must be reviewed by:
- 1. **The code reviewer** for the MR, to confirm accuracy, clarity, and completeness.
- 1. Optionally: Others involved in the work, such as other devs or the PM.
- 1. Optionally: The technical writer for the DevOps stage. If not prior to merging, the technical writer will review after the merge.
- This helps us ensure that the developer has time to merge good content by the freeze, and that it can be further refined by the release, if needed.
- - To decide whether to request this review before the merge, consider the amount of time left before the code freeze, the size of the change,
- and your degree of confidence in having users of an RC use your docs as written.
- - Pre-merge tech writer reviews should be most common when the code is complete well in advance of the freeze and/or for larger documentation changes.
- - You can request a review and if there is not sufficient time to complete it prior to the freeze,
- the maintainer can merge the current doc changes (if complete) and create a follow-up doc review issue.
- - The technical writer can also help decide what docs to merge before the freeze and whether to work on further changes in a follow up MR.
- - **To request a pre-merge technical writer review**, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
- - **To request a post-merge technical writer review**, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change.
- 1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability.
+ 1. **The code reviewer** for the MR, to confirm accuracy, clarity, and completeness.
+ 1. Optionally: Others involved in the work, such as other devs or the PM.
+ 1. Optionally: The technical writer for the DevOps stage. If not prior to merging, the technical writer will review after the merge.
+ This helps us ensure that the developer has time to merge good content by the freeze, and that it can be further refined by the release, if needed.
+ - To decide whether to request this review before the merge, consider the amount of time left before the code freeze, the size of the change,
+ and your degree of confidence in having users of an RC use your docs as written.
+ - Pre-merge tech writer reviews should be most common when the code is complete well in advance of the freeze and/or for larger documentation changes.
+ - You can request a review and if there is not sufficient time to complete it prior to the freeze,
+ the maintainer can merge the current doc changes (if complete) and create a follow-up doc review issue.
+ - The technical writer can also help decide what docs to merge before the freeze and whether to work on further changes in a follow up MR.
+ - **To request a pre-merge technical writer review**, assign the writer listed for the applicable [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages).
+ - **To request a post-merge technical writer review**, [create an issue for one using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR that makes the doc change.
+ 1. **The maintainer** who is assigned to merge the MR, to verify clarity, completeness, and quality, to the best of their ability.
- Upon merging, if a technical writer review has not been performed and there is not yet a linked issue for a follow-up review, the maintainer should [create an issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review), link it from the MR, and
mention the original MR author in the new issue. Alternatively, the maintainer can ask the MR author to create and link this issue before the MR is merged.
- After merging, documentation changes are reviewed by:
- 1. The technical writer--**if** their review was not performed prior to the merge.
- 2. Optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used).
+ 1. The technical writer -- **if** their review was not performed prior to the merge.
+ 1. Optionally: by the PM (for accuracy and to ensure it's consistent with the vision for how the product will be used).
Any party can raise the item to the PM for review at any point: the dev, the technical writer, or the PM, who can request/plan a review at the outset.
### Technical Writer role
diff --git a/doc/development/documentation/improvement-workflow.md b/doc/development/documentation/improvement-workflow.md
index a12c3d5ea7b..80fbd4b6427 100644
--- a/doc/development/documentation/improvement-workflow.md
+++ b/doc/development/documentation/improvement-workflow.md
@@ -52,9 +52,9 @@ To request a post-merge review, [create an issue for one using the Doc Review te
**3. Maintainer**
1. Review by assigned maintainer, who can always request/require the above reviews. Maintainer review can occur before or after a technical writer review.
-2. Ensure a release milestone of the format XX.Y is set. If the freeze for that release has begun, add the label `pick into <XX.Y>` unless this change is not required for the release. In that case, simply change the milestone.
-3. If EE and CE MRs exist, merge the EE MR first, then the CE MR.
-4. After merging, if there has not been a technical writer review and an issue for a follow-up review was not already created and linked from the MR, [create the issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR.
+1. Ensure a release milestone of the format XX.Y is set. If the freeze for that release has begun, add the label `pick into <XX.Y>` unless this change is not required for the release. In that case, simply change the milestone.
+1. If EE and CE MRs exist, merge the EE MR first, then the CE MR.
+1. After merging, if there has not been a technical writer review and an issue for a follow-up review was not already created and linked from the MR, [create the issue using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and link it from the MR.
## Other ways to help
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index 36a2f47a55b..c9ae00d148a 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -57,35 +57,22 @@ See the [Structure](styleguide.md#structure) section of the [Documentation Style
We currently maintain two sets of docs: one in the
[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc) repo and
one in [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/doc).
-They are similar, and most pages are identical, but they are different repositories.
-With the single codebase effort, we want to make those two sets identical, so when the
-time comes to have only one codebase, we'll be ready.
-
-Here are some links to get you up to speed with the current effort:
-
-- [CE/EE codebases blueprint](https://about.gitlab.com/handbook/engineering/infrastructure/blueprint/ce-ee-codebases/)
-- [CE/EE codebases merge design](https://about.gitlab.com/handbook/engineering/infrastructure/design/merge-ce-ee-codebases/)
-- [Single docs codebase epic](https://gitlab.com/groups/gitlab-org/-/epics/199)
-- [Issue board of related issues](https://gitlab.com/groups/gitlab-org/-/boards/981090?&label_name[]=Documentation&label_name[]=single%20codebase)
-- [Related merge requests](https://gitlab.com/groups/gitlab-org/-/merge_requests?scope=all&utf8=%E2%9C%93&state=all&label_name[]=Documentation&label_name[]=single%20codebase)
-- [Visualize the existing diffs](https://leipert-projects.gitlab.io/is-gitlab-pretty-yet/diff/?search=%5Edoc)
+They are identical, but they are different repositories. When the
+time comes to have only one codebase for the GitLab project, we'll be ready.
### CE first
-After a given documentation path is aligned across CE and EE, all merge requests
-affecting that path must be submitted to CE, regardless of the content it has.
-This means that:
+All merge requests for documentation must be submitted to CE, regardless of the content
+it has. This means that:
-- For **EE-only docs changes**, you only have to submit a CE MR.
+- For **EE-only docs changes**, you only have to submit an MR in the CE project.
- For **EE-only features** that touch both the code and the docs, you have to submit
- an EE MR containing all changes, and a CE MR containing only the docs changes
+ an EE MR containing all code changes, and a CE MR containing only the docs changes
and without a changelog entry.
This might seem like a duplicate effort, but it's only for the short term.
-A list of the already aligned docs can be found in
-[the epic description](https://gitlab.com/groups/gitlab-org/-/epics/199#ee-specific-lines-check).
-Since the docs will be combined, it's crucial to add the relevant
+Since the CE and EE docs are combined, it's crucial to add the relevant
[product badges](styleguide.md#product-badges) for all EE documentation, so that
we can discern which features belong to which tier.
@@ -94,19 +81,9 @@ we can discern which features belong to which tier.
There's a special test in place
([`ee_specific_check.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/scripts/ee_specific_check/ee_specific_check.rb)),
which, among others, checks and prevents creating/editing new files and directories
-in EE under `doc/`.
-
-We have a long list of documentation paths that are either whitelisted or not.
-Paths in the whitelist (not commented out) will not be subject to the test,
-which means you are allowed to create/change docs content in EE for the time
-being. The goal is to not have any doc whitelisted.
-
-At the time of this writing, the only items left to be aligned are the API docs:
-
-- `doc/api/*` ([issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/60045) / [merge request](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27491))
-
-Eventually, once all docs are aligned, we'll remove any doc reference from that
-script, so it catches everything.
+in EE under `doc/`. This should fail when changes to anything in `/doc` are submitted
+in an EE MR. To pass the test, simply remove the docs changes from the EE MR, and
+[submit them in CE](#ce-first).
## Changing document location
@@ -134,18 +111,18 @@ For example, if you were to move `doc/workflow/lfs/lfs_administration.md` to
1. Copy `doc/workflow/lfs/lfs_administration.md` to `doc/administration/lfs.md`
1. Replace the contents of `doc/workflow/lfs/lfs_administration.md` with:
- ```md
- This document was moved to [another location](../../administration/lfs.md).
- ```
+ ```md
+ This document was moved to [another location](../../administration/lfs.md).
+ ```
1. Find and replace any occurrences of the old location with the new one.
A quick way to find them is to use `git grep`. First go to the root directory
where you cloned the `gitlab-ce` repository and then do:
- ```sh
- git grep -n "workflow/lfs/lfs_administration"
- git grep -n "lfs/lfs_administration"
- ```
+ ```sh
+ git grep -n "workflow/lfs/lfs_administration"
+ git grep -n "lfs/lfs_administration"
+ ```
NOTE: **Note:**
If the document being moved has any Disqus comments on it, there are extra steps
@@ -193,7 +170,7 @@ Disqus uses an identifier per page, and for docs.gitlab.com, the page identifier
is configured to be the page URL. Therefore, when we change the document location,
we need to preserve the old URL as the same Disqus identifier.
-To do that, add to the frontmatter the variable `redirect_from`,
+To do that, add to the frontmatter the variable `disqus_identifier`,
using the old URL as value. For example, let's say I moved the document
available under `https://docs.gitlab.com/my-old-location/README.html` to a new location,
`https://docs.gitlab.com/my-new-location/index.html`.
@@ -202,11 +179,11 @@ Into the **new document** frontmatter add the following:
```yaml
---
-redirect_from: 'https://docs.gitlab.com/my-old-location/README.html'
+disqus_identifier: 'https://docs.gitlab.com/my-old-location/README.html'
---
```
-Note: it is necessary to include the file name in the `redirect_from` URL,
+Note: it is necessary to include the file name in the `disqus_identifier` URL,
even if it's `index.html` or `README.html`.
## Branch naming
@@ -319,45 +296,45 @@ You can combine one or more of the following:
1. **Linking to an anchor link.** Use `anchor` as part of the `help_page_path`
method:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions', anchor: 'anchor-link')
+ ```
1. **Opening links in a new tab.** This should be the default behavior:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), target: '_blank'
+ ```
1. **Linking to a circle icon.** Usually used in settings where a long
description cannot be used, like near checkboxes. You can basically use
any font awesome icon, but prefer the `question-circle`:
- ```haml
- = link_to icon('question-circle'), help_page_path('user/permissions')
- ```
+ ```haml
+ = link_to icon('question-circle'), help_page_path('user/permissions')
+ ```
1. **Using a button link.** Useful in places where text would be out of context
with the rest of the page layout:
- ```haml
- = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
- ```
+ ```haml
+ = link_to 'Help page', help_page_path('user/permissions'), class: 'btn btn-info'
+ ```
1. **Using links inline of some text.**
- ```haml
- Description to #{link_to 'Help page', help_page_path('user/permissions')}.
- ```
+ ```haml
+ Description to #{link_to 'Help page', help_page_path('user/permissions')}.
+ ```
1. **Adding a period at the end of the sentence.** Useful when you don't want
the period to be part of the link:
- ```haml
- = succeed '.' do
- Learn more in the
- = link_to 'Help page', help_page_path('user/permissions')
- ```
+ ```haml
+ = succeed '.' do
+ Learn more in the
+ = link_to 'Help page', help_page_path('user/permissions')
+ ```
### GitLab `/help` tests
@@ -488,15 +465,17 @@ Currently, the following tests are in place:
that all cURL examples in API docs use the full switches. It's recommended
to [check locally](#previewing-the-changes-live) before pushing to GitLab by executing the command
`bundle exec nanoc check internal_links` on your local
- [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory.
+ [`gitlab-docs`](https://gitlab.com/gitlab-org/gitlab-docs) directory. In addition,
+ `docs-lint` also runs [markdownlint](styleguide.md#markdown-rules) to ensure the
+ markdown is consistent across all documentation.
1. [`ee_compat_check`](../automatic_ce_ee_merge.md#avoiding-ce-ee-merge-conflicts-beforehand) (runs on CE only):
- When you submit a merge request to GitLab Community Edition (CE),
- there is this additional job that runs against Enterprise Edition (EE)
- and checks if your changes can apply cleanly to the EE codebase.
- If that job fails, read the instructions in the job log for what to do next.
- As CE is merged into EE once a day, it's important to avoid merge conflicts.
- Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
- essential to avoid them.
+ When you submit a merge request to GitLab Community Edition (CE),
+ there is this additional job that runs against Enterprise Edition (EE)
+ and checks if your changes can apply cleanly to the EE codebase.
+ If that job fails, read the instructions in the job log for what to do next.
+ As CE is merged into EE once a day, it's important to avoid merge conflicts.
+ Submitting an EE-equivalent merge request cherry-picking all commits from CE to EE is
+ essential to avoid them.
1. [`ee-files-location-check`/`ee-specific-lines-check`](#ee-specific-lines-check) (runs on EE only):
This test ensures that no new files/directories are created/changed in EE.
All docs should be submitted in CE instead, regardless the tier they are on.
@@ -559,15 +538,16 @@ A file with `proselint` configuration must be placed in a
#### `markdownlint`
`markdownlint` checks that certain rules ([example](https://github.com/DavidAnson/markdownlint/blob/master/README.md#rules--aliases))
- are followed for Markdown syntax.
- Our [Documentation Style Guide](styleguide.md) and [Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/)
- elaborate on which choices must be made when selecting Markdown syntax for
- GitLab documentation. This tool helps catch deviations from those guidelines.
+are followed for Markdown syntax. Our [Documentation Style Guide](styleguide.md) and
+[Markdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/)
+elaborate on which choices must be made when selecting Markdown syntax for GitLab
+documentation. This tool helps catch deviations from those guidelines, and matches the
+tests run on the documentation by [`docs-lint`](#testing).
`markdownlint` can be used [on the command line](https://github.com/igorshubovych/markdownlint-cli#markdownlint-cli--),
- either on a single Markdown file or on all Markdown files in a project. For example, to run
- `markdownlint` on all documentation in the [`gitlab-ce` project](https://gitlab.com/gitlab-org/gitlab-ce),
- run the following commands from within the `gitlab-ce` project:
+either on a single Markdown file or on all Markdown files in a project. For example, to run
+`markdownlint` on all documentation in the [`gitlab-ce` project](https://gitlab.com/gitlab-org/gitlab-ce),
+run the following commands from within the `gitlab-ce` project:
```sh
cd doc
@@ -597,7 +577,7 @@ The following sample `markdownlint` configuration modifies the available default
"line-length": false,
"no-trailing-punctuation": false,
"ol-prefix": { "style": "one" },
- "blanks-around-fences": false,
+ "blanks-around-fences": true,
"no-inline-html": {
"allowed_elements": [
"table",
@@ -612,11 +592,15 @@ The following sample `markdownlint` configuration modifies the available default
"a",
"strong",
"i",
- "div"
+ "div",
+ "b"
]
},
"hr-style": { "style": "---" },
- "fenced-code-language": false
+ "code-block-style": { "style": "fenced" },
+ "fenced-code-language": false,
+ "no-duplicate-header": { "allow_different_nesting": true },
+ "commands-show-output": false
}
```
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 1ca965f8ae9..c1e3eb9680b 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -36,17 +36,17 @@ For the Troubleshooting sections, people in GitLab Support can merge additions t
Include any media types/sources if the content is relevant to readers. You can freely include or link presentations, diagrams, videos, etc.; no matter who it was originally composed for, if it is helpful to any of our audiences, we can include it.
- - If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
- - Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
+- If you use an image that has a separate source file (for example, a vector or diagram format), link the image to the source file so that it may be reused or updated by anyone.
+- Do not copy and paste content from other sources unless it is a limited quotation with the source cited. Typically it is better to either rephrase relevant information in your own words or link out to the other source.
### No special types
-In the software industry, it is a best practice to organize documentatioin in different types. For example, [Divio recommends](https://www.divio.com/blog/documentation/):
+In the software industry, it is a best practice to organize documentation in different types. For example, [Divio recommends](https://www.divio.com/blog/documentation/):
1. Tutorials
-2. How-to guides
-3. Explanation
-4. Reference (for example, a glossary)
+1. How-to guides
+1. Explanation
+1. Reference (for example, a glossary)
At GitLab, we have so many product changes in our monthly releases that we can't afford to continually update multiple types of information.
If we have multiple types, the information will become outdated. Therefore, we have a [single template](structure.md) for documentation.
@@ -107,6 +107,22 @@ Hard-coded HTML is valid, although it's discouraged to be used while we have `/h
- Special styling is required.
- Reviewed and approved by a technical writer.
+### Markdown Rules
+
+GitLab ensures that the Markdown used across all documentation is consistent, as
+well as easy to review and maintain, by testing documentation changes with
+[Markdownlint (mdl)](https://github.com/markdownlint/markdownlint). This lint test
+checks many common problems with Markdown, and fails when any document has an issue
+with Markdown formatting that may cause the page to render incorrectly within GitLab.
+It will also fail when a document is using non-standard Markdown (which may render
+correctly, but is not the current standard in GitLab documentation).
+
+Each formatting issue that mdl checks has an associated [rule](https://github.com/markdownlint/markdownlint/blob/master/docs/RULES.md),
+and the rules that are currently enabled for GitLab documentation are listed in the
+[`.mdlrc.style`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.mdlrc.style) file.
+Configuration options are set in the [`.mdlrc`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.mdlrc.style)
+file.
+
## Structure
### Organize by topic, not by type
@@ -142,9 +158,9 @@ The table below shows what kind of documentation goes where.
### Working with directories and files
1. When you create a new directory, always start with an `index.md` file.
- Do not use another file name and **do not** create `README.md` files.
+ Do not use another file name and **do not** create `README.md` files.
1. **Do not** use special characters and spaces, or capital letters in file names,
- directory names, branch names, and anything that generates a path.
+ directory names, branch names, and anything that generates a path.
1. When creating a new document and it has more than one word in its name,
make sure to use underscores instead of spaces or dashes (`-`). For example,
a proper naming would be `import_projects_from_github.md`. The same rule
@@ -221,14 +237,14 @@ Do not include the same information in multiple places. [Link to a SSOT instead.
- Use sentence case for titles, headings, labels, menu items, and buttons.
- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
- ```md
- ## Header
+ ```md
+ ## Header
- Paragraph.
+ Paragraph.
- - List item 1
- - List item 2
- ```
+ - List item 1
+ - List item 2
+ ```
### Tables overlapping the TOC
@@ -267,32 +283,52 @@ Check specific punctuation rules for [list items](#list-items) below.
## List items
-- Always start list items with a capital letter.
+- Always start list items with a capital letter, unless they are parameters or commands
+ that are in backticks, or similar.
- Always leave a blank line before and after a list.
-- Begin a line with spaces (not tabs) to denote a subitem.
-- To nest subitems, indent them with two spaces.
-- To nest code blocks, indent them with four spaces.
-- Only use ordered lists when their items describe a sequence of steps to follow.
+- Begin a line with spaces (not tabs) to denote a [nested subitem](#nesting-inside-a-list-item).
+- Only use ordered lists when their items describe a sequence of steps to follow:
+
+ Do:
+
+ These are the steps to do something:
+
+ 1. First, do step 1
+ 1. Then, do step 2
+ 1. Finally, do step 3
+
+ Don't:
+
+ This is a list of different features:
+
+ 1. Feature 1
+ 1. Feature 2
+ 1. Feature 3
**Markup:**
- Use dashes (`-`) for unordered lists instead of asterisks (`*`).
-- Use the number one (`1`) for each item in an ordered list.
- When rendered, the list items will appear with sequential numbering.
+- Prefix `1.` to each item in an ordered list.
+ When rendered, the list items will appear with sequential numbering automatically.
**Punctuation:**
-- Do not add commas (`,`) or semicolons (`;`) to the end of a list item.
-- Only add periods to the end of a list item if the item consists of a complete sentence. The [definition of full sentence](https://www2.le.ac.uk/offices/ld/resources/writing/grammar/grammar-guides/sentence) is: _"a complete sentence always contains a verb, expresses a complete idea, and makes sense standing alone"_.
-- Be consistent throughout the list: if the majority of the items do not end in a period, do not end any of the items in a period, even if they consist of a complete sentence. The opposite is also valid: if the majority of the items end with a period, end all with a period.
+- Do not add commas (`,`) or semicolons (`;`) to the end of list items.
+- Only add periods to the end of a list item if the item consists of a complete sentence.
+ The [definition of full sentence](https://www2.le.ac.uk/offices/ld/resources/writing/grammar/grammar-guides/sentence)
+ is: _"a complete sentence always contains a verb, expresses a complete idea, and makes sense standing alone"_.
+- Be consistent throughout the list: if the majority of the items do not end in a period,
+ do not end any of the items in a period, even if they consist of a complete sentence.
+ The opposite is also valid: if the majority of the items end with a period, end
+ all with a period.
- Separate list items from explanatory text with a colon (`:`). For example:
- ```md
- The list is as follows:
+ ```md
+ The list is as follows:
- - First item: this explains the first item.
- - Second item: this explains the second item.
- ```
+ - First item: this explains the first item.
+ - Second item: this explains the second item.
+ ```
**Examples:**
@@ -314,12 +350,86 @@ Do:
- Let's say this is also a complete sentence.
- Not a complete sentence.
-Don't:
+Don't (third item should have a `.` to match the first and second items):
- Let's say this is a complete sentence.
- Let's say this is also a complete sentence.
- Not a complete sentence
+### Nesting inside a list item
+
+It is possible to nest items under a list item, so that they render with the same indentation
+as the list item. This can be done with:
+
+- [Code blocks](#code-blocks)
+- [Blockquotes](#blockquotes)
+- [Alert boxes](#alert-boxes)
+- [Images](#images)
+
+Items nested in lists should always align with the first character of the list item.
+In unordered lists (using `-`), this means two spaces for each level of indentation:
+
+~~~md
+- Unordered list item 1
+
+ A line nested using 2 spaces to align with the `U` above.
+
+- Unordered list item 2
+
+ > A quote block that will nest
+ > inside list item 2.
+
+- Unordered list item 3
+
+ ```text
+ a codeblock that will next inside list item 3
+ ```
+
+- Unordered list item 4
+
+ ![an image that will nest inside list item 4](image.png)
+~~~
+
+For ordered lists, use three spaces for each level of indentation:
+
+~~~md
+1. Ordered list item 1
+
+ A line nested using 3 spaces to align with the `O` above.
+
+1. Ordered list item 2
+
+ > A quote block that will nest
+ > inside list item 2.
+
+1. Ordered list item 3
+
+ ```text
+ a codeblock that will next inside list item 3
+ ```
+
+1. Ordered list item 4
+
+ ![an image that will nest inside list item 4](image.png)
+~~~
+
+You can nest full lists inside other lists using the same rules as above. If you wish
+to mix types, that is also possible, as long as you don't mix items at the same level:
+
+```
+1. Ordered list item one.
+1. Ordered list item two.
+ - Nested unordered list item one.
+ - Nested unordered list item two.
+1. Ordered list item three.
+
+- Unordered list item one.
+- Unordered list item two.
+ 1. Nested ordered list item one.
+ 1. Nested ordered list item two.
+- Unordered list item three.
+```
+
## Quotes
Valid for markdown content only, not for frontmatter entries:
@@ -504,16 +614,16 @@ To embed a video, follow the instructions below and make sure
you have your MR reviewed and approved by a technical writer.
1. Copy the code below and paste it into your markdown file.
- Leave a blank line above and below it. Do NOT edit the code
- (don't remove or add any spaces, etc).
+ Leave a blank line above and below it. Do NOT edit the code
+ (don't remove or add any spaces, etc).
1. On YouTube, visit the video URL you want to display. Copy
- the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
- and replace the video title and link in the line under `<div class="video-fallback">`.
+ the regular URL from your browser (`https://www.youtube.com/watch?v=VIDEO-ID`)
+ and replace the video title and link in the line under `<div class="video-fallback">`.
1. On YouTube, click **Share**, then **Embed**.
1. Copy the `<iframe>` source (`src`) **URL only**
- (`https://www.youtube.com/embed/VIDEO-ID`),
- and paste it, replacing the content of the `src` field in the
- `iframe` tag.
+ (`https://www.youtube.com/embed/VIDEO-ID`),
+ and paste it, replacing the content of the `src` field in the
+ `iframe` tag.
```html
leave a blank line here
@@ -545,15 +655,16 @@ nicely on different mobile devices.
## Code blocks
-- Always wrap code added to a sentence in inline code blocks (``` ` ```).
+- Always wrap code added to a sentence in inline code blocks (`` ` ``).
E.g., `.gitlab-ci.yml`, `git add .`, `CODEOWNERS`, `only: master`.
File names, commands, entries, and anything that refers to code should be added to code blocks.
To make things easier for the user, always add a full code block for things that can be
useful to copy and paste, as they can easily do it with the button on code blocks.
+- Add a blank line above and below code blocks.
- For regular code blocks, always use a highlighting class corresponding to the
language for better readability. Examples:
- ````md
+ ~~~md
```ruby
Ruby code
```
@@ -563,16 +674,17 @@ nicely on different mobile devices.
```
```md
- Markdown code
+ [Markdown code example](example.md)
```
```text
- Code for which no specific highlighting class is available.
+ Code or text for which no specific highlighting class is available.
```
- ````
+ ~~~
-- To display raw markdown instead of rendered markdown, use four backticks on their own lines around the
- markdown to display. See [example](https://gitlab.com/gitlab-org/gitlab-ce/blob/8c1991b9bb7e3b8d606481fdea316d633cfa5eb7/doc/development/documentation/styleguide.md#L275-287).
+- To display raw markdown instead of rendered markdown, you can use triple backticks
+ with `md`, like the `Markdown code` example above, unless you want to include triple
+ backticks in the code block as well. In that case, use triple tildes (`~~~`) instead.
- For a complete reference on code blocks, check the [Kramdown guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/#code-blocks).
## Alert boxes
@@ -595,7 +707,7 @@ In most cases, content considered for a note should be included:
#### When to use
Use a note when there is a reason that most or all readers who browse the
-section should see the content. That is, if missed, it’s likely to cause
+section should see the content. That is, if missed, it’s likely to cause
major trouble for a minority of users or significant trouble for a majority
of users.
@@ -731,24 +843,24 @@ a helpful link back to how the feature was developed.
- For features that need to declare the GitLab version that the feature was introduced. Text similar
to the following should be added immediately below the heading as a blockquote:
- ```md
- > Introduced in GitLab 11.3.
- ```
+ ```md
+ > Introduced in GitLab 11.3.
+ ```
- Whenever possible, version text should have a link to the issue, merge request, or epic that introduced the feature.
An issue is preferred over a merge request, and a merge request is preferred over an epic. For example:
- ```md
- > [Introduced](<link-to-issue>) in GitLab 11.3.
- ```
+ ```md
+ > [Introduced](<link-to-issue>) in GitLab 11.3.
+ ```
- If the feature is only available in GitLab Enterprise Edition, mention
the [paid tier](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers)
the feature is available in:
- ```md
- > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
- ```
+ ```md
+ > [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 11.3.
+ ```
### Removing version text
@@ -767,10 +879,10 @@ Other text includes deprecation notices and version-specific how-to information.
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
+- For GitLab Core and GitLab.com Free: `**(CORE)**`.
- For GitLab Starter and GitLab.com Bronze: `**(STARTER)**`.
- For GitLab Premium and GitLab.com Silver: `**(PREMIUM)**`.
- For GitLab Ultimate and GitLab.com Gold: `**(ULTIMATE)**`.
-- For GitLab Core and GitLab.com Free: `**(CORE)**`.
To exclude GitLab.com tiers (when the feature is not available in GitLab.com), add the
keyword "only":
@@ -782,6 +894,7 @@ keyword "only":
For GitLab.com only tiers (when the feature is not available for self-hosted instances):
+- For GitLab Free and higher tiers: `**(FREE ONLY)**`.
- For GitLab Bronze and higher tiers: `**(BRONZE ONLY)**`.
- For GitLab Silver and higher tiers: `**(SILVER ONLY)**`.
- For GitLab Gold: `**(GOLD ONLY)**`.
@@ -855,14 +968,14 @@ When there is a list of steps to perform, usually that entails editing the
configuration file and reconfiguring/restarting GitLab. In such case, follow
the style below as a guide:
-```md
+````md
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb`:
- ```ruby
- external_url "https://gitlab.example.com"
- ```
+ ```ruby
+ external_url "https://gitlab.example.com"
+ ```
1. Save the file and [reconfigure] GitLab for the changes to take effect.
@@ -872,17 +985,16 @@ the style below as a guide:
1. Edit `config/gitlab.yml`:
- ```yaml
- gitlab:
- host: "gitlab.example.com"
- ```
+ ```yaml
+ gitlab:
+ host: "gitlab.example.com"
+ ```
1. Save the file and [restart] GitLab for the changes to take effect.
-
[reconfigure]: path/to/administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart]: path/to/administration/restart_gitlab.md#installations-from-source
-```
+````
In this case:
@@ -901,9 +1013,9 @@ on this document. Further explanation is given below.
- Every method must have the REST API request. For example:
- ```
- GET /projects/:id/repository/branches
- ```
+ ```
+ GET /projects/:id/repository/branches
+ ```
- Every method must have a detailed
[description of the parameters](#method-description).
@@ -914,7 +1026,7 @@ on this document. Further explanation is given below.
The following can be used as a template to get started:
-````md
+~~~md
## Descriptive title
One or two sentence description of what endpoint does.
@@ -942,7 +1054,7 @@ Example response:
}
]
```
-````
+~~~
### Fake tokens
@@ -955,7 +1067,7 @@ You can use the following fake tokens as examples.
| Token type | Token value |
|:----------------------|:-------------------------------------------------------------------|
-| Private user token | `<your_access_token>` |
+| Private user token | `<your_access_token>` |
| Personal access token | `n671WNGecHugsdEDPsyo` |
| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` |
| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` |
@@ -970,7 +1082,7 @@ You can use the following fake tokens as examples.
### Method description
Use the following table headers to describe the methods. Attributes should
-always be in code blocks using backticks (``` ` ```).
+always be in code blocks using backticks (`` ` ``).
```md
| Attribute | Type | Required | Description |
@@ -1076,6 +1188,6 @@ curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --data "domain_
[cURL]: http://curl.haxx.se/ "cURL website"
[single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html
-[gfm]: https://docs.gitlab.com/ce/user/markdown.html#newlines "GitLab flavored markdown documentation"
+[gfm]: ../../user/markdown.md#newlines "GitLab flavored markdown documentation"
[ce-1242]: https://gitlab.com/gitlab-org/gitlab-ce/issues/1242
[doc-restart]: ../../administration/restart_gitlab.md "GitLab restart documentation"
diff --git a/doc/development/ee_features.md b/doc/development/ee_features.md
index 7131b717353..2217dedccd3 100644
--- a/doc/development/ee_features.md
+++ b/doc/development/ee_features.md
@@ -125,20 +125,24 @@ This also applies to views.
### EE features based on CE features
For features that build on existing CE features, write a module in the `EE`
-namespace and `prepend` it in the CE class, on the last line of the file that
-the class resides in. This makes conflicts less likely to happen during CE to EE
-merges because only one line is added to the CE class - the `prepend` line. For
-example, to prepend a module into the `User` class you would use the following
-approach:
+namespace and inject it in the CE class, on the last line of the file that the
+class resides in. This makes conflicts less likely to happen during CE to EE
+merges because only one line is added to the CE class - the line that injects
+the module. For example, to prepend a module into the `User` class you would use
+the following approach:
```ruby
class User < ActiveRecord::Base
# ... lots of code here ...
end
-User.prepend(EE::User)
+User.prepend_if_ee('EE::User')
```
+Do not use methods such as `prepend`, `extend`, and `include`. Instead, use
+`prepend_if_ee`, `extend_if_ee`, or `include_if_ee`. These methods take a
+_String_ containing the full module name as the argument, not the module itself.
+
Since the module would require an `EE` namespace, the file should also be
put in an `ee/` sub-directory. For example, we want to extend the user model
in EE, so we have a module called `::EE::User` put inside
@@ -255,7 +259,7 @@ class ApplicationController < ActionController::Base
# ...
end
-ApplicationController.prepend(EE::ApplicationController)
+ApplicationController.prepend_if_ee('EE::ApplicationController')
```
And create a new file in the `ee/` sub-directory with the altered
@@ -504,9 +508,9 @@ EE-specific LDAP classes in `ee/lib/ee/gitlab/ldap`.
### Code in `lib/api/`
-It can be very tricky to extend EE features by a single line of `prepend`,
-and for each different [Grape](https://github.com/ruby-grape/grape) feature,
-we might need different strategies to extend it. To apply different strategies
+It can be very tricky to extend EE features by a single line of `prepend_if_ee`,
+and for each different [Grape](https://github.com/ruby-grape/grape) feature, we
+might need different strategies to extend it. To apply different strategies
easily, we would use `extend ActiveSupport::Concern` in the EE module.
Put the EE module files following
@@ -543,12 +547,12 @@ constants.
We can define `params` and utilize `use` in another `params` definition to
include params defined in EE. However, we need to define the "interface" first
in CE in order for EE to override it. We don't have to do this in other places
-due to `prepend`, but Grape is complex internally and we couldn't easily do
-that, so we'll follow regular object-oriented practices that we define the
+due to `prepend_if_ee`, but Grape is complex internally and we couldn't easily
+do that, so we'll follow regular object-oriented practices that we define the
interface first here.
For example, suppose we have a few more optional params for EE. We can move the
-params out of the `Grape::API` class to a helper module, so we can `prepend` it
+params out of the `Grape::API` class to a helper module, so we can inject it
before it would be used in the class.
```ruby
@@ -583,7 +587,7 @@ module API
end
end
-API::Helpers::ProjectsHelpers.prepend(EE::API::Helpers::ProjectsHelpers)
+API::Helpers::ProjectsHelpers.prepend_if_ee('EE::API::Helpers::ProjectsHelpers')
```
We could override it in EE module:
@@ -624,7 +628,7 @@ module API
end
end
-API::JobArtifacts.prepend(EE::API::JobArtifacts)
+API::JobArtifacts.prepend_if_ee('EE::API::JobArtifacts')
```
And then we can follow regular object-oriented practices to override it:
@@ -677,7 +681,7 @@ module API
end
end
-API::MergeRequests.prepend(EE::API::MergeRequests)
+API::MergeRequests.prepend_if_ee('EE::API::MergeRequests')
```
Note that `update_merge_request_ee` doesn't do anything in CE, but
@@ -717,8 +721,8 @@ Sometimes we need to use different arguments for a particular API route, and we
can't easily extend it with an EE module because Grape has different context in
different blocks. In order to overcome this, we need to move the data to a class
method that resides in a separate module or class. This allows us to extend that
-module or class before its data is used, without having to place a `prepend` in
-the middle of CE code.
+module or class before its data is used, without having to place a
+`prepend_if_ee` in the middle of CE code.
For example, in one place we need to pass an extra argument to
`at_least_one_of` so that the API could consider an EE-only argument as the
@@ -739,7 +743,7 @@ module API
end
end
-API::MergeRequests::Parameters.prepend(EE::API::MergeRequests::Parameters)
+API::MergeRequests::Parameters.prepend_if_ee('EE::API::MergeRequests::Parameters')
# api/merge_requests.rb
module API
@@ -789,7 +793,7 @@ class Identity < ActiveRecord::Base
[:provider]
end
- prepend EE::Identity
+ prepend_if_ee('EE::Identity')
validates :extern_uid,
allow_blank: true,
@@ -841,7 +845,7 @@ class Identity < ActiveRecord::Base
end
end
-Identity::UniquenessScopes.prepend(EE::Identity::UniquenessScopes)
+Identity::UniquenessScopes.prepend_if_ee('EE::Identity::UniquenessScopes')
# app/models/identity.rb
class Identity < ActiveRecord::Base
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 0965db29557..635895051bc 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -148,6 +148,36 @@ 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
+
+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:
+
+* Indexing (writes) to multiple indexes, with different mappings
+* Switching to different index for searches (reads) on the fly
+
+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.
+
+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.
+
+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.
+
+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 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.
+
+If the current version is `v12p1`, and we need to create a new version for `v12p3`, the steps are as follows:
+
+1. Copy the entire folder of `v12p1` as `v12p3`
+1. Change the namespace for files under `v12p3` folder from `V12p1` to `V12p3` (which are still aliased to `Latest`)
+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
+
## Troubleshooting
### Getting `flood stage disk watermark [95%] exceeded`
diff --git a/doc/development/fe_guide/architecture.md b/doc/development/fe_guide/architecture.md
index 49b74b5ebcf..3d27f67a8a6 100644
--- a/doc/development/fe_guide/architecture.md
+++ b/doc/development/fe_guide/architecture.md
@@ -11,7 +11,7 @@ Architectural decisions should be accessible to everyone, so please document
them in the relevant Merge Request discussion or by updating our documentation
when appropriate.
-You can find the Frontend Architecture experts on the [team page](https://about.gitlab.com/company/team).
+You can find the Frontend Architecture experts on the [team page](https://about.gitlab.com/company/team/).
## Examples
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 0d9397c3bd5..6e7cf523f36 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -1,15 +1,18 @@
# Axios
+
We use [axios][axios] to communicate with the server in Vue applications and most new code.
In order to guarantee all defaults are set you *should not use `axios` directly*, you should import `axios` from `axios_utils`.
## CSRF token
+
All our request require a CSRF token.
To guarantee this token is set, we are importing [axios][axios], setting the token, and exporting `axios` .
This exported module should be used instead of directly using `axios` to ensure the token is set.
## Usage
+
```javascript
import axios from './lib/utils/axios_utils';
@@ -35,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/components.md b/doc/development/fe_guide/components.md
index 52462a4bec9..096ce8ca25a 100644
--- a/doc/development/fe_guide/components.md
+++ b/doc/development/fe_guide/components.md
@@ -14,28 +14,29 @@ See also the [corresponding UX guide](https://design.gitlab.com/#/components/dro
1. Use the HTML structure provided by the [docs][bootstrap-dropdowns]
1. Add a specific class to the top level `.dropdown` element
- ```Haml
- .dropdown.my-dropdown
- %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
- %span.dropdown-toggle-text
- Toggle Dropdown
- = icon('chevron-down')
-
- %ul.dropdown-menu
- %li
- %a
- item!
- ```
-
- Or use the helpers
- ```Haml
- .dropdown.my-dropdown
- = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
- = dropdown_content
- %li
- %a
- item!
- ```
+ ```Haml
+ .dropdown.my-dropdown
+ %button{ type: 'button', data: { toggle: 'dropdown' }, 'aria-haspopup': true, 'aria-expanded': false }
+ %span.dropdown-toggle-text
+ Toggle Dropdown
+ = icon('chevron-down')
+
+ %ul.dropdown-menu
+ %li
+ %a
+ item!
+ ```
+
+ Or use the helpers
+
+ ```Haml
+ .dropdown.my-dropdown
+ = dropdown_toggle('Toogle!', { toggle: 'dropdown' })
+ = dropdown_content
+ %li
+ %a
+ item!
+ ```
[bootstrap-dropdowns]: https://getbootstrap.com/docs/3.3/javascript/#dropdowns
diff --git a/doc/development/fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index 716f6ad7f92..1b417d4c8c2 100644
--- a/doc/development/fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
@@ -1,79 +1,76 @@
-# Event Tracking
+# Event tracking
-We use [Snowplow](https://github.com/snowplow/snowplow) for tracking custom events (available in GitLab [Enterprise Edition](https://about.gitlab.com/pricing/) only).
+GitLab provides `Tracking`, an interface that wraps
+[Snowplow](https://github.com/snowplow/snowplow) for tracking custom events.
+It uses Snowplow's custom event tracking functions.
-## Generic tracking function
-
-In addition to Snowplow's built-in method for tracking page views, we use a generic tracking function which enables us to selectively apply listeners to events.
-
-The generic tracking function can be imported in EE-specific JS files as follows:
+The tracking interface can be imported in JS files as follows:
```javascript
-import { trackEvent } from `ee/stats`;
+import Tracking from `~/tracking`;
```
-This gives the user access to the `trackEvent` method, which takes the following parameters:
+## Tracking in HAML or Vue templates
-| parameter | type | description | required |
-| ---------------- | ------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
-| `category` | string | Describes the page that you're capturing click events on. Unless infeasible, please use the Rails page attribute `document.body.dataset.page` by default. | true |
-| `eventName` | string | Describes the action the user is taking. The first word should always describe the action. For example, clicks should be `click` and activations should be `activate`. Use underscores to describe what was acted on. For example, activating a form field would be `activate_form_input`. Clicking on a dropdown is `click_dropdown`. | true |
-| `additionalData` | object | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). | false |
+To avoid having to do create a bunch of custom javascript event handlers, when working within HAML or Vue templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have a `data-track-event` attribute to automatically have event tracking bound.
-Read more about instrumentation and the taxonomy in the [Product Handbook](https://about.gitlab.com/handbook/product/feature-instrumentation).
-
-### Tracking in `.js` and `.vue` files
+Below is an example of `data-track-*` attributes assigned to a button in HAML:
-The most simple use case is to add tracking programmatically to an event of interest in Javascript.
+```haml
+%button.btn{ data: { track_event: "click_button", track_label: "template_preview", track_property: "my-template", track_value: "" } }
+```
-The following example demonstrates how to track a click on a button in Javascript by calling the `trackEvent` method explicitly:
+We can then setup tracking for large sections of a page, or an entire page by telling the Tracking interface to bind to it.
```javascript
-import { trackEvent } from `ee/stats`;
+import Tracking from '~/tracking';
-trackEvent('dashboard:projects:index', 'click_button', {
- label: 'create_from_template',
- property: 'template_preview',
- value: 'rails',
+// for the entire document
+new Tracking().bind();
+
+// for a container element
+document.addEventListener('DOMContentLoaded', () => {
+ new Tracking('my_category').bind(document.getElementById('my-container'));
});
+
```
-### Tracking in HAML templates
+When you instantiate a Tracking instance you can provide a category. If none is provided, `document.body.dataset.page` will be used. When you bind the Tracking instance you can provide an element. If no element is provided to bind to, the `document` is assumed.
-Sometimes we want to track clicks for multiple elements on a page. Creating event handlers for all elements could soon turn into a tedious task.
+Below is a list of supported `data-track-*` attributes:
-There's a more convenient solution to this problem. When working with HAML templates, we can add `data-track-*` attributes to elements of interest. This way, all elements that have both `data-track-label` and `data-track-event` attributes assigned get marked for event tracking. All we have to do is call the `bindTrackableContainer` method on a container which allows for better scoping.
+| attribute | required | description |
+|:----------------------|:---------|:------------|
+| `data-track-event` | true | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
+| `data-track-label` | false | The `label` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
+| `data-track-property` | false | The `property` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). |
+| `data-track-value` | false | The `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). If omitted, this will be the elements `value` property or an empty string. For checkboxes, the default value will be the element's checked attribute or `false` when unchecked. |
-Below is an example of `data-track-*` attributes assigned to a button in HAML:
+## Tracking in raw Javascript
-```ruby
-%button.btn{ data: { track_label: "template_preview", track_property: "my-template", track_event: "click_button", track_value: "" } }
-```
+Custom events can be tracked by directly calling the `Tracking.event` static function, which accepts the following arguments:
+
+| argument | type | default value | description |
+|:-----------|:-------|:---------------------------|:------------|
+| `category` | string | document.body.dataset.page | Page or subsection of a page that events are being captured within. |
+| `event` | string | 'generic' | Action the user is taking. Clicks should be `click` and activations should be `activate`, so for example, focusing a form field would be `activate_form_input`, and clicking a button would be `click_button`. |
+| `data` | object | {} | Additional data such as `label`, `property`, and `value` as described [in our Feature Instrumentation taxonomy](https://about.gitlab.com/handbook/product/feature-instrumentation/#taxonomy). These will be set as empty strings if you don't provide them. |
-By calling `bindTrackableContainer('.my-container')`, click handlers get bound to all elements located in `.my-container` provided that they have the necessary `data-track-*` attributes assigned to them.
+Tracking can be programmatically added to an event of interest in Javascript, and the following example demonstrates tracking a click on a button by calling `Tracking.event` manually.
```javascript
-import Stats from 'ee/stats';
+import Tracking from `~/tracking`;
-document.addEventListener('DOMContentLoaded', () => {
- Stats.bindTrackableContainer('.my-container', 'category');
-});
+document.getElementById('my_button').addEventListener('click', () => {
+ Tracking.event('dashboard:projects:index', 'click_button', {
+ label: 'create_from_template',
+ property: 'template_preview',
+ value: 'rails',
+ });
+})
```
-The second parameter in `bindTrackableContainer` is optional. If omitted, the value of `document.body.dataset.page` will be used as category instead.
-
-Below is a list of supported `data-track-*` attributes:
-
-| attribute | description | required |
-| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
-| `data-track-label` | The `label` in `trackEvent` | true |
-| `data-track-event` | The `eventName` in `trackEvent` | true |
-| `data-track-property` | The `property` in `trackEvent`. If omitted, an empty string will be used as a default value. | false |
-| `data-track-value` | The `value` in `trackEvent`. If omitted, this will be `target.value` or empty string. For checkboxes, the default value being tracked will be the element's checked attribute if `data-track-value` is omitted. | false |
-
-Since Snowplow is an Enterprise Edition feature, it's necessary to create a CE backport when adding `data-track-*` attributes to HAML templates in most cases.
-
-## Testing
+## Toggling tracking on or off
Snowplow can be enabled by navigating to:
diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md
index e4225f2bc39..0d2aeffeac0 100644
--- a/doc/development/fe_guide/frontend_faq.md
+++ b/doc/development/fe_guide/frontend_faq.md
@@ -5,12 +5,12 @@
1. **You talk about Frontend FAQ.**
Please share links to it whenever applicable, so more eyes catch when content
gets outdated.
-2. **Keep it short and simple.**
+1. **Keep it short and simple.**
Whenever an answer needs more than two sentences it does not belong here.
-3. **Provide background when possible.**
+1. **Provide background when possible.**
Linking to relevant source code, issue / epic, or other documentation helps
to understand the answer.
-4. **If you see something, do something.**
+1. **If you see something, do something.**
Please remove or update any content that is outdated as soon as you see it.
## FAQ
diff --git a/doc/development/fe_guide/graphql.md b/doc/development/fe_guide/graphql.md
index 55b719227e5..4fc5dfc8c3d 100644
--- a/doc/development/fe_guide/graphql.md
+++ b/doc/development/fe_guide/graphql.md
@@ -47,7 +47,7 @@ new Vue({
});
```
-Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation][vue-apollo-docs].
+Read more about [Vue Apollo][vue-apollo] in the [Vue Apollo documentation](https://vue-apollo.netlify.com/guide/).
### Local state with Apollo
@@ -118,7 +118,6 @@ Read more about the [Apollo] client in the [Apollo documentation](https://www.ap
[Apollo]: https://www.apollographql.com/
[vue-apollo]: https://github.com/Akryum/vue-apollo/
-[vue-apollo-docs]: https://akryum.github.io/vue-apollo/
[feature-flags]: ../feature_flags.md
[default-client]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/app/assets/javascripts/lib/graphql.js
[vue-test-utils]: https://vue-test-utils.vuejs.org/
diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md
index 533e2001300..4f687d8642e 100644
--- a/doc/development/fe_guide/icons.md
+++ b/doc/development/fe_guide/icons.md
@@ -21,10 +21,10 @@ To use a sprite Icon in HAML or Rails we use a specific helper function :
sprite_icon(icon_name, size: nil, css_class: '')
```
-- **icon_name** Use the icon_name that you can find in the SVG Sprite
- ([Overview is available here][svg-preview]).
-- **size (optional)** Use one of the following sizes : 16, 24, 32, 48, 72 (this will be translated into a `s16` class)
-- **css_class (optional)** If you want to add additional css classes
+- **icon_name** Use the icon_name that you can find in the SVG Sprite
+ ([Overview is available here][svg-preview]).
+- **size (optional)** Use one of the following sizes : 16, 24, 32, 48, 72 (this will be translated into a `s16` class)
+- **css_class (optional)** If you want to add additional css classes
**Example**
@@ -65,10 +65,10 @@ export default {
</template>
```
-- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]).
-- **size (optional)** Number value for the size which is then mapped to a specific CSS class
- (Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` css classes)
-- **css-classes (optional)** Additional CSS Classes to add to the svg tag.
+- **name** Name of the Icon in the SVG Sprite ([Overview is available here][svg-preview]).
+- **size (optional)** Number value for the size which is then mapped to a specific CSS class
+ (Available Sizes: 8, 12, 16, 18, 24, 32, 48, 72 are mapped to `sXX` css classes)
+- **css-classes (optional)** Additional CSS Classes to add to the svg tag.
### Usage in HTML/JS
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 6bf6113dd81..deaef8e768b 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -17,6 +17,8 @@ Working with our frontend assets requires Node (v8.10.0 or greater) and Yarn
For our currently-supported browsers, see our [requirements][requirements].
+Use [BrowserStack](https://www.browserstack.com/) to test with our supported browsers. Login to BrowserStack with the credentials saved in GitLab's [shared 1Password account](https://about.gitlab.com/handbook/security/#1password-for-teams).
+
## Initiatives
Current high-level frontend goals are listed on [Frontend Epics](https://gitlab.com/groups/gitlab-org/-/epics?label_name%5B%5D=frontend).
diff --git a/doc/development/fe_guide/performance.md b/doc/development/fe_guide/performance.md
index 2628e95dbc1..676bce32998 100644
--- a/doc/development/fe_guide/performance.md
+++ b/doc/development/fe_guide/performance.md
@@ -30,8 +30,8 @@ To improve the time to first render we are using lazy loading for images. This w
the actual image source on the `data-src` attribute. After the HTML is rendered and JavaScript is loaded,
the value of `data-src` will be moved to `src` automatically if the image is in the current viewport.
-- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
-- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
+- Prepare images in HTML for lazy loading by renaming the `src` attribute to `data-src` AND adding the class `lazy`.
+- If you are using the Rails `image_tag` helper, all images will be lazy-loaded by default unless `lazy: false` is provided.
If you are asynchronously adding content which contains lazy images then you need to call the function
`gl.lazyLoader.searchLazyImages()` which will search for lazy images and load them if needed.
@@ -96,26 +96,26 @@ bundle and included on the page.
DOM has loaded, you should attach an event handler to the `DOMContentLoaded`
event with:
- ```javascript
- import initMyWidget from './my_widget';
+ ```javascript
+ import initMyWidget from './my_widget';
- document.addEventListener('DOMContentLoaded', () => {
- initMyWidget();
- });
- ```
+ document.addEventListener('DOMContentLoaded', () => {
+ initMyWidget();
+ });
+ ```
- **Supporting Module Placement:**
- - If a class or a module is _specific to a particular route_, try to locate
- it close to the entry point it will be used. For instance, if
- `my_widget.js` is only imported within `pages/widget/show/index.js`, you
- should place the module at `pages/widget/show/my_widget.js` and import it
- with a relative path (e.g. `import initMyWidget from './my_widget';`).
- - If a class or module is _used by multiple routes_, place it within a
- shared directory at the closest common parent directory for the entry
- points that import it. For example, if `my_widget.js` is imported within
- both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
- place the module at `pages/widget/shared/my_widget.js` and import it with
- a relative path if possible (e.g. `../shared/my_widget`).
+ - If a class or a module is _specific to a particular route_, try to locate
+ it close to the entry point it will be used. For instance, if
+ `my_widget.js` is only imported within `pages/widget/show/index.js`, you
+ should place the module at `pages/widget/show/my_widget.js` and import it
+ with a relative path (e.g. `import initMyWidget from './my_widget';`).
+ - If a class or module is _used by multiple routes_, place it within a
+ shared directory at the closest common parent directory for the entry
+ points that import it. For example, if `my_widget.js` is imported within
+ both `pages/widget/show/index.js` and `pages/widget/run/index.js`, then
+ place the module at `pages/widget/shared/my_widget.js` and import it with
+ a relative path if possible (e.g. `../shared/my_widget`).
- **Enterprise Edition Caveats:**
For GitLab Enterprise Edition, page-specific entry points will override their
@@ -161,7 +161,7 @@ General tips:
- Use code-splitting dynamic imports wherever possible to lazy-load code that is not needed initially.
- [High Performance Animations][high-perf-animations]
--------
+---
## Additional Resources
diff --git a/doc/development/fe_guide/style_guide_js.md b/doc/development/fe_guide/style_guide_js.md
index b50159c2b75..125b11afcd0 100644
--- a/doc/development/fe_guide/style_guide_js.md
+++ b/doc/development/fe_guide/style_guide_js.md
@@ -20,148 +20,148 @@ 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 */
+ ```javascript
+ // 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 */
+ ```javascript
+ // 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:
- 1. [no-new][eslint-new]
- 1. [class-methods-use-this][eslint-this]
+ 1. [no-new](https://eslint.org/docs/rules/no-new)
+ 1. [class-methods-use-this](https://eslint.org/docs/rules/class-methods-use-this)
1. When they are needed _always_ place ESlint directive comment blocks on the first line of a script,
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';
+ ```javascript
+ // 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.
1. When declaring multiple globals, always use one `/* global [name] */` line per variable.
- ```javascript
- // bad
- /* globals Flash, Cookies, jQuery */
+ ```javascript
+ // 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) {}
+ ```javascript
+ // bad
+ fn(p1, p2, p3, p4) {}
- // good
- fn(options) {}
- ```
+ // good
+ fn(options) {}
+ ```
#### Modules, Imports, and Exports
1. Use ES module syntax to import modules
- ```javascript
- // bad
- const SomeClass = require('some_class');
+ ```javascript
+ // 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:
+ Import statements are following usual naming guidelines, for example object literals use camel case:
- ```javascript
- // some_object file
- export default {
- key: 'value',
- };
+ ```javascript
+ // 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
directory, or an immediate parent directory prefer relative paths. When
importing a module which is two or more levels up, prefer either `~/` or `ee/`.
- In **app/assets/javascripts/my-feature/subdir**:
+ In **app/assets/javascripts/my-feature/subdir**:
- ```javascript
- // bad
- import Foo from '~/my-feature/foo';
- import Bar from '~/my-feature/subdir/bar';
- import Bin from '~/my-feature/subdir/lib/bin';
+ ```javascript
+ // bad
+ import Foo from '~/my-feature/foo';
+ import Bar from '~/my-feature/subdir/bar';
+ import Bin from '~/my-feature/subdir/lib/bin';
- // good
- import Foo from '../foo';
- import Bar from './bar';
- import Bin from './lib/bin';
- ```
+ // good
+ import Foo from '../foo';
+ import Bar from './bar';
+ import Bin from './lib/bin';
+ ```
- In **spec/javascripts**:
+ In **spec/javascripts**:
- ```javascript
- // bad
- import Foo from '../../app/assets/javascripts/my-feature/foo';
+ ```javascript
+ // bad
+ import Foo from '../../app/assets/javascripts/my-feature/foo';
- // good
- import Foo from '~/my-feature/foo';
- ```
+ // good
+ import Foo from '~/my-feature/foo';
+ ```
- When referencing an **EE component**:
+ When referencing an **EE component**:
- ```javascript
- // bad
- import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
+ ```javascript
+ // bad
+ import Foo from '../../../../../ee/app/assets/javascripts/my-feature/ee-foo';
- // good
- import Foo from 'ee/my-feature/foo';
- ```
+ // good
+ import Foo from 'ee/my-feature/foo';
+ ```
1. Avoid using IIFE. Although we have a lot of examples of files which wrap their
contents in IIFEs (immediately-invoked function expressions),
@@ -170,136 +170,136 @@ 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 { /* ... */ };
+ ```javascript
+ // 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 { /* ... */ }
+ ```javascript
+ // bad
+ export default class MyClass { /* ... */ }
- document.addEventListener("DOMContentLoaded", function(event) {
- new MyClass();
- }
- ```
+ document.addEventListener("DOMContentLoaded", function(event) {
+ new MyClass();
+ }
+ ```
#### Data Mutation and Pure functions
1. Strive to write many small pure functions, and minimize where mutations occur.
- ```javascript
- // bad
- const values = {foo: 1};
+ ```javascript
+ // bad
+ const values = {foo: 1};
- function impureFunction(items) {
- const bar = 1;
+ function impureFunction(items) {
+ const bar = 1;
- items.foo = items.a * bar + 2;
+ items.foo = items.a * bar + 2;
- return items.a;
- }
+ return items.a;
+ }
- const c = impureFunction(values);
+ const c = impureFunction(values);
- // good
- var values = {foo: 1};
+ // good
+ var values = {foo: 1};
- function pureFunction (foo) {
- var bar = 1;
+ function pureFunction (foo) {
+ var bar = 1;
- foo = foo * bar + 2;
+ foo = foo * bar + 2;
- return foo;
- }
+ return foo;
+ }
- var c = pureFunction(values.foo);
+ var c = pureFunction(values.foo);
```
1. Avoid constructors with side-effects.
Although we aim for code without side-effects we need some side-effects for our code to run.
- If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
-
- ```javascript
- // Bad
- export class Foo {
- constructor() {
- this.init();
- }
- init() {
- document.addEventListener('click', this.handleCallback)
- },
- handleCallback() {
-
- }
- }
-
- // Good
- export class Foo {
- constructor() {
- document.addEventListener()
- }
- handleCallback() {
- }
- }
- ```
-
- On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
+ If the class won't do anything if we only instantiate it, it's ok to add side effects into the constructor (_Note:_ The following is just an example. If the only purpose of the class is to add an event listener and handle the callback a function will be more suitable.)
+
+ ```javascript
+ // Bad
+ export class Foo {
+ constructor() {
+ this.init();
+ }
+ init() {
+ document.addEventListener('click', this.handleCallback)
+ },
+ handleCallback() {
+
+ }
+ }
+
+ // Good
+ export class Foo {
+ constructor() {
+ document.addEventListener()
+ }
+ handleCallback() {
+ }
+ }
+ ```
+
+ On the other hand, if a class only needs to extend a third party/add event listeners in some specific cases, they should be initialized outside of the constructor.
1. Prefer `.map`, `.reduce` or `.filter` over `.forEach`
A forEach will most likely cause side effects, it will be mutating the array being iterated. Prefer using `.map`,
`.reduce` or `.filter`
- ```javascript
- const users = [ { name: 'Foo' }, { name: 'Bar' } ];
+ ```javascript
+ 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
1. `parseInt()` is preferable over `Number()` or `+`
- ```javascript
- // bad
- +'10' // 10
+ ```javascript
+ // bad
+ +'10' // 10
- // good
- Number('10') // 10
+ // good
+ Number('10') // 10
- // better
- parseInt('10', 10);
- ```
+ // better
+ parseInt('10', 10);
+ ```
#### CSS classes used for JavaScript
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>
+ ```html
+ // 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
@@ -314,43 +314,44 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. The store has it's own file
1. Use a function in the bundle file to instantiate the Vue component:
- ```javascript
- // bad
- class {
- init() {
- new Component({})
- }
- }
-
- // good
- document.addEventListener('DOMContentLoaded', () => new Vue({
- el: '#element',
- components: {
- componentName
- },
- render: createElement => createElement('component-name'),
- }));
- ```
+ ```javascript
+ // bad
+ class {
+ init() {
+ new Component({})
+ }
+ }
+
+ // 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
- }
- }
- }
+ ```javascript
+ // 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.
#### Naming
@@ -358,38 +359,38 @@ Please check this [rules][eslint-plugin-vue-rules] for more documentation.
1. **Extensions**: Use `.vue` extension for Vue components. Do not use `.js` as file extension ([#34371]).
1. **Reference Naming**: Use PascalCase for their instances:
- ```javascript
- // bad
- import cardBoard from 'cardBoard.vue'
+ ```javascript
+ // 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">
+ ```javascript
+ // 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
@@ -399,205 +400,205 @@ 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" />
+ ```javascript
+ // 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" />
+ ```javascript
+ // good
+ <component bar="bar" />
- // good
- <component
- bar="bar"
- />
+ // good
+ <component
+ bar="bar"
+ />
- // bad
- <component
- bar="bar" />
- ```
+ // bad
+ <component
+ bar="bar" />
+ ```
#### Quotes
1. Always use double quotes `"` inside templates and single quotes `'` for all other JS.
- ```javascript
- // bad
- template: `
- <button :class='style'>Button</button>
- `
+ ```javascript
+ // bad
+ template: `
+ <button :class='style'>Button</button>
+ `
- // good
- template: `
- <button :class="style">Button</button>
- `
- ```
+ // good
+ template: `
+ <button :class="style">Button</button>
+ `
+ ```
#### Props
1. Props should be declared as an object
- ```javascript
- // bad
- props: ['foo']
+ ```javascript
+ // bad
+ props: ['foo']
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
- ```
+ // 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,
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
- ```
+ ```javascript
+ // bad
+ props: {
+ foo: {
+ type: String,
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
+ }
+ }
+ ```
1. Default key should be provided if the prop is not required.
_Note:_ There are some scenarios where we need to check for the existence of the property.
On those a default key should not be provided.
- ```javascript
- // good
- props: {
- foo: {
- type: String,
- required: false,
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: false,
- default: 'bar'
- }
- }
-
- // good
- props: {
- foo: {
- type: String,
- required: true
- }
- }
- ```
+ ```javascript
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: false,
+ default: 'bar'
+ }
+ }
+
+ // good
+ props: {
+ foo: {
+ type: String,
+ required: true
+ }
+ }
+ ```
#### Data
1. `data` method should always be a function
- ```javascript
- // bad
- data: {
- foo: 'foo'
- }
+ ```javascript
+ // bad
+ data: {
+ foo: 'foo'
+ }
- // good
- data() {
- return {
- foo: 'foo'
- };
- }
- ```
+ // good
+ data() {
+ return {
+ foo: 'foo'
+ };
+ }
+ ```
#### Directives
1. Shorthand `@` is preferable over `v-on`
- ```javascript
- // bad
- <component v-on:click="eventHandler"/>
+ ```javascript
+ // bad
+ <component v-on:click="eventHandler"/>
- // good
- <component @click="eventHandler"/>
- ```
+ // good
+ <component @click="eventHandler"/>
+ ```
-2. Shorthand `:` is preferable over `v-bind`
+1. Shorthand `:` is preferable over `v-bind`
- ```javascript
- // bad
- <component v-bind:class="btn"/>
+ ```javascript
+ // bad
+ <component v-bind:class="btn"/>
- // good
- <component :class="btn"/>
- ```
+ // good
+ <component :class="btn"/>
+ ```
-3. Shorthand `#` is preferable over `v-slot`
+1. Shorthand `#` is preferable over `v-slot`
- ```javascript
- // bad
- <template v-slot:header></template>
+ ```javascript
+ // bad
+ <template v-slot:header></template>
- // good
- <template #header></template>
- ```
+ // good
+ <template #header></template>
+ ```
#### Closing tags
1. Prefer self closing component tags
- ```javascript
- // bad
- <component></component>
+ ```javascript
+ // bad
+ <component></component>
- // good
- <component />
- ```
+ // good
+ <component />
+ ```
#### Ordering
1. Tag order in `.vue` file
- ```
- <script>
- // ...
- </script>
-
- <template>
- // ...
- </template>
-
- // We don't use scoped styles but there are few instances of this
- <style>
- // ...
- </style>
- ```
+ ```
+ <script>
+ // ...
+ </script>
+
+ <template>
+ // ...
+ </template>
+
+ // We don't use scoped styles but there are few instances of this
+ <style>
+ // ...
+ </style>
+ ```
1. Properties in a Vue Component:
Check [order of properties in components rule][vue-order].
@@ -608,50 +609,50 @@ 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>
- ```
+ ```html
+ <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>
- ```
+ ```html
+ <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>
- ```
+ ```html
+ <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"
- >
- <span
- v-for="element in array"
- :key="element.id"
- >
- <!-- content -->
- </span>
- </div>
- ```
+ ```html
+ <div
+ v-for="item in items"
+ :key="item.id"
+ >
+ <span
+ v-for="element in array"
+ :key="element.id"
+ >
+ <!-- content -->
+ </span>
+ </div>
+ ```
Useful links:
@@ -662,35 +663,35 @@ 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>
-
- // good
- <span
- v-tooltip
- title="Some tooltip text">
- Text
- </span>
- ```
+ ```javascript
+ // bad
+ <span
+ class="has-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`
1. Don't change `data-original-title`.
- ```javascript
- // bad
- <span data-original-title="tooltip text">Foo</span>
+ ```javascript
+ // 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
@@ -713,8 +714,6 @@ The goal of this accord is to make sure we are all on the same page.
[airbnb-js-style-guide]: https://github.com/airbnb/javascript
[eslintrc]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.eslintrc
-[eslint-this]: http://eslint.org/docs/rules/class-methods-use-this
-[eslint-new]: http://eslint.org/docs/rules/no-new
[eslint-plugin-vue]: https://github.com/vuejs/eslint-plugin-vue
[eslint-plugin-vue-rules]: https://github.com/vuejs/eslint-plugin-vue#bulb-rules
[vue-order]: https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/order-in-components.md
diff --git a/doc/development/fe_guide/style_guide_scss.md b/doc/development/fe_guide/style_guide_scss.md
index 5220c9eeea3..95c4a094c04 100644
--- a/doc/development/fe_guide/style_guide_scss.md
+++ b/doc/development/fe_guide/style_guide_scss.md
@@ -35,7 +35,7 @@ New utility classes should be added to [`utilities.scss`](https://gitlab.com/git
We recommend a "utility-first" approach.
1. Start with utility classes.
-2. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it.
+1. If composing utility classes into a component class removes code duplication and encapsulates a clear responsibility, do it.
This encourages an organic growth of component classes and prevents the creation of one-off unreusable classes. Also, the kind of classes that emerge from "utility-first" tend to be design-centered (e.g. `.button`, `.alert`, `.card`) rather than domain-centered (e.g. `.security-report-widget`, `.commit-header-icon`).
@@ -212,6 +212,7 @@ selectors are intended for use only with JavaScript to allow for removal or
renaming without breaking styling.
### IDs
+
Don't use ID selectors in CSS.
```scss
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index bf248b7f8af..557d3132d71 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -1,15 +1,18 @@
# Vuex
+
To manage the state of an application you should use [Vuex][vuex-docs].
_Note:_ All of the below is explained in more detail in the official [Vuex documentation][vuex-docs].
## Separation of concerns
+
Vuex is composed of State, Getters, Mutations, Actions and Modules.
When a user clicks on an action, we need to `dispatch` it. This action will `commit` a mutation that will change the state.
_Note:_ The action itself will not update the state, only a mutation should update the state.
## File structure
+
When using Vuex at GitLab, separate this concerns into different files to improve readability:
```
@@ -21,10 +24,12 @@ When using Vuex at GitLab, separate this concerns into different files to improv
├── state.js # state
└── mutation_types.js # mutation types
```
+
The following example shows an application that lists and adds users to the state.
(For a more complex example implementation take a look at the security applications store in [here](https://gitlab.com/gitlab-org/gitlab-ee/tree/master/ee/app/assets/javascripts/vue_shared/security_reports/store))
### `index.js`
+
This is the entry point for our store. You can use the following as a guide:
```javascript
@@ -47,6 +52,7 @@ export default createStore();
```
### `state.js`
+
The first thing you should do before writing any code is to design the state.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
@@ -66,9 +72,11 @@ Often we need to provide data from haml to our Vue application. Let's store it i
```
#### Access `state` properties
+
You can use `mapState` to access state properties in the components.
### `actions.js`
+
An action is a payload of information to send data from our application to our store.
An action is usually composed by a `type` and a `payload` and they describe what happened.
@@ -110,6 +118,7 @@ In this file, we will write the actions that will call the respective mutations:
```
#### Actions Pattern: `request` and `receive` namespaces
+
When a request is made we often want to show a loading state to the user.
Instead of creating an action to toggle the loading state and dispatch it in the component,
@@ -136,6 +145,7 @@ By following this pattern we guarantee:
1. Actions are simple and straightforward
#### Dispatching actions
+
To dispatch an action from a component, use the `mapActions` helper:
```javascript
@@ -154,6 +164,7 @@ import { mapActions } from 'vuex';
```
### `mutations.js`
+
The mutations specify how the application state changes in response to actions sent to the store.
The only way to change state in a Vuex store should be by committing a mutation.
@@ -193,6 +204,7 @@ Remember that actions only describe that something happened, they don't describe
```
### `getters.js`
+
Sometimes we may need to get derived state based on store state, like filtering for a specific prop.
Using a getter will also cache the result based on dependencies due to [how computed props work](https://vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods)
This can be done through the `getters`:
@@ -219,6 +231,7 @@ import { mapGetters } from 'vuex';
```
### `mutation_types.js`
+
From [vuex mutations docs][vuex-mutations]:
> It is a commonly seen pattern to use constants for mutation types in various Flux implementations. This allows the code to take advantage of tooling like linters, and putting all constants in a single file allows your collaborators to get an at-a-glance view of what mutations are possible in the entire application.
@@ -227,6 +240,7 @@ export const ADD_USER = 'ADD_USER';
```
### How to include the store in your application
+
The store should be included in the main component of your application:
```javascript
@@ -241,6 +255,7 @@ The store should be included in the main component of your application:
```
### Communicating with the Store
+
```javascript
<script>
import { mapActions, mapState, mapGetters } from 'vuex';
@@ -298,29 +313,33 @@ 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
+ ```javascript
+ // component.vue
- // bad
- created() {
- this.$store.commit('mutation');
- }
+ // bad
+ created() {
+ this.$store.commit('mutation');
+ }
+
+ // good
+ created() {
+ this.$store.dispatch('action');
+ }
+ ```
- // good
- created() {
- this.$store.dispatch('action');
- }
- ```
1. Use mutation types instead of hardcoding strings. It will be less error prone.
1. The State will be accessible in all components descending from the use where the store is instantiated.
### Testing Vuex
+
#### Testing Vuex concerns
+
Refer to [vuex docs][vuex-testing] regarding testing Actions, Getters and Mutations.
#### Testing components that need a store
+
Smaller components might use `store` properties to access the data.
In order to write unit tests for those components, we need to include the store and provide the correct state:
@@ -363,6 +382,7 @@ describe('component', () => {
```
#### Testing Vuex actions and getters
+
Because we're currently using [`babel-plugin-rewire`](https://github.com/speedskater/babel-plugin-rewire), you may encounter the following error when testing your Vuex actions and getters:
`[vuex] actions should be function or object with "handler" function`
diff --git a/doc/development/file_storage.md b/doc/development/file_storage.md
index 02874d18a30..475d1c1611e 100644
--- a/doc/development/file_storage.md
+++ b/doc/development/file_storage.md
@@ -92,8 +92,8 @@ in your uploader, you need to either 1) include `RecordsUpload::Concern` and pre
The `CarrierWave::Uploader#store_dir` is overridden to
- - `GitlabUploader.base_dir` + `GitlabUploader.dynamic_segment` when the store is LOCAL
- - `GitlabUploader.dynamic_segment` when the store is REMOTE (the bucket name is used to namespace)
+- `GitlabUploader.base_dir` + `GitlabUploader.dynamic_segment` when the store is LOCAL
+- `GitlabUploader.dynamic_segment` when the store is REMOTE (the bucket name is used to namespace)
### Using `ObjectStorage::Extension::RecordsUploads`
diff --git a/doc/development/filtering_by_label.md b/doc/development/filtering_by_label.md
new file mode 100644
index 00000000000..5e7376db725
--- /dev/null
+++ b/doc/development/filtering_by_label.md
@@ -0,0 +1,166 @@
+# Filtering by label
+
+## Introduction
+
+GitLab has [labels](../user/project/labels.md) that can be assigned to issues,
+merge requests, and epics. Labels on those objects are a many-to-many relation
+through the polymorphic `label_links` table.
+
+To filter these objects by multiple labels - for instance, 'all open
+issues with the label ~Plan and the label ~backend' - we generate a
+query containing a `GROUP BY` clause. In a simple form, this looks like:
+
+```sql
+SELECT
+ issues.*
+FROM
+ issues
+ INNER JOIN label_links ON label_links.target_id = issues.id
+ AND label_links.target_type = 'Issue'
+ INNER JOIN labels ON labels.id = label_links.label_id
+WHERE
+ issues.project_id = 13083
+ AND (issues.state IN ('opened'))
+ AND labels.title IN ('Plan',
+ 'backend')
+GROUP BY
+ issues.id
+HAVING (COUNT(DISTINCT labels.title) = 2)
+ORDER BY
+ issues.updated_at DESC,
+ issues.id DESC
+LIMIT 20 OFFSET 0
+```
+
+In particular, note that:
+
+1. We `GROUP BY issues.id` so that we can ...
+1. Use the `HAVING (COUNT(DISTINCT labels.title) = 2)` condition to ensure that
+ all matched issues have both labels.
+
+This is more complicated than is ideal. It makes the query construction more
+prone to errors (such as
+[gitlab-org/gitlab-ce#15557](https://gitlab.com/gitlab-org/gitlab-ce/issues/15557)).
+
+## Attempt A: WHERE EXISTS
+
+### Attempt A1: use multiple subqueries with WHERE EXISTS
+
+In
+[gitlab-org/gitlab-ce#37137](https://gitlab.com/gitlab-org/gitlab-ce/issues/37137)
+and its associated merge request
+[gitlab-org/gitlab-ce!14022](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14022),
+we tried to replace the `GROUP BY` with multiple uses of `WHERE EXISTS`. For the
+example above, this would give:
+
+```sql
+WHERE (EXISTS (
+ SELECT
+ TRUE
+ FROM
+ label_links
+ INNER JOIN labels ON labels.id = label_links.label_id
+ WHERE
+ labels.title = 'Plan'
+ AND target_type = 'Issue'
+ AND target_id = issues.id))
+AND (EXISTS (
+ SELECT
+ TRUE
+ FROM
+ label_links
+ INNER JOIN labels ON labels.id = label_links.label_id
+ WHERE
+ labels.title = 'backend'
+ AND target_type = 'Issue'
+ AND target_id = issues.id))
+```
+
+While this worked without schema changes, and did improve readability somewhat,
+it did not improve query performance.
+
+## Attempt B: Denormalize using an array column
+
+Having [removed MySQL support in GitLab
+12.1](https://about.gitlab.com/2019/06/27/removing-mysql-support/), using
+[Postgres's arrays](https://www.postgresql.org/docs/9.6/arrays.html) became more
+tractable as we didn't have to support two databases. We discussed denormalizing
+the `label_links` table for querying in
+[gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651),
+with two options: label IDs and titles.
+
+We can think of both of those as array columns on `issues`, `merge_requests`,
+and `epics`: `issues.label_ids` would be an array column of label IDs, and
+`issues.label_titles` would be an array of label titles.
+
+These array columns can be complemented with [GIN
+indexes](https://www.postgresql.org/docs/9.6/gin-intro.html) to improve
+matching.
+
+### Attempt B1: store label IDs for each object
+
+This has some strong advantages over titles:
+
+1. Unless a label is deleted, or a project is moved, we never need to
+ bulk-update the denormalized column.
+1. It uses less storage than the titles.
+
+Unfortunately, our application design makes this hard. If we were able to query
+just by label ID easily, we wouldn't need the `INNER JOIN labels` in the initial
+query at the start of this document. GitLab allows users to filter by label
+title across projects and even across groups, so a filter by the label ~Plan may
+include labels with multiple distinct IDs.
+
+We do not want users to have to know about the different IDs, which means that
+given this data set:
+
+| Project | ~Plan label ID | ~backend label ID |
+| ------- | -------------- | ----------------- |
+| A | 11 | 12 |
+| B | 21 | 22 |
+| C | 31 | 32 |
+
+We would need something like:
+
+```sql
+WHERE
+ label_ids @> ARRAY[11, 12]
+ OR label_ids @> ARRAY[21, 22]
+ OR label_ids @> ARRAY[31, 32]
+```
+
+This can get even more complicated when we consider that in some cases, there
+might be two ~backend labels - with different IDs - that could apply to the same
+object, so the number of combinations would balloon further.
+
+### Attempt B2: store label titles for each object
+
+From the perspective of updating the labelable object, this is the worst
+option. We have to bulk update the objects when:
+
+1. The objects are moved from one project to another.
+1. The project is moved from one group to another.
+1. The label is renamed.
+1. The label is deleted.
+
+It also uses much more storage. Querying is simple, though:
+
+```sql
+WHERE
+ label_titles @> ARRAY['Plan', 'backend']
+```
+
+And our [tests in
+gitlab-org/gitlab-ce#49651](https://gitlab.com/gitlab-org/gitlab-ce/issues/49651#note_188777346)
+showed that this could be fast.
+
+However, at present, the disadvantages outweigh the advantages.
+
+## Conclusion
+
+We have yet to find a method that is demonstratably better than the current
+method, when considering:
+
+1. Query performance.
+1. Readability.
+1. Ease of maintaining schema consistency.
diff --git a/doc/development/geo.md b/doc/development/geo.md
index 685d4e44ad3..24f16eae9fa 100644
--- a/doc/development/geo.md
+++ b/doc/development/geo.md
@@ -341,9 +341,9 @@ not used, so sessions etc. aren't shared between nodes.
GitLab can optionally use Object Storage to store data it would
otherwise store on disk. These things can be:
- - LFS Objects
- - CI Job Artifacts
- - Uploads
+- LFS Objects
+- CI Job Artifacts
+- Uploads
Objects that are stored in object storage, are not handled by Geo. Geo
ignores items in object storage. Either:
@@ -412,15 +412,15 @@ The Geo **primary** stores events in the `geo_event_log` table. Each
entry in the log contains a specific type of event. These type of
events include:
- - Repository Deleted event
- - Repository Renamed event
- - Repositories Changed event
- - Repository Created event
- - Hashed Storage Migrated event
- - Lfs Object Deleted event
- - Hashed Storage Attachments event
- - Job Artifact Deleted event
- - Upload Deleted event
+- Repository Deleted event
+- Repository Renamed event
+- Repositories Changed event
+- Repository Created event
+- Hashed Storage Migrated event
+- Lfs Object Deleted event
+- Hashed Storage Attachments event
+- Job Artifact Deleted event
+- Upload Deleted event
### Geo Log Cursor
@@ -526,4 +526,4 @@ old method:
- Replication is synchronous and we preserve the order of events.
- Replication of the events happen at the same time as the changes in the
- database.
+ database.
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
index c103a4527ff..5ce59891afa 100644
--- a/doc/development/git_object_deduplication.md
+++ b/doc/development/git_object_deduplication.md
@@ -79,11 +79,11 @@ at the Rails application level in SQL.
In conclusion, we need three things for effective object deduplication
across a collection of GitLab project repositories at the Git level:
-1. A pool repository must exist.
-2. The participating project repositories must be linked to the pool
- repository via their respective `objects/info/alternates` files.
-3. The pool repository must contain Git object data common to the
- participating project repositories.
+1. A pool repository must exist.
+1. The participating project repositories must be linked to the pool
+ repository via their respective `objects/info/alternates` files.
+1. The pool repository must contain Git object data common to the
+ participating project repositories.
### Deduplication factor
@@ -105,71 +105,71 @@ With pool repositories we made a fresh start. These live in their own
`pool_repositories` SQL table. The relations between these two tables
are as follows:
-- a `Project` belongs to at most one `PoolRepository`
- (`project.pool_repository`)
-- as an automatic consequence of the above, a `PoolRepository` has
- many `Project`s
-- a `PoolRepository` has exactly one "source `Project`"
- (`pool.source_project`)
+- a `Project` belongs to at most one `PoolRepository`
+ (`project.pool_repository`)
+- as an automatic consequence of the above, a `PoolRepository` has
+ many `Project`s
+- a `PoolRepository` has exactly one "source `Project`"
+ (`pool.source_project`)
> TODO Fix invalid SQL data for pools created prior to GitLab 11.11
> <https://gitlab.com/gitlab-org/gitaly/issues/1653>.
### Assumptions
-- All repositories in a pool must use [hashed
- storage](../administration/repository_storage_types.md). This is so
- that we don't have to ever worry about updating paths in
- `object/info/alternates` files.
-- All repositories in a pool must be on the same Gitaly storage shard.
- The Git alternates mechanism relies on direct disk access across
- multiple repositories, and we can only assume direct disk access to
- be possible within a Gitaly storage shard.
-- The only two ways to remove a member project from a pool are (1) to
- delete the project or (2) to move the project to another Gitaly
- storage shard.
+- All repositories in a pool must use [hashed
+ storage](../administration/repository_storage_types.md). This is so
+ that we don't have to ever worry about updating paths in
+ `object/info/alternates` files.
+- All repositories in a pool must be on the same Gitaly storage shard.
+ The Git alternates mechanism relies on direct disk access across
+ multiple repositories, and we can only assume direct disk access to
+ be possible within a Gitaly storage shard.
+- The only two ways to remove a member project from a pool are (1) to
+ delete the project or (2) to move the project to another Gitaly
+ storage shard.
### Creating pools and pool memberships
-- When a pool gets created, it must have a source project. The initial
- contents of the pool repository are a Git clone of the source
- project repository.
-- The occasion for creating a pool is when an existing eligible
- (public, hashed storage, non-forked) GitLab project gets forked and
- this project does not belong to a pool repository yet. The fork
- parent project becomes the source project of the new pool, and both
- the fork parent and the fork child project become members of the new
- pool.
-- Once project A has become the source project of a pool, all future
- eligible forks of A will become pool members.
-- If the fork source is itself a fork, the resulting repository will
- neither join the repository nor will a new pool repository be
- seeded.
-
- eg:
-
- Suppose fork A is part of a pool repository, any forks created off
- of fork A *will not* be a part of the pool repository that fork A is
- a part of.
-
- Suppose B is a fork of A, and A does not belong to an object pool.
- Now C gets created as a fork of B. C will not be part of a pool
- repository.
+- When a pool gets created, it must have a source project. The initial
+ contents of the pool repository are a Git clone of the source
+ project repository.
+- The occasion for creating a pool is when an existing eligible
+ (public, hashed storage, non-forked) GitLab project gets forked and
+ this project does not belong to a pool repository yet. The fork
+ parent project becomes the source project of the new pool, and both
+ the fork parent and the fork child project become members of the new
+ pool.
+- Once project A has become the source project of a pool, all future
+ eligible forks of A will become pool members.
+- If the fork source is itself a fork, the resulting repository will
+ neither join the repository nor will a new pool repository be
+ seeded.
+
+ eg:
+
+ Suppose fork A is part of a pool repository, any forks created off
+ of fork A *will not* be a part of the pool repository that fork A is
+ a part of.
+
+ Suppose B is a fork of A, and A does not belong to an object pool.
+ Now C gets created as a fork of B. C will not be part of a pool
+ repository.
> TODO should forks of forks be deduplicated?
> <https://gitlab.com/gitlab-org/gitaly/issues/1532>
### Consequences
-- If a normal Project participating in a pool gets moved to another
- Gitaly storage shard, its "belongs to PoolRepository" relation will
- be broken. Because of the way moving repositories between shard is
- implemented, we will automatically get a fresh self-contained copy
- of the project's repository on the new storage shard.
-- If the source project of a pool gets moved to another Gitaly storage
- shard or is deleted the "source project" relation is not broken.
- However, as of GitLab 12.0 a pool will not fetch from a source
- unless the source is on the same Gitaly shard.
+- If a normal Project participating in a pool gets moved to another
+ Gitaly storage shard, its "belongs to PoolRepository" relation will
+ be broken. Because of the way moving repositories between shard is
+ implemented, we will automatically get a fresh self-contained copy
+ of the project's repository on the new storage shard.
+- If the source project of a pool gets moved to another Gitaly storage
+ shard or is deleted the "source project" relation is not broken.
+ However, as of GitLab 12.0 a pool will not fetch from a source
+ unless the source is on the same Gitaly shard.
## Consistency between the SQL pool relation and Gitaly
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index f09339eb3a4..83444093f9c 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -107,6 +107,32 @@ Modules](https://github.com/golang/go/wiki/Modules). It provides a way to
define and lock dependencies for reproducible builds. It should be used
whenever possible.
+When Go Modules are in use, there should not be a `vendor/` directory. Instead,
+Go will automatically download dependencies when they are needed to build the
+project. This is in line with how dependencies are handled with Bundler in Ruby
+projects, and makes merge requests easier to review.
+
+In some cases, such as building a Go project for it to act as a dependency of a
+CI run for another project, removing the `vendor/` directory means the code must
+be downloaded repeatedly, which can lead to intermittent problems due to rate
+limiting or network failures. In these circumstances, you should cache the
+downloaded code between runs with a `.gitlab-ci.yml` snippet like this:
+
+```yaml
+.go-cache:
+ variables:
+ GOPATH: $CI_PROJECT_DIR/.go
+ before_script:
+ - mkdir -p .go
+ cache:
+ paths:
+ - .go/pkg/mod/
+
+test:
+ extends: .go-cache
+ # ...
+```
+
There was a [bug on modules
checksums](https://github.com/golang/go/issues/29278) in Go < v1.11.4, so make
sure to use at least this version to avoid `checksum mismatch` errors.
@@ -129,17 +155,89 @@ deploy a new pod, migrating the data automatically.
## Testing
+### Testing frameworks
+
We should not use any specific library or framework for testing, as the
[standard library](https://golang.org/pkg/) provides already everything to get
-started. For example, some external dependencies might be worth considering in
-case we decide to use a specific library or framework:
+started. If there is a need for more sophisticated testing tools, the following
+external dependencies might be worth considering in case we decide to use a specific
+library or framework:
- [Testify](https://github.com/stretchr/testify)
- [httpexpect](https://github.com/gavv/httpexpect)
+### Subtests
+
Use [subtests](https://blog.golang.org/subtests) whenever possible to improve
code readability and test output.
+### Better output in tests
+
+When comparing expected and actual values in tests, use
+[testify/require.Equal](https://godoc.org/github.com/stretchr/testify/require#Equal),
+[testify/require.EqualError](https://godoc.org/github.com/stretchr/testify/require#EqualError),
+[testify/require.EqualValues](https://godoc.org/github.com/stretchr/testify/require#EqualValues),
+and others to improve readability when comparing structs, errors,
+large portions of text, or JSON documents:
+
+```go
+type TestData struct {
+ // ...
+}
+
+func FuncUnderTest() TestData {
+ // ...
+}
+
+func Test(t *testing.T) {
+ t.Run("FuncUnderTest", func(t *testing.T) {
+ want := TestData{}
+ got := FuncUnderTest()
+
+ require.Equal(t, want, got) // note that expected value comes first, then comes the actual one ("diff" semantics)
+ })
+}
+```
+
+### Table-Driven Tests
+
+Using [Table-Driven Tests](https://github.com/golang/go/wiki/TableDrivenTests)
+is generally good practice when you have multiple entries of
+inputs/outputs for the same function. Below are some guidelines one can
+follow when writing table-driven test. These guidelines are mostly
+extracted from Go standard library source code. Keep in mind it's OK not
+to follow these guidelines when it makes sense.
+
+#### Defining test cases
+
+Each table entry is a complete test case with inputs and expected
+results, and sometimes with additional information such as a test name
+to make the test output easily readable.
+
+- [Define a slice of anonymous struct](https://github.com/golang/go/blob/50bd1c4d4eb4fac8ddeb5f063c099daccfb71b26/src/encoding/csv/reader_test.go#L16)
+ inside of the test.
+- [Define a slice of anonymous struct](https://github.com/golang/go/blob/55d31e16c12c38d36811bdee65ac1f7772148250/src/cmd/go/internal/module/module_test.go#L9-L66)
+ outside of the test.
+- [Named structs](https://github.com/golang/go/blob/2e0cd2aef5924e48e1ceb74e3d52e76c56dd34cc/src/cmd/go/internal/modfetch/coderepo_test.go#L54-L69)
+ for code reuse.
+- [Using `map[string]struct{}`](https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/trace/annotations_test.go#L180-L235).
+
+#### Contents of the test case
+
+- Ideally, each test case should have a field with a unique identifier
+ to use for naming subtests. In the Go standard library, this is commonly the
+ `name string` field.
+- Use `want`/`expect`/`actual` when you are specifcing something in the
+ test case that will be used for assertion.
+
+#### Variable names
+
+- Each table-driven test map/slice of struct can be named `tests`.
+- When looping through `tests` the anonymous struct can be referred
+ to as `tt` or `tc`.
+- The description of the test can be referred to as
+ `name`/`testName`/`tn`.
+
### Benchmarks
Programs handling a lot of IO or complex operations should always include
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 13dda17bb7d..941eea2609e 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -53,7 +53,7 @@ When run, this spec doesn't do what we might expect:
(compared using ==)
```
-That's because FactoryBot sequences are not reseted for each example.
+This is because FactoryBot sequences are not reset for each example.
Please remember that sequence-generated values exist only to avoid having to
explicitly set attributes that have a uniqueness constraint when using a factory.
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/i18n/externalization.md b/doc/development/i18n/externalization.md
index 17462887162..141f5a8d6d9 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -21,18 +21,18 @@ The following tools are used:
1. [`gettext_i18n_rails`](https://github.com/grosser/gettext_i18n_rails): this
gem allow us to translate content from models, views and controllers. Also
it gives us access to the following raketasks:
- - `rake gettext:find`: Parses almost all the files from the
- Rails application looking for content that has been marked for
- translation. Finally, it updates the PO files with the new content that
- it has found.
- - `rake gettext:pack`: Processes the PO files and generates the
- MO files that are binary and are finally used by the application.
+ - `rake gettext:find`: Parses almost all the files from the
+ Rails application looking for content that has been marked for
+ translation. Finally, it updates the PO files with the new content that
+ it has found.
+ - `rake gettext:pack`: Processes the PO files and generates the
+ MO files that are binary and are finally used by the application.
1. [`gettext_i18n_rails_js`](https://github.com/webhippie/gettext_i18n_rails_js):
this gem is useful to make the translations available in JavaScript. It
provides the following raketask:
- - `rake gettext:po_to_json`: Reads the contents from the PO files and
- generates JSON files containing all the available translations.
+ - `rake gettext:po_to_json`: Reads the contents from the PO files and
+ generates JSON files containing all the available translations.
1. PO editor: there are multiple applications that can help us to work with PO
files, a good option is [Poedit](https://poedit.net/download) which is
@@ -139,60 +139,61 @@ For example use `%{created_at}` in Ruby but `%{createdAt}` in JavaScript. Make s
- In Ruby/HAML:
- ```ruby
- _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe'
- ```
+ ```ruby
+ _("Hello %{name}") % { name: 'Joe' } => 'Hello Joe'
+ ```
- In JavaScript:
- ```js
- import { __, sprintf } from '~/locale';
+ ```js
+ import { __, sprintf } from '~/locale';
- sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
- ```
+ sprintf(__('Hello %{username}'), { username: 'Joe' }); // => 'Hello Joe'
+ ```
- By default, `sprintf` escapes the placeholder values.
- If you want to take care of that yourself, you can pass `false` as third argument.
+ By default, `sprintf` escapes the placeholder values.
+ If you want to take care of that yourself, you can pass `false` as third argument.
- ```js
- import { __, sprintf } from '~/locale';
+ ```js
+ import { __, sprintf } from '~/locale';
- sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
- sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
- ```
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }); // => 'This is &lt;strong&gt;bold&lt;/strong&gt;'
+ sprintf(__('This is %{value}'), { value: '<strong>bold</strong>' }, false); // => 'This is <strong>bold</strong>'
+ ```
### Plurals
- In Ruby/HAML:
- ```ruby
- n_('Apple', 'Apples', 3)
- # => 'Apples'
- ```
+ ```ruby
+ n_('Apple', 'Apples', 3)
+ # => 'Apples'
+ ```
- Using interpolation:
- ```ruby
- n_("There is a mouse.", "There are %d mice.", size) % size
- # => When size == 1: 'There is a mouse.'
- # => When size == 2: 'There are 2 mice.'
- ```
+ Using interpolation:
- Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
+ ```ruby
+ n_("There is a mouse.", "There are %d mice.", size) % size
+ # => When size == 1: 'There is a mouse.'
+ # => When size == 2: 'There are 2 mice.'
+ ```
+
+ Avoid using `%d` or count variables in singular strings. This allows more natural translation in some languages.
- In JavaScript:
- ```js
- n__('Apple', 'Apples', 3)
- // => 'Apples'
- ```
+ ```js
+ n__('Apple', 'Apples', 3)
+ // => 'Apples'
+ ```
- Using interpolation:
+ Using interpolation:
- ```js
- n__('Last day', 'Last %d days', x)
- // => When x == 1: 'Last day'
- // => When x == 2: 'Last 2 days'
- ```
+ ```js
+ n__('Last day', 'Last %d days', x)
+ // => When x == 1: 'Last day'
+ // => When x == 2: 'Last 2 days'
+ ```
### Namespaces
@@ -202,17 +203,17 @@ Namespaces should be PascalCase.
- In Ruby/HAML:
- ```ruby
- s_('OpenedNDaysAgo|Opened')
- ```
+ ```ruby
+ s_('OpenedNDaysAgo|Opened')
+ ```
- In case the translation is not found it will return `Opened`.
+ In case the translation is not found it will return `Opened`.
- In JavaScript:
- ```js
- s__('OpenedNDaysAgo|Opened')
- ```
+ ```js
+ s__('OpenedNDaysAgo|Opened')
+ ```
Note: The namespace should be removed from the translation. See the [translation
guidelines for more details](translation.md#namespaced-strings).
@@ -235,12 +236,12 @@ This makes use of [`Intl.DateTimeFormat`].
- In Ruby/HAML, we have two ways of adding format to dates and times:
1. **Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
- [dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
- If you need to add a new format, because other parts of the code could benefit from it,
- you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
+ [dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
+ If you need to add a new format, because other parts of the code could benefit from it,
+ you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
1. **Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
- defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
- specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
+ defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
+ specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
## Best practices
@@ -268,40 +269,40 @@ should be externalized as follows:
This also applies when using links in between translated sentences, otherwise these texts are not translatable in certain languages.
- In Ruby/HAML, instead of:
-
- ```haml
- - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
- = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
- ```
-
- Set the link starting and ending HTML fragments as variables like so:
-
- ```haml
- - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
- - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
- = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
- ```
+
+ ```haml
+ - zones_link = link_to(s_('ClusterIntegration|zones'), 'https://cloud.google.com/compute/docs/regions-zones/regions-zones', target: '_blank', rel: 'noopener noreferrer')
+ = s_('ClusterIntegration|Learn more about %{zones_link}').html_safe % { zones_link: zones_link }
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```haml
+ - zones_link_url = 'https://cloud.google.com/compute/docs/regions-zones/regions-zones'
+ - zones_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: zones_link_url }
+ = s_('ClusterIntegration|Learn more about %{zones_link_start}zones%{zones_link_end}').html_safe % { zones_link_start: zones_link_start, zones_link_end: '</a>'.html_safe }
+ ```
- In JavaScript, instead of:
- ```js
- {{
- sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
- link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
- })
- }}
- ```
-
- Set the link starting and ending HTML fragments as variables like so:
-
- ```js
- {{
- sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
- linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
- linkEnd: '</a>',
- })
- }}
- ```
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{link}"), {
+ link: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">zones</a>'
+ })
+ }}
+ ```
+
+ Set the link starting and ending HTML fragments as variables like so:
+
+ ```js
+ {{
+ sprintf(s__("ClusterIntegration|Learn more about %{linkStart}zones%{linkEnd}"), {
+ linkStart: '<a href="https://cloud.google.com/compute/docs/regions-zones/regions-zones" target="_blank" rel="noopener noreferrer">'
+ linkEnd: '</a>',
+ })
+ }}
+ ```
The reasoning behind this is that in some languages words change depending on context. For example in Japanese は is added to the subject of a sentence and を to the object. This is impossible to translate correctly if we extract individual words from the sentence.
@@ -374,29 +375,29 @@ Let's suppose you want to add translations for a new language, let's say French.
1. The first step is to register the new language in `lib/gitlab/i18n.rb`:
- ```ruby
- ...
- AVAILABLE_LANGUAGES = {
- ...,
- 'fr' => 'Français'
- }.freeze
- ...
- ```
+ ```ruby
+ ...
+ AVAILABLE_LANGUAGES = {
+ ...,
+ 'fr' => 'Français'
+ }.freeze
+ ...
+ ```
1. Next, you need to add the language:
- ```sh
- bin/rake gettext:add_language[fr]
- ```
+ ```sh
+ bin/rake gettext:add_language[fr]
+ ```
- If you want to add a new language for a specific region, the command is similar,
- you just need to separate the region with an underscore (`_`). For example:
+ If you want to add a new language for a specific region, the command is similar,
+ you just need to separate the region with an underscore (`_`). For example:
- ```sh
- bin/rake gettext:add_language[en_GB]
- ```
+ ```sh
+ bin/rake gettext:add_language[en_GB]
+ ```
- Please note that you need to specify the region part in capitals.
+ Please note that you need to specify the region part in capitals.
1. Now that the language is added, a new directory has been created under the
path: `locale/fr/`. You can now start using your PO editor to edit the PO file
@@ -406,9 +407,9 @@ Let's suppose you want to add translations for a new language, let's say French.
in order to generate the binary MO files and finally update the JSON files
containing the translations:
- ```sh
- bin/rake gettext:compile
- ```
+ ```sh
+ bin/rake gettext:compile
+ ```
1. In order to see the translated content we need to change our preferred language
which can be found under the user's **Settings** (`/profile`).
@@ -416,7 +417,7 @@ Let's suppose you want to add translations for a new language, let's say French.
1. After checking that the changes are ok, you can proceed to commit the new files.
For example:
- ```sh
- git add locale/fr/ app/assets/javascripts/locale/fr/
- git commit -m "Add French translations for Cycle Analytics page"
- ```
+ ```sh
+ git add locale/fr/ app/assets/javascripts/locale/fr/
+ git commit -m "Add French translations for Cycle Analytics page"
+ ```
diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md
index 910d7296057..492e3d48164 100644
--- a/doc/development/i18n/proofreader.md
+++ b/doc/development/i18n/proofreader.md
@@ -80,6 +80,7 @@ are very appreciative of the work done by translators and proofreaders!
- Russian
- Nikita Grylov - [GitLab](https://gitlab.com/nixel2007), [Crowdin](https://crowdin.com/profile/nixel2007)
- Alexy Lustin - [GitLab](https://gitlab.com/allustin), [Crowdin](https://crowdin.com/profile/lustin)
+ - Mark Minakou - [GitLab](https://gitlab.com/sandzhaj), [Crowdin](https://crowdin.com/profile/sandzhaj)
- NickVolynkin - [Crowdin](https://crowdin.com/profile/NickVolynkin)
- Serbian (Cyrillic)
- Proofreaders needed.
diff --git a/doc/development/img/architecture_simplified.png b/doc/development/img/architecture_simplified.png
index 1698c167c5e..1ad57b65468 100644
--- a/doc/development/img/architecture_simplified.png
+++ b/doc/development/img/architecture_simplified.png
Binary files differ
diff --git a/doc/development/img/distributed_tracing_jaeger_ui.png b/doc/development/img/distributed_tracing_jaeger_ui.png
index 57517dacced..dcd18b1ec9f 100644
--- a/doc/development/img/distributed_tracing_jaeger_ui.png
+++ b/doc/development/img/distributed_tracing_jaeger_ui.png
Binary files differ
diff --git a/doc/development/img/distributed_tracing_performance_bar.png b/doc/development/img/distributed_tracing_performance_bar.png
index c9998cedd2d..8c819045104 100644
--- a/doc/development/img/distributed_tracing_performance_bar.png
+++ b/doc/development/img/distributed_tracing_performance_bar.png
Binary files differ
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
index 64c91f151c5..0c343bc22e4 100644
--- a/doc/development/import_export.md
+++ b/doc/development/import_export.md
@@ -19,6 +19,7 @@ Project.find_by_full_path('group/project').import_state.slice(:jid, :status, :la
grep JID /var/log/gitlab/sidekiq/current
grep "Import/Export error" /var/log/gitlab/sidekiq/current
grep "Import/Export backtrace" /var/log/gitlab/sidekiq/current
+tail /var/log/gitlab/gitlab-rails/importer.log
```
## Troubleshooting performance issues
@@ -229,8 +230,8 @@ meaning that we want to bump the version up in the next version (or patch releas
For example:
1. Add rename to `RelationRenameService` in X.Y
-2. Remove it from `RelationRenameService` in X.Y + 1
-3. Bump Import/Export version in X.Y + 1
+1. Remove it from `RelationRenameService` in X.Y + 1
+1. Bump Import/Export version in X.Y + 1
```ruby
module Gitlab
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
new file mode 100644
index 00000000000..5e6dc8d460a
--- /dev/null
+++ b/doc/development/interacting_components.md
@@ -0,0 +1,29 @@
+# Developing against interacting components or features
+
+It's not uncommon that a single code change can reflect and interact with multiple parts of GitLab
+codebase. Furthermore, an existing feature might have an underlying integration or behavior that
+might go unnoticed even by reviewers and maintainers.
+
+The goal of this section is to briefly list interacting pieces to think about
+when making _backend_ changes that might involve multiple features or [components](architecture.md#components).
+
+## Uploads
+
+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
+against [Minio](https://gitlab.com/gitlab-org/gitlab-development-kit/blob/master/doc/howto/object_storage.md).
+
+See also [File Storage in GitLab](file_storage.md).
+
+## Merge requests
+
+### Forks
+
+GitLab supports a great amount of features for [merge requests](../user/project/merge_requests/index.md). One
+of them is the ability to create merge requests from and to [forks](../gitlab-basics/fork-project.md),
+which should also be highly considered and tested upon development phase.
+
+[object storage]: https://docs.gitlab.com/charts/advanced/external-object-storage/
diff --git a/doc/development/licensed_feature_availability.md b/doc/development/licensed_feature_availability.md
index 80ec7b8c0cf..29e4ace157b 100644
--- a/doc/development/licensed_feature_availability.md
+++ b/doc/development/licensed_feature_availability.md
@@ -14,7 +14,7 @@ it should be restricted on namespace scope.
1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
`ee/app/models/license.rb`. Note on `ee/app/models/ee/namespace.rb` that _Bronze_ GitLab.com
features maps to on-premise _EES_, _Silver_ to _EEP_ and _Gold_ to _EEU_.
-2. Check using:
+1. Check using:
```ruby
project.feature_available?(:feature_symbol)
@@ -29,8 +29,8 @@ the instance license.
1. Add the feature symbol on `EES_FEATURES`, `EEP_FEATURES` or `EEU_FEATURES` constants in
`ee/app/models/license.rb`.
-2. Add the same feature symbol to `GLOBAL_FEATURES`
-3. Check using:
+1. Add the same feature symbol to `GLOBAL_FEATURES`
+1. Check using:
```ruby
License.feature_available?(:feature_symbol)
diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md
index 0c7601b415e..3181b3a88cc 100644
--- a/doc/development/migration_style_guide.md
+++ b/doc/development/migration_style_guide.md
@@ -10,9 +10,7 @@ migrations are written carefully, can be applied online and adhere to the style
guide below.
Migrations are **not** allowed to require GitLab installations to be taken
-offline unless _absolutely necessary_. Downtime assumptions should be based on
-the behaviour of a migration when performed using PostgreSQL, as various
-operations in MySQL may require downtime without there being alternatives.
+offline unless _absolutely necessary_.
When downtime is necessary the migration has to be approved by:
@@ -343,10 +341,7 @@ class AddOptionsToBuildMetadata < ActiveRecord::Migration[5.0]
end
```
-On MySQL the `JSON` and `JSONB` is translated to `TEXT 1MB`, as `JSONB` is PostgreSQL only feature.
-
-For above reason you have to use a serializer to provide a translation layer
-in order to support PostgreSQL and MySQL seamlessly:
+You have to use a serializer to provide a translation layer:
```ruby
class BuildMetadata
@@ -356,7 +351,7 @@ end
## Testing
-Make sure that your migration works with MySQL and PostgreSQL with data. An
+Make sure that your migration works for databases with data. An
empty database does not guarantee that your migration is correct.
Make sure your migration can be reversed.
diff --git a/doc/development/module_with_instance_variables.md b/doc/development/module_with_instance_variables.md
index 7bdfa04fc57..443eee0b62c 100644
--- a/doc/development/module_with_instance_variables.md
+++ b/doc/development/module_with_instance_variables.md
@@ -1,6 +1,6 @@
-## Modules with instance variables could be considered harmful
+# Modules with instance variables could be considered harmful
-### Background
+## Background
Rails somehow encourages people using modules and instance variables
everywhere. For example, using instance variables in the controllers,
@@ -9,7 +9,7 @@ helpers, and views. They're also encouraging the use of
saving everything in a giant, single object, and people could access
everything in that one giant object.
-### The problems
+## The problems
Of course this is convenient to develop, because we just have everything
within reach. However this has a number of downsides when that chosen object
@@ -24,7 +24,7 @@ manipulated from 3 different modules. It's hard to track when those variables
start giving us troubles. We don't know which module would suddenly change
one of the variables. Everything could touch anything.
-### Similar concerns
+## Similar concerns
People are saying multiple inheritance is bad. Mixing multiple modules with
multiple instance variables scattering everywhere suffer from the same issue.
@@ -40,7 +40,7 @@ Note that `included` doesn't solve the whole issue. They define the
dependencies, but they still allow each modules to talk implicitly via the
instance variables in the final giant object, and that's where the problem is.
-### Solutions
+## Solutions
We should split the giant object into multiple objects, and they communicate
with each other with the API, i.e. public methods. In short, composition over
@@ -53,7 +53,7 @@ With clearly defined API, this would make things less coupled and much easier
to debug and track, and much more extensible for other objects to use, because
they communicate in a clear way, rather than implicit dependencies.
-### Acceptable use
+## Acceptable use
However, it's not always bad to use instance variables in a module,
as long as it's contained in the same module; that is, no other modules or
@@ -74,7 +74,7 @@ Unfortunately it's not easy to code more complex rules into the cop, so
we rely on people's best judgement. If we could find another good pattern
we could easily add to the cop, we should do it.
-### How to rewrite and avoid disabling this cop
+## How to rewrite and avoid disabling this cop
Even if we could just disable the cop, we should avoid doing so. Some code
could be easily rewritten in simple form. Consider this acceptable method:
@@ -181,7 +181,7 @@ rather than whatever includes the module, and those modules which were also
included, making it much easier to track down any issues,
and reducing the chance of having name conflicts.
-### How to disable this cop
+## How to disable this cop
Put the disabling comment right after your code in the same line:
@@ -210,14 +210,14 @@ end
Note that you need to enable it at some point, otherwise everything below
won't be checked.
-### Things we might need to ignore right now
+## Things we might need to ignore right now
Because of the way Rails helpers and mailers work, we might not be able to
avoid the use of instance variables there. For those cases, we could ignore
them at the moment. At least we're not going to share those modules with
other random objects, so they're still somewhat isolated.
-### Instance variables in views
+## Instance variables in views
They're bad because we can't easily tell who's using the instance variables
(from controller's point of view) and where we set them up (from partials'
diff --git a/doc/development/new_fe_guide/dependencies.md b/doc/development/new_fe_guide/dependencies.md
index 12a4f089d41..8a6930acd37 100644
--- a/doc/development/new_fe_guide/dependencies.md
+++ b/doc/development/new_fe_guide/dependencies.md
@@ -15,6 +15,18 @@ Exceptions are made for some tools that we require in the
`gitlab:assets:compile` CI job such as `webpack-bundle-analyzer` to analyze our
production assets post-compile.
+To add or upgrade a dependency, run:
+
+```sh
+yarn add <your dependency here>
+```
+
+This may introduce duplicate dependencies. To de-duplicate `yarn.lock`, run:
+
+```sh
+node_modules/.bin/yarn-deduplicate --list --strategy fewer yarn.lock && yarn install
+```
+
---
> TODO: Add Dependencies
diff --git a/doc/development/new_fe_guide/development/accessibility.md b/doc/development/new_fe_guide/development/accessibility.md
index 81a29170129..ae5c4c6a6cc 100644
--- a/doc/development/new_fe_guide/development/accessibility.md
+++ b/doc/development/new_fe_guide/development/accessibility.md
@@ -1,17 +1,21 @@
# Accessiblity
+
Using semantic HTML plays a key role when it comes to accessibility.
## Accessible Rich Internet Applications - ARIA
+
WAI-ARIA, the Accessible Rich Internet Applications specification, defines a way to make Web content and Web applications more accessible to people with disabilities.
> Note: It is [recommended][using-aria] to use semantic elements as the primary method to achieve accessibility rather than adding aria attributes. Adding aria attributes should be seen as a secondary method for creating accessible elements.
### Role
+
The `role` attribute describes the role the element plays in the context of the document.
Check the list of WAI-ARIA roles [here][roles]
## Icons
+
When using icons or images that aren't absolutely needed to understand the context, we should use `aria-hidden="true"`.
On the other hand, if an icon is crucial to understand the context we should do one of the following:
@@ -20,6 +24,7 @@ On the other hand, if an icon is crucial to understand the context we should do
1. Use `aria-labelledby` to point to an element that contains the explanation for that icon
## Form inputs
+
In forms we should use the `for` attribute in the label statement:
```
diff --git a/doc/development/new_fe_guide/development/performance.md b/doc/development/new_fe_guide/development/performance.md
index c54b8305991..d41239693bf 100644
--- a/doc/development/new_fe_guide/development/performance.md
+++ b/doc/development/new_fe_guide/development/performance.md
@@ -5,7 +5,7 @@
We have a performance dashboard available in one of our [grafana instances](https://dashboards.gitlab.net/d/1EBTz3Dmz/sitespeed-page-summary?orgId=1). This dashboard automatically aggregates metric data from [sitespeed.io](https://www.sitespeed.io/) every 6 hours. These changes are displayed after a set number of pages are aggregated.
These pages can be found inside a text file in the gitlab-build-images [repository](https://gitlab.com/gitlab-org/gitlab-build-images) called [gitlab.txt](https://gitlab.com/gitlab-org/gitlab-build-images/blob/master/scripts/gitlab.txt)
-Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/company/team) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
+Any frontend engineer can contribute to this dashboard. They can contribute by adding or removing urls of pages from this text file. Please have a [frontend monitoring expert](https://about.gitlab.com/company/team/) review your changes before assigning to a maintainer of the `gitlab-build-images` project. The changes will go live on the next scheduled run after the changes are merged into `master`.
There are 3 recommended high impact metrics to review on each page:
diff --git a/doc/development/new_fe_guide/development/testing.md b/doc/development/new_fe_guide/development/testing.md
index 2b62c2a41fe..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/2.15.1)).
-
-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/index.md b/doc/development/new_fe_guide/style/index.md
index 335d9e66240..f073dc56f1f 100644
--- a/doc/development/new_fe_guide/style/index.md
+++ b/doc/development/new_fe_guide/style/index.md
@@ -8,7 +8,7 @@
## [Vue style guide](vue.md)
-# Tooling
+## Tooling
## [Prettier](prettier.md)
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 3019eaa089c..b742d567f41 100644
--- a/doc/development/new_fe_guide/style/javascript.md
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -71,7 +71,6 @@ class myClass {
}
const instance = new myClass();
instance.makeRequest();
-
```
## Avoid classes to handle DOM events
@@ -189,8 +188,8 @@ disabled due to legacy compatibility reasons but they are in the process of bein
Do not disable specific ESLint rules. Due to technical debt, you may disable the following
rules only if you are invoking/instantiating existing code modules.
- - [no-new](http://eslint.org/docs/rules/no-new)
- - [class-method-use-this](http://eslint.org/docs/rules/class-methods-use-this)
+- [no-new](http://eslint.org/docs/rules/no-new)
+- [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/performance.md b/doc/development/performance.md
index 8b569a677b6..14b3f8204d2 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -267,7 +267,7 @@ piece of code is worth optimizing. The only two things you can do are:
1. Think about what the code does, how it's used, how many times it's called and
how much time is spent in it relative to the total execution time (e.g., the
total time spent in a web request).
-2. Ask others (preferably in the form of an issue).
+1. Ask others (preferably in the form of an issue).
Some examples of changes that aren't really important/worth the effort:
@@ -285,10 +285,10 @@ directly in a web request as much as possible. This has numerous benefits such
as:
1. An error won't prevent the request from completing.
-2. The process being slow won't affect the loading time of a page.
-3. In case of a failure it's easy to re-try the process (Sidekiq takes care of
+1. The process being slow won't affect the loading time of a page.
+1. In case of a failure it's easy to re-try the process (Sidekiq takes care of
this automatically).
-4. By isolating the code from a web request it will hopefully be easier to test
+1. By isolating the code from a web request it will hopefully be easier to test
and maintain.
It's especially important to use Sidekiq as much as possible when dealing with
diff --git a/doc/development/policies.md b/doc/development/policies.md
index c4ac42bb40a..833b0acb13e 100644
--- a/doc/development/policies.md
+++ b/doc/development/policies.md
@@ -89,8 +89,8 @@ Each line represents a rule that was evaluated. There are a few things to note:
1. The `-` or `+` symbol indicates whether the rule block was evaluated to be
`false` or `true`, respectively.
-2. The number inside the brackets indicates the score.
-3. The last part of the line (e.g. `@john : Issue/1`) shows the username
+1. The number inside the brackets indicates the score.
+1. The last part of the line (e.g. `@john : Issue/1`) shows the username
and subject for that rule.
Here you can see that the first four rules were evaluated `false` for
diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md
index a6b60149ea4..3787e2ef187 100644
--- a/doc/development/query_recorder.md
+++ b/doc/development/query_recorder.md
@@ -36,6 +36,13 @@ it "avoids N+1 database queries" do
end
```
+## Use request specs instead of controller specs
+
+Use a [request spec](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/spec/requests) when writing a N+1 test on the controller level.
+
+Controller specs should not be used to write N+1 tests as the controller is only initialized once per example.
+This could lead to false successes where subsequent "requests" could have queries reduced (e.g. because of memoization).
+
## Finding the source of the query
It may be useful to identify the source of the queries by looking at the call backtrace.
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index c97e179910b..fc96820c555 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -9,7 +9,7 @@ bundle exec rake setup
```
The `setup` task is an alias for `gitlab:setup`.
-This tasks calls `db:reset` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and finally it calls `db:seed_fu` to seed the database.
+This tasks calls `db:reset` to create the database, and calls `db:seed_fu` to seed the database.
Note: `db:setup` calls `db:seed` but this does nothing.
### Seeding issues for all or a given project
@@ -216,3 +216,4 @@ bundle exec rake routes
Since these take some time to create, it's often helpful to save the output to
a file for quick reference.
+
diff --git a/doc/development/reusing_abstractions.md b/doc/development/reusing_abstractions.md
index 59da02ed6fd..fce144f8dc2 100644
--- a/doc/development/reusing_abstractions.md
+++ b/doc/development/reusing_abstractions.md
@@ -14,13 +14,13 @@ on maintainability, the ability to easily debug problems, or even performance.
An example would be to use `ProjectsFinder` in `IssuesFinder` to limit issues to
those belonging to a set of projects. While initially this may seem like a good
idea, both classes provide a very high level interface with very little control.
-This means that `IssuesFinder` may not be able to produce a better optimised
+This means that `IssuesFinder` may not be able to produce a better optimized
database query, as a large portion of the query is controlled by the internals
of `ProjectsFinder`.
To work around this problem, you would use the same code used by
`ProjectsFinder`, instead of using `ProjectsFinder` itself directly. This allows
-you to compose your behaviour better, giving you more control over the behaviour
+you to compose your behavior better, giving you more control over the behavior
of the code.
To illustrate, consider the following code from `IssuableFinder#projects`:
@@ -52,7 +52,7 @@ functionality is added to this (high level) interface. Instead of _only_
affecting the cases where this is necessary, it may also start affecting
`IssuableFinder` in a negative way. For example, the query produced by
`GroupProjectsFinder` may include unnecessary conditions. Since we're using a
-finder here, we can't easily opt-out of that behaviour. We could add options to
+finder here, we can't easily opt-out of that behavior. We could add options to
do so, but then we'd need as many options as we have features. Every option adds
two code paths, which means that for four features we have to cover 8 different
code paths.
@@ -213,6 +213,5 @@ The API provided by Active Record itself, such as the `where` method, `save`,
Everything in `app/workers`.
-The scheduling of Sidekiq jobs using `SomeWorker.perform_async`, `perform_in`,
-etc. Directly invoking a worker using `SomeWorker.new.perform` should be avoided
-at all times in application code, though this is fine to use in tests.
+Use `SomeWorker.perform_async` or `SomeWorker.perform_in` to schedule Sidekiq
+jobs. Never directly invoke a worker using `SomeWorker.new.perform`.
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
new file mode 100644
index 00000000000..0809f8b1a0a
--- /dev/null
+++ b/doc/development/shell_scripting_guide/index.md
@@ -0,0 +1,118 @@
+# Shell scripting standards and style guidelines
+
+## Overview
+
+GitLab consists of many various services and sub-projects. The majority of
+their backend code is written in [Ruby](https://www.ruby-lang.org) and
+[Go](https://golang.org). However, some of them use shell scripts for
+automation of routine system administration tasks like deployment,
+installation, etc. It's being done either for historical reasons or as an effort
+to minimize the dependencies, for instance, for Docker images.
+
+This page aims to define and organize our shell scripting guidelines,
+based on our various experiences. All shell scripts across GitLab project
+should be eventually harmonized with this guide. If there are any per-project
+deviations from this guide, they should be described in the
+`README.md` or `PROCESS.md` file for such a project.
+
+### Avoid using shell scripts
+
+CAUTION: **Caution:**
+This is a must-read section.
+
+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.
+
+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.
+
+## Scope of this guide
+
+According to the [GitLab installation requirements](../../install/requirements.md),
+this guide covers only those shells that are used by
+[supported Linux distributions](../../install/requirements.md#supported-linux-distributions),
+that is:
+
+- [POSIX Shell](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html)
+- [Bash](https://www.gnu.org/software/bash/)
+
+## Shell language choice
+
+- When you need to reduce the dependencies list, use what's provided by the environment. For example, for Docker images it's `sh` from `alpine` which is the base image for most of our tool images.
+- Everywhere else, use `bash` if possible. It's more powerful than `sh` but still a widespread shell.
+
+## Code style and format
+
+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.
+
+### Linting
+
+We're using the [ShellCheck](https://www.shellcheck.net/) utility in its default configuration to lint our
+shell scripts.
+
+All projects with shell scripts should use this GitLab CI/CD job:
+
+```yaml
+shell check:
+ image: koalaman/shellcheck-alpine
+ stage: test
+ before_script:
+ - shellcheck --version
+ script:
+ - shellcheck scripts/**/*.sh # path to your shell scripts
+```
+
+TIP: **Tip:**
+By default, ShellCheck will use the [shell detection](https://github.com/koalaman/shellcheck/wiki/SC2148#rationale)
+to determine the shell dialect in use. If the shell file is out of your control and ShellCheck cannot
+detect the dialect, use `-s` flag to specify it: `-s sh` or `-s bash`.
+
+### Formatting
+
+It's recommended to use the [shfmt](https://github.com/mvdan/sh#shfmt) tool to maintain consistent formatting.
+We format shell scripts according to the [Google Shell Style Guide](https://google.github.io/styleguide/shell.xml),
+so the following `shfmt` invocation should be applied to the project's script files:
+
+```bash
+shfmt -i 2 -ci scripts/**/*.sh
+```
+
+TIP: **Tip:**
+By default, shfmt will use the [shell detection](https://github.com/mvdan/sh#shfmt) similar to one of ShellCheck
+and ignore files starting with a period. To override this, use `-ln` flag to specify the shell dialect:
+`-ln posix` or `-ln bash`.
+
+NOTE: **Note:**
+Currently, the `shfmt` tool [is not shipped](https://github.com/mvdan/sh/issues/68) as a Docker image containing
+a Linux shell. This makes it impossible to use the [official Docker image](https://hub.docker.com/r/mvdan/shfmt)
+in GitLab Runner. This [may change](https://github.com/mvdan/sh/issues/68#issuecomment-507721371) in future.
+
+## Testing
+
+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)).
+
+## Code Review
+
+The code review should be performed according to:
+
+- [ShellCheck Checks list](https://github.com/koalaman/shellcheck/wiki/Checks)
+- [Google Shell Style Guide](https://google.github.io/styleguide/shell.xml)
+- [Shfmt formatting caveats](https://github.com/mvdan/sh#caveats)
+
+However, the recommended course of action is to use the aforementioned
+tools and address reported offenses. This should eliminate the need
+for code review.
+
+---
+
+[Return to Development documentation](../README.md).
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 448d9fd01c4..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
@@ -70,6 +60,7 @@ bundle exec rspec spec/[path]/[to]/[spec].rb
- On `before` and `after` hooks, prefer it scoped to `:context` over `:all`
- When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element,
use a Capyabara matcher beforehand (e.g. `find('.js-foo')`) to ensure the element actually exists.
+- Use `focus: true` to isolate parts of the specs you want to run.
[four-phase-test]: https://robots.thoughtbot.com/four-phase-test
@@ -454,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 2dc06ba10a5..d6b944a3e74 100644
--- a/doc/development/testing_guide/end_to_end/index.md
+++ b/doc/development/testing_guide/end_to_end/index.md
@@ -65,28 +65,23 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
-![QA on merge requests CI/CD architecture](../img/qa_on_merge_requests_cicd_architecture.png)
-
-<details>
-<summary>Show mermaid source</summary>
-<pre>
+```mermaid
graph LR
A1 -.->|1. Triggers an omnibus-gitlab pipeline and wait for it to be done| A2
- B2[<b>`Trigger-qa` stage</b><br />`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
+ 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[<b>`test` stage</b><br />`package-and-qa` job]
+subgraph "gitlab-ce/ee pipeline"
+ A1[`test` stage<br>`package-and-qa` job]
end
-subgraph omnibus-gitlab pipeline
- A2[<b>`Trigger-docker` stage</b><br />`Trigger:gitlab-docker` job] -->|once done| B2
+subgraph "omnibus-gitlab pipeline"
+ A2[`Trigger-docker` stage<br>`Trigger:gitlab-docker` job] -->|once done| B2
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
+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
end
-</pre>
-</details>
+```
1. Developer triggers a manual action, that can be found in CE / EE merge
requests. This starts a chain of pipelines in multiple projects.
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 29ad49403fe..47e58a425fd 100644
--- a/doc/development/testing_guide/end_to_end/page_objects.md
+++ b/doc/development/testing_guide/end_to_end/page_objects.md
@@ -27,7 +27,7 @@ When someone later changes `t.text_field :login` in the view associated with
this page to `t.text_field :username` it will generate a different field
identifier, what would effectively break all tests.
-Because we are using `Page::Main::Login.act { sign_in_using_credentials }`
+Because we are using `Page::Main::Login.perform(&:sign_in_using_credentials)`
everywhere, when we want to sign into GitLab, the page object is the single
source of truth, and we will need to update `fill_in :user_login`
to `fill_in :user_username` only in a one place.
@@ -105,7 +105,7 @@ code but **this is deprecated** in favor of the above method for two reasons:
view 'app/views/my/view.html.haml' do
### Good ###
-
+
# Implicitly require the CSS selector `[data-qa-selector="logout_button"]` to be present in the view
element :logout_button
@@ -152,10 +152,9 @@ Things to note:
- The name of the element and the qa_selector must match and be snake_cased
- If the element appears on the page unconditionally, add `required: true` to the element. See
[Dynamic element validation](dynamic_element_validation.md)
-- You may see `.qa-selector` classes in existing Page Objects. We should prefer the [`data-qa-selector`](#data-qa-selector-vs-qa-selector)
+- You may see `.qa-selector` classes in existing Page Objects. We should prefer the [`data-qa-selector`](#data-qa-selector-vs-qa-selector)
method of definition over the `.qa-selector` CSS class
-
### `data-qa-selector` vs `.qa-selector`
> Introduced in GitLab 12.1
diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md
index 3bbf8feab39..e1df8be8b6f 100644
--- a/doc/development/testing_guide/end_to_end/quick_start_guide.md
+++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md
@@ -110,15 +110,15 @@ end
```
> Notice that the test itself is simple. The most challenging part is the creation of the application state, which will be covered later.
-
+>
> The exemplified test case's MVC is not enough for the change to be merged, but it helps to build up the test logic. The reason is that we do not want to use locators directly in the tests, and tests **must** use [Page Objects] before they can be merged. This way we better separate the responsibilities, where the Page Objects encapsulate elements and methods that allow us to interact with pages, while the spec files describe the test cases in more business-related language.
Below are the steps that the test covers:
1. The test finds the 'Edit' link for the labels and clicks on it.
-2. Then it fills in the 'Assign labels' input field with the value 'animal::dolphin' and press enters.
-3. Then it clicks in the content body to apply the label and refreshes the page.
-4. Finally, the expectations check that the previous scoped label was removed and that the new one was added.
+1. Then it fills in the 'Assign labels' input field with the value 'animal::dolphin' and press enters.
+1. Then it clicks in the content body to apply the label and refreshes the page.
+1. Finally, the expectations check that the previous scoped label was removed and that the new one was added.
Let's now see how the second test case would look.
@@ -144,9 +144,9 @@ end
Below are the steps that the test covers:
1. The test finds the 'Edit' link for the labels and clicks on it.
-2. Then it fills in the 'Assign labels' input field with the value 'plant::orchid' and press enters.
-3. Then it clicks in the content body to apply the label and refreshes the page.
-4. Finally, the expectations check that both scoped labels are present.
+1. Then it fills in the 'Assign labels' input field with the value 'plant::orchid' and press enters.
+1. Then it clicks in the content body to apply the label and refreshes the page.
+1. Finally, the expectations check that both scoped labels are present.
> Similar to the previous test, this one is also very straightforward, but there is some code duplication. Let's address it.
@@ -211,7 +211,7 @@ A pre-condition for the entire test suite is defined in the `before :context` bl
> For our test suite, due to the need of the tests being completely independent of each other, we won't use the `before :context` block. The `before :context` block would make the tests dependent on each other because the first test changes the label of the issue, and the second one depends on the `'animal::fox'` label being set.
-> **Tip:** In case of a test suite with only one `it` block it's ok to use only the `before` block (see below) with all the test's pre-conditions.
+TIP: **Tip:** In case of a test suite with only one `it` block it's ok to use only the `before` block (see below) with all the test's pre-conditions.
#### `before`
@@ -274,11 +274,11 @@ end
In the `before` block we create all the application state needed for the tests to run. We do that by using the `Runtime::Browser.visit` method to go to the login page, by performing a `sign_in_using_credentials` from the `Login` Page Object, by fabricating resources via APIs (`issue`, and `Resource::Label`), and by using the `issue.visit!` to visit the issue page.
> A project is created in the background by creating the `issue` resource.
-
+>
> When creating the [Resources], notice that when calling the `fabricate_via_api` method, we pass some attribute:values, like `title`, and `labels` for the `issue` resource; and `project` and `title` for the `label` resource.
-
+>
> What's important to understand here is that by creating the application state mostly using the public APIs we save a lot of time in the test suite setup stage.
-
+>
> Soon we will cover the use of the already existing resources' methods and the creation of your own `fabricate_via_api` methods for resources where this is still not available, but first, let's optimize our implementation.
### 6. Optimization
@@ -290,7 +290,7 @@ As already mentioned in the [best practices](best_practices.md) document, end-to
Some improvements that we could make in our test suite to optimize its time to run are:
1. Having a single test case (an `it` block) that exercises both scenarios to avoid "wasting" time in the tests' pre-conditions, instead of having two different test cases.
-2. Making the selection of labels more performant by allowing for the selection of more than one label in the same reusable method.
+1. Making the selection of labels more performant by allowing for the selection of more than one label in the same reusable method.
Let's look at a suggestion that addresses the above points, one by one:
@@ -332,8 +332,8 @@ To address point 1, we changed the test implementation from two `it` blocks into
> Notice that the implementation of the new and unique `it` block had to change a little bit. Below we describe in details what it does.
1. It selects two scoped labels simultaneously, one from the same scope of the one already applied in the issue during the setup phase (in the `before` block), and another one from a different scope.
-2. It asserts that the correct labels are visible in the `labels_block`, and that the labels were correctly added and removed;
-3. Finally, the `select_label_and_refresh` method is changed to `select_labels_and_refresh`, which accepts an array of labels instead of a single label, and it iterates on them for faster label selection (this is what is used in step 1 explained above.)
+1. It asserts that the correct labels are visible in the `labels_block`, and that the labels were correctly added and removed;
+1. Finally, the `select_label_and_refresh` method is changed to `select_labels_and_refresh`, which accepts an array of labels instead of a single label, and it iterates on them for faster label selection (this is what is used in step 1 explained above.)
### 7. Resources
@@ -362,7 +362,7 @@ First, in the [issue resource](https://gitlab.com/gitlab-org/gitlab-ee/blob/d358
Add the following `attribute :id` and `attribute :labels` right above the [`attribute :title`](https://gitlab.com/gitlab-org/gitlab-ee/blob/d3584e80b4236acdf393d815d604801573af72cc/qa/qa/resource/issue.rb#L15).
> This line is needed to allow for the issue fabrication, and for labels to be automatically added to the issue when fabricating it via API.
-
+>
> We add the attributes above the existing attribute to keep them alphabetically organized.
Then, let's initialize an instance variable for labels to allow an empty array as default value when such information is not passed during the resource fabrication, since this optional. [Between the attributes and the `fabricate!` method](https://gitlab.com/gitlab-org/gitlab-ee/blob/1a1f1408728f19b2aa15887cd20bddab7e70c8bd/qa/qa/resource/issue.rb#L18), add the following:
@@ -437,7 +437,7 @@ By defining the `resource_web_url(resource)` method, we override the one from th
By defining the `api_get_path` method, we **would** allow for the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to get a single label, but since there's no path available for that in the publich API, we raise a `NotImplementedError` instead.
-By defining the `api_post_path` method, we allow for the [`ApiFabricator `](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new label in a specific project.
+By defining the `api_post_path` method, we allow for the [`ApiFabricator`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/qa/qa/resource/api_fabricator.rb) module to know which path to use to create a new label in a specific project.
By defining the `api_post_body` method, we we allow for the [`ApiFabricator.api_post`](https://gitlab.com/gitlab-org/gitlab-ee/blob/a9177ca1812bac57e2b2fa4560e1d5dd8ffac38b/qa/qa/resource/api_fabricator.rb#L68) method to know which data to send when making the `POST` request.
@@ -542,9 +542,9 @@ end
Notice that we have not only moved the `select_labels_and_refresh` method, but we have also changed its implementation to:
1. Click the `:edit_link_labels` element previously defined, instead of using `find('.block.labels .edit-link').click`
-2. Use `within_element(:dropdown_menu_labels, text: label)`, and inside of it, we call `send_keys_to_element(:dropdown_input_field, [label, :enter])`, which is a method that we will implement in the `QA::Page::Base` class to replace `find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]`
-3. Use `click_body` after iterating on each label, instead of using `find('#content-body').click`
-4. Iterate on every label again, and then we use `has_element?(:labels_block, text: label)` after clicking the page body (which applies the labels), and before refreshing the page, to avoid test flakiness due to refreshing too fast.
+1. Use `within_element(:dropdown_menu_labels, text: label)`, and inside of it, we call `send_keys_to_element(:dropdown_input_field, [label, :enter])`, which is a method that we will implement in the `QA::Page::Base` class to replace `find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]`
+1. Use `click_body` after iterating on each label, instead of using `find('#content-body').click`
+1. Iterate on every label again, and then we use `has_element?(:labels_block, text: label)` after clicking the page body (which applies the labels), and before refreshing the page, to avoid test flakiness due to refreshing too fast.
##### Details of `text_of_labels_block`
@@ -580,7 +580,7 @@ filter_output = search_field_tag search_id, nil, class: "dropdown-input-field",
> `data-qa-*` data attributes and CSS classes starting with `qa-` are used solely for the purpose of QA and testing.
> By defining these, we add **testability** to the application.
-
+>
> When defining a data attribute like: `qa_selector: 'labels_block'`, it should match the element definition: `element :labels_block`. We use a [sanity test](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/qa/qa/page#how-did-we-solve-fragile-tests-problem) to check that defined elements have their respective selectors in the specified views.
#### Updates in the `QA::Page::Base` class
@@ -599,8 +599,6 @@ This method receives an element (`name`) and the `keys` that it will send to tha
As you might remember, in the Issue Page Object we call this method like this: `send_keys_to_element(:dropdown_input_field, [label, :enter])`.
-___
-
With that, you should be able to start writing end-to-end tests yourself. *Congratulations!*
[Page Objects]: page_objects.md
diff --git a/doc/development/testing_guide/end_to_end/style_guide.md b/doc/development/testing_guide/end_to_end/style_guide.md
index 6a888142575..97560e616a1 100644
--- a/doc/development/testing_guide/end_to_end/style_guide.md
+++ b/doc/development/testing_guide/end_to_end/style_guide.md
@@ -45,7 +45,7 @@ Notice that in the above example, before clicking the `:operations_environments_
> We can create these methods as helpers to abstract multi-step navigation.
-### Element naming convention
+## Element naming convention
When adding new elements to a page, it's important that we have a uniform element naming convention.
@@ -67,7 +67,7 @@ We follow a simple formula roughly based on hungarian notation.
*Note: This list is a work in progress. This list will eventually be the end-all enumeration of all available types.
I.e., any element that does not end with something in this list is bad form.*
-#### Examples
+### Examples
**Good**
@@ -98,3 +98,47 @@ view '...' do
element :ssh_clone_url
end
```
+
+## Block argument naming
+
+To have a standard on how we call pages when using the `.perform` method, we use the name of page object being called, all lowercased, and separated by underscore, if needed (see good and bad examples below.) This also applies to resources. We chose not to simply use `page` because that would shadow the Capybara DSL, potentially leading to confusion and bugs.
+
+### Examples
+
+**Good**
+
+```ruby
+# qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+
+Page::Project::Settings::Members.perform do |members|
+ members.do_something
+end
+```
+
+```ruby
+# qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb
+
+Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.do_something_else
+end
+```
+
+**Bad**
+
+```ruby
+# qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+
+Page::Project::Settings::Members.perform do |project_settings_members_page|
+ project_settings_members_page.do_something
+end
+```
+
+```ruby
+# qa/specs/features/ee/browser_ui/3_create/merge_request/add_batch_comments_in_merge_request_spec.rb
+
+Resource::MergeRequest.fabricate! do |merge_request_page|
+ merge_request_page.do_something_else
+end
+```
+
+> Besides the advantage of having a standard in place, by following this standard we also write shorter lines of code. \ No newline at end of file
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 c909745b1ab..7dc89a3fcdb 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -80,18 +80,20 @@ describe('Component', () => {
Remember that the performance of each test depends on the environment.
### Manual module mocks
+
Jest supports [manual module mocks](https://jestjs.io/docs/en/manual-mocks) by placing a mock in a `__mocks__/` directory next to the source module. **Don't do this.** We want to keep all of our test-related code in one place (the `spec/` folder), and the logic that Jest uses to apply mocks from `__mocks__/` is rather inconsistent.
Instead, our test runner detects manual mocks from `spec/frontend/mocks/`. Any mock placed here is automatically picked up and injected whenever you import its source module.
- Files in `spec/frontend/mocks/ce` will mock the corresponding CE module from `app/assets/javascripts`, mirroring the source module's path.
- - Example: `spec/frontend/mocks/ce/lib/utils/axios_utils` will mock the module `~/lib/utils/axios_utils`.
+ - Example: `spec/frontend/mocks/ce/lib/utils/axios_utils` will mock the module `~/lib/utils/axios_utils`.
- Files in `spec/frontend/mocks/node` will mock NPM packages of the same name or path.
- We don't support mocking EE modules yet.
If a mock is found for which a source module doesn't exist, the test suite will fail. 'Virtual' mocks, or mocks that don't have a 1-to-1 association with a source module, are not supported yet.
#### Writing a mock
+
Create a JS module in the appropriate place in `spec/frontend/mocks/`. That's it. It will automatically mock its source package in all tests.
Make sure that your mock's export has the same format as the mocked module. So, if you're mocking a CommonJS module, you'll need to use `module.exports` instead of the ES6 `export`.
@@ -99,14 +101,15 @@ Make sure that your mock's export has the same format as the mocked module. So,
It might be useful for a mock to expose a property that indicates if the mock was loaded. This way, tests can assert the presence of a mock without calling any logic and causing side-effects. The `~/lib/utils/axios_utils` module mock has such a property, `isMock`, that is `true` in the mock and undefined in the original class. Jest's mock functions also have a `mock` property that you can test.
#### Bypassing mocks
+
If you ever need to import the original module in your tests, use [`jest.requireActual()`](https://jestjs.io/docs/en/jest-object#jestrequireactualmodulename) (or `jest.requireActual().default` for the default export). The `jest.mock()` and `jest.unmock()` won't have an effect on modules that have a manual mock, because mocks are imported and cached before any tests are run.
#### Keep mocks light
+
Global mocks introduce magic and can affect how modules are imported in your tests. Try to keep them as light as possible and dependency-free. A global mock should be useful for any unit test. For example, the `axios_utils` and `jquery` module mocks throw an error when an HTTP request is attempted, since this is useful behaviour in &gt;99% of tests.
When in doubt, construct mocks in your test file using [`jest.mock()`](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options), [`jest.spyOn()`](https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname), etc.
-
## Karma test suite
GitLab uses the [Karma][karma] test runner with [Jasmine] as its test
@@ -229,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';
@@ -238,7 +241,7 @@ export default function doSomething() {
}
```
-```js
+```javascript
// my_module_spec.js
import doSomething from '~/my_module';
@@ -462,7 +465,7 @@ See this [section][vue-test].
For running the frontend tests, you need the following commands:
-- `rake karma:fixtures` (re-)generates [fixtures](#frontend-test-fixtures).
+- `rake frontend:fixtures` (re-)generates [fixtures](#frontend-test-fixtures).
- `yarn test` executes the tests.
As long as the fixtures don't change, `yarn test` is sufficient (and saves you some time).
@@ -515,8 +518,8 @@ Information on setting up and running RSpec integration tests with
Code that is added to HAML templates (in `app/views/`) or makes Ajax requests to the backend has tests that require HTML or JSON from the backend.
Fixtures for these tests are located at:
-- `spec/javascripts/fixtures/`, for running tests in CE.
-- `ee/spec/javascripts/fixtures/`, for running tests in EE.
+- `spec/frontend/fixtures/`, for running tests in CE.
+- `ee/spec/frontend/fixtures/`, for running tests in EE.
Fixture files in:
@@ -527,7 +530,7 @@ The following are examples of tests that work for both Karma and Jest:
```javascript
it('makes a request', () => {
- const responseBody = getJSONFixture('some/fixture.json'); // loads spec/javascripts/fixtures/some/fixture.json
+ const responseBody = getJSONFixture('some/fixture.json'); // loads spec/frontend/fixtures/some/fixture.json
axiosMock.onGet(endpoint).reply(200, responseBody);
myButton.click();
@@ -536,7 +539,7 @@ it('makes a request', () => {
});
it('uses some HTML element', () => {
- loadFixtures('some/page.html'); // loads spec/javascripts/fixtures/some/page.html and adds it to the DOM
+ loadFixtures('some/page.html'); // loads spec/frontend/fixtures/some/page.html and adds it to the DOM
const element = document.getElementById('#my-id');
@@ -544,12 +547,12 @@ it('uses some HTML element', () => {
});
```
-HTML and JSON fixtures are generated from backend views and controllers using RSpec (see `spec/javascripts/fixtures/*.rb`).
+HTML and JSON fixtures are generated from backend views and controllers using RSpec (see `spec/frontend/fixtures/*.rb`).
For each fixture, the content of the `response` variable is stored in the output file.
This variable gets automagically set if the test is marked as `type: :request` or `type: :controller`.
-Fixtures are regenerated using the `bin/rake karma:fixtures` command but you can also generate them individually,
-for example `bin/rspec spec/javascripts/fixtures/merge_requests.rb`.
+Fixtures are regenerated using the `bin/rake frontend:fixtures` command but you can also generate them individually,
+for example `bin/rspec spec/frontend/fixtures/merge_requests.rb`.
When creating a new fixture, it often makes sense to take a look at the corresponding tests for the endpoint in `(ee/)spec/controllers/` or `(ee/)spec/requests/`.
## Gotchas
@@ -585,11 +588,522 @@ end
[jasmine-focus]: https://jasmine.github.io/2.5/focused_specs.html
[karma]: http://karma-runner.github.io/
-[vue-test]: https://docs.gitlab.com/ce/development/fe_guide/vue.html#testing-vue-components
+[vue-test]: ../fe_guide/vue.md#testing-vue-components
[rspec]: https://github.com/rspec/rspec-rails#feature-specs
[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/img/qa_on_merge_requests_cicd_architecture.png b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
deleted file mode 100644
index 5b93a05db96..00000000000
--- a/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/testing_guide/img/review_apps_cicd_architecture.png b/doc/development/testing_guide/img/review_apps_cicd_architecture.png
deleted file mode 100644
index 1ee28d3db91..00000000000
--- a/doc/development/testing_guide/img/review_apps_cicd_architecture.png
+++ /dev/null
Binary files differ
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index ae40d628717..11449712a04 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -8,38 +8,33 @@ Review Apps are automatically deployed by each pipeline, both in
### CI/CD architecture diagram
-![Review Apps CI/CD architecture](img/review_apps_cicd_architecture.png)
-
-<details>
-<summary>Show mermaid source</summary>
-<pre>
+```mermaid
graph TD
build-qa-image -.->|once the `prepare` stage is done| gitlab:assets:compile
review-build-cng -->|triggers a CNG-mirror pipeline and wait for it to be done| CNG-mirror
review-build-cng -.->|once the `test` stage is done| review-deploy
review-deploy -.->|once the `review` stage is done| review-qa-smoke
-subgraph 1. gitlab-ce/ee `prepare` stage
+subgraph "1. gitlab-ce/ee `prepare` stage"
build-qa-image
end
-subgraph 2. gitlab-ce/ee `test` stage
+subgraph "2. gitlab-ce/ee `test` stage"
gitlab:assets:compile -->|plays dependent job once done| review-build-cng
end
-subgraph 3. gitlab-ce/ee `review` stage
- review-deploy["review-deploy<br /><br />Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br /><br />Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br />Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
+subgraph "3. gitlab-ce/ee `review` stage"
+ review-deploy["review-deploy<br><br>Helm deploys the Review App using the Cloud<br/>Native images built by the CNG-mirror pipeline.<br><br>Cloud Native images are deployed to the `review-apps-ce` or `review-apps-ee`<br>Kubernetes (GKE) cluster, in the GCP `gitlab-review-apps` project."]
end
-subgraph 4. gitlab-ce/ee `qa` stage
- review-qa-smoke[review-qa-smoke<br /><br />gitlab-qa runs the smoke suite against the Review App.]
+subgraph "4. gitlab-ce/ee `qa` stage"
+ review-qa-smoke[review-qa-smoke<br><br>gitlab-qa runs the smoke suite against the Review App.]
end
-subgraph CNG-mirror pipeline
+subgraph "CNG-mirror pipeline"
CNG-mirror>Cloud Native images are built];
end
-</pre>
-</details>
+```
### Detailed explanation
@@ -115,6 +110,28 @@ On every [pipeline][gitlab-pipeline] in the `qa` stage, the
browser performance testing using a
[Sitespeed.io Container](../../user/project/merge_requests/browser_performance_testing.md).
+## Cluster configuration
+
+### Node pools
+
+Both `review-apps-ce` and `review-apps-ee` clusters are currently set up with
+two node pools:
+
+- a node pool of non-preemptible `n1-standard-2` (2 vCPU, 7.5 GB memory) nodes
+ dedicated to the `tiller` deployment (see below) with a single node.
+- a node pool of preemptible `n1-standard-2` (2 vCPU, 7.5 GB memory) nodes,
+ with a minimum of 1 node and a maximum of 250 nodes.
+
+### Helm/Tiller
+
+The `tiller` deployment (the Helm server) is deployed to a dedicated node pool
+that has the `app=helm` label and a specific
+[taint](https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/)
+to prevent other pods from being scheduled on this node pool.
+
+This is to ensure Tiller isn't affected by "noisy" neighbors that could put
+their node under pressure.
+
## How to:
### Log into my Review App
@@ -137,8 +154,8 @@ secure note named **gitlab-{ce,ee} Review App's root password**.
### Run a Rails console
-1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps)
- , e.g. `review-qa-raise-e-12chm0`.
+1. [Filter Workloads by your Review App slug](https://console.cloud.google.com/kubernetes/workload?project=gitlab-review-apps),
+ e.g. `review-qa-raise-e-12chm0`.
1. Find and open the `task-runner` Deployment, e.g. `review-qa-raise-e-12chm0-task-runner`.
1. Click on the Pod in the "Managed pods" section, e.g. `review-qa-raise-e-12chm0-task-runner-d5455cc8-2lsvz`.
1. Click on the `KUBECTL` dropdown, then `Exec` -> `task-runner`.
@@ -196,7 +213,7 @@ For the record, the debugging steps to find out this issue were:
1. `kubectl describe pod <pod name>` & confirm exact error message
1. Web search for exact error message, following rabbit hole to [a relevant kubernetes bug report](https://github.com/kubernetes/kubernetes/issues/57345)
1. Access the node over SSH via the GCP console (**Computer Engine > VM
- instances** then click the "SSH" button for the node where the `dns-gitlab-review-app-external-dns` pod runs)
+ instances** then click the "SSH" button for the node where the `dns-gitlab-review-app-external-dns` pod runs)
1. In the node: `systemctl --version` => systemd 232
1. Gather some more information:
- `mount | grep kube | wc -l` => e.g. 290
@@ -211,7 +228,7 @@ For the record, the debugging steps to find out this issue were:
To resolve the problem, we needed to (forcibly) drain some nodes:
1. Try a normal drain on the node where the `dns-gitlab-review-app-external-dns`
- pod runs so that Kubernetes automatically move it to another node: `kubectl drain NODE_NAME`
+ pod runs so that Kubernetes automatically move it to another node: `kubectl drain NODE_NAME`
1. If that doesn't work, you can also perform a forcible "drain" the node by removing all pods: `kubectl delete pods --field-selector=spec.nodeName=NODE_NAME`
1. In the node:
- Perform `systemctl daemon-reload` to remove the dead/inactive units
@@ -238,17 +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.
-
-**How big are the Kubernetes clusters (`review-apps-ce` and `review-apps-ee`)?**
-
- > The clusters are currently set up with a single pool of preemptible nodes,
- with a minimum of 1 node and a maximum of 500 nodes.
-
-**What are the machine running on the cluster?**
-
- > We're currently using `n1-standard-1` (1 vCPU, 3.75 GB memory) machines.
+ > 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/understanding_explain_plans.md b/doc/development/understanding_explain_plans.md
index 11aafd7b639..7c926c83a36 100644
--- a/doc/development/understanding_explain_plans.md
+++ b/doc/development/understanding_explain_plans.md
@@ -199,7 +199,7 @@ more common ones here.
A full list of all the available nodes and their descriptions can be found in
the [PostgreSQL source file
-"plannodes.h"](https://github.com/postgres/postgres/blob/master/src/include/nodes/plannodes.h)
+"plannodes.h"](https://gitlab.com/postgres/postgres/blob/master/src/include/nodes/plannodes.h)
### Seq Scan
@@ -224,7 +224,7 @@ used when we would read too much data from an index scan, but too little to
perform a sequential scan. A bitmap scan uses what is known as a [bitmap
index](https://en.wikipedia.org/wiki/Bitmap_index) to perform its work.
-The [source code of PostgreSQL](https://github.com/postgres/postgres/blob/1c2cb2744bf3d8ad751cd5cf3b347f10f48492b3/src/include/nodes/plannodes.h#L446-L457)
+The [source code of PostgreSQL](https://gitlab.com/postgres/postgres/blob/REL_11_STABLE/src/include/nodes/plannodes.h#L441)
states the following on bitmap scans:
> Bitmap Index Scan delivers a bitmap of potential tuple locations; it does not
diff --git a/doc/development/utilities.md b/doc/development/utilities.md
index 0e396baccff..4021756343c 100644
--- a/doc/development/utilities.md
+++ b/doc/development/utilities.md
@@ -6,44 +6,44 @@ We developed a number of utilities to ease development.
- Deep merges an array of hashes:
- ``` ruby
- Gitlab::Utils::MergeHash.merge(
- [{ hello: ["world"] },
- { hello: "Everyone" },
- { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
- "Goodbye", "Hallo"]
- )
- ```
-
- Gives:
-
- ``` ruby
- [
- {
- hello:
- [
- "world",
- "Everyone",
- { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
- ]
- },
- "Goodbye"
- ]
- ```
+ ``` ruby
+ Gitlab::Utils::MergeHash.merge(
+ [{ hello: ["world"] },
+ { hello: "Everyone" },
+ { hello: { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] } },
+ "Goodbye", "Hallo"]
+ )
+ ```
+
+ Gives:
+
+ ``` ruby
+ [
+ {
+ hello:
+ [
+ "world",
+ "Everyone",
+ { greetings: ['Bonjour', 'Hello', 'Hallo', 'Dzien dobry'] }
+ ]
+ },
+ "Goodbye"
+ ]
+ ```
- Extracts all keys and values from a hash into an array:
- ``` ruby
- Gitlab::Utils::MergeHash.crush(
- { hello: "world", this: { crushes: ["an entire", "hash"] } }
- )
- ```
+ ``` ruby
+ Gitlab::Utils::MergeHash.crush(
+ { hello: "world", this: { crushes: ["an entire", "hash"] } }
+ )
+ ```
- Gives:
+ Gives:
- ``` ruby
- [:hello, "world", :this, :crushes, "an entire", "hash"]
- ```
+ ``` ruby
+ [:hello, "world", :this, :crushes, "an entire", "hash"]
+ ```
## [`Override`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/utils/override.rb)
@@ -53,9 +53,9 @@ We developed a number of utilities to ease development.
`ENV['STATIC_VERIFICATION']` is set to avoid production runtime overhead.
This is useful to check:
- - If we have typos in overriding methods.
- - If we renamed the overridden methods, making original overriding methods
- overrides nothing.
+ - If we have typos in overriding methods.
+ - If we renamed the overridden methods, making original overriding methods
+ overrides nothing.
Here's a simple example:
@@ -94,47 +94,47 @@ We developed a number of utilities to ease development.
- Memoize the value even if it is `nil` or `false`.
- We often do `@value ||= compute`, however this doesn't work well if
- `compute` might eventually give `nil` and we don't want to compute again.
- Instead we could use `defined?` to check if the value is set or not.
- However it's tedious to write such pattern, and `StrongMemoize` would
- help us use such pattern.
+ We often do `@value ||= compute`, however this doesn't work well if
+ `compute` might eventually give `nil` and we don't want to compute again.
+ Instead we could use `defined?` to check if the value is set or not.
+ However it's tedious to write such pattern, and `StrongMemoize` would
+ help us use such pattern.
- Instead of writing patterns like this:
+ Instead of writing patterns like this:
- ``` ruby
- class Find
- def result
- return @result if defined?(@result)
+ ``` ruby
+ class Find
+ def result
+ return @result if defined?(@result)
- @result = search
- end
+ @result = search
end
- ```
+ end
+ ```
- We could write it like:
+ We could write it like:
- ``` ruby
- class Find
- include Gitlab::Utils::StrongMemoize
+ ``` ruby
+ class Find
+ include Gitlab::Utils::StrongMemoize
- def result
- strong_memoize(:result) do
- search
- end
+ def result
+ strong_memoize(:result) do
+ search
end
end
- ```
+ end
+ ```
- Clear memoization
- ``` ruby
- class Find
- include Gitlab::Utils::StrongMemoize
- end
+ ``` ruby
+ class Find
+ include Gitlab::Utils::StrongMemoize
+ end
- Find.new.clear_memoization(:result)
- ```
+ Find.new.clear_memoization(:result)
+ ```
## [`RequestCache`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/cache/request_cache.rb)
diff --git a/doc/development/ux_guide/animation.md b/doc/development/ux_guide/animation.md
index 583ff19bc69..a998ab74a96 100644
--- a/doc/development/ux_guide/animation.md
+++ b/doc/development/ux_guide/animation.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/foundations/motion'
+redirect_to: 'https://design.gitlab.com/product-foundations/motion'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/motion).
diff --git a/doc/development/ux_guide/illustrations.md b/doc/development/ux_guide/illustrations.md
index ed072b6515f..3592d25c95d 100644
--- a/doc/development/ux_guide/illustrations.md
+++ b/doc/development/ux_guide/illustrations.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/foundations/illustration/'
+redirect_to: 'https://design.gitlab.com/product-foundations/illustration'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
+The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/product-foundations/illustration).
diff --git a/doc/development/verifying_database_capabilities.md b/doc/development/verifying_database_capabilities.md
index 661ab9cef1a..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:
@@ -25,7 +25,7 @@ else
end
```
-# Read-only database
+## Read-only database
The database can be used in read-only mode. In this case we have to
make sure all GET requests don't attempt any write operations to the
diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md
index 24edd05da2f..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
@@ -140,7 +134,7 @@ done without requiring downtime. However, this does require that any application
changes are deployed _first_. Thus, changing the constraints of a column should
happen in a post-deployment migration.
NOTE: Avoid using `change_column` as it produces inefficient query because it re-defines
-the whole column type. For example, to add a NOT NULL constraint, prefer `change_column_null `
+the whole column type. For example, to add a NOT NULL constraint, prefer `change_column_null`
## Changing Column Types
@@ -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/README.md b/doc/gitlab-basics/README.md
index fd16047b8e4..fc3d36910f2 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -1,15 +1,18 @@
---
comments: false
+type: index
---
# GitLab basics guides
-This section provides resources to help you start with GitLab by focusing on basic functionality.
+This section provides resources to help you start working with GitLab and Git by focusing
+on the basic features that you will need to use.
This documentation is split into the following groups:
- [GitLab-specific functionality](#gitlab-basics), for basic GitLab features.
-- [General Git functionality](#git-basics), for working with Git in conjunction with GitLab.
+- [General Git functionality](#working-with-git-from-the-command-line), for working
+ with Git in conjunction with GitLab.
## GitLab basics
@@ -17,22 +20,26 @@ The following are guides to basic GitLab functionality:
- [Create and add your SSH public key](create-your-ssh-keys.md), for enabling Git over SSH.
- [Create a project](create-project.md), to start using GitLab.
-- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer projects together.
+- [Create a group](../user/group/index.md#create-a-new-group), to combine and administer
+ projects together.
- [Create a branch](create-branch.md), to make changes to files stored in a project's repository.
- [Fork a project](fork-project.md), to duplicate projects so they can be worked on in parallel.
- [Add a file](add-file.md), to add new files to a project's repository.
-- [Add an image](add-image.md), to add new images to a project's repository.
-- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue), to start collaborating within a project.
-- [Create a merge request](add-merge-request.md), to request changes made in a branch be merged into a project's repository.
-- See how these features come together in the [GitLab Flow introduction video](https://youtu.be/InKNIvky2KE) and [GitLab Flow page](../workflow/gitlab_flow.md).
+- [Create an issue](../user/project/issues/managing_issues.md#create-a-new-issue),
+ to start collaborating within a project.
+- [Create a merge request](add-merge-request.md), to request changes made in a branch
+ be merged into a project's repository.
+- See how these features come together in the [GitLab Flow introduction video](https://youtu.be/InKNIvky2KE)
+ and [GitLab Flow page](../workflow/gitlab_flow.md).
-## Git basics
+## Working with Git from the command line
-If you're familiar with Git on the command line, you can interact with your GitLab projects just as you would with any other Git repository.
+If you're familiar with Git on the command line, you can interact with your GitLab
+projects just as you would with any other Git repository.
These resources will help get further acclimated to working on the command line.
- [Start using Git on the command line](start-using-git.md), for some simple Git commands.
- [Command line basics](command-line-commands.md), to create and edit files using the command line.
-More Git resources are available at GitLab's [Git documentation](../topics/git/index.md).
+More Git resources are available in GitLab's [Git documentation](../topics/git/index.md).
diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md
index e9fbcbc23a9..41cc8bc4aeb 100644
--- a/doc/gitlab-basics/add-file.md
+++ b/doc/gitlab-basics/add-file.md
@@ -1,5 +1,91 @@
-# How to add a file
+---
+type: howto
+---
-You can create a file in your [terminal](command-line-commands.md) and push
-to GitLab or you can use the
+# Add a file to a repository
+
+Adding files to a repository is a small, but key task. Bringing files in to a repository,
+such as code, images, or documents, allows them to be tracked by Git, even though they
+may have been created elsewhere.
+
+You can add a file to a repository in your [terminal](#add-a-file-using-the-command-line), and
+then push to GitLab. You can also use the [web interface](../user/project/repository/web_editor.md#upload-a-file),
+which may be a simpler solution.
+
+If you need to create a file first, for example a `README.md` text file, that can
+also be done from the [terminal](command-line-commands.md#create-a-text-file-in-the-current-directory) or
[web interface](../user/project/repository/web_editor.md#create-a-file).
+
+## Add a file using the command line
+
+Open a [terminal/shell](command-line-commands.md), and change into the folder of your
+GitLab project. This usually means running the following command until you get
+to the desired destination:
+
+```sh
+cd <destination folder>
+```
+
+[Create a branch](create-branch.md) to add your file to, before it is added to the master
+(main) branch of the project. It is not strictly necessary, but working directly in
+the `master` branch is not recommended unless your project is very small, and you are
+the only person working on it. You can [switch to an existing branch](start-using-git.md#work-on-an-existing-branch),
+if you have one already.
+
+Using your standard tool for copying files (for example, Finder in macOS, or File Explorer
+in Windows), put the file into a directory within the GitLab project.
+
+Check if your file is actually present in the directory (if you are in Windows,
+use `dir` instead):
+
+```sh
+ls
+```
+
+You should see the name of the file in the list shown.
+
+Check the status:
+
+```sh
+git status
+```
+
+Your file's name should appear in red, so `git` took notice of it! Now add it
+to the repository:
+
+```sh
+git add <name of file>
+```
+
+Check the status again, your file's name should have turned green:
+
+```sh
+git status
+```
+
+Commit (save) your file to the repository:
+
+```sh
+git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
+```
+
+Now you can push (send) your changes (in the branch `<branch-name>`) to GitLab
+(the git remote named 'origin'):
+
+```sh
+git push origin <branch-name>
+```
+
+Your image will be added to your branch in your repository in GitLab.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/add-image.md b/doc/gitlab-basics/add-image.md
index 17c210c43e0..11a4a6bbd97 100644
--- a/doc/gitlab-basics/add-image.md
+++ b/doc/gitlab-basics/add-image.md
@@ -1,56 +1,5 @@
-# How to add an image
+---
+redirect_to: 'add-file.md'
+---
-Using your standard tool for copying files (e.g. Finder in Mac OS, or Explorer
-in Windows, or...), put the image file into the GitLab project. You can find the
-project as a regular folder in your files.
-
-Go to your [shell](command-line-commands.md), and move into the folder of your
-GitLab project. This usually means running the following command until you get
-to the desired destination:
-
-```
-cd NAME-OF-FOLDER-YOU'D-LIKE-TO-OPEN
-```
-
-Check if your image is actually present in the directory (if you are in Windows,
-use `dir` instead):
-
-```
-ls
-```
-
-You should see the name of the image in the list shown.
-
-Check the status:
-
-```
-git status
-```
-
-Your image's name should appear in red, so `git` took notice of it! Now add it
-to the repository:
-
-```
-git add NAME-OF-YOUR-IMAGE
-```
-
-Check the status again, your image's name should have turned green:
-
-```
-git status
-```
-
-Commit:
-
-```
-git commit -m "DESCRIBE COMMIT IN A FEW WORDS"
-```
-
-Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab
-(the git remote named 'origin'):
-
-```
-git push origin NAME-OF-BRANCH
-```
-
-Your image will be added to your branch in your repository in GitLab.
+This document was moved to [another location](add-file.md).
diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md
index 7bb2e014738..1a6a26152fa 100644
--- a/doc/gitlab-basics/add-merge-request.md
+++ b/doc/gitlab-basics/add-merge-request.md
@@ -1,9 +1,16 @@
+---
+type: howto
+---
+
# How to create a merge request
-Merge requests are useful to integrate separate changes that you've made to a
-project, on different branches. This is a brief guide on how to create a merge
-request. For more information, check the
-[merge requests documentation](../user/project/merge_requests/index.md).
+Merge requests are how you integrate separate changes that you've made in a
+[branch](create-branch.md) to a [project](create-project.md).
+
+This is a brief guide on how to create a merge request. For more detailed information,
+check the [merge requests documentation](../user/project/merge_requests/index.md), or
+you can watch our [GitLab Flow video](https://www.youtube.com/watch?v=InKNIvky2KE) for
+a quick overview of working with merge requests.
---
@@ -12,19 +19,31 @@ request. For more information, check the
1. Go to the project where you'd like to merge your changes and click on the
**Merge requests** tab.
1. Click on **New merge request** on the right side of the screen.
-1. From there on, you have the option to select the source branch and the target
+1. From there, you have the option to select the source branch and the target
branch you'd like to compare to. The default target project is the upstream
repository, but you can choose to compare across any of its forks.
- ![Select a branch](img/merge_request_select_branch.png)
+ ![Select a branch](img/merge_request_select_branch.png)
1. When ready, click on the **Compare branches and continue** button.
1. At a minimum, add a title and a description to your merge request. Optionally,
- select a user to review your merge request and to accept or close it. You may
- also select a milestone and labels.
+ select a user to review your merge request. You may also select a milestone and
+ labels.
- ![New merge request page](img/merge_request_page.png)
+ ![New merge request page](img/merge_request_page.png)
1. When ready, click on the **Submit merge request** button.
-Your merge request will be ready to be approved and merged.
+Your merge request will be ready to be reviewed, approved, and merged.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
index c2a3415cbc4..4e0cc2de2bc 100644
--- a/doc/gitlab-basics/basic-git-commands.md
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -1,3 +1,5 @@
-# Basic Git commands
+---
+redirect_to: 'start-using-git.md'
+---
-This section is now merged into [Start using Git](start-using-git.md).
+This document was moved to [another location](start-using-git.md).
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index b8ebbbea9d4..ed70d3ce598 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -1,75 +1,47 @@
-# Command Line basic commands
-
-## Start working on your project
-
-In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab.
-
-When you are on your Dashboard, click on the project that you'd like to clone.
-To work in the project, you can copy a link to the Git repository through a SSH
-or a HTTPS protocol. SSH is easier to use after it's been
-[set up](create-your-ssh-keys.md). While you are at the **Project** tab, select
-HTTPS or SSH from the dropdown menu and copy the link using the _Copy URL to clipboard_
-button (you'll have to paste it on your shell in the next step).
-
-![Copy the HTTPS or SSH](img/project_clone_url.png)
-
-## Working with project files on the command line
-
-This section has examples of some basic shell commands that you might find useful. For more information, search the web for _bash commands_.
-
-Alternatively, you can edit files using your choice of editor (IDE) or the GitLab user interface.
-
-### Clone your project
-
-Go to your computer's shell and type the following command with your SSH or HTTPS URL:
+---
+type: howto, reference
+---
-```
-git clone PASTE HTTPS OR SSH HERE
-```
-
-A clone of the project will be created in your computer.
-
-NOTE: **Note:**
-If you clone your project via a URL that contains special characters, make sure
-that characters are URL-encoded.
-
-### Go into a project directory to work in it
-
-```
-cd NAME-OF-PROJECT
-```
+# Command Line basic commands
-### Go back one directory
+When [working with Git from the command line](start-using-git.md), you will need to
+use more than just the Git commands. There are several basic commands that you should
+learn, in order to make full use of the command line.
-```
-cd ..
-```
+## Start working on your project
-### List what’s in the current directory
+To work on a git project locally (from your own computer), with the command line,
+first you will need to [clone (copy) it](start-using-git.md#clone-a-repository) to
+your computer.
-```
-ls
-```
+## Working with files on the command line
-### List what’s in the current directory that starts with `a`
+This section has examples of some basic shell commands that you might find useful.
+For more information, search the web for _bash commands_.
-```
-ls a*
-```
+Alternatively, you can edit files using your choice of editor (IDE), or the GitLab user
+interface (not locally).
-### List what’s in the current directory that ends with `.md`
+### Common commands
-```
-ls *.md
-```
+The list below is not exhaustive, but contains many of the most commonly used commands.
-### Create a new directory
+| Command | Description |
+|--------------------------------|---------------------------------------------|
+| `cd NAME-OF-DIRECTORY` | Go into a directory to work in it |
+| `cd ..` | Go back one directory |
+| `ls` | List what’s in the current directory |
+| `ls a*` | List what’s in the current directory that starts with `a` |
+| `ls *.md` | List what’s in the current directory that ends with `.md` |
+| `mkdir NAME-OF-YOUR-DIRECTORY` | Create a new directory |
+| `cat README.md` | Display the contents of a [text file you created previously](#create-a-text-file-in-the-current-directory) |
+| `pwd` | Show the current directory |
+| `clear` | Clear the shell window |
-```
-mkdir NAME-OF-YOUR-DIRECTORY
-```
+### Create a text file in the current directory
-### Create a README.md file in the current directory
+To create a text file from the command line, for example `README.md`, follow these
+steps:
```
touch README.md
@@ -80,37 +52,37 @@ nano README.md
#### Press: enter
```
-### Show the contents of the README.md file
+### Remove a file or directory
-```
-cat README.md
-```
-
-### Remove a file
+It is easy to delete (remove) a file or directory, but be careful:
DANGER: **Danger:**
-This will permanently delete the file.
+This will **permanently** delete a file.
```
rm NAME-OF-FILE
```
-### Remove a directory and all of its contents
-
DANGER: **Danger:**
-This will permanently delete the directory and all of its contents.
+This will **permanently** delete a directory and **all** of its contents.
```
rm -r NAME-OF-DIRECTORY
```
-### View command history
+### View and Execute commands from history
+
+You can view the history of all the commands you executed from the command line,
+and then execute any of them again, if needed.
+
+First, list the commands you executed previously:
```
history
```
-### Execute command 123 from history
+Then, choose a command from the list and check the number next to the command (`123`,
+for example) . Execute the same full command with:
```
!123
@@ -118,28 +90,32 @@ history
### Carry out commands for which the account you are using lacks authority
-You will be asked for an administrator’s password.
+Not all commands can be executed from a basic user account on a computer, you may
+need administrator's rights to execute commands that affect the system, or try to access
+protected data, for example. You can use `sudo` to execute these commands, but you
+will likely be asked for an administrator password.
```
-sudo COMMAND
+sudo RESTRICTED-COMMAND
```
CAUTION: **Caution:**
Be careful of the commands you run with `sudo`. Certain commands may cause
-damage to your data and system.
+damage to your data or system.
-### Show which directory I am in
+## Sample Git taskflow
-```
-pwd
-```
-
-### Clear the shell window
+If you are completely new to Git, looking through some [sample taskflows](https://rogerdudler.github.io/git-guide/)
+will help you understand the best practices for using these commands as you work.
-```
-clear
-```
+<!-- ## Troubleshooting
-### Sample Git taskflow
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-If you are completely new to Git, looking through some [sample taskflows](https://rogerdudler.github.io/git-guide/) will help you understand best practices for using these commands as you work.
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
index ad94f0dad29..e2a2fb52af8 100644
--- a/doc/gitlab-basics/create-branch.md
+++ b/doc/gitlab-basics/create-branch.md
@@ -1,12 +1,16 @@
+---
+type: howto
+---
+
# How to create a branch
-A branch is an independent line of development.
+A branch is an independent line of development in a [project](../user/project/index.md).
-New commits are recorded in the history for the current branch, which results
-in taking the source from someone’s repository (the place where the history of
-your work is stored) at certain point in time, and apply your own changes to it
-in the history of the project.
+When you create a new branch (in your [terminal](basic-git-commands.md) or with
+[the web interface](../user/project/repository/web_editor.md#create-a-new-branch)),
+you are creating a snapshot of a certain branch, usually the main `master` branch,
+at it's current state. From there, you can start to make your own changes without
+affecting the main codebase. The history of your changes will be tracked in your branch.
-To add changes to your GitLab project, you should create a branch. You can do
-it in your [terminal](basic-git-commands.md) or by
-[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch).
+When your changes are ready, you then merge them into the rest of the codebase with a
+[merge request](add-merge-request.md).
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index ccba72f0ef8..8bbaf5d1927 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,10 +1,13 @@
-# Create a project
+---
+type: howto
+---
-[Projects](../user/project/index.md) combine many features of GitLab together.
+# Creating projects
-NOTE: **Note:**
-For a list of words that cannot be used as project names see
-[Reserved project and group names](../user/reserved_names.md).
+Most work in GitLab is done within a [Project](../user/project/index.md). Files and
+code are saved in projects, and most features are used within the scope of projects.
+
+## Create a project in GitLab
To create a project in GitLab:
@@ -14,94 +17,98 @@ To create a project in GitLab:
- Create a [blank project](#blank-projects).
- Create a project using with one of the available [project templates](#project-templates).
- [Import a project](../user/project/import/index.md) from a different repository,
- if enabled on your GitLab instance. Contact your GitLab admin if this
- is unavailable.
+ if enabled on your GitLab instance. Contact your GitLab admin if this is unavailable.
- Run [CI/CD pipelines for external repositories](../ci/ci_cd_for_external_repos/index.md). **(PREMIUM)**
-## Blank projects
+NOTE: **Note:**
+For a list of words that cannot be used as project names see
+[Reserved project and group names](../user/reserved_names.md).
+
+### Blank projects
To create a new blank project on the **New project** page:
1. On the **Blank project** tab, provide the following information:
- - The name of your project in the **Project name** field. You can't use
- special characters, but you can use spaces, hyphens, underscores or even
- emoji.
- - The **Project description (optional)** field enables you to enter a
- description for your project's dashboard, which will help others
- understand what your project is about. Though it's not required, it's a good
- idea to fill this in.
- - Changing the **Visibility Level** modifies the project's
- [viewing and access rights](../public_access/public_access.md) for users.
- - Selecting the **Initialize repository with a README** option creates a
- README file so that the Git repository is initialized, has a default branch, and
- can be cloned.
+ - The name of your project in the **Project name** field. You can't use
+ special characters, but you can use spaces, hyphens, underscores or even
+ emoji.
+ - The **Project description (optional)** field enables you to enter a
+ description for your project's dashboard, which will help others
+ understand what your project is about. Though it's not required, it's a good
+ idea to fill this in.
+ - Changing the **Visibility Level** modifies the project's
+ [viewing and access rights](../public_access/public_access.md) for users.
+ - Selecting the **Initialize repository with a README** option creates a
+ README file so that the Git repository is initialized, has a default branch, and
+ can be cloned.
1. Click **Create project**.
-## Project templates
+### Project templates
-Project templates can pre-populate your project with necessary files to get you started quickly.
+Project templates can pre-populate a new project with the necessary files to get you
+started quickly.
There are two types of project templates:
- [Built-in templates](#built-in-templates), sourced from the following groups:
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
- [`pages`](https://gitlab.com/pages)
-- [Custom project templates](#custom-project-templates-premium-only), for custom templates configured by GitLab administrators and users.
+- [Custom project templates](#custom-project-templates-premium), for custom templates
+ configured by GitLab administrators and users.
-### Built-in templates
+#### Built-in templates
Built-in templates are project templates that are:
-- Developed and maintained in the
- [`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
+- Developed and maintained in the [`project-templates`](https://gitlab.com/gitlab-org/project-templates)
+ and [`pages`](https://gitlab.com/pages) groups.
- Released with GitLab.
To use a built-in template on the **New project** page:
1. On the **Create from template** tab, select the **Built-in** tab.
1. From the list of available built-in templates, click the:
- - **Preview** button to look at the template source itself.
- - **Use template** button to start creating the project.
-1. Finish creating the project by filling out the project's details. The process is the same as for
- using a [blank project](#blank-projects).
+ - **Preview** button to look at the template source itself.
+ - **Use template** button to start creating the project.
+1. Finish creating the project by filling out the project's details. The process is
+ the same as creating a [blank project](#blank-projects).
TIP: **Tip:**
-You can improve the existing built-in templates or contribute new ones on the
-[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and [`pages`](https://gitlab.com/pages) groups.
+You can improve the existing built-in templates or contribute new ones in the
+[`project-templates`](https://gitlab.com/gitlab-org/project-templates) and
+[`pages`](https://gitlab.com/pages) groups.
-### Custom project templates **(PREMIUM ONLY)**
+#### Custom project templates **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in
[GitLab Premium](https://about.gitlab.com/pricing) 11.2.
-Creating new projects based on custom project templates is a convenient option to bootstrap a project.
+Creating new projects based on custom project templates is a convenient option to
+bootstrap a project.
-Custom projects are available from the **Instance** or **Group** tabs under the **Create from template** tab,
-depending on the type of template.
+Custom projects are available at the [instance-level](../user/admin_area/custom_project_templates.md)
+from the **Instance** tab, or at the [group-level](../user/group/custom_project_templates.md)
+from the **Group** tab, under the **Create from template** tab.
To use a custom project template on the **New project** page:
1. On the **Create from template** tab, select the **Instance** tab or the **Group** tab.
1. From the list of available custom templates, click the:
- - **Preview** button to look at the template source itself.
- - **Use template** button to start creating the project.
-1. Finish creating the project by filling out the project's details. The process is the same as for
- using a [blank project](#blank-projects).
-
-For information on configuring custom project templates, see:
-
-- [Custom instance-level project templates](../user/admin_area/custom_project_templates.md), for instance-level templates.
-- [Custom group-level project templates](../user/group/custom_project_templates.md), for group-level templates.
+ - **Preview** button to look at the template source itself.
+ - **Use template** button to start creating the project.
+1. Finish creating the project by filling out the project's details. The process is
+ the same as creating a [blank project](#blank-projects).
## Push to create a new project
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/26388) in GitLab 10.5.
-When you create a new repo locally, instead of going to GitLab to manually
-create a new project and then push the repo, you can directly push it to
-GitLab to create the new project, all without leaving your terminal. If you have access to that
-namespace, we will automatically create a new project under that GitLab namespace with its
-visibility set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
+When you create a new repository locally, instead of going to GitLab to manually
+create a new project and then [clone the repo](start-using-git.md#clone-a-repository)
+locally, you can directly push it to GitLab to create the new project, all without leaving
+your terminal. If you have access rights to the associated namespace, GitLab will
+automatically create a new project under that GitLab namespace with its visibility
+set to Private by default (you can later change it in the [project's settings](../public_access/public_access.md#how-to-change-project-visibility)).
This can be done by using either SSH or HTTPS:
@@ -127,3 +134,15 @@ remote: To view the project, visit:
remote: https://gitlab.example.com/namespace/nonexistent-project
remote:
```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index aac73d4c9c5..338b96374aa 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,22 +1,27 @@
-# Create and add your SSH public key
+---
+type: howto
+---
-This topic describes how to:
+# Create and add your SSH public key
-- Create an SSH key pair to use with GitLab.
-- Add the SSH public key to your GitLab account.
+It is best practice to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
+In order to use SSH, you will need to
-You do this to use [Git over SSH instead of Git over HTTP](https://git-scm.com/book/en/v2/Git-on-the-Server-The-Protocols).
+1. [Create an SSH key pair](#creating-your-ssh-key-pair) on your local computer.
+1. [Add the key to GitLab](#adding-your-ssh-public-key-to-gitlab).
## Creating your SSH key pair
-1. Go to your [command line](start-using-git.md).
-1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate your SSH key pair.
+1. Go to your [command line](start-using-git.md#open-a-shell).
+1. Follow the [instructions](../ssh/README.md#generating-a-new-ssh-key-pair) to generate
+ your SSH key pair.
## Adding your SSH public key to GitLab
-To add the SSH public key to GitLab,
-see [Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
+To add the SSH public key to GitLab, see
+[Adding an SSH key to your GitLab account](../ssh/README.md#adding-an-ssh-key-to-your-gitlab-account).
NOTE: **Note:**
-Once you add a key, you cannot edit it. If the paste
-[didn't work](../ssh/README.md#testing-that-everything-is-set-up-correctly), you need to remove the key and re-add it.
+Once you add a key, you cannot edit it. If it didn't paste properly, it
+[will not work](../ssh/README.md#testing-that-everything-is-set-up-correctly), and
+you will need to remove the key from GitLab and try adding it again.
diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md
index a128a7c7dd3..5c19985121d 100644
--- a/doc/gitlab-basics/fork-project.md
+++ b/doc/gitlab-basics/fork-project.md
@@ -1,20 +1,11 @@
+---
+type: howto
+---
+
# How to fork a project
A fork is a copy of an original repository that you put in another namespace
where you can experiment and apply changes that you can later decide whether or
not to share, without affecting the original project.
-It takes just a few steps to fork a project in GitLab.
-
-1. Go to a project's dashboard under the **Project** tab and click on the
- **Fork** button.
-
- ![Click on Fork button](img/fork_new.png)
-
-1. You will be asked where to fork the repository. Click on the user or group
- to where you'd like to add the forked project.
-
- ![Choose namespace](img/fork_choose_namespace.png)
-
-1. After a few moments, depending on the repository's size, the forking will
- complete.
+It takes just a few steps to [fork a project in GitLab](../workflow/forking_workflow.md#creating-a-fork).
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index b3c5d32f2f5..5de173abbff 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,38 +1,43 @@
+---
+type: howto, tutorial
+---
+
# Start using Git on the command line
-If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab.
+While GitLab has a powerful user interface, if you want to use Git itself, you will
+have to do so from the command line. If you want to start using Git and GitLab together,
+make sure that you have created and/or signed into an account on GitLab.
## Open a shell
-Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions:
-
-- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
+Depending on your operating system, you will need to use a shell of your preference.
+Here are some suggestions:
+- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on macOS
- [GitBash](https://msysgit.github.io) on Windows
-
- [Linux Terminal](http://www.howtogeek.com/140679/beginner-geek-how-to-start-using-the-linux-terminal/) on Linux
## Check if Git has already been installed
-Git is usually preinstalled on Mac and Linux.
-
-Type the following command and then press enter:
+Git is usually preinstalled on Mac and Linux, so run the following command:
```bash
git --version
```
-You should receive a message that tells you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+You should receive a message that tells you which Git version you have on your computer.
+If you don’t receive a "Git version" message, it means that you need to
+[download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
-If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
-
-After you are finished installing Git, open a new shell and type `git --version` again to verify that it was correctly installed.
+After you are finished installing Git, open a new shell and type `git --version` again
+to verify that it was correctly installed.
## Add your Git username and set your email
-It is important to configure your Git username and email address, since every Git commit will use this information to identify you as the author.
+It is important to configure your Git username and email address, since every Git
+commit will use this information to identify you as the author.
-On your shell, type the following command to add your username:
+In your shell, type the following command to add your username:
```bash
git config --global user.name "YOUR_USERNAME"
@@ -56,7 +61,10 @@ To verify that you entered your email correctly, type:
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.
+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 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
@@ -68,12 +76,12 @@ git config --global --list
## Basic Git commands
-Start using Git via the command line with the most basic
-commands as described below.
+Start using Git via the command line with the most basic commands as described below.
-## Initialize a local directory for Git version control
+### Initialize a local directory for Git version control
-If you have an existing local directory that you want to *initialize* for version control, use the `init` command to instruct Git to begin tracking the directory:
+If you have an existing local directory that you want to *initialize* for version
+control, use the `init` command to instruct Git to begin tracking the directory:
```bash
git init
@@ -81,34 +89,32 @@ git init
This creates a `.git` directory that contains the Git configuration files.
-Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository) and [send changes to GitLab.com](#send-changes-to-gitlabcom). View the instructions on [Create a project](../gitlab-basics/create-project.html#push-to-create-a-new-project) to create a new project on GitLab with your changes.
+Once the directory has been initialized, you can [add a remote repository](#add-a-remote-repository)
+and [send changes to GitLab.com](#send-changes-to-gitlabcom). You will also need to
+[create a new project in GitLab](../gitlab-basics/create-project.html#push-to-create-a-new-project)
+for your Git repository.
### Clone a repository
-To start working locally on an existing remote repository,
-clone it with the command `git clone <repository path>`.
-By cloning a repository, you'll download a copy of its
-files into your local computer, preserving the Git
-connection with the remote repository.
+To start working locally on an existing remote repository, clone it with the command
+`git clone <repository path>`. By cloning a repository, you'll download a copy of its
+files to your local computer, automatically preserving the Git connection with the
+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 once and can pull and push straightaway.
+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. 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
+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
and paste in your command line.
-As an example, consider a repository path:
+As an example, consider this repository path:
- HTTPS: `https://gitlab.com/gitlab-org/gitlab-ce.git`
-- SSH: `` git@gitlab.com:gitlab-org/gitlab-ce.git ``
+- SSH: `git@gitlab.com:gitlab-org/gitlab-ce.git`
-To get started, open a terminal window in the directory
-you wish to clone the repository files into, and run one
-of the following commands.
+To get started, open a terminal window in the directory you wish to clone the repository
+files into, and run one of the following commands.
Clone via HTTPS:
@@ -122,13 +128,15 @@ Clone via SSH:
git clone git@gitlab.com:gitlab-org/gitlab-ce.git
```
-Both commands will download a copy of the files in a
-folder named after the project's name.
-
-You can then navigate to the directory and start working
+Both commands will download a copy of the files in a folder named after the project's
+name. You can then navigate to the directory and start working
on it locally.
-### Go to the master branch to pull the latest changes from there
+### Switch to the master branch
+
+You are always in a branch when working with Git. The main branch is the master branch,
+but you can use the same command to switch to a different branch by changing `master`
+to the branch name.
```bash
git checkout master
@@ -136,13 +144,22 @@ git checkout master
### Download the latest changes in the project
-This is for you to work on an up-to-date copy (it is important to do this every time you start working on a project), while you set up tracking branches. You pull from remote repositories to get all the changes made by users since the last time you cloned or pulled the project. Later, you can push your local commits to the remote repositories.
+To work on an up-to-date copy of the project (it is important to do this every time
+you start working on a project), you `pull` to get all the changes made by users since
+the last time you cloned or pulled the project. Use `master` for the `<name-of-branch>`
+to get the main branch code, or the branch name of the branch you are currently working
+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 repository came 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.
+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. 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
@@ -152,22 +169,27 @@ 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:
```bash
-git remote add SOURCE-NAME REPOSITORY-PATH
+git remote add <source-name> <repository-path>
```
-You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom), so use something easy to remember and type.
+You'll use this source name every time you [push changes to GitLab.com](#send-changes-to-gitlabcom),
+so use something easy to remember and type.
### Create a branch
-To create a branch, type the following (spaces won't be recognized in the branch name, so you will need to use a hyphen or underscore):
+To create a new branch, to work from without affecting the `master` branch, type the
+following (spaces won't be recognized in the branch name, so you will need to use a
+hyphen or underscore):
```bash
-git checkout -b NAME-OF-BRANCH
+git checkout -b <name-of-branch>
```
### Work on an existing branch
@@ -175,12 +197,14 @@ git checkout -b NAME-OF-BRANCH
To switch to an existing branch, so you can work on it:
```bash
-git checkout NAME-OF-BRANCH
+git checkout <name-of-branch>
```
### View the changes you've made
-It's important to be aware of what's happening and the status of your changes. When you add, change, or delete files/folders, Git knows about it. To check the status of your changes:
+It's important to be aware of what's happening and the status of your changes. When
+you add, change, or delete files/folders, Git knows about it. To check the status of
+your changes:
```bash
git status
@@ -188,7 +212,8 @@ git status
### View differences
-To view the differences between your local, unstaged changes and the repository versions that you cloned or pulled, type:
+To view the differences between your local, unstaged changes and the repository versions
+that you cloned or pulled, type:
```bash
git diff
@@ -196,16 +221,19 @@ git diff
### Add and commit local changes
-You'll see your local changes in red when you type `git status`. These changes may be new, modified, or deleted files/folders. Use `git add` to stage a local file/folder for committing. Then use `git commit` to commit the staged files:
+You'll see any local changes in red when you type `git status`. These changes may
+be new, modified, or deleted files/folders. Use `git add` to first stage (prepare)
+a local file/folder for committing. Then use `git commit` to commit (save) the staged
+files:
```bash
-git add FILE OR FOLDER
+git add <file-name OR folder-name>
git commit -m "COMMENT TO DESCRIBE THE INTENTION OF THE COMMIT"
```
### Add all changes to commit
-To add and commit all local changes in one command:
+To add and commit (save) all local changes quickly:
```bash
git add .
@@ -213,39 +241,36 @@ 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
-To push all local commits to the remote repository:
+To push all local commits (saved changes) to the remote repository:
```bash
-git push REMOTE NAME-OF-BRANCH
+git push <remote> <name-of-branch>
```
-For example, to push your local commits to the _master_ branch of the _origin_ remote:
+For example, to push your local commits to the _`master`_ branch of the _`origin`_ remote:
```bash
git push origin master
```
-### Delete all changes in the Git repository
+### Delete all changes in the branch
-To delete all local changes in the repository that have not been added to the staging area, and leave unstaged files/folders, type:
+To delete all local changes in the branch that have not been added to the staging
+area, and leave unstaged files/folders, type:
```bash
git checkout .
```
-### Delete all untracked changes in the Git repository
-
-```bash
-git clean -f
-```
+Note that this removes *changes* to files, not the files themselves.
### Unstage all changes that have been added to the staging area
-To undo the most recent add, but not committed, files/folders:
+To undo the most recently added, but not committed, changes to files/folders:
```bash
git reset .
@@ -259,25 +284,31 @@ To undo the most recent commit, type:
git reset HEAD~1
```
-This leaves the files and folders unstaged in your local repository.
+This leaves the changed files and folders unstaged in your local repository.
CAUTION: **Warning:**
-A Git commit is mostly irreversible, particularly if you already pushed it to the remote repository. Although you can undo a commit, the best option is to avoid the situation altogether.
+A Git commit should not usually be reverse, particularly if you already pushed it
+to the remote repository. Although you can undo a commit, the best option is to avoid
+the situation altogether by working carefully.
-### Merge created branch with master branch
+### Merge a branch with master branch
-You need to be in the created branch.
+When you are ready to make all the changes in a branch a permanent addition to
+the master branch, you `merge` the two together:
```bash
-git checkout NAME-OF-BRANCH
+git checkout <name-of-branch>
git merge master
```
-### Merge master branch with created branch
+<!-- ## Troubleshooting
-You need to be in the master branch.
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-```bash
-git checkout master
-git merge NAME-OF-BRANCH
-```
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/install/aws/index.md b/doc/install/aws/index.md
index fed3b1ca595..358ba971049 100644
--- a/doc/install/aws/index.md
+++ b/doc/install/aws/index.md
@@ -610,7 +610,7 @@ To back up GitLab:
1. Take a backup:
```sh
- sudo gitlab-rake gitlab:backup:create
+ sudo gitlab-backup create
```
### Restoring GitLab from a backup
@@ -628,7 +628,7 @@ released, you can update your GitLab instance:
1. Take a backup:
```sh
- sudo gitlab-rake gitlab:backup:create
+ sudo gitlab-backup create
```
1. Update the repositories and install GitLab:
diff --git a/doc/install/azure/index.md b/doc/install/azure/index.md
index c0e1b0ebbc8..c5939dc6856 100644
--- a/doc/install/azure/index.md
+++ b/doc/install/azure/index.md
@@ -70,7 +70,7 @@ The first items we need to configure are the basic settings of the underlying vi
> **Note:** if you're unsure which authentication type to use, select **Password**
1. If you chose **SSH public key** - enter your `SSH public key` into the field provided
- _(read the [SSH documentation][GitLab-Docs-SSH] to learn more about how to set up SSH
+ _(read the [SSH documentation](../../ssh/README.md) to learn more about how to set up SSH
public keys)_
1. If you chose **Password** - enter the password you wish to use _(this is the password that you
will use later in this tutorial to [SSH] into the VM, so make sure it's a strong password/passphrase)_
@@ -407,7 +407,7 @@ on any cloud service you choose.
## Where to next?
-Check out our other [Technical Articles][GitLab-Technical-Articles] or browse the [GitLab Documentation][GitLab-Docs] 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
@@ -423,9 +423,6 @@ Check out our other [Technical Articles][GitLab-Technical-Articles] or browse th
- [SSH], [PuTTY] and [Using SSH in PuTTY][Using-SSH-In-Putty]
[Original-Blog-Post]: https://about.gitlab.com/2016/07/13/how-to-setup-a-gitlab-instance-on-microsoft-azure/ "How to Set up a GitLab Instance on Microsoft Azure"
-[GitLab-Docs]: https://docs.gitlab.com/ce/README.html "GitLab Documentation"
-[GitLab-Technical-Articles]: https://docs.gitlab.com/ce/articles/index.html "GitLab Technical Articles"
-[GitLab-Docs-SSH]: https://docs.gitlab.com/ce/ssh/README.html "GitLab Documentation: SSH"
[CE]: https://about.gitlab.com/features/
[EE]: https://about.gitlab.com/features/#ee-starter
diff --git a/doc/install/docker.md b/doc/install/docker.md
index 06da65189ba..e0cef71a4d8 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -10,7 +10,7 @@ GitLab provides official Docker images allowing you to easily take advantage of
## Omnibus GitLab based images
-GitLab maintains a set of [official Docker images](https://hub.docker.com/r/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
+GitLab maintains a set of [official Docker images](https://hub.docker.com/u/gitlab) based on our [Omnibus GitLab package](https://docs.gitlab.com/omnibus/README.html). These images include:
- [GitLab Community Edition](https://hub.docker.com/r/gitlab/gitlab-ce/)
- [GitLab Enterprise Edition](https://hub.docker.com/r/gitlab/gitlab-ee/)
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 06ec00cecc4..295d9804497 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -6,7 +6,7 @@ type: howto
This is the official installation guide to set up a production GitLab server
using the source files. To set up a **development installation** or for many
-other installation options, see the [main installation page](index.md).
+other installation options, see the [main installation page](README.md).
It was created for and tested on **Debian/Ubuntu** operating systems.
Read [requirements.md](requirements.md) for hardware and operating system requirements.
If you want to install on RHEL/CentOS, we recommend using the
@@ -134,7 +134,7 @@ Make sure you have the right version of Git installed:
# Install Git
sudo apt-get install -y git-core
-# Make sure Git is version 2.21.0 or higher
+# Make sure Git is version 2.22.0 or higher
git --version
```
@@ -171,9 +171,9 @@ sudo make install
# Download and compile from source
cd /tmp
-curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.21.0.tar.gz
-echo '85eca51c7404da75e353eba587f87fea9481ba41e162206a6f70ad8118147bee git-2.21.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.21.0.tar.gz
-cd git-2.21.0/
+curl --remote-name --location --progress https://www.kernel.org/pub/software/scm/git/git-2.22.0.tar.gz
+echo 'a4b7e4365bee43caa12a38d646d2c93743d755d1cea5eab448ffb40906c9da0b git-2.22.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.22.0.tar.gz
+cd git-2.22.0/
./configure --with-libpcre
make prefix=/usr/local all
@@ -202,8 +202,8 @@ Then select 'Internet Site' and press enter to confirm the hostname.
The Ruby interpreter is required to run GitLab.
-**Note:** The current supported Ruby (MRI) version is 2.5.x. GitLab 11.6
- dropped support for Ruby 2.4.x.
+**Note:** The current supported Ruby (MRI) version is 2.6.x. GitLab 12.2
+ dropped support for Ruby 2.5.x.
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
in production, frequently leads to hard to diagnose problems. For example,
@@ -938,7 +938,7 @@ To use GitLab with Puma:
cd /home/git/gitlab
# Copy config file for the web server
- sudo -u git -H config/puma.rb.example config/puma.rb
+ sudo -u git -H cp config/puma.rb.example config/puma.rb
```
1. Edit the system `init.d` script to use `EXPERIMENTAL_PUMA=1` flag. If you have `/etc/default/gitlab`, then you should edit it instead.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 25ab608de3a..234e5acb394 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -40,7 +40,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6.
+GitLab requires Ruby (MRI) 2.6. Support for Ruby versions below 2.6 (2.4, 2.5) will stop with GitLab 12.2.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
@@ -62,17 +62,19 @@ NOTE: **Note:** Since file system performance may affect GitLab's overall perfor
### CPU
+This is the recommended minimum hardware for a handful of example GitLab user base sizes. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repo/change size.
+
- 1 core supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core
-- **2 cores** is the **recommended** number of cores and supports up to 500 users
-- 4 cores supports up to 2,000 users
-- 8 cores supports up to 5,000 users
-- 16 cores supports up to 10,000 users
-- 32 cores supports up to 20,000 users
-- 64 cores supports up to 40,000 users
-- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+- **2 cores** is the **recommended** minimum number of cores and supports up to 100 users
+- 4 cores supports up to 500 users
+- 8 cores supports up to 1,000 users
+- 32 cores supports up to 5,000 users
+- More users? Run it high-availability on [multiple application servers](https://about.gitlab.com/high-availability/)
### Memory
+This is the recommended minimum hardware for a handful of example GitLab user base sizes. Your exact needs may be more, depending on your workload. Your workload is influenced by factors such as - but not limited to - how active your users are, how much automation you use, mirroring, and repo/change size.
+
You need at least 8GB of addressable memory (RAM + swap) to install and use GitLab!
The operating system and any other running applications will also be using memory
so keep in mind that you need at least 4GB available before running GitLab. With
@@ -80,13 +82,11 @@ less memory GitLab will give strange errors during the reconfigure run and 500
errors during usage.
- 4GB RAM + 4GB swap supports up to 100 users but it will be very slow
-- **8GB RAM** is the **recommended** memory size for all installations and supports up to 100 users
-- 16GB RAM supports up to 2,000 users
-- 32GB RAM supports up to 4,000 users
-- 64GB RAM supports up to 8,000 users
-- 128GB RAM supports up to 16,000 users
-- 256GB RAM supports up to 32,000 users
-- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+- **8GB RAM** is the **recommended** minimum memory size for all installations and supports up to 100 users
+- 16GB RAM supports up to 500 users
+- 32GB RAM supports up to 1,000 users
+- 128GB RAM supports up to 5,000 users
+- More users? Run it high-availability on [multiple application servers](https://about.gitlab.com/high-availability/)
We recommend having at least [2GB of swap on your server](https://askubuntu.com/a/505344/310789), even if you currently have
enough available RAM. Having swap will help reduce the chance of errors occurring
@@ -94,6 +94,8 @@ if your available memory changes. We also recommend [configuring the kernel's sw
to a low value like `10` to make the most of your RAM while still having the swap
available when needed.
+Our [Memory Team](https://about.gitlab.com/handbook/engineering/development/enablement/memory/) is actively working to reduce the memory requirement.
+
NOTE: **Note:** The 25 workers of Sidekiq will show up as separate processes in your process overview (such as `top` or `htop`) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about how many you need of those.
## Database
@@ -146,8 +148,8 @@ CREATE EXTENSION postgres_fdw;
## Unicorn Workers
-For most instances we recommend using: CPU cores + 1 = unicorn workers.
-So for a machine with 2 cores, 3 unicorn workers is ideal.
+For most instances we recommend using: (CPU cores * 1.5) + 1 = unicorn workers.
+For example a node with 4 cores would have 7 unicorn workers.
For all machines that have 2GB and up we recommend a minimum of three unicorn workers.
If you have a 1GB machine we recommend to configure only two Unicorn workers to prevent excessive swapping.
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 135952a1b08..55f9666e3a3 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -27,7 +27,7 @@ See the documentation below for details on how to configure these services.
- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [Trello](trello_power_up.md) Integrate Trello with GitLab
-> GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
+> GitLab Enterprise Edition contains [advanced Jenkins support](jenkins.md).
## Project services
@@ -70,5 +70,3 @@ After that restart GitLab with:
```bash
sudo gitlab-ctl restart
```
-
-[jenkins]: https://docs.gitlab.com/ee/integration/jenkins.html
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 20caac4cb74..eee05eaef02 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -24,18 +24,21 @@ special searches:
## Installing Elasticsearch
Elasticsearch is _not_ included in the Omnibus packages. You will have to
-install it yourself whether you are using the Omnibus package or installed
-GitLab from source. Providing detailed information on installing Elasticsearch
-is out of the scope of this document.
+[install it yourself](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html "Elasticsearch installation documentation")
+whether you are using the Omnibus package or installed GitLab from source.
+Providing detailed information on installing Elasticsearch is out of the scope
+of this document.
+
+NOTE: **Note:**
+Elasticsearch should be installed on a separate server, whether you install
+it yourself or by using the
+[Amazon Elasticsearch](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html)
+service. Running Elasticsearch on the same server as GitLab is not recommended
+and it will likely cause performance degradation on the GitLab installation.
Once the data is added to the database or repository and [Elasticsearch is
enabled in the admin area](#enabling-elasticsearch) the search index will be
-updated automatically. Elasticsearch can be installed on the same machine as
-GitLab or on a separate server, or you can use the [Amazon Elasticsearch](http://docs.aws.amazon.com/elasticsearch-service/latest/developerguide/es-gsg.html)
-service.
-
-You can follow the steps as described in the [official web site](https://www.elastic.co/guide/en/elasticsearch/reference/current/install-elasticsearch.html "Elasticsearch installation documentation") or
-use the packages that are available for your OS.
+updated automatically.
## Elasticsearch repository indexer (beta)
@@ -330,6 +333,10 @@ curl --request PUT localhost:9200/gitlab-production/_settings --data '{
Enable Elasticsearch search in **Admin > Settings > Integrations**. That's it. Enjoy it!
+### Index limit
+
+Currently for repository and snippet files, GitLab would only index up to 1 MB of content, in order to avoid indexing timeout.
+
## GitLab Elasticsearch Rake Tasks
There are several rake tasks available to you via the command line:
@@ -354,6 +361,8 @@ There are several rake tasks available to you via the command line:
- Does the same thing as `sudo gitlab-rake gitlab:elastic:create_empty_index`
- [sudo gitlab-rake gitlab:elastic:index_snippets](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
- Performs an Elasticsearch import that indexes the snippets data.
+- [sudo gitlab-rake gitlab:elastic:projects_not_indexed](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake)
+ - Displays which projects are not indexed.
### Environment Variables
@@ -469,6 +478,10 @@ Here are some common pitfalls and how to overcome them:
The more data present in your GitLab instance, the longer the indexing process takes.
+- **There are some projects that weren't indexed, but we don't know which ones**
+
+ You can run `sudo gitlab-rake gitlab:elastic:projects_not_indexed` to display projects that aren't indexed.
+
- **No new data is added to the Elasticsearch index when I push code**
When performing the initial indexing of blobs, we lock all projects until the project finishes indexing. It could
diff --git a/doc/integration/facebook.md b/doc/integration/facebook.md
index 837434da737..49b3d194a01 100644
--- a/doc/integration/facebook.md
+++ b/doc/integration/facebook.md
@@ -19,7 +19,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. Enter the address of your GitLab installation at the bottom of the package
- ![Facebook Website URL](img/facebook_website_url.png)
+ ![Facebook Website URL](img/facebook_website_url.png)
1. Choose "Next"
@@ -29,7 +29,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. Fill in a contact email for your app
- ![Facebook App Settings](img/facebook_app_settings.png)
+ ![Facebook App Settings](img/facebook_app_settings.png)
1. Choose "Save Changes"
@@ -45,7 +45,7 @@ To enable the Facebook OmniAuth provider you must register your application with
1. You should now see an app key and app secret (see screenshot). Keep this page open as you continue configuration.
- ![Facebook API Keys](img/facebook_api_keys.png)
+ ![Facebook API Keys](img/facebook_api_keys.png)
1. On your GitLab server, open the configuration file.
diff --git a/doc/integration/img/limit_namespace_filter.png b/doc/integration/img/limit_namespace_filter.png
index 88f5caa41db..437aecad467 100644
--- a/doc/integration/img/limit_namespace_filter.png
+++ b/doc/integration/img/limit_namespace_filter.png
Binary files differ
diff --git a/doc/integration/img/limit_namespaces_projects_options.png b/doc/integration/img/limit_namespaces_projects_options.png
index 488341f7e92..fa666c7491e 100644
--- a/doc/integration/img/limit_namespaces_projects_options.png
+++ b/doc/integration/img/limit_namespaces_projects_options.png
Binary files differ
diff --git a/doc/integration/img/salesforce_app_details.png b/doc/integration/img/salesforce_app_details.png
index 00e66f07282..c7a4084102e 100644
--- a/doc/integration/img/salesforce_app_details.png
+++ b/doc/integration/img/salesforce_app_details.png
Binary files differ
diff --git a/doc/integration/img/salesforce_app_secret_details.png b/doc/integration/img/salesforce_app_secret_details.png
index fad2a4a1f97..8734a7a5cbb 100644
--- a/doc/integration/img/salesforce_app_secret_details.png
+++ b/doc/integration/img/salesforce_app_secret_details.png
Binary files differ
diff --git a/doc/integration/img/salesforce_oauth_app_details.png b/doc/integration/img/salesforce_oauth_app_details.png
index a5fb680cca6..e29c55df656 100644
--- a/doc/integration/img/salesforce_oauth_app_details.png
+++ b/doc/integration/img/salesforce_oauth_app_details.png
Binary files differ
diff --git a/doc/integration/jenkins.md b/doc/integration/jenkins.md
index 50cb3d50009..6755640d39c 100644
--- a/doc/integration/jenkins.md
+++ b/doc/integration/jenkins.md
@@ -35,6 +35,9 @@ 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.**
+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
- [Jenkins GitLab Plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Plugin)
diff --git a/doc/integration/jenkins_deprecated.md b/doc/integration/jenkins_deprecated.md
index eae705c9637..bac89ae2dc6 100644
--- a/doc/integration/jenkins_deprecated.md
+++ b/doc/integration/jenkins_deprecated.md
@@ -19,7 +19,7 @@ Requirements:
## Jenkins
1. Install [GitLab Hook plugin](https://wiki.jenkins.io/display/JENKINS/GitLab+Hook+Plugin)
-2. Set up jenkins project
+1. Set up jenkins project
![screen](img/jenkins_project.png)
diff --git a/doc/integration/jira_development_panel.md b/doc/integration/jira_development_panel.md
index 60c7bdabf93..3e894371df9 100644
--- a/doc/integration/jira_development_panel.md
+++ b/doc/integration/jira_development_panel.md
@@ -43,67 +43,68 @@ There are no special requirements if you are using GitLab.com.
1. In GitLab, create a new application in order to allow Jira to connect with your GitLab account
- While logged-in, go to `Settings -> Applications`. (Click your profile avatar at
- the top right, choose `Settings`, and then navigate to `Applications` from the left
- navigation menu.) Use the form to create a new application.
+ While logged-in, go to `Settings -> Applications`. (Click your profile avatar at
+ the top right, choose `Settings`, and then navigate to `Applications` from the left
+ navigation menu.) Use the form to create a new application.
- Enter a useful name for the `Name` field.
+ Enter a useful name for the `Name` field.
- For the `Redirect URI` field, enter `https://<your-gitlab-instance-domain>/login/oauth/callback`,
- replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
- this would be `https://gitlab.com/login/oauth/callback`.
+ For the `Redirect URI` field, enter `https://<your-gitlab-instance-domain>/login/oauth/callback`,
+ replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
+ this would be `https://gitlab.com/login/oauth/callback`.
- NOTE: **Note**:
- If using a GitLab version earlier than 11.3 the `Redirect URI` value should be `https://<your-gitlab-instance-domain>/-/jira/login/oauth/callback`.
+ NOTE: **Note**:
+ If using a GitLab version earlier than 11.3 the `Redirect URI` value should be `https://<your-gitlab-instance-domain>/-/jira/login/oauth/callback`.
- ![GitLab Application setup](img/jira_dev_panel_gl_setup_1.png)
- - Check `api` in the Scopes section.
+ ![GitLab Application setup](img/jira_dev_panel_gl_setup_1.png)
-2. Click `Save application`. You will see the generated 'Application Id' and 'Secret' values.
- Copy these values that you will use on the Jira configuration side.
+ - Check `api` in the Scopes section.
+
+1. Click `Save application`. You will see the generated 'Application Id' and 'Secret' values.
+ Copy these values that you will use on the Jira configuration side.
## Jira Configuration
1. In Jira, from the gear menu at the top right, go to `Applications`. Navigate to `DVCS accounts`
- from the left navigation menu. Click `Link GitHub account` to start creating a new integration.
- (We are pretending to be GitHub in this integration until there is further platform support from Jira.)
+ from the left navigation menu. Click `Link GitHub account` to start creating a new integration.
+ (We are pretending to be GitHub in this integration until there is further platform support from Jira.)
- ![Jira DVCS from Dashboard](img/jira_dev_panel_jira_setup_1.png)
+ ![Jira DVCS from Dashboard](img/jira_dev_panel_jira_setup_1.png)
-2. Complete the form
+1. Complete the form
- Select GitHub Enterprise for the `Host` field.
+ Select GitHub Enterprise for the `Host` field.
- For the `Team or User Account` field, enter the relative path of a top-level GitLab group that you have access to,
- or the relative path of your personal namespace.
+ For the `Team or User Account` field, enter the relative path of a top-level GitLab group that you have access to,
+ or the relative path of your personal namespace.
- ![Creation of Jira DVCS integration](img/jira_dev_panel_jira_setup_2.png)
+ ![Creation of Jira DVCS integration](img/jira_dev_panel_jira_setup_2.png)
- For the `Host URL` field, enter `https://<your-gitlab-instance-domain>/`,
- replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
- this would be `https://gitlab.com/`.
+ For the `Host URL` field, enter `https://<your-gitlab-instance-domain>/`,
+ replacing `<your-gitlab-instance-domain>` appropriately. So for example, if you are using GitLab.com,
+ this would be `https://gitlab.com/`.
- NOTE: **Note**:
- If using a GitLab version earlier than 11.3 the `Host URL` value should be `https://<your-gitlab-instance-domain>/-/jira`
+ NOTE: **Note**:
+ If using a GitLab version earlier than 11.3 the `Host URL` value should be `https://<your-gitlab-instance-domain>/-/jira`
- For the `Client ID` field, use the `Application ID` value from the previous section.
+ For the `Client ID` field, use the `Application ID` value from the previous section.
- For the `Client Secret` field, use the `Secret` value from the previous section.
+ For the `Client Secret` field, use the `Secret` value from the previous section.
- Ensure that the rest of the checkboxes are checked.
+ Ensure that the rest of the checkboxes are checked.
-3. Click `Add` to complete and create the integration.
+1. Click `Add` to complete and create the integration.
- Jira takes up to a few minutes to know about (import behind the scenes) all the commits and branches
- for all the projects in the GitLab group you specified in the previous step. These are refreshed
- every 60 minutes.
+ Jira takes up to a few minutes to know about (import behind the scenes) all the commits and branches
+ for all the projects in the GitLab group you specified in the previous step. These are refreshed
+ every 60 minutes.
- > **Note:**
- > In the future, we plan on implementating real-time integration. If you need
- > to refresh the data manually, you can do this from the `Applications -> DVCS
- > accounts` screen where you initially set up the integration:
- >
- > ![Refresh GitLab information in Jira](img/jira_dev_panel_manual_refresh.png)
+ > **Note:**
+ > In the future, we plan on implementating real-time integration. If you need
+ > to refresh the data manually, you can do this from the `Applications -> DVCS
+ > accounts` screen where you initially set up the integration:
+ >
+ > ![Refresh GitLab information in Jira](img/jira_dev_panel_manual_refresh.png)
To connect additional GitLab projects from other GitLab top-level groups (or personal namespaces), repeat the above
steps with additional Jira DVCS accounts.
diff --git a/doc/integration/kerberos.md b/doc/integration/kerberos.md
index b4f2025265e..b791cbd428d 100644
--- a/doc/integration/kerberos.md
+++ b/doc/integration/kerberos.md
@@ -67,8 +67,6 @@ For source installations, make sure the `kerberos` gem group
1. [Restart GitLab] for the changes to take effect.
----
-
**Omnibus package installations**
1. Edit `/etc/gitlab/gitlab.rb`:
@@ -83,8 +81,6 @@ For source installations, make sure the `kerberos` gem group
1. [Reconfigure GitLab] for the changes to take effect.
----
-
GitLab will now offer the `negotiate` authentication method for signing in and
HTTP Git access, enabling Git clients that support this authentication protocol
to authenticate with Kerberos tokens.
@@ -172,8 +168,6 @@ keep offering only `basic` authentication.
1. [Restart GitLab] and NGINX for the changes to take effect.
----
-
**For Omnibus package installations**
1. Edit `/etc/gitlab/gitlab.rb`:
@@ -186,8 +180,6 @@ keep offering only `basic` authentication.
1. [Reconfigure GitLab] for the changes to take effect.
----
-
After this change, all Git remote URLs will have to be updated to
`https://gitlab.example.com:8443/mygroup/myproject.git` in order to use
Kerberos ticket-based authentication.
@@ -223,8 +215,6 @@ remove the OmniAuth provider named `kerberos` from your `gitlab.yml` /
1. [Restart GitLab] for the changes to take effect.
----
-
**For Omnibus installations**
1. Edit `/etc/gitlab/gitlab.rb` and remove the `{ "name" => "kerberos" }` line
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index f84ab769218..9fcf2c2d99a 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../project_services/slack.md'
+redirect_to: '../user/project/integrations/slack.md'
---
-This document was moved to [project_services/slack.md](../project_services/slack.md).
+This document was moved to [project_services/slack.md](../user/project/integrations/slack.md).
diff --git a/doc/legal/corporate_contributor_license_agreement.md b/doc/legal/corporate_contributor_license_agreement.md
index 7f08188bd65..8c1f4a126b1 100644
--- a/doc/legal/corporate_contributor_license_agreement.md
+++ b/doc/legal/corporate_contributor_license_agreement.md
@@ -2,28 +2,28 @@
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions.
-1. Definitions.
+- **Definitions:**
- "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
- "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+ "Contribution" shall mean the code, documentation or other original works of authorship, including any modifications or additions to an existing work, that is submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
-2. Grant of Copyright License.
+- **Grant of Copyright License:**
-Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+ Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
-3. Grant of Patent License.
+- **Grant of Patent License:**
-Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+ Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
-4. You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of [name of Your corporation here]." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com.
+ You represent that You are legally entitled to grant the above license. You represent further that each of Your employees is authorized to submit Contributions on Your behalf, but excluding employees that are designated in writing by You as "Not authorized to submit Contributions on behalf of (name of Your corporation here)." Such designations of exclusion for unauthorized employees are to be submitted via email to legal@gitlab.com. It is Your responsibility to notify GitLab B.V. when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf. Such notification should also be sent via email to legal@gitlab.com.
-5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others).
+- **Contributions:**
-6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+ You represent that each of Your Contributions is Your original creation.
+
+ Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: (named here)".
-7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [named here]".
-
-8. It is Your responsibility to notify GitLab.com when any change is required to the list of designated employees excluded from submitting Contributions on Your behalf per Section 4. Such notification should be sent via email to legal@gitlab.com.
+ You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/legal/individual_contributor_license_agreement.md b/doc/legal/individual_contributor_license_agreement.md
index 59803aea080..7a071414629 100644
--- a/doc/legal/individual_contributor_license_agreement.md
+++ b/doc/legal/individual_contributor_license_agreement.md
@@ -2,24 +2,30 @@
You accept and agree to the following terms and conditions for Your present and future Contributions submitted to GitLab B.V.. Except for the license granted herein to GitLab B.V. and recipients of software distributed by GitLab B.V., You reserve all right, title, and interest in and to Your Contributions.
-1. Definitions.
+- **Definitions:**
- "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
+ "You" (or "Your") shall mean the copyright owner or legal entity authorized by the copyright owner that is making this Agreement with GitLab B.V.. For legal entities, the entity making a Contribution and all other entities that control, are controlled by, or are under common control with that entity are considered to be a single Contributor. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
- "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
+ "Contribution" shall mean any original work of authorship, including any modifications or additions to an existing work, that is intentionally submitted by You to GitLab B.V. for inclusion in, or documentation of, any of the products owned or managed by GitLab B.V. (the "Work"). For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to GitLab B.V. or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, GitLab B.V. for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by You as "Not a Contribution."
-2. Grant of Copyright License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
+- **Grant of Copyright License:**
-3. Grant of Patent License. Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
+ Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, and distribute Your Contributions and such derivative works.
-4. You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V..
+- **Grant of Patent License:**
-5. You represent that each of Your Contributions is Your original creation (see section 7 for submissions on behalf of others). You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
+ Subject to the terms and conditions of this Agreement, You hereby grant to GitLab B.V. and to recipients of software distributed by GitLab B.V. a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by You that are necessarily infringed by Your Contribution(s) alone or by combination of Your Contribution(s) with the Work to which such Contribution(s) was submitted. If any entity institutes patent litigation against You or any other entity (including a cross-claim or counterclaim in a lawsuit) alleging that your Contribution, or the Work to which you have contributed, constitutes direct or contributory patent infringement, then any patent licenses granted to that entity under this Agreement for that Contribution or Work shall terminate as of the date such litigation is filed.
-6. You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+ You represent that you are legally entitled to grant the above license. If your employer(s) has rights to intellectual property that you create that includes your Contributions, you represent that you have received permission to make Contributions on behalf of that employer, that your employer has waived such rights for your Contributions to GitLab B.V., or that your employer has executed a separate Corporate CLA with GitLab B.V..
-7. Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: [insert_name_here]".
+- **Contributions:**
-8. You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
+ You represent that each of Your Contributions is Your original creation. You represent that Your Contribution submissions include complete details of any third-party license or other restriction (including, but not limited to, related patents and trademarks) of which you are personally aware and which are associated with any part of Your Contributions.
+
+ Should You wish to submit work that is not Your original creation, You may submit it to GitLab B.V. separately from any Contribution, identifying the complete details of its source and of any license or other restriction (including, but not limited to, related patents, trademarks, and license agreements) of which you are personally aware, and conspicuously marking the work as "Submitted on behalf of a third-party: (insert_name_here)".
+
+ You are not expected to provide support for Your Contributions, except to the extent You desire to provide support. You may provide support for free, for a fee, or not at all. Unless required by applicable law or agreed to in writing, You provide Your Contributions on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON- INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE.
+
+You agree to notify GitLab B.V. of any facts or circumstances of which you become aware that would make these representations inaccurate in any respect.
This text is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) and the original source is the Google Open Source Programs Office.
diff --git a/doc/migrate_ci_to_ce/README.md b/doc/migrate_ci_to_ce/README.md
index 51ff56b3541..9947c19a667 100644
--- a/doc/migrate_ci_to_ce/README.md
+++ b/doc/migrate_ci_to_ce/README.md
@@ -67,7 +67,7 @@ Also check on your GitLab server.
```
# On your GitLab server:
# Omnibus
-sudo gitlab-rake gitlab:backup:create SKIP=repositories,uploads
+sudo gitlab-backup create SKIP=repositories,uploads
# Source
cd /home/git/gitlab
diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md
index d04428fdbfe..0142e5075cc 100644
--- a/doc/public_access/public_access.md
+++ b/doc/public_access/public_access.md
@@ -12,7 +12,7 @@ public access directory (`/public` under your GitLab instance), like at [https:/
### Public projects
-Public projects can be cloned **without any** authentication.
+Public projects can be cloned **without any** authentication over https.
They will be listed in the public access directory (`/public`) for all users.
@@ -37,7 +37,7 @@ visibility setting keep this setting. You can read more about the change in the
### Private projects
-Private projects can only be cloned and viewed by project members.
+Private projects can only be cloned and viewed by project members (except for guests).
They will appear in the public access directory (`/public`) for project members only.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 0d86df04367..51f04df27c7 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -77,7 +77,7 @@ You are highly advised to [read about storing configuration files](#storing-conf
Use this command if you've installed GitLab with the Omnibus package:
```sh
-sudo gitlab-rake gitlab:backup:create
+sudo gitlab-backup create
```
Use this if you've installed GitLab from source:
@@ -89,7 +89,7 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
If you are running GitLab within a Docker container, you can run the backup from the host:
```sh
-docker exec -t <container name> gitlab-rake gitlab:backup:create
+docker exec -t <container name> gitlab-backup create
```
If you are using the [GitLab helm chart](https://gitlab.com/charts/gitlab) on a
@@ -199,7 +199,7 @@ To use the `copy` strategy instead of the default streaming strategy, specify
`STRATEGY=copy` in the Rake task command. For example:
```sh
-sudo gitlab-rake gitlab:backup:create STRATEGY=copy
+sudo gitlab-backup create STRATEGY=copy
```
### Backup filename
@@ -207,7 +207,7 @@ sudo gitlab-rake gitlab:backup:create STRATEGY=copy
By default a backup file is created according to the specification in [the Backup timestamp](#backup-timestamp) section above. You can however override the `[TIMESTAMP]` part of the filename by setting the `BACKUP` environment variable. For example:
```sh
-sudo gitlab-rake gitlab:backup:create BACKUP=dump
+sudo gitlab-backup create BACKUP=dump
```
The resulting file will then be `dump_gitlab_backup.tar`. This is useful for systems that make use of rsync and incremental backups, and will result in considerably faster transfer speeds.
@@ -219,7 +219,7 @@ To make sure the generated archive is intelligently transferable by rsync, the `
Note that the `--rsyncable` option in `gzip` is not guaranteed to be available on all distributions. To verify that it is available in your distribution you can run `gzip --help` or consult the man pages.
```sh
-sudo gitlab-rake gitlab:backup:create BACKUP=dump GZIP_RSYNCABLE=yes
+sudo gitlab-backup create BACKUP=dump GZIP_RSYNCABLE=yes
```
### Excluding specific directories from the backup
@@ -244,7 +244,7 @@ will be skipped during a backup.
For Omnibus GitLab packages:
```sh
-sudo gitlab-rake gitlab:backup:create SKIP=db,uploads
+sudo gitlab-backup create SKIP=db,uploads
```
For installations from source:
@@ -448,8 +448,8 @@ Note: This option only works for remote storage. If you want to group your backu
you can pass a `DIRECTORY` environment variable:
```
-sudo gitlab-rake gitlab:backup:create DIRECTORY=daily
-sudo gitlab-rake gitlab:backup:create DIRECTORY=weekly
+sudo gitlab-backup create DIRECTORY=daily
+sudo gitlab-backup create DIRECTORY=weekly
```
### Uploading to locally mounted shares
@@ -566,7 +566,7 @@ crontab -e
There, add the following line to schedule the backup for everyday at 2 AM:
```
-0 2 * * * /opt/gitlab/bin/gitlab-rake gitlab:backup:create CRON=1
+0 2 * * * /opt/gitlab/bin/gitlab-backup create CRON=1
```
You may also want to set a limited lifetime for backups to prevent regular
@@ -726,7 +726,7 @@ restore:
```shell
# This command will overwrite the contents of your GitLab database!
-sudo gitlab-rake gitlab:backup:restore BACKUP=1493107454_2018_04_25_10.6.4-ce
+sudo gitlab-backup restore BACKUP=1493107454_2018_04_25_10.6.4-ce
```
Next, restore `/etc/gitlab/gitlab-secrets.json` if necessary as mentioned above.
@@ -760,7 +760,7 @@ backup location (default location is `/var/opt/gitlab/backups`).
For docker installations, the restore task can be run from host:
```sh
-docker exec -it <name of container> gitlab-rake gitlab:backup:restore
+docker exec -it <name of container> gitlab-backup restore
```
The GitLab helm chart uses a different process, documented in
@@ -960,3 +960,22 @@ want to run the chown against your custom location instead of
[reconfigure GitLab]: ../administration/restart_gitlab.md#omnibus-gitlab-reconfigure
[restart GitLab]: ../administration/restart_gitlab.md#installations-from-source
+
+### Backup fails to complete with Gzip error
+
+While running the backup, you may receive a gzip error:
+
+```sh
+sudo /opt/gitlab/bin/gitlab-backup create
+Dumping ...
+...
+gzip: stdout: Input/output error
+
+Backup failed
+```
+
+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 f880f31c39e..832078d23cb 100644
--- a/doc/raketasks/cleanup.md
+++ b/doc/raketasks/cleanup.md
@@ -137,3 +137,13 @@ level with `NICENESS`. Below are the valid levels, but consult
- `1` or `Realtime`
- `2` or `Best-effort` (default)
- `3` or `Idle`
+
+## Remove expired ActiveSession lookup keys
+
+```
+# omnibus-gitlab
+sudo gitlab-rake gitlab:cleanup:sessions:active_sessions_lookup_keys
+
+# installation from source
+bundle exec rake gitlab:cleanup:sessions:active_sessions_lookup_keys RAILS_ENV=production
+```
diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md
index a498e9793c1..cc1166a04cc 100644
--- a/doc/raketasks/web_hooks.md
+++ b/doc/raketasks/web_hooks.md
@@ -53,3 +53,8 @@ sudo gitlab-rake gitlab:web_hook:list NAMESPACE=acme
# source installations
bundle exec rake gitlab:web_hook:list NAMESPACE=acme RAILS_ENV=production
```
+
+## Local requests in webhooks
+
+[Requests to local network by webhooks](../security/webhooks.md) can be allowed
+or blocked by an administrator.
diff --git a/doc/security/README.md b/doc/security/README.md
index c48d5bc2065..5d498ac7602 100644
--- a/doc/security/README.md
+++ b/doc/security/README.md
@@ -7,7 +7,7 @@ type: index
- [Password length limits](password_length_limits.md)
- [Restrict SSH key technologies and minimum length](ssh_keys_restrictions.md)
-- [Rack attack](rack_attack.md)
+- [Rate limits](rate_limits.md)
- [Webhooks and insecure internal web services](webhooks.md)
- [Information exclusivity](information_exclusivity.md)
- [Reset your root password](reset_root_password.md)
diff --git a/doc/security/img/outbound_requests_section.png b/doc/security/img/outbound_requests_section.png
deleted file mode 100644
index f7783f34cdd..00000000000
--- a/doc/security/img/outbound_requests_section.png
+++ /dev/null
Binary files differ
diff --git a/doc/security/img/outbound_requests_section_v12_2.png b/doc/security/img/outbound_requests_section_v12_2.png
new file mode 100644
index 00000000000..3dc99868a35
--- /dev/null
+++ b/doc/security/img/outbound_requests_section_v12_2.png
Binary files differ
diff --git a/doc/security/img/whitelist.png b/doc/security/img/whitelist.png
new file mode 100644
index 00000000000..897000e804d
--- /dev/null
+++ b/doc/security/img/whitelist.png
Binary files differ
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 1e5678ec47c..b99bfb16829 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -2,7 +2,9 @@
type: reference, howto
---
-# Rack Attack
+# Rack Attack initializer
+
+## Overview
[Rack Attack](https://github.com/kickstarter/rack-attack), also known as Rack::Attack, is a Ruby gem
that is meant to protect GitLab with the ability to customize throttling and
@@ -14,19 +16,72 @@ If you find throttling is not enough to protect you against abusive clients,
Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering, and
tracking.
-**Note:** Starting with 11.2, Rack Attack is disabled by default. To continue
-using Rack Attack, please enable it by [configuring `gitlab.rb` as described in Settings](#settings).
+For more information on how to use these options see the [Rack Attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
+
+NOTE: **Note:** See
+[User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md)
+for simpler limits that are configured in the UI.
+
+NOTE: **Note:** Starting with GitLab 11.2, Rack Attack is disabled by default. If your
+instance is not exposed to the public internet, it is recommended that you leave
+Rack Attack disabled.
+
+## Behavior
+
+If set up as described in the [Settings](#settings) section below, two behaviors
+will be enabled:
+
+- Protected paths will be throttled.
+- Failed authentications for Git and container registry requests will trigger a temporary IP ban.
+
+### Protected paths throttle
+
+GitLab responds with HTTP status code `429` to POST requests at protected paths
+that exceed 10 requests per minute per IP address.
+
+By default, protected paths are:
+
+```ruby
+default['gitlab']['gitlab-rails']['rack_attack_protected_paths'] = [
+ '/users/password',
+ '/users/sign_in',
+ '/api/#{API::API.version}/session.json',
+ '/api/#{API::API.version}/session',
+ '/users',
+ '/users/confirmation',
+ '/unsubscribes/',
+ '/import/github/personal_access_token'
+]
+```
+
+This header is included in responses to blocked requests:
+
+```
+Retry-After: 60
+```
+
+For example, the following are limited to a maximum 10 requests per minute:
+
+- User sign-in
+- User sign-up (if enabled)
+- User password reset
+
+After 10 requests, the client must wait a minute before it can
+try again.
+
+### Git and container registry failed authentication ban
+
+GitLab responds with HTTP status code `403` for 1 hour, if 30 failed
+authentication requests were received in a 3-minute period from a single IP address.
-By default, user sign-in, user sign-up (if enabled), and user password reset is
-limited to 6 requests per minute. After trying for 6 times, the client will
-have to wait for the next minute to be able to try again.
+This applies only to Git requests and container registry (`/jwt/auth`) requests
+(combined).
-If you installed or upgraded GitLab by following the [official guides](../install/README.md),
-Rack Attack should be disabled by default. If your instance is not exposed to any incoming
-connections, it is recommended that you leave Rack Attack disabled.
+This limit is reset by requests that authenticate successfully. For example, 29
+failed authentication requests followed by 1 successful request, followed by 29
+more failed authentication requests would not trigger a ban.
-For more information on how to use these options check out
-[rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
+No response headers are provided.
## Settings
@@ -90,7 +145,7 @@ If you want more restrictive/relaxed throttle rules, edit
For example, more relaxed throttle rules will be if you set
`limit: 3` and `period: 1.seconds` (this will allow 3 requests per second).
You can also add other paths to the protected list by adding to `paths_to_be_protected`
-variable. If you change any of these settings do not forget to restart your
+variable. If you change any of these settings you must restart your
GitLab instance.
## Remove blocked IPs from Rack Attack via Redis
diff --git a/doc/security/rate_limits.md b/doc/security/rate_limits.md
new file mode 100644
index 00000000000..c80f2f264b2
--- /dev/null
+++ b/doc/security/rate_limits.md
@@ -0,0 +1,33 @@
+---
+type: reference, howto
+---
+
+# Rate limits
+
+NOTE: **Note:**
+For GitLab.com, please see
+[GitLab.com-specific rate limits](../user/gitlab_com/index.md#gitlabcom-specific-rate-limits).
+
+Rate limiting is a common technique used to improve the security and durability
+of a web application.
+
+For example, a simple script can make thousands of web requests per second.
+Whether malicious, apathetic, or just a bug, your application and infrastructure
+may not be able to cope with the load. For more details, see
+[Denial-of-service attack](https://en.wikipedia.org/wiki/Denial-of-service_attack).
+Most cases can be mitigated by limiting the rate of requests from a single IP address.
+
+Most [brute-force attacks](https://en.wikipedia.org/wiki/Brute-force_attack) are
+similarly mitigated by a rate limit.
+
+## Admin Area settings
+
+- [User and IP rate limits](../user/admin_area/settings/user_and_ip_rate_limits.md).
+- [Rate limits on raw endpoints](../user/admin_area/settings/rate_limits_on_raw_endpoints.md)
+
+## Rack Attack initializer
+
+This method of rate limiting is cumbersome, but has some advantages. It allows
+throttling of specific paths, and is also integrated into Git and container
+registry requests. See [Rack Attack initializer](rack_attack.md).
+
diff --git a/doc/security/reset_root_password.md b/doc/security/reset_root_password.md
index 6a6c5262179..ec360e2d338 100644
--- a/doc/security/reset_root_password.md
+++ b/doc/security/reset_root_password.md
@@ -23,7 +23,7 @@ user = User.where(id: 1).first
or
```bash
-user = User.find_by(email: 'admin@local.host')
+user = User.find_by(email: 'admin@example.com')
```
Now you can change your password:
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 1194234a295..e39bc9a9626 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -34,15 +34,46 @@ to 127.0.0.1, ::1 and 0.0.0.0, as well as IPv4 10.0.0.0/8, 172.16.0.0/12,
192.168.0.0/16 and IPv6 site-local (ffc0::/10) addresses won't be allowed.
This behavior can be overridden by enabling the option *"Allow requests to the
-local network from hooks and services"* in the *"Outbound requests"* section
+local network from web hooks and services"* in the *"Outbound requests"* section
inside the Admin area under **Settings**
(`/admin/application_settings/network`):
-![Outbound requests admin settings](img/outbound_requests_section.png)
+![Outbound requests admin settings](img/outbound_requests_section_v12_2.png)
->**Note:**
-*System hooks* are exempt from this protection because they are set up by
-admins.
+NOTE: **Note:**
+*System hooks* are enabled to make requests to local network by default since they are
+set up by administrators. However, you can turn this off by disabling the
+**Allow requests to the local network from system hooks** option.
+
+## Whitelist for local requests
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/44496) in GitLab 12.2
+
+You can allow certain domains and IP addresses to be accessible to both *system hooks*
+and *webhooks* even when local requests are not allowed by adding them to the
+whitelist. Navigate to **Admin Area > Settings > Network** (`/admin/application_settings/network`)
+and expand **Outbound requests**:
+
+![Outbound local requests whitelist](img/whitelist.png)
+
+The whilelist entries can be separated by semicolons, commas or whitespaces
+(including newlines) and be in different formats like hostnames, IP addresses and/or
+IP ranges. IPv6 is supported. Hostnames that contain unicode characters should
+use IDNA encoding.
+
+The whitelist can hold a maximum of 1000 entries. Each entry can be a maximum of
+255 characters.
+
+Example:
+
+```text
+example.com;gitlab.example.com
+127.0.0.1,1:0:0:0:0:0:0:1
+127.0.0.0/8 1:0:0:0:0:0:0:0/124
+```
+
+NOTE: **Note:**
+Wildcards (`*.example.com`) and ports (`127.0.0.1:3000`) are not currently supported.
<!-- ## Troubleshooting
diff --git a/doc/subscriptions/billing_table.png b/doc/subscriptions/billing_table.png
deleted file mode 100644
index acd1b6193ec..00000000000
--- a/doc/subscriptions/billing_table.png
+++ /dev/null
Binary files differ
diff --git a/doc/subscriptions/img/additional_minutes.png b/doc/subscriptions/img/additional_minutes.png
new file mode 100644
index 00000000000..b159b98c9ce
--- /dev/null
+++ b/doc/subscriptions/img/additional_minutes.png
Binary files differ
diff --git a/doc/subscriptions/img/buy_btn.png b/doc/subscriptions/img/buy_btn.png
new file mode 100644
index 00000000000..4fd05c0fba7
--- /dev/null
+++ b/doc/subscriptions/img/buy_btn.png
Binary files differ
diff --git a/doc/subscriptions/img/buy_minutes_card.png b/doc/subscriptions/img/buy_minutes_card.png
new file mode 100644
index 00000000000..cab098300cd
--- /dev/null
+++ b/doc/subscriptions/img/buy_minutes_card.png
Binary files differ
diff --git a/doc/subscriptions/index.md b/doc/subscriptions/index.md
index 745a2253e84..13c406727ab 100644
--- a/doc/subscriptions/index.md
+++ b/doc/subscriptions/index.md
@@ -2,118 +2,314 @@
type: index, reference
---
-# Subscription setup and management
+# Customer Docs
-This page will help get you started with your new subscription or manage an existing one, whether you have subscribed to GitLab.com or self-managed GitLab.
+This section contains information for:
-To subscribe, upgrade, or read more about the types of subscriptions, please see [Subscribe to GitLab](../README.md#subscribe-to-gitlab) on the GitLab Documentation landing page.
+- New customers about choosing [which GitLab](#which-gitlab) is right for you.
+- Existing customers about [managing subscriptions](#managing-subscriptions).
-## Set up GitLab
+Also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/).
-Learn how GitLab helps you in the stages of the DevOps lifecycle by learning more [about the GitLab product](https://about.gitlab.com/product/), [GitLab features](https://about.gitlab.com/features/), and [GitLab Documentation](../README.md).
+## Which GitLab?
-### Self-managed: Install GitLab
+There are two ways to use GitLab:
-Take a look at [installing GitLab](https://about.gitlab.com/install/) and our [administrator documentation](../administration/index.md). Then, follow the instructions below under [Your subscription](#your-subscription) to apply your license file.
+- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install
+ anything to use GitLab.com, you only need to
+ [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
+- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain
+ your own GitLab instance.
-### GitLab.com: Create a user and group
+The following sections outline tiers and features within GitLab.com
+and GitLab self-managed.
-Start with creating a user account for yourself using our [sign up page](https://gitlab.com/users/sign_in#register-pane).
+### GitLab.com
+
+GitLab.com is hosted, managed, and administered by GitLab, Inc., with
+[free and paid subscriptions](https://about.gitlab.com/pricing/) for individuals
+and teams in the following tiers:
-[GitLab groups](../user/group/index.md) help assemble related projects together allowing you to grant members access to several projects at once. A group is not required if you plan on having [projects](../user/project/) inside a personal namespace.
+| Tier | Includes same features available in |
+|:-------|:----------------------------------------------------|
+| Free | [Core](#gitlab-self-managed) self-managed tier. |
+| Bronze | [Starter](#gitlab-self-managed) self-managed tier. |
+| Silver | [Premium](#gitlab-self-managed) self-managed tier. |
+| Gold | [Ultimate](#gitlab-self-managed) self-managed tier. |
-## Your subscription
+GitLab.com subscriptions grant access
+to the same features available in GitLab self-managed, **except
+[administration](../administration/index.md) tools and settings**.
-You can view and manage subscriptions through our [Customers portal](https://customers.gitlab.com/). Information on applying your subscription is below.
+GitLab.com allows you to apply your subscription to a group or your personal user.
-Please also see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/)
+When applied to:
-### View subscription and seats
+- A **group**, the group, all subgroups, and all projects under the selected
+ group on GitLab.com will have the features of the associated plan. It is
+ recommended to go with a group plan when managing projects and users of an
+ organization.
+- A **personal userspace** instead, all projects will have features with the
+ subscription applied, but as it is not a group, group features will not be available.
-To view and manage the subscriptions you have purchased and the number of seats associated with the subscription, please visit and log into the [Customers’ Portal](https://customers.gitlab.com/subscriptions). For more information, please see our [subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/) and [pricing page](https://about.gitlab.com/pricing/), which includes information on our [true-up pricing policy](https://about.gitlab.com/handbook/product/pricing/#true-up-pricing) when adding more users than at the time of purchase.
+TIP: **Tip:**
+To support the open source community and encourage the development of open
+source projects, GitLab grants access to **Gold** features for all GitLab.com
+**public** projects, regardless of the subscription.
-Please note that this account may have the same email, but is a _separate_ login from your GitLab.com account. If the two accounts are linked together, then you can use the "sign in with GitLab.com account" link underneath the `Sign In` button.
+The following resources are available for more information on GitLab.com:
-### Change billing information
+- [Feature comparison](https://about.gitlab.com/pricing/gitlab-com/feature-comparison/), for information on what features are available at each tier.
+- [GitLab pricing page](https://about.gitlab.com/pricing/), for subscription information and a free trial.
+- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
+ - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
+ - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
+
+#### Subscribing to GitLab.com
+
+To subscribe to GitLab.com:
+
+1. Create a user account for yourself using our
+ [sign up page](https://gitlab.com/users/sign_in#register-pane).
+1. Create a [group](../user/group/index.md). GitLab groups help assemble related
+ projects together allowing you to grant members access to several projects
+ at once. A group is not required if you plan on having projects inside a personal
+ namespace.
+1. Create additional users and
+ [add them to the group](../user/group/index.md#add-users-to-a-group).
+1. Select the **Bronze**, **Silver**, or **Gold** GitLab.com plan through the
+ [GitLab Subscription Manager](https://customers.gitlab.com/).
+1. Link your GitLab.com account with your GitLab Subscription Manager account.
+ Once signed into the GitLab Subscription Manager, if your account is not
+ already linked, you will prompted to link your account with a
+ **Link my GitLab Account** button.
+1. Associate the group with the subscription.
+
+TIP: **Tip:**
+You can also go to the [**My Account**](https://customers.gitlab.com/customers/edit)
+page to add or change the GitLab.com account link.
+
+### GitLab self-managed
+
+With GitLab self-managed, you deploy your own GitLab instance on-premises or on a cloud of your choice.
+GitLab self-managed is available for [free and with paid subscriptions](https://about.gitlab.com/pricing/#self-managed) in the following tiers:
+
+| Tier | Includes |
+|:---------|:-----------------------------------------------|
+| Core | Core features. |
+| Starter | Core and Starter features. |
+| Premium | Core, Starter, and Premium features. |
+| Ultimate | Core, Starter, Premium, and Ultimate features. |
+
+The following resources are available for more information on GitLab self-managed:
-In the customers portal, go to the `My Account` page, then revise the `Account Details` information and click on the `Update Account` button.
+- [Feature comparison](https://about.gitlab.com/pricing/self-managed/feature-comparison/), for information on what features are available at each tier.
+- [GitLab pricing page](https://about.gitlab.com/pricing/#self-managed), for subscription information and a free trial.
+- Our [product marketing page](https://about.gitlab.com/handbook/marketing/product-marketing/), for additional information including:
+ - How [different tiers are licensed](https://about.gitlab.com/handbook/marketing/product-marketing/#tiers).
+ - The different [GitLab distributions](https://about.gitlab.com/handbook/marketing/product-marketing/#distributions).
-Future purchases will use the information in this section. The email listed in this section is used for the Customers Portal login and for license related email communication.
+#### Subscribing through GitLab self-managed
-### Self-managed: Apply your license file
+To subscribe to GitLab through a self-managed installation:
+
+1. [Install](https://about.gitlab.com/install/) GitLab.
+1. Complete the installation with
+ [administration tasks](https://docs.gitlab.com/ee/administration/).
+1. Select the **Starter**, **Premium**, or **Ultimate** self-managed plan
+ through the [GitLab Subscription Manager](https://customers.gitlab.com/).
+1. Apply your license file. After purchase, a license file is sent to the email
+ address associated to the GitLab Subscription Manager account,
+ which needs to be
+ [uploaded to your GitLab instance](../user/admin_area/license.md#uploading-your-license).
+
+TIP: **Tip:**
+If you are purchasing a subscription for an existing **Core** self-managed
+instance, ensure you are purchasing enough seats to
+[cover your users](../user/admin_area/index.md#administering-users).
+
+## Managing subscriptions
+
+You can view and manage subscriptions through our
+[GitLab Subscription Manager](https://customers.gitlab.com/).
-After purchase, the license file is sent to the email address tied to the Customers portal account, which needs to be [uploaded to the GitLab instance](../user/admin_area/license.md#uploading-your-license).
+### View subscription and seats
+
+Visit the
+[GitLab Subscription Manager](https://customers.gitlab.com/subscriptions) to
+view and manage:
+
+- The subscriptions you have purchased.
+- The number of seats associated with the subscription.
+- Retrieve copies of invoices.
+- Change the credit card on file.
-### Link your GitLab.com account with your Customers Portal account
+For more information, please see our:
+
+- [Subscription FAQ](https://about.gitlab.com/pricing/licensing-faq/).
+- [Pricing page](https://about.gitlab.com/pricing/), which includes information
+ on our [true-up pricing policy](https://about.gitlab.com/handbook/product/pricing/#true-up-pricing)
+ when adding more users other than at the time of purchase.
NOTE: **Note:**
-This is *required* for GitLab.com subscriptions.
+The GitLab Subscription Manager account can have the same email address as your
+GitLab.com account, but is a _separate_ login. If the two accounts are
+linked together, you can use the **Or sign in with GitLab.com**
+link underneath the **Sign In** button.
+
+### Change billing information
+
+To change billing information:
-Once signed into the customers portal, if your account is not already linked, you should be prompted to link your account with a "Link my GitLab Account" button.
+1. Log in to [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in).
+1. Go to the **My Account** page.
+1. Make the required changes to the **Account Details** information.
+1. Click **Update Account**.
-You can also go to the [My Account](https://customers.gitlab.com/customers/edit) page to add or change the GitLab.com account link.
+NOTE: **Note:**
+Future purchases will use the information in this section.
+The email listed in this section is used for the GitLab Subscription Manager
+login and for license-related email communication.
-### Change the linked GitLab.com account for your Customers Portal account
+### Manage GitLab.com account
-To change which GitLab.com account is associated with a Customers Portal account, please follow these steps:
+This section provided information specific to managing subscriptions with a
+GitLab.com account.
-1. Log into the [Customers Portal](https://customers.gitlab.com/customers/sign_in).
-1. In a separate browser tab, visit [GitLab.com](https://gitlab.com) to ensure you are not logged in, or if you are, log out.
-1. Back on the Customers Portal page, click [My Account](https://customers.gitlab.com/customers/edit) in the top menu.
-1. Under `Your GitLab.com account`, click the `Change linked account` button.
-1. Have the user you want associated log in to their [GitLab.com](https://gitlab.com) account.
+#### Change linked account
-### GitLab.com: Associate your namespace with your subscription
+To change the GitLab.com account associated with a GitLab Subscription Manager
+account:
-Once your GitLab.com account is linked, you can go to your [Subscriptions](https://customers.gitlab.com/subscriptions) page to choose or change the namespace your subscription applies to.
+1. Log in to the
+ [GitLab Subscription Manager](https://customers.gitlab.com/customers/sign_in).
+1. Go to [GitLab.com](https://gitlab.com) in a separate browser tab. Ensure you
+ are not logged in.
+1. On the GitLab Subscription Manager page, click
+ [**My Account**](https://customers.gitlab.com/customers/edit) in the top menu.
+1. Under **Your GitLab.com account**, click **Change linked account** button.
+1. Log in to [GitLab.com](https://gitlab.com) account to link to.
-Please note that you need to be a group owner to associate a group to your subscription.
+#### Change associated namespace
-### Confirm or upgrade your GitLab.com subscription details within GitLab
+With a linked GitLab.com account, go to the
+[**Subscriptions**](https://customers.gitlab.com/subscriptions) page to choose
+or change the namespace your subscription applies to.
+
+NOTE: **Note:**
+Please note that you need to be a group owner to associate a group to your
+subscription.
-To see the status of your GitLab.com subscription, you can click on the Billings
-section of the relevant namespace:
+### Confirm or upgrade your subscription
-- For individuals, this is located at <https://gitlab.com/profile/billings> under
- in your Settings,
-- For groups, this is located under the group's Settings dropdown, under Billing.
+To see the status of your GitLab.com subscription, you can click on the
+**Billings** section of the relevant namespace:
-For groups, you can see details of your subscription - including your current
-plan - in the included table:
+- For individuals:
+ 1. Go to **User Avatar > Settings**.
+ 1. Click **Billing**.
+- For groups, go to the group's **Settings** dropdown, under **Billing**.
-![Billing table](billing_table.png)
+The following table describes details of your subscription for groups:
| Field | Description |
| ------ | ------ |
-| Seats in subscription | If this is a paid plan, this represents the number of seats you've paid to support in your group. |
-| Seats currently in use | The number of active seats currently in use. |
-| Max seats used | The highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. |
+| Seats in subscription | If this is a paid plan, represents the number of seats you've paid to support in your group. |
+| Seats currently in use | Number of active seats currently in use. |
+| Max seats used | Highest number of seats you've used. If this exceeds the seats in subscription, you may owe an additional fee for the additional users. |
| Seats owed | If your max seats used exceeds the seats in your subscription, you'll owe an additional fee for the users you've added. |
-| Subscription start date | The date your subscription started. If this is for a Free plan, this is the date you transitioned off your group's paid plan. |
-| Subscription end date | The date your current subscription will end. This does not apply to Free plans. |
+| Subscription start date | Date your subscription started. If this is for a Free plan, is the date you transitioned off your group's paid plan. |
+| Subscription end date | Date your current subscription will end. Does not apply to Free plans. |
-### Subscription changes and your data
+#### Extra Shared Runners pipeline minutes
-When your subscription or trial expires, GitLab does not delete your data, however, depending on the tier and feature, it may become inaccessible. Please note that some features may not behave as expected if a graceful fallback is not currently implemented, such as [environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-ce/issues/52825).
+If you're using GitLab.com, you can purchase additional CI minutes so your
+pipelines will not be blocked after you have used all your CI minutes from your
+main quota. Additional minutes:
+
+- Are only used once the shared quota included in your subscription runs out.
+- Roll over month to month.
+
+In order to purchase additional minutes, you should follow these steps:
+
+1. Go to **Group > Settings > Pipelines quota**. Once you are on that page, click on **Buy additional minutes**.
+
+ ![Buy additional minutes](img/buy_btn.png)
+
+1. Locate the subscription card that is linked to your group on GitLab.com,
+ click on **Buy more CI minutes**, and complete the details about the transaction.
+
+ ![Buy additional minutes](img/buy_minutes_card.png)
+
+1. Once we have processed your payment, the extra CI minutes
+ will be synced to your Group and you can visualize it from the
+ **Group > Settings > Pipelines quota** page:
+
+ ![Additional minutes](img/additional_minutes.png)
+
+Be aware that:
+
+1. If you have purchased extra CI minutes before the purchase of a paid plan,
+ we will calculate a pro-rated charge for your paid plan. That means you may
+ be charged for less than one year since your subscription was previously
+ created with the extra CI minutes.
+1. Once the extra CI minutes has been assigned to a Group they cannot be transferred
+ to a different Group.
+1. If you have some minutes used over your default quota, these minutes will
+ be deducted from your Additional Minutes quota immediately after your purchase of additional
+ minutes.
+
+##### What happens when my CI minutes run out
+
+When the CI minutes run out, an email is sent automatically to notify the owner(s)
+of the group/namespace, including a link to [purchase more minutes](https://customers.gitlab.com/plans).
+
+If you are not the owner of the group, you will need to contact them to let them know they need to
+[purchase more minutes](https://customers.gitlab.com/plans).
+
+## Subscription changes and your data
+
+When your subscription or trial expires, GitLab does not delete your data.
+
+However, depending on the tier and feature, your data may become inaccessible.
+
+Please note that some features may not behave as expected if a graceful
+fallback is not currently implemented. For example,
+[environment specific variables not being passed](https://gitlab.com/gitlab-org/gitlab-ce/issues/52825).
If you renew or upgrade, your data will again be accessible.
-For self-managed customers, there is a two-week grace period when your features will continue to work as-is, after which the entire instance will become read only. However, if you remove the license, you will immediately revert to Core features.
+### Self-managed data
+
+For self-managed customers, there is a two-week grace period when your features
+will continue to work as-is, after which the entire instance will become read
+only.
+
+However, if you remove the license, you will immediately revert to Core
+features.
## Need help?
-[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of topics covering the use and administration of GitLab.
+[GitLab's Documentation](https://docs.gitlab.com/) offers a wide range of
+topics covering the use and administration of GitLab.
-We also encourage all users to search our project trackers for known issues and existing feature requests in:
+We also encourage all users to search our project trackers for known issues and
+existing feature requests in:
-- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/) for features included in all tiers, and
-- [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/issues/) for paid-tier features.
+- [GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/issues/) for features
+ included in all tiers.
+- [GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/issues/) for paid-tier
+ features.
-These issues are the best avenue for getting updates on specific product plans and for communicating directly with the relevant GitLab team members.
+These issues are the best avenue for getting updates on specific product plans
+and for communicating directly with the relevant GitLab team members.
### Contacting Support
-Learn more about the tiers of [GitLab Support](https://about.gitlab.com/support/) or [submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
+Learn more about:
+
+- The tiers of [GitLab Support](https://about.gitlab.com/support/).
+- [Submit a request via the Support Portal](https://support.gitlab.com/hc/en-us/requests/new).
<!-- ## Troubleshooting
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index e8bd35fba5c..1e9eb15533a 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -644,6 +644,11 @@ X-Gitlab-Event: System Hook
}
```
+## Local requests in system hooks
+
+[Requests to local network by system hooks](../security/webhooks.md) can be allowed
+or blocked by an administrator.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/tools/email.md b/doc/tools/email.md
index 72a5d094bc9..3088b0b63e7 100644
--- a/doc/tools/email.md
+++ b/doc/tools/email.md
@@ -19,12 +19,12 @@ at their primary email address.
1. Go to the admin area using the wrench icon in the top right corner and
navigate to **Overview > Users > Send email to users**.
- ![admin users](email1.png)
+ ![admin users](email1.png)
1. Compose an email and choose where it will be sent (all users or users of a
chosen group or project):
- ![compose an email](email2.png)
+ ![compose an email](email2.png)
## Unsubscribing from emails
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index 8b4a2f1630b..ad196e27f53 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -23,7 +23,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- [How to Configure LDAP with GitLab EE](../../administration/auth/how_to_configure_ldap_gitlab_ee/index.md) **(STARTER)**
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html)
+ - [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/debugging_ldap.html)
- **Integrations:**
- [OmniAuth](../../integration/omniauth.md)
- [Authentiq OmniAuth Provider](../../administration/auth/authentiq.md#authentiq-omniauth-provider)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index f9ad952aaad..4bfcd4aad96 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -1,6 +1,7 @@
# Auto DevOps
-> [Introduced][ce-37115] in GitLab 10.0. Generally available on GitLab 11.0.
+> - [Introduced][ce-37115] in GitLab 10.0.
+> - Generally available on GitLab 11.0.
Auto DevOps provides pre-defined CI/CD configuration which allows you to automatically detect, build, test,
deploy, and monitor your applications. Leveraging CI/CD best practices and tools, Auto DevOps aims
@@ -43,16 +44,16 @@ platform or a Platform as a Service (PaaS). It takes inspiration from the
innovative work done by [Heroku](https://www.heroku.com/) and goes beyond it
in multiple ways:
-1. Auto DevOps works with any Kubernetes cluster; you're not limited to running
- on GitLab's infrastructure. (Note that many features also work without Kubernetes.)
-1. There is no additional cost (no markup on the infrastructure costs), and you
- can use a self-hosted Kubernetes cluster or Containers as a Service on any
- public cloud (for example, [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)).
-1. Auto DevOps has more features including security testing, performance testing,
- and code quality testing.
-1. Auto DevOps offers an incremental graduation path. If you need advanced customizations,
- you can start modifying the templates without having to start over on a
- completely different platform. Review the [customizing](#customizing) section for more information.
+- Auto DevOps works with any Kubernetes cluster; you're not limited to running
+ on GitLab's infrastructure. (Note that many features also work without Kubernetes).
+- There is no additional cost (no markup on the infrastructure costs), and you
+ can use a self-hosted Kubernetes cluster or Containers as a Service on any
+ public cloud (for example, [Google Kubernetes Engine](https://cloud.google.com/kubernetes-engine/)).
+- Auto DevOps has more features including security testing, performance testing,
+ and code quality testing.
+- Auto DevOps offers an incremental graduation path. If you need advanced customizations,
+ you can start modifying the templates without having to start over on a
+ completely different platform. Review the [customizing](#customizing) section for more information.
## Features
@@ -86,42 +87,46 @@ Auto DevOps provides great defaults for all the stages; you can, however,
For an overview on the creation of Auto DevOps, read the blog post [From 2/3 of the Self-Hosted Git Market, to the Next-Generation CI System, to Auto DevOps](https://about.gitlab.com/2017/06/29/whats-next-for-gitlab-ci/).
+NOTE: **Note**
+Kubernetes clusters can [be used without](../../user/project/clusters/index.md)
+Auto DevOps.
+
## Requirements
To make full use of Auto DevOps, you will need:
-1. **GitLab Runner** (needed for all stages) - Your Runner needs to be
- configured to be able to run Docker. Generally this means using the
- [Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
- executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
- [privileged mode enabled](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode).
- The Runners do not need to be installed in the Kubernetes cluster, but the
- Kubernetes executor is easy to use and is automatically autoscaling.
- Docker-based Runners can be configured to autoscale as well, using [Docker
- Machine](https://docs.gitlab.com/runner/install/autoscaling.html). Runners
- should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
- for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
- that are assigned to specific projects.
-1. **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
- a domain configured with wildcard DNS which is going to be used by all of your
- Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
-1. **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
- To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters]
- for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
- for the entire GitLab installation.
- 1. **A load balancer** - You can use NGINX ingress by deploying it to your
- Kubernetes cluster using the
- [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
- Helm chart.
-1. **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
- will need Prometheus installed somewhere (inside or outside your cluster) and
- configured to scrape your Kubernetes cluster. To get response metrics
- (in addition to system metrics), you need to
- [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring).
- The [Prometheus service](../../user/project/integrations/prometheus.md)
- integration needs to be enabled for the project, or enabled as a
- [default service template](../../user/project/integrations/services_templates.md)
- for the entire GitLab installation.
+- **GitLab Runner** (needed for all stages) - Your Runner needs to be
+ configured to be able to run Docker. Generally this means using the
+ [Docker](https://docs.gitlab.com/runner/executors/docker.html) or [Kubernetes
+ executor](https://docs.gitlab.com/runner/executors/kubernetes.html), with
+ [privileged mode enabled](https://docs.gitlab.com/runner/executors/docker.html#use-docker-in-docker-with-privileged-mode).
+ The Runners do not need to be installed in the Kubernetes cluster, but the
+ Kubernetes executor is easy to use and is automatically autoscaling.
+ Docker-based Runners can be configured to autoscale as well, using [Docker
+ Machine](https://docs.gitlab.com/runner/install/autoscaling.html). Runners
+ should be registered as [shared Runners](../../ci/runners/README.md#registering-a-shared-runner)
+ for the entire GitLab instance, or [specific Runners](../../ci/runners/README.md#registering-a-specific-runner)
+ that are assigned to specific projects.
+- **Base domain** (needed for Auto Review Apps and Auto Deploy) - You will need
+ a domain configured with wildcard DNS which is going to be used by all of your
+ Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
+- **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
+ To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters]
+ for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
+ for the entire GitLab installation.
+ - **A load balancer** - You can use NGINX ingress by deploying it to your
+ Kubernetes cluster using the
+ [`nginx-ingress`](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
+ Helm chart.
+- **Prometheus** (needed for Auto Monitoring) - To enable Auto Monitoring, you
+ will need Prometheus installed somewhere (inside or outside your cluster) and
+ configured to scrape your Kubernetes cluster. To get response metrics
+ (in addition to system metrics), you need to
+ [configure Prometheus to monitor NGINX](../../user/project/integrations/prometheus_library/nginx_ingress.md#configuring-nginx-ingress-monitoring).
+ The [Prometheus service](../../user/project/integrations/prometheus.md)
+ integration needs to be enabled for the project, or enabled as a
+ [default service template](../../user/project/integrations/services_templates.md)
+ for the entire GitLab installation.
If you do not have Kubernetes or Prometheus installed, then Auto Review Apps,
Auto Deploy, and Auto Monitoring will be silently skipped.
@@ -147,7 +152,7 @@ NOTE: **Note**
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
-```
+```text
*.example.com 3600 A 1.2.3.4
```
@@ -185,16 +190,16 @@ Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so
except for the environment scope, they would also need to have a different
domain they would be deployed to. This is why you need to define a separate
`KUBE_INGRESS_BASE_DOMAIN` variable for all the above
-[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
+[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables).
The following table is an example of how the three different clusters would
be configured.
| Cluster name | Cluster environment scope | `KUBE_INGRESS_BASE_DOMAIN` variable value | Variable environment scope | Notes |
-| ------------ | -------------- | ----------------------------- | ------------- | ------ |
-| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
-| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
-| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). |
+|--------------|---------------------------|-------------------------------------------|----------------------------|---|
+| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
+| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
+| production | `production` | `example.com` | `production` | The production cluster which will run the deployments of the production environment. You can use [incremental rollouts](#incremental-rollout-to-production-premium). |
To add a different cluster for each environment:
@@ -224,7 +229,7 @@ full use of Auto DevOps are available. If this is your fist time, we recommend y
GitLab.com users can enable/disable Auto DevOps at the project-level only. Self-managed users
can enable/disable Auto DevOps at the project-level, group-level or instance-level.
-### Enabling/disabling Auto DevOps at the instance-level (Administrators only)
+### At the instance level (Administrators only)
Even when disabled at the instance level, group owners and project maintainers can still enable
Auto DevOps at the group and project level, respectively.
@@ -234,7 +239,7 @@ Auto DevOps at the group and project level, respectively.
1. If enabling, optionally set up the Auto DevOps [base domain](#auto-devops-base-domain) which will be used for Auto Deploy and Auto Review Apps.
1. Click **Save changes** for the changes to take effect.
-### Enabling/disabling Auto DevOps at the group-level
+### At the group level
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10.
@@ -250,7 +255,7 @@ When enabling or disabling Auto DevOps at group-level, group configuration will
the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on
the subgroup or project.
-### Enabling/disabling Auto DevOps at the project-level
+### At the project level
If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one exists, remove it.
@@ -263,7 +268,7 @@ If enabling, check that your project doesn't have a `.gitlab-ci.yml`, or if one
When the feature has been enabled, an Auto DevOps pipeline is triggered on the default branch.
-### Feature flag to enable for a percentage of projects
+### Enable for a percentage of projects
There is also a feature flag to enable Auto DevOps by default to your chosen percentage of projects.
@@ -371,7 +376,7 @@ Any differences between the source and target branches are also
Static Application Security Testing (SAST) uses the
[SAST Docker image](https://gitlab.com/gitlab-org/security-products/sast) to run static
analysis on the current code and checks for potential security issues. The
-the Auto SAST stage will be skipped on licenses other than Ultimate and requires GitLab Runner 11.5 or above.
+Auto SAST stage will be skipped on licenses other than Ultimate and requires GitLab Runner 11.5 or above.
Once the report is created, it's uploaded as an artifact which you can later download and
check out.
@@ -488,7 +493,7 @@ Any security warnings are also shown in the merge request widget. Read how
Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example:
-```
+```text
/
/features
/direction
@@ -579,6 +584,52 @@ Unless you have a `Dockerfile` in your repo, your image is built with
Herokuish, and you must prefix commands run in these images with `/bin/herokuish
procfile exec` to replicate the environment where your application will run.
+#### Workers
+
+Some web applications need to run extra deployments for "worker processes". For
+example it is common in a Rails application to have a separate worker process
+to run background tasks like sending emails.
+
+The [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app)
+used in Auto Deploy [has support for running worker
+processes](https://gitlab.com/gitlab-org/charts/auto-deploy-app/merge_requests/9).
+
+In order to run a worker you'll need to ensure that it is able to respond to
+the standard health checks which expect a successful HTTP response on port
+`5000`. For sidekiq you could make use of the
+[sidekiq_alive gem](https://rubygems.org/gems/sidekiq_alive) to do this.
+
+In order to work with sidekiq you'll also need to ensure your deployments have
+access to a redis instance. Auto DevOps won't deploy this for you so you'll
+need to manage this separately and then set a CI variable
+`K8S_SECRET_REDIS_URL` which the URL of this instance to ensure it's passed
+into your deployments.
+
+Once you have configured your worker to respond to health checks you you will
+need to configure a CI variable `HELM_UPGRADE_EXTRA_ARGS` with the value
+`--values helm-values.yaml`. Then you can, for example, run a
+[sidekiq](https://github.com/mperham/sidekiq) worker for your rails application
+by adding a file named `helm-values.yaml` to your repo with the following
+content:
+
+```yml
+workers:
+ sidekiq:
+ replicaCount: 1
+ command:
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiq
+ preStopCommand:
+ - /bin/herokuish
+ - procfile
+ - exec
+ - sidekiqctl
+ - quiet
+ terminationGracePeriodSeconds: 60
+```
+
### Auto Monitoring
See the [requirements](#requirements) for Auto Monitoring to enable this stage.
@@ -657,17 +708,13 @@ repo or by specifying a project variable:
You can also make use of the `HELM_UPGRADE_EXTRA_ARGS` environment variable to override the default values in the `values.yaml` file in the [default Helm chart](https://gitlab.com/gitlab-org/charts/auto-deploy-app).
To apply your own `values.yaml` file to all Helm upgrade commands in Auto Deploy set `HELM_UPGRADE_EXTRA_ARGS` to `--values my-values.yaml`.
-### Custom Helm chart per environment **(PREMIUM)**
+### Custom Helm chart per environment
You can specify the use of a custom Helm chart per environment by scoping the environment variable
-to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium).
+to the desired environment. See [Limiting environment scopes of variables](../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables).
### Customizing `.gitlab-ci.yml`
-Everything about Auto DevOps is customizable since the [Auto DevOps template]
-is just an example of a [`.gitlab-ci.yml`](../../ci/yaml/README.md) and uses
-only features that are available to any `.gitlab-ci.yml`.
-
Auto DevOps is completely customizable because the [Auto DevOps template]:
- Is just an implementation of a [`.gitlab-ci.yml`](../../ci/yaml/README.md) file.
@@ -724,46 +771,34 @@ The following variables can be used for setting up the Auto DevOps domain,
providing a custom Helm chart, or scaling your application. PostgreSQL can
also be customized, and you can easily use a [custom buildpack](#custom-buildpacks).
-| **Variable** | **Description** |
-| ------------ | --------------- |
-| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
-| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
-| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, this variable can be used to set the name of the helm repository; defaults to "gitlab" |
-| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, this variable can be used to set a username to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD) |
-| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, this variable can be used to set a password to connect to the helm repository. Defaults to no credentials. (Also set AUTO_DEVOPS_CHART_REPOSITORY_USERNAME) |
-| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
-| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. |
-| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md); defaults to 1 |
-| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
-| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
-| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
-| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
-| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
-| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
-| `POSTGRES_DB` | The PostgreSQL database name; defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
-| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
-| `BUILDPACK_URL` | The buildpack's full URL. It can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`, for example `https://github.com/heroku/heroku-buildpack-ruby.git#v142` |
-| `SAST_CONFIDENCE_LEVEL` | The minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High; defaults to `3`.|
-| `DEP_SCAN_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled; defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](https://gitlab.com/gitlab-org/security-products/dependency-scanning#remote-checks).|
-| `DB_INITIALIZE` | From GitLab 11.4, this variable can be used to specify the command to run to initialize the application's PostgreSQL database. It runs inside the application pod. |
-| `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. |
-| `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
-| `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
-| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
-| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
-| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
-| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
-| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
-| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
-| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. |
-| `REVIEW_DISABLED` | From GitLab 11.0, this variable can be used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
-| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
-| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
-| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
-| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
-| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, this variable allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
-| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, this variable allows to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
-| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, this variable allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](https://docs.gitlab.com/ee/topics/autodevops/index.html#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
+#### Build and deployment
+
+The following table lists variables related to building and deploying
+applications.
+
+| **Variable** | **Description** |
+|-----------------------------------------|------------------------------------|
+| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
+| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
+| `AUTO_DEVOPS_CHART` | Helm Chart used to deploy your apps. Defaults to the one [provided by GitLab](https://gitlab.com/gitlab-org/charts/auto-deploy-app). |
+| `AUTO_DEVOPS_CHART_REPOSITORY` | Helm Chart repository used to search for charts. Defaults to `https://charts.gitlab.io`. |
+| `AUTO_DEVOPS_CHART_REPOSITORY_NAME` | From Gitlab 11.11, used to set the name of the helm repository. Defaults to `gitlab`. |
+| `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME` | From Gitlab 11.11, used to set a username to connect to the helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD`. |
+| `AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD` | From Gitlab 11.11, used to set a password to connect to the helm repository. Defaults to no credentials. Also set `AUTO_DEVOPS_CHART_REPOSITORY_USERNAME`. |
+| `BUILDPACK_URL` | Buildpack's full URL. Can point to either Git repositories or a tarball URL. For Git repositories, it is possible to point to a specific `ref`. For example `https://github.com/heroku/heroku-buildpack-ruby.git#v142`. |
+| `CANARY_ENABLED` | From GitLab 11.0, used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments-premium). |
+| `CANARY_PRODUCTION_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md) in the production environment. Takes precedence over `CANARY_REPLICAS`. Defaults to 1. |
+| `CANARY_REPLICAS` | Number of canary replicas to deploy for [Canary Deployments](../../user/project/canary_deployments.md). Defaults to 1. |
+| `HELM_RELEASE_NAME` | From GitLab 12.1, allows the `helm` release name to be overridden. Can be used to assign unique release names when deploying multiple projects to a single namespace. |
+| `HELM_UPGRADE_EXTRA_ARGS` | From GitLab 11.11, allows extra arguments in `helm` commands when deploying the application. Note that using quotes will not prevent word splitting. **Tip:** you can use this variable to [customize the Auto Deploy helm chart](#custom-helm-chart) by applying custom override values with `--values my-values.yaml`. |
+| `INCREMENTAL_ROLLOUT_MODE` | From GitLab 11.4, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment. Set to `manual` for manual deployment jobs or `timed` for automatic rollout deployments with a 5 minute delay each one. |
+| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
+| `PRODUCTION_REPLICAS` | Number of replicas to deploy in the production environment. Takes precedence over `REPLICAS` and defaults to 1. For zero downtime upgrades, set to 2 or greater. |
+| `REPLICAS` | Number of replicas to deploy. Defaults to 1. |
+| `ROLLOUT_RESOURCE_TYPE` | From GitLab 11.9, allows specification of the resource type being deployed when using a custom helm chart. Default value is `deployment`. |
+| `ROLLOUT_STATUS_DISABLED` | From GitLab 12.0, used to disable rollout status check because it doesn't support all resource types, for example, `cronjob`. |
+| `STAGING_ENABLED` | From GitLab 10.8, used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). |
TIP: **Tip:**
Set up the replica variables using a
@@ -775,6 +810,42 @@ You should *not* scale your application using Kubernetes directly. This can
cause confusion with Helm not detecting the change, and subsequent deploys with
Auto DevOps can undo your changes.
+#### Database
+
+The following table lists variables related to the database.
+
+| **Variable** | **Description** |
+| `DB_INITIALIZE` | From GitLab 11.4, used to specify the command to run to initialize the application's PostgreSQL database. Runs inside the application pod. |
+| `DB_MIGRATE` | From GitLab 11.4, used to specify the command to run to migrate the application's PostgreSQL database. Runs inside the application pod. |
+| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled. Defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
+| `POSTGRES_USER` | The PostgreSQL user. Defaults to `user`. Set it to use a custom username. |
+| `POSTGRES_PASSWORD` | The PostgreSQL password. Defaults to `testing-password`. Set it to use a custom password. |
+| `POSTGRES_DB` | The PostgreSQL database name. Defaults to the value of [`$CI_ENVIRONMENT_SLUG`](../../ci/variables/README.md#predefined-environment-variables). Set it to use a custom database name. |
+| `POSTGRES_VERSION` | Tag for the [`postgres` Docker image](https://hub.docker.com/_/postgres) to use. Defaults to `9.6.2`. |
+
+#### Security tools
+
+The following table lists variables related to security tools.
+
+| **Variable** | **Description** |
+| `SAST_CONFIDENCE_LEVEL` | Minimum confidence level of security issues you want to be reported; `1` for Low, `2` for Medium, `3` for High. Defaults to `3`. |
+| `DS_DISABLE_REMOTE_CHECKS` | Whether remote Dependency Scanning checks are disabled. Defaults to `"false"`. Set to `"true"` to disable checks that send data to GitLab central servers. [Read more about remote checks](../../user/application_security/dependency_scanning/index.md#remote-checks). |
+
+#### Disable jobs
+
+The following table lists variables used to disable jobs.
+
+| **Variable** | **Description** |
+| `CODE_QUALITY_DISABLED` | From GitLab 11.0, used to disable the `codequality` job. If the variable is present, the job will not be created. |
+| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, used to disable the `sast:container` job. If the variable is present, the job will not be created. |
+| `DAST_DISABLED` | From GitLab 11.0, used to disable the `dast` job. If the variable is present, the job will not be created. |
+| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
+| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, used to disable the `license_management` job. If the variable is present, the job will not be created. |
+| `PERFORMANCE_DISABLED` | From GitLab 11.0, used to disable the `performance` job. If the variable is present, the job will not be created. |
+| `REVIEW_DISABLED` | From GitLab 11.0, used to disable the `review` and the manual `review:stop` job. If the variable is present, these jobs will not be created. |
+| `SAST_DISABLED` | From GitLab 11.0, used to disable the `sast` job. If the variable is present, the job will not be created. |
+| `TEST_DISABLED` | From GitLab 11.0, used to disable the `test` job. If the variable is present, the job will not be created. |
+
#### Application secret variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/49056) in GitLab 11.7.
@@ -789,11 +860,11 @@ To configure your application variables:
1. Go to your project's **Settings > CI/CD**, then expand the section
called **Variables**.
-2. Create a CI Variable, ensuring the key is prefixed with
+1. Create a CI Variable, ensuring the key is prefixed with
`K8S_SECRET_`. For example, you can create a variable with key
-`K8S_SECRET_RAILS_MASTER_KEY`.
+ `K8S_SECRET_RAILS_MASTER_KEY`.
-3. Run an Auto Devops pipeline either by manually creating a new
+1. Run an Auto Devops pipeline either by manually creating a new
pipeline or by pushing a code change to GitLab.
Auto DevOps pipelines will take your application secret variables to
@@ -992,10 +1063,10 @@ Everything behaves the same way, except:
- It's enabled by setting the `INCREMENTAL_ROLLOUT_MODE` variable to `timed`.
- Instead of the standard `production` job, the following jobs with a 5 minute delay between each are created:
- 1. `timed rollout 10%`
- 1. `timed rollout 25%`
- 1. `timed rollout 50%`
- 1. `timed rollout 100%`
+ 1. `timed rollout 10%`
+ 1. `timed rollout 25%`
+ 1. `timed rollout 50%`
+ 1. `timed rollout 100%`
## Currently supported languages
@@ -1008,7 +1079,7 @@ multi-buildpack does not.
As of GitLab 10.0, the supported buildpacks are:
-```
+```text
- heroku-buildpack-multi v1.0.0
- heroku-buildpack-ruby v168
- heroku-buildpack-nodejs v99
@@ -1044,6 +1115,12 @@ Container Registry. **Restarting a pod, scaling a service, or other actions whic
require on-going access to the registry may fail**. On-going secure access is
planned for a subsequent release.
+### Private registry support
+
+There is no documented way of using private container registry with Auto DevOps.
+We strongly advise using GitLab Container Registry with Auto DevOps in order to
+simplify configuration and prevent any unforeseen issues.
+
## Troubleshooting
- Auto Build and Auto Test may fail in detecting your language/framework. There
@@ -1056,7 +1133,7 @@ planned for a subsequent release.
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
- Auto Deploy will fail if GitLab can not create a Kubernetes namespace and
service account for your project. For help debugging this issue, see
- [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs).
+ [Troubleshooting failed deployment jobs](../../user/project/clusters/index.md#troubleshooting).
### Disable the banner instance wide
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index c1771a57da0..7ab59b80374 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -38,13 +38,13 @@ those projects provide a barebones application built on some well-known framewor
Rails, Spring, or NodeJS Express project. For this example,
we'll use the Ruby on Rails template.
- ![Select project template](img/guide_project_template.png)
+ ![Select project template](img/guide_project_template.png)
1. Give your project a name, optionally a description, and make it public so that
you can take advantage of the features available in the
[GitLab Gold plan](https://about.gitlab.com/pricing/#gitlab-com).
- ![Create project](img/guide_create_project.png)
+ ![Create project](img/guide_create_project.png)
1. Click **Create project**.
@@ -56,20 +56,20 @@ under which this application will be deployed.
1. On the project's landing page, click the button labeled **Add Kubernetes cluster**
(note that this option is also available when you navigate to **Operations > Kubernetes**).
- ![Project landing page](img/guide_project_landing_page.png)
+ ![Project landing page](img/guide_project_landing_page.png)
1. Choose **Create on Google Kubernetes Engine**.
- ![Choose GKE](img/guide_choose_gke.png)
+ ![Choose GKE](img/guide_choose_gke.png)
1. Sign in with Google.
- ![Google sign in](img/guide_google_signin.png)
+ ![Google sign in](img/guide_google_signin.png)
1. Connect with your Google account and press **Allow** when asked (this will
be shown only the first time you connect GitLab with your Google account).
- ![Google auth](img/guide_google_auth.png)
+ ![Google auth](img/guide_google_auth.png)
1. The last step is to fill in the cluster details. Give it a name, leave the
environment scope as is, and choose the GCP project under which the cluster
@@ -80,7 +80,7 @@ under which this application will be deployed.
cluster will be created, enter the number of nodes you want it to have, and
finally choose their [machine type](https://cloud.google.com/compute/docs/machine-types).
- ![GitLab GKE cluster details](img/guide_gitlab_gke_details.png)
+ ![GitLab GKE cluster details](img/guide_gitlab_gke_details.png)
1. Once ready, click **Create Kubernetes cluster**.
@@ -133,7 +133,7 @@ Now that the Kubernetes cluster is set up and ready, let's enable Auto DevOps.
successfully runs on the `master` branch.
1. Click **Save changes**.
- ![Auto DevOps settings](img/guide_enable_autodevops.png)
+ ![Auto DevOps settings](img/guide_enable_autodevops.png)
Once you complete all the above and save your changes, a new pipeline is
automatically created. To view the pipeline, go to **CI/CD > Pipelines**.
@@ -201,7 +201,7 @@ applications. In the rightmost column for the production environment, you can ma
Prometheus collects data about the Kubernetes cluster and how the application
affects it (in terms of memory/CPU usage, latency, etc.).
- ![Environments metrics](img/guide_environments_metrics.png)
+ ![Environments metrics](img/guide_environments_metrics.png)
- The third icon is the [web terminal](../../ci/environments.md#web-terminals)
and it will open a terminal session right inside the container where the
diff --git a/doc/topics/git/index.md b/doc/topics/git/index.md
index cdcd8215b23..6a539b526f3 100644
--- a/doc/topics/git/index.md
+++ b/doc/topics/git/index.md
@@ -48,6 +48,7 @@ The following are resources about version control concepts:
The following resources may help you become more efficient at using Git:
+- [Useful Git commands](useful_git_commands.md) collected by the GitLab support team.
- [Git Tips & Tricks](https://about.gitlab.com/2016/12/08/git-tips-and-tricks/)
- [Eight Tips to help you work better with Git](https://about.gitlab.com/2015/02/19/8-tips-to-help-you-work-better-with-git/)
@@ -71,6 +72,7 @@ The following are advanced topics for those who want to get the most out of Git:
- [Custom Git Hooks](../../administration/custom_hooks.md)
- [Git Attributes](../../user/project/git_attributes.md)
- Git Submodules: [Using Git submodules with GitLab CI](../../ci/git_submodules.md#using-git-submodules-with-gitlab-ci)
+- [Partial Clone](partial_clone.md)
## API
@@ -82,6 +84,8 @@ Git-related queries from GitLab.
The following relate to Git Large File Storage:
- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
-- [GitLab Git LFS documentation](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- [Migrate an existing Git repo with Git LFS](migrate_to_git_lfs/index.md)
+- [GitLab Git LFS user documentation](../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- [GitLab Git LFS admin documentation](../../workflow/lfs/lfs_administration.md)
- [Git-Annex to Git-LFS migration guide](../../workflow/lfs/migrate_from_git_annex_to_git_lfs.md)
- [Towards a production quality open source Git LFS server](https://about.gitlab.com/2015/08/13/towards-a-production-quality-open-source-git-lfs-server/)
diff --git a/doc/topics/git/migrate_to_git_lfs/index.md b/doc/topics/git/migrate_to_git_lfs/index.md
new file mode 100644
index 00000000000..c879e404997
--- /dev/null
+++ b/doc/topics/git/migrate_to_git_lfs/index.md
@@ -0,0 +1,174 @@
+---
+type: tutorial, concepts
+description: "How to migrate an existing Git repository to Git LFS with BFG."
+last_updated: 2019-07-11
+---
+
+# Migrate a Git repo into Git LFS with BFG
+
+Using Git LFS can help you to reduce the size of your Git
+repository and improve its performance.
+
+However, simply adding the
+large files that are already in your repository to Git LFS,
+will not actually reduce the size of your repository because
+the files are still referenced by previous commits.
+
+Through the method described on this document, first migrate
+to Git LFS with [BFG](https://rtyley.github.io/bfg-repo-cleaner/)
+through a mirror repo, then clean up the repository's history,
+and lastly create LFS tracking rules to prevent new binary files
+from being added.
+
+This tutorial was inspired by the guide
+[Use BFG to migrate a repo to Git LFS](https://confluence.atlassian.com/bitbucket/use-bfg-to-migrate-a-repo-to-git-lfs-834233484.html).
+For more information on Git LFS, see the [references](#references)
+below.
+
+CAUTION: **Warning:**
+The method described on this guide rewrites Git history. Make
+sure to back up your repo before beginning and use it at your
+own risk.
+
+## Requirements
+
+Before beginning, make sure:
+
+- You have enough LFS storage for the files you want to convert.
+ Storage is required for the entire history of all files.
+- All the team members you share the repository with have pushed all changes.
+ Branches based on the repository before applying this method cannot be merged.
+ Branches based on the repo before applying this method cannot be merged.
+
+To follow this tutorial, you'll need:
+
+- Maintainer permissions to the existing Git repository
+ you'd like to migrate to LFS with access through the command line.
+- [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+ and [Java Runtime Environment](https://www.java.com/en/download/manual.jsp)
+ (Java 7 or above) installed locally.
+- BFG installed locally:
+
+ ```bash
+ brew install bfg
+ ```
+
+- Git LFS installed locally:
+
+ ```bash
+ brew install git-lfs
+ ```
+
+NOTE: **Note:**
+This guide was tested on macOS Mojave.
+
+## Steps
+
+Consider an example upstream project, `git@gitlab.com:gitlab-tests/test-git-lfs-repo-migration.git`.
+
+1. Back up your repository:
+
+ Create a copy of your repository so that you can
+ recover it in case something goes wrong.
+
+1. Clone `--mirror` the repo:
+
+ Cloning with the mirror flag will create a bare repository.
+ This ensures you get all the branches within the repo.
+
+ It creates a directory called `<repo-name>.git`
+ (in our example, `test-git-lfs-repo-migration.git`),
+ mirroring the upstream project:
+
+ ```bash
+ git clone --mirror git@gitlab.com:gitlab-tests/test-git-lfs-repo-migration.git
+ ```
+
+1. Convert the Git history with BFG:
+
+ ```bash
+ bfg --convert-to-git-lfs "*.{png,mp4,jpg,gif}" --no-blob-protection test-git-lfs-repo-migration.git
+ ```
+
+ It is scanning all the history, and looking for any files with
+ that extension, and then converting them to an LFS pointer.
+
+1. Clean up the repository:
+
+ ```bash
+ # cd path/to/mirror/repo:
+ cd test-git-lfs-repo-migration.git
+ # clean up the repo:
+ git reflog expire --expire=now --all && git gc --prune=now --aggressive
+ ```
+
+ You can also take a look on how to further [clean the repo](../../../user/project/repository/reducing_the_repo_size_using_git.md),
+ but it's not necessary for the purposes of this guide.
+
+1. Install Git LFS in the mirror repository:
+
+ ```bash
+ git lfs install
+ ```
+
+1. [Unprotect the default branch](../../../user/project/protected_branches.md),
+ so that we can force-push the rewritten repository:
+
+ 1. Navigate to your project's **Settings > Repository** and
+ expand **Protected Branches**.
+ 1. Scroll down to locate the protected branches and click
+ **Unprotect** the default branch.
+
+1. Force-push to GitLab:
+
+ ```bash
+ git push --force
+ ```
+
+1. Track the files you want with LFS:
+
+ ```bash
+ # cd path/to/upstream/repo:
+ cd test-git-lfs-repo-migration
+ # You may need to reset your local copy with upstream's `master` after force-pushing from the mirror:
+ git reset --hard origin/master
+ # Track the files with LFS:
+ git lfs track "*.gif" "*.png" "*.jpg" "*.psd" "*.mp4" ".gitattributes" "img/"
+ ```
+
+ Now all existing the files you converted, as well as the new
+ ones you add, will be properly tracked with LFS.
+
+1. [Re-protect the default branch](../../../user/project/protected_branches.md):
+
+ 1. Navigate to your project's **Settings > Repository** and
+ expand **Protected Branches**.
+ 1. Select the default branch from the **Branch** dropdown menu,
+ and set up the
+ **Allowed to push** and **Allowed to merge** rules.
+ 1. Click **Protect**.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
+
+## References
+
+- [Getting Started with Git LFS](https://about.gitlab.com/2017/01/30/getting-started-with-git-lfs-tutorial/)
+- [Migrate from Git Annex to Git LFS](../../../workflow/lfs/migrate_from_git_annex_to_git_lfs.md)
+- [GitLab's Git LFS user documentation](../../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+- [GitLab's Git LFS administrator documentation](../../../workflow/lfs/lfs_administration.md)
+- Alternative method to [migrate an existing repo to Git LFS](https://github.com/git-lfs/git-lfs/wiki/Tutorial#migrating-existing-repository-data-to-lfs)
+
+<!--
+Test project:
+https://gitlab.com/gitlab-tests/test-git-lfs-repo-migration
+-->
diff --git a/doc/topics/git/numerous_undo_possibilities_in_git/index.md b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
index 84201e11831..5cae532bf54 100644
--- a/doc/topics/git/numerous_undo_possibilities_in_git/index.md
+++ b/doc/topics/git/numerous_undo_possibilities_in_git/index.md
@@ -110,21 +110,21 @@ At this point there are 3 options to undo the local changes you have:
- Discard all local changes, but save them for possible re-use [later](#quickly-save-local-changes):
- ```shell
- git stash
- ```
+ ```shell
+ git stash
+ ```
- Discarding local changes (permanently) to a file:
- ```shell
- git checkout -- <file>
- ```
+ ```shell
+ git checkout -- <file>
+ ```
- Discard all local changes to all files permanently:
- ```shell
- git reset --hard
- ```
+ ```shell
+ git reset --hard
+ ```
Before executing `git reset --hard`, keep in mind that there is also a way to
just temporary store the changes without committing them using `git stash`.
@@ -182,27 +182,27 @@ Now you have 4 options to undo your changes:
- Unstage the file to current commit (HEAD):
- ```shell
- git reset HEAD <file>
- ```
+ ```shell
+ git reset HEAD <file>
+ ```
- Unstage everything - retain changes:
- ```shell
- git reset
- ```
+ ```shell
+ git reset
+ ```
- Discard all local changes, but save them for [later](#quickly-save-local-changes):
- ```shell
- git stash
- ```
+ ```shell
+ git stash
+ ```
- Discard everything permanently:
- ```shell
- git reset --hard
- ```
+ ```shell
+ git reset --hard
+ ```
## Committed local changes
@@ -240,21 +240,21 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
- Undo (swap additions and deletions) changes introduced by commit `B`:
- ```shell
- git revert commit-B-id
- ```
+ ```shell
+ git revert commit-B-id
+ ```
- Undo changes on a single file or directory from commit `B`, but retain them in the staged state:
- ```shell
- git checkout commit-B-id <file>
- ```
+ ```shell
+ git checkout commit-B-id <file>
+ ```
- Undo changes on a single file or directory from commit `B`, but retain them in the unstaged state:
- ```shell
- git reset commit-B-id <file>
- ```
+ ```shell
+ git reset commit-B-id <file>
+ ```
- There is one command we also must not forget: **creating a new branch**
from the point where changes are not applicable or where the development has hit a
@@ -270,14 +270,14 @@ In our example we will end up with commit `B`, that introduced bug/error. We hav
you can [cherry-pick](../../../user/project/merge_requests/cherry_pick_changes.md#cherry-picking-a-commit)
that commit into a new merge request.
- ![Create a new branch to avoid clashing](img/branching.png)
+ ![Create a new branch to avoid clashing](img/branching.png)
- ```shell
- git checkout commit-B-id
- git checkout -b new-path-of-feature
- # Create <commit F>
- git commit -a
- ```
+ ```shell
+ git checkout commit-B-id
+ git checkout -b new-path-of-feature
+ # Create <commit F>
+ git commit -a
+ ```
### With history modification
@@ -297,9 +297,9 @@ delete commit `B`.
- Rebase the range from current commit D to A:
- ```shell
- git rebase -i A
- ```
+ ```shell
+ git rebase -i A
+ ```
- Command opens your favorite editor where you write `drop` in front of commit
`B`, but you leave default `pick` with all other commits. Save and exit the
@@ -310,9 +310,9 @@ In case you want to modify something introduced in commit `B`.
- Rebase the range from current commit D to A:
- ```shell
- git rebase -i A
- ```
+ ```shell
+ git rebase -i A
+ ```
- Command opens your favorite text editor where you write `edit` in front of commit
`B`, but leave default `pick` with all other commits. Save and exit the editor to
@@ -320,9 +320,9 @@ In case you want to modify something introduced in commit `B`.
- Now do your edits and commit changes:
- ```shell
- git commit -a
- ```
+ ```shell
+ git commit -a
+ ```
You can find some more examples in [below section where we explain how to modify
history](#how-modifying-history-is-done)
diff --git a/doc/topics/git/partial_clone.md b/doc/topics/git/partial_clone.md
new file mode 100644
index 00000000000..ea4223355d8
--- /dev/null
+++ b/doc/topics/git/partial_clone.md
@@ -0,0 +1,147 @@
+# Partial Clone for Large Repositories
+
+CAUTION: **Alpha:**
+Partial Clone is an experimental feature, and will significantly increase
+Gitaly resource utilization when performing a partial clone, and decrease
+performance of subsequent fetch operations.
+
+As Git repositories become very large, usability decreases as performance
+decreases. One major challenge is cloning the repository, because Git will
+download the entire repository including every commit and every version of
+every object. This can be slow to transfer, and require large amounts of disk
+space.
+
+Historically, performing a **shallow clone**
+([`--depth`](https://www.git-scm.com/docs/git-clone#Documentation/git-clone.txt---depthltdepthgt))
+has been the only way to reduce the amount of data transferred when cloning
+a Git repository. This does not, however, allow filtering by sub-tree which is
+important for monolithic repositories containing many projects, or by object
+size preventing unnecessary large objects being downloaded.
+
+[Partial clone](https://github.com/git/git/blob/master/Documentation/technical/partial-clone.txt)
+is a performance optimization that "allows Git to function without having a
+complete copy of the repository. The goal of this work is to allow Git better
+handle extremely large repositories."
+
+Specifically, using partial clone, it should be possible for Git to natively
+support:
+
+- large objects, instead of using [Git LFS](https://git-lfs.github.com/)
+- enormous repositories
+
+Briefly, partial clone works by:
+
+- excluding objects from being transferred when cloning or fetching a
+ repository using a new `--filter` flag
+- downloading missing objects on demand
+
+Follow [Git for enormous repositories](https://gitlab.com/groups/gitlab-org/-/epics/773) for roadmap and updates.
+
+## Enabling partial clone
+
+GitLab 12.1 uses Git 2.21.0 which has an arbitrary file access security
+vulnerability when `uploadpack.allowFilter` is enabled, and should not be
+enabled in production environments.
+
+A feature flag is planned to enable `uploadpack.allowFilter` and
+`uploadpack.allowAnySHA1InWant` once the version of Git used by GitLab has been
+updated to Git 2.22.0.
+
+Follow [this issue](https://gitlab.com/gitlab-org/gitaly/issues/1553) for
+updated.
+
+## Excluding objects by size
+
+Partial Clone allows large objects to be stored directly in the Git repository,
+and be excluded from clones as desired by the user. This eliminates the error
+prone process of deciding which objects should be stored in LFS or not. Using
+partial clone, all files – large or small – may be treated the same.
+
+With the `uploadpack.allowFilter` and `uploadpack.allowAnySHA1InWant` options
+enabled on the Git server:
+
+```bash
+# clone the repo, excluding blobs larger than 1 megabyte
+git clone --filter=blob:limit=1m <url>
+
+# in the checkout step of the clone, and any subsequent operations
+# any blobs that are needed will be downloaded on demand
+git checkout feature-branch
+```
+
+## Excluding objects by path
+
+Partial Clone allows clones to be filtered by path using a format similar to a
+`.gitignore` file stored inside the repository.
+
+With the `uploadpack.allowFilter` and `uploadpack.allowAnySHA1InWant` options
+enabled on the Git server:
+
+1. **Create a filter spec.** For example, consider a monolithic repository with
+ many applications, each in a different subdirectory in the root. Create a file
+ `shiny-app/.filterspec` using the GitLab web interface:
+
+ ```.gitignore
+ # Only the paths listed in the file will be downloaded when performing a
+ # partial clone using `--filter=sparse:oid=shiny-app/.gitfilterspec`
+
+ # Explicitly include filterspec needed to configure sparse checkout with
+ # git config --local core.sparsecheckout true
+ # git show master:snazzy-app/.gitfilterspec >> .git/info/sparse-checkout
+ shiny-app/.gitfilterspec
+
+ # Shiny App
+ shiny-app/
+
+ # Dependencies
+ shimmery-app/
+ shared-component-a/
+ shared-component-b/
+ ```
+
+1. *Create a new Git repository and fetch.* Support for `--filter=sparse:oid`
+ using the clone command is incomplete, so we will emulate the clone command
+ by hand, using `git init` and `git fetch`. Follow
+ [gitaly#1769](https://gitlab.com/gitlab-org/gitaly/issues/1769) for updates.
+
+ ```bash
+ # Create a new directory for the Git repository
+ mkdir jumbo-repo && cd jumbo-repo
+
+ # Initialize a new Git repository
+ git init
+
+ # Add the remote
+ git remote add origin git@gitlab.com/example/jumbo-repo
+
+ # Enable partial clone support for the remote
+ git config --local extensions.partialClone origin
+
+ # Fetch the filtered set of objects using the filterspec stored on the
+ # server. WARNING: this step is slow!
+ git fetch --filter=sparse:oid=master:shiny-app/.gitfilterspec origin
+
+ # Optional: observe there are missing objects that we have not fetched
+ git rev-list --all --quiet --objects --missing=print | wc -l
+ ```
+
+ CAUTION: **IDE and Shell integrations:**
+ Git integrations with `bash`, `zsh`, etc and editors that automatically
+ show Git status information often run `git fetch` which will fetch the
+ entire repository. You many need to disable or reconfigure these
+ integrations.
+
+1. **Sparse checkout** must be enabled and configured to prevent objects from
+ other paths being downloaded automatically when checking out branches. Follow
+ [gitaly#1765](https://gitlab.com/gitlab-org/gitaly/issues/1765) for updates.
+
+ ```bash
+ # Enable sparse checkout
+ git config --local core.sparsecheckout true
+
+ # Configure sparse checkout
+ git show master:snazzy-app/.gitfilterspec >> .git/info/sparse-checkout
+
+ # Checkout master
+ git checkout master
+ ```
diff --git a/doc/topics/git/troubleshooting_git.md b/doc/topics/git/troubleshooting_git.md
index 417d91bf834..11284da30af 100644
--- a/doc/topics/git/troubleshooting_git.md
+++ b/doc/topics/git/troubleshooting_git.md
@@ -51,11 +51,11 @@ Configuring *both* the client and the server is unnecessary.
- On UNIX, edit `~/.ssh/config` (create the file if it doesn’t exist) and
add or edit:
- ```text
- Host your-gitlab-instance-url.com
- ServerAliveInterval 60
- ServerAliveCountMax 5
- ```
+ ```text
+ Host your-gitlab-instance-url.com
+ ServerAliveInterval 60
+ ServerAliveCountMax 5
+ ```
- On Windows, if you are using PuTTY, go to your session properties, then
navigate to "Connection" and under "Sending of null packets to keep
diff --git a/doc/topics/git/useful_git_commands.md b/doc/topics/git/useful_git_commands.md
new file mode 100644
index 00000000000..030e62f485a
--- /dev/null
+++ b/doc/topics/git/useful_git_commands.md
@@ -0,0 +1,210 @@
+---
+type: reference
+---
+
+# Useful Git commands
+
+Here are some useful Git commands collected by the GitLab support team. You may not
+need to use often, but they can can come in handy when needed.
+
+## Remotes
+
+### Add another URL to a remote, so both remotes get updated on each push
+
+```sh
+git remote set-url --add <remote_name> <remote_url>
+```
+
+## Staging and reverting changes
+
+### Remove last commit and leave the changes in unstaged
+
+```sh
+git reset --soft HEAD^
+```
+
+### Unstage a certain number of commits from HEAD
+
+To unstage 3 commits, for example, run:
+
+```sh
+git reset HEAD^3
+```
+
+### Unstage changes to a certain file from HEAD
+
+```sh
+git reset <filename>
+```
+
+### Revert a file to HEAD state and remove changes
+
+There are two options to revert changes to a file:
+
+- `git checkout <filename>`
+- `git reset --hard <filename>`
+
+### Undo a previous commit by creating a new replacement commit
+
+```sh
+git revert <commit-sha>
+```
+
+### Create a new message for last commit
+
+```sh
+git commit --amend
+```
+
+### Add a file to the last commit
+
+```sh
+git add <filename>
+git commit --amend
+```
+
+Append `--no-edit` to the `commit` command if you do not want to edit the commit
+message.
+
+## Stashing
+
+### Stash changes
+
+```sh
+git stash save
+```
+
+The default behavor of `stash` is to save, so you can also use just:
+
+```sh
+git stash
+```
+
+### Unstash your changes
+
+```sh
+git stash apply
+```
+
+### Discard your stashed changes
+
+```sh
+git stash drop
+```
+
+### Apply and drop your stashed changes
+
+```sh
+git stash pop
+```
+
+## Refs and Log
+
+### Use reflog to show the log of reference changes to HEAD
+
+```sh
+git reflog
+```
+
+### Check the Git history of a file
+
+The basic command to check the git history of a file:
+
+```sh
+git log <file>
+```
+
+If you get this error message:
+
+```text
+fatal: ambiguous argument <file_name>: unknown revision or path not in the working tree.
+Use '--' to separate paths from revisions, like this:
+```
+
+Use this to check the Git history of the file:
+
+```sh
+git log -- <file>
+```
+
+### Find the tags that contain a particular SHA
+
+```sh
+git tag --contains <sha>
+```
+
+### Check the content of each change to a file
+
+```sh
+gitk <file>
+```
+
+### Check the content of each change to a file, follows it past file renames
+
+```sh
+gitk --follow <file>
+```
+
+## Debugging
+
+### Use a custom SSH key for a git command
+
+```sh
+GIT_SSH_COMMAND="ssh -i ~/.ssh/gitlabadmin" git <command>
+```
+
+### Debug cloning
+
+With SSH:
+
+```sh
+GIT_SSH_COMMAND="ssh -vvv" git clone <git@url>
+```
+
+With HTTPS:
+
+```sh
+GIT_TRACE_PACKET=1 GIT_TRACE=2 GIT_CURL_VERBOSE=1 git clone <url>
+```
+
+## Rebasing
+
+### Rebase your branch onto master
+
+The -i flag stands for 'interactive':
+
+```sh
+git rebase -i master
+```
+
+### Continue the rebase if paused
+
+```sh
+git rebase --continue
+```
+
+### Use git rerere
+
+To _reuse_ recorded solutions to the same problems when repeated:
+
+```sh
+git rerere
+```
+
+To enable `rerere` functionality:
+
+```sh
+git config --global rerere.enabled true
+```
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/university/README.md b/doc/university/README.md
index f696db2df20..b17f40b91a5 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -9,6 +9,10 @@ GitLab University is a great place to start when learning about version control
If you're looking for a GitLab subscription for _your university_, see our [Education](https://about.gitlab.com/solutions/education/) page.
+CAUTION: **Caution:**
+Some of the content in GitLab University may be out of date and we plan to
+[evaluate](https://gitlab.com/gitlab-org/gitlab-ce/issues/41064) it.
+
The GitLab University curriculum is composed of GitLab videos, screencasts, presentations, projects and external GitLab content hosted on other services and has been organized into the following sections:
1. [GitLab Beginner](#1-gitlab-beginner).
diff --git a/doc/university/training/end-user/README.md b/doc/university/training/end-user/README.md
index 99fb5e83387..1218465c87a 100644
--- a/doc/university/training/end-user/README.md
+++ b/doc/university/training/end-user/README.md
@@ -9,12 +9,8 @@ which can be found at [End User Slides](https://gitlab-org.gitlab.io/end-user-tr
through it's [RevealJS](https://gitlab.com/gitlab-org/end-user-training-slides)
project.
----
-
## Git Intro
----
-
### What is a Version Control System (VCS)
- Records changes to a file
@@ -22,8 +18,6 @@ project.
- Disaster Recovery
- Types of VCS: Local, Centralized and Distributed
----
-
### Short Story of Git
- 1991-2002: The Linux kernel was being maintained by sharing archived files
@@ -31,8 +25,6 @@ project.
- 2002: The Linux kernel project began using a DVCS called BitKeeper
- 2005: BitKeeper revoked the free-of-charge status and Git was created
----
-
### What is Git
- Distributed Version Control System
@@ -42,8 +34,6 @@ project.
- Disaster recovery friendly
- Open Source
----
-
### Getting Help
- Use the tools at your disposal when you get stuck.
@@ -51,12 +41,9 @@ project.
- Use Google (i.e. StackOverflow, Google groups)
- Read documentation at <https://git-scm.com>
----
-
## Git Setup
-Workshop Time!
----
+Workshop Time!
### Setup
@@ -68,8 +55,6 @@ Workshop Time!
- Debian: `sudo apt-get install git-all`
- Red Hat `sudo yum install git-all`
----
-
### Configure
- One-time configuration of the Git client:
@@ -88,9 +73,7 @@ git config --global --list
```
- You might want or be required to use an SSH key.
- - Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html)
-
----
+ - Instructions: [SSH](http://doc.gitlab.com/ce/ssh/README.html)
### Workspace
@@ -98,8 +81,6 @@ git config --global --list
- Create a workspace or development directory
- This is where we'll be working and adding content
----
-
```bash
mkdir ~/development
cd ~/development
@@ -110,24 +91,18 @@ mkdir ~/workspace
cd ~/workspace
```
----
-
## Git Basics
----
-
### Git Workflow
- Untracked files
- - New files that Git has not been told to track previously.
+ - New files that Git has not been told to track previously.
- Working area (Workspace)
- - Files that have been modified but are not committed.
+ - Files that have been modified but are not committed.
- Staging area (Index)
- - Modified files that have been marked to go in the next commit.
+ - Modified files that have been marked to go in the next commit.
- Upstream
- - Hosted repository on a shared server
-
----
+ - Hosted repository on a shared server
### GitLab
@@ -136,8 +111,6 @@ cd ~/workspace
issue tracking, Merge Requests, and other features.
- The hosted version of GitLab is gitlab.com
----
-
### New Project
- Sign in into your gitlab.com account
@@ -145,8 +118,6 @@ cd ~/workspace
- Choose to import from 'Any Repo by URL' and use <https://gitlab.com/gitlab-org/training-examples.git>
- On your machine clone the `training-examples` project
----
-
### Git and GitLab basics
1. Edit `edit_this_file.rb` in `training-examples`
@@ -157,8 +128,6 @@ cd ~/workspace
1. Push the commit to the remote
1. View the git log
----
-
```shell
# Edit `edit_this_file.rb`
git status
@@ -169,8 +138,6 @@ git push origin master
git log
```
----
-
### Feature Branching
1. Create a new feature branch called `squash_some_bugs`
@@ -178,8 +145,6 @@ git log
1. Commit
1. Push
----
-
```shell
git checkout -b squash_some_bugs
# Edit `bugs.rb`
@@ -189,14 +154,8 @@ git commit -m 'Fix some buggy code'
git push origin squash_some_bugs
```
----
-
## Merge Request
----
-
-### Merge requests
-
- When you want feedback create a merge request
- Target is the ‘default’ branch (usually master)
- Assign or mention the person you would like to review
@@ -205,8 +164,6 @@ git push origin squash_some_bugs
- Anyone can comment, not just the assignee
- Push corrections to the same branch
----
-
### Merge request example
- Create your first merge request
@@ -215,8 +172,6 @@ git push origin squash_some_bugs
- Push a new commit to the same branch
- Review the changes again and notice the update
----
-
### Feedback and Collaboration
- Merge requests are a time for feedback and collaboration
@@ -229,26 +184,17 @@ git push origin squash_some_bugs
---
-### Feedback and Collaboration
-
-- Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:[Thoughtbot](https://github.com/thoughtbot/guides/tree/master/code-review)
+- Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
+ [Thoughtbot](https://github.com/thoughtbot/guides/tree/master/code-review)
- See GitLab merge requests for examples: [Merge Requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests)
----
-
## Merge Conflicts
----
-
-### Merge Conflicts
-
- Happen often
- Learning to fix conflicts is hard
- Practice makes perfect
- Force push after fixing conflicts. Be careful!
----
-
### Example Plan
1. Checkout a new branch and edit conflicts.rb. Add 'Line4' and 'Line5'.
@@ -262,47 +208,45 @@ git push origin squash_some_bugs
1. Force push the changes
1. Finally continue with the Merge Request
----
-
### Example 1/2
- git checkout -b conflicts_branch
+```sh
+git checkout -b conflicts_branch
- # vi conflicts.rb
- # Add 'Line4' and 'Line5'
+# vi conflicts.rb
+# Add 'Line4' and 'Line5'
- git commit -am "add line4 and line5"
- git push origin conflicts_branch
+git commit -am "add line4 and line5"
+git push origin conflicts_branch
- git checkout master
+git checkout master
- # vi conflicts.rb
- # Add 'Line6' and 'Line7'
- git commit -am "add line6 and line7"
- git push origin master
-
----
+# vi conflicts.rb
+# Add 'Line6' and 'Line7'
+git commit -am "add line6 and line7"
+git push origin master
+```
### Example 2/2
Create a merge request on the GitLab web UI. You'll see a conflict warning.
- git checkout conflicts_branch
- git fetch
- git rebase master
-
- # Fix conflicts by editing the files.
+```sh
+git checkout conflicts_branch
+git fetch
+git rebase master
- git add conflicts.rb
- # No need to commit this file
+# Fix conflicts by editing the files.
- git rebase --continue
+git add conflicts.rb
+# No need to commit this file
- # Remember that we have rewritten our commit history so we
- # need to force push so that our remote branch is restructured
- git push origin conflicts_branch -f
+git rebase --continue
----
+# Remember that we have rewritten our commit history so we
+# need to force push so that our remote branch is restructured
+git push origin conflicts_branch -f
+```
### Notes
@@ -311,55 +255,63 @@ Create a merge request on the GitLab web UI. You'll see a conflict warning.
- Merge when bringing changes from feature to master
- Reference: <https://www.atlassian.com/git/tutorials/merging-vs-rebasing/>
----
-
## Revert and Unstage
----
-
### Unstage
To remove files from stage use reset HEAD. Where HEAD is the last commit of the current branch:
- git reset HEAD <file>
+```sh
+git reset HEAD <file>
+```
This will unstage the file but maintain the modifications. To revert the file back to the state it was in before the changes we can use:
- git checkout -- <file>
+```sh
+git checkout -- <file>
+```
To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
- git rm '*.txt'
- git rm -r <dirname>
+```sh
+git rm '*.txt'
+git rm -r <dirname>
+```
If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our .gitignore file then use `--cache`:
- git rm <filename> --cache
-
----
+```sh
+git rm <filename> --cache
+```
### Undo Commits
Undo last commit putting everything back into the staging area:
- git reset --soft HEAD^
+```sh
+git reset --soft HEAD^
+```
Add files and change message with:
- git commit --amend -m "New Message"
+```sh
+git commit --amend -m "New Message"
+```
Undo last and remove changes
- git reset --hard HEAD^
+```sh
+git reset --hard HEAD^
+```
Same as last one but for two commits back:
- git reset --hard HEAD^^
+```sh
+git reset --hard HEAD^^
+```
Don't reset after pushing
----
-
### Reset Workflow
1. Edit file again 'edit_this_file.rb'
@@ -373,53 +325,46 @@ Don't reset after pushing
1. Pull for updates
1. Push changes
-----
-
- # Change file edit_this_file.rb
- git status
- git commit -am "kjkfjkg"
- git log
- git commit --amend -m "New comment added"
- git log
- git reset --soft HEAD^
- git log
- git pull origin master
- git push origin master
-
----
+```sh
+# Change file edit_this_file.rb
+git status
+git commit -am "kjkfjkg"
+git log
+git commit --amend -m "New comment added"
+git log
+git reset --soft HEAD^
+git log
+git pull origin master
+git push origin master
+```
-### Note
+### git revert vs git reset
-git revert vs git reset
Reset removes the commit while revert removes the changes but leaves the commit
Revert is safer considering we can revert a revert
- # Changed file
- git commit -am "bug introduced"
- git revert HEAD
- # New commit created reverting changes
- # Now we want to re apply the reverted commit
- git log # take hash from the revert commit
- git revert <rev commit hash>
- # reverted commit is back (new commit created again)
-
----
+```sh
+# Changed file
+git commit -am "bug introduced"
+git revert HEAD
+# New commit created reverting changes
+# Now we want to re apply the reverted commit
+git log # take hash from the revert commit
+git revert <rev commit hash>
+# reverted commit is back (new commit created again)
+```
## Questions
----
-
## Instructor Notes
----
-
### Version Control
- - Local VCS was used with a filesystem or a simple db.
- - Centralized VCS such as Subversion includes collaboration but
- still is prone to data loss as the main server is the single point of
- failure.
- - Distributed VCS enables the team to have a complete copy of the project
- and work with little dependency to the main server. In case of a main
- server failing the project can be recovered by any of the latest copies
- from the team
+- Local VCS was used with a filesystem or a simple db.
+- Centralized VCS such as Subversion includes collaboration but
+ still is prone to data loss as the main server is the single point of
+ failure.
+- Distributed VCS enables the team to have a complete copy of the project
+ and work with little dependency to the main server. In case of a main
+ server failing the project can be recovered by any of the latest copies
+ from the team
diff --git a/doc/university/training/topics/bisect.md b/doc/university/training/topics/bisect.md
index 4178afa2086..24dc670d9d5 100644
--- a/doc/university/training/topics/bisect.md
+++ b/doc/university/training/topics/bisect.md
@@ -4,13 +4,11 @@ comments: false
# Bisect
-## Bisect
-
- Find a commit that introduced a bug
- Works through a process of elimination
- Specify a known good and bad revision to begin
-## Bisect
+## Bisect sample workflow
1. Start the bisect process
1. Enter the bad revision (usually latest commit)
diff --git a/doc/university/training/topics/cherry_picking.md b/doc/university/training/topics/cherry_picking.md
index fa0cb5fe6a4..f5bcdfcbf12 100644
--- a/doc/university/training/topics/cherry_picking.md
+++ b/doc/university/training/topics/cherry_picking.md
@@ -4,13 +4,11 @@ comments: false
# Cherry Pick
-## Cherry Pick
-
- Given an existing commit on one branch, apply the change to another branch
- Useful for backporting bug fixes to previous release branches
- Make the commit on the master branch and pick in to stable
-## Cherry Pick
+## Cherry Pick sample workflow
1. Check out a new 'stable' branch from 'master'
1. Change back to 'master'
@@ -19,8 +17,6 @@ comments: false
1. Check out the 'stable' branch
1. Cherry pick the commit using the SHA obtained earlier
-## Commands
-
```bash
git checkout master
git checkout -b stable
diff --git a/doc/university/training/topics/feature_branching.md b/doc/university/training/topics/feature_branching.md
index d2efe634533..f530389d4da 100644
--- a/doc/university/training/topics/feature_branching.md
+++ b/doc/university/training/topics/feature_branching.md
@@ -11,15 +11,13 @@ comments: false
- Push branches to the server frequently
- Hint: This is a cheap backup for your work-in-progress code
-## Feature branching
+## Feature branching sample workflow
1. Create a new feature branch called 'squash_some_bugs'
1. Edit '`bugs.rb`' and remove all the bugs.
1. Commit
1. Push
-## Commands
-
```sh
git checkout -b squash_some_bugs
# Edit `bugs.rb`
diff --git a/doc/university/training/topics/getting_started.md b/doc/university/training/topics/getting_started.md
index 08027c5d15b..3fadb58e804 100644
--- a/doc/university/training/topics/getting_started.md
+++ b/doc/university/training/topics/getting_started.md
@@ -8,14 +8,15 @@ comments: false
- Create a new repository by instantiating it through:
- ```bash
- git init
- ```
+ ```bash
+ git init
+ ```
+
- Copy an existing project by cloning the repository through:
- ```bash
- git clone <url>
- ```
+ ```bash
+ git clone <url>
+ ```
## Central Repos
@@ -23,9 +24,9 @@ comments: false
- Bare repositories don't allow file editing or committing changes.
- Create a bare repo with:
- ```bash
- git init --bare project-name.git
- ```
+ ```bash
+ git init --bare project-name.git
+ ```
## Instantiate workflow with clone
@@ -34,8 +35,6 @@ comments: false
1. Create a '`Workspace`' directory in your home directory.
1. Clone the '`training-examples`' project.
-## Commands
-
```sh
mkdir ~/workspace
cd ~/workspace
@@ -68,8 +67,6 @@ Modified files that have been marked to go in the next commit.
1. Push the commit to the remote
1. View the git log
-## Commands
-
```sh
# Edit `edit_this_file.rb`
git status
diff --git a/doc/university/training/topics/git_add.md b/doc/university/training/topics/git_add.md
index 4c61d5eb175..0c9a50bb5e1 100644
--- a/doc/university/training/topics/git_add.md
+++ b/doc/university/training/topics/git_add.md
@@ -4,38 +4,34 @@ comments: false
# Git Add
-## Git Add
-
Adds content to the index or staging area.
- Adds a list of file:
- ```bash
- git add <files>
- ```
+ ```bash
+ git add <files>
+ ```
- Adds all files including deleted ones:
- ```bash
- git add -A
- ```
-
-## Git add continued
+ ```bash
+ git add -A
+ ```
- Add all text files in current dir:
- ```bash
- git add *.txt
- ```
+ ```bash
+ git add *.txt
+ ```
- Add all text file in the project:
- ```bash
- git add "*.txt*"
- ```
+ ```bash
+ git add "*.txt*"
+ ```
- Adds all files in directory:
- ```bash
- git add views/layouts/
- ```
+ ```bash
+ git add views/layouts/
+ ```
diff --git a/doc/university/training/topics/git_log.md b/doc/university/training/topics/git_log.md
index 11addcd3ee1..bae734554f5 100644
--- a/doc/university/training/topics/git_log.md
+++ b/doc/university/training/topics/git_log.md
@@ -8,33 +8,33 @@ Git log lists commit history. It allows searching and filtering.
- Initiate log:
- ```sh
- git log
- ```
+ ```sh
+ git log
+ ```
- Retrieve set number of records:
- ```sh
- git log -n 2
- ```
+ ```sh
+ git log -n 2
+ ```
- Search commits by author. Allows user name or a regular expression.
- ```sh
- git log --author="user_name"
- ```
+ ```sh
+ git log --author="user_name"
+ ```
- Search by comment message:
- ```sh
- git log --grep="<pattern>"
- ```
+ ```sh
+ git log --grep="<pattern>"
+ ```
- Search by date:
- ```sh
- git log --since=1.month.ago --until=3.weeks.ago
- ```
+ ```sh
+ git log --since=1.month.ago --until=3.weeks.ago
+ ```
## Git Log Workflow
diff --git a/doc/university/training/topics/merge_conflicts.md b/doc/university/training/topics/merge_conflicts.md
index dd235fe3a81..97bb038f405 100644
--- a/doc/university/training/topics/merge_conflicts.md
+++ b/doc/university/training/topics/merge_conflicts.md
@@ -9,7 +9,7 @@ comments: false
- Practice makes perfect
- Force push after fixing conflicts. Be careful!
-## Merge conflicts
+## Merge conflicts sample workflow
1. Checkout a new branch and edit `conflicts.rb`. Add 'Line4' and 'Line5'.
1. Commit and push.
@@ -22,8 +22,6 @@ comments: false
1. Force push the changes.
1. Finally continue with the Merge Request.
-## Commands
-
```sh
git checkout -b conflicts_branch
diff --git a/doc/university/training/topics/merge_requests.md b/doc/university/training/topics/merge_requests.md
index b5bbe7b2e1e..656871ae5b2 100644
--- a/doc/university/training/topics/merge_requests.md
+++ b/doc/university/training/topics/merge_requests.md
@@ -30,8 +30,6 @@ comments: false
- Be as receptive as possible
- Feedback is about the best code, not the person. You are not your code
-## Feedback and Collaboration
-
Review the Thoughtbot code-review guide for suggestions to follow when reviewing merge requests:
[https://github.com/thoughtbot/guides/tree/master/code-review](https://github.com/thoughtbot/guides/tree/master/code-review)
diff --git a/doc/university/training/topics/rollback_commits.md b/doc/university/training/topics/rollback_commits.md
index 1e6602deef2..c17e8d59737 100644
--- a/doc/university/training/topics/rollback_commits.md
+++ b/doc/university/training/topics/rollback_commits.md
@@ -8,29 +8,29 @@ comments: false
- Undo last commit putting everything back into the staging area:
- ```sh
- git reset --soft HEAD^
- ```
+ ```sh
+ git reset --soft HEAD^
+ ```
- Add files and change message with:
- ```sh
- git commit --amend -m "New Message"
- ```
+ ```sh
+ git commit --amend -m "New Message"
+ ```
- Undo last and remove changes:
- ```sh
- git reset --hard HEAD^
- ```
+ ```sh
+ git reset --hard HEAD^
+ ```
- Same as last one but for two commits back:
- ```sh
- git reset --hard HEAD^^
- ```
+ ```sh
+ git reset --hard HEAD^^
+ ```
-** Don't reset after pushing **
+**Don't reset after pushing**
## Reset Workflow
diff --git a/doc/university/training/topics/stash.md b/doc/university/training/topics/stash.md
index 02b2f610266..d3e63db0c6a 100644
--- a/doc/university/training/topics/stash.md
+++ b/doc/university/training/topics/stash.md
@@ -9,52 +9,52 @@ and we need to change to a different branch.
- Stash:
- ```sh
- git stash save
- # or
- git stash
- # or with a message
- git stash save "this is a message to display on the list"
- ```
+ ```sh
+ git stash save
+ # or
+ git stash
+ # or with a message
+ git stash save "this is a message to display on the list"
+ ```
- Apply stash to keep working on it:
- ```sh
- git stash apply
- # or apply a specific one from out stack
- git stash apply stash@{3}
- ```
+ ```sh
+ git stash apply
+ # or apply a specific one from out stack
+ git stash apply stash@{3}
+ ```
-- Every time we save a stash it gets stacked so by using list we can see all our
+- Every time we save a stash it gets stacked so by using `list` we can see all our
stashes.
- ```sh
- git stash list
- # or for more information (log methods)
- git stash list --stat
- ```
+ ```sh
+ git stash list
+ # or for more information (log methods)
+ git stash list --stat
+ ```
- To clean our stack we need to manually remove them:
- ```sh
- # drop top stash
- git stash drop
- # or
- git stash drop <name>
- # to clear all history we can use
- git stash clear
- ```
+ ```sh
+ # drop top stash
+ git stash drop
+ # or
+ git stash drop <name>
+ # to clear all history we can use
+ git stash clear
+ ```
- Apply and drop on one command:
- ```sh
- git stash pop
- ```
+ ```sh
+ git stash pop
+ ```
- If we meet conflicts we need to either reset or commit our changes.
- Conflicts through `pop` will not drop a stash afterwards.
-## Git Stash
+## Git Stash sample workflow
1. Modify a file
1. Stage file
@@ -64,8 +64,6 @@ and we need to change to a different branch.
1. Apply with pop
1. View list to confirm changes
-## Commands
-
```sh
# Modify edit_this_file.rb file
git add .
diff --git a/doc/university/training/topics/tags.md b/doc/university/training/topics/tags.md
index cdbb8a2da7c..631b93cc384 100644
--- a/doc/university/training/topics/tags.md
+++ b/doc/university/training/topics/tags.md
@@ -11,18 +11,12 @@ type: reference
- Many projects combine an annotated release tag with a stable branch
- Consider setting deployment/release tags automatically
-# Tags
+## Tags sample workflow
- Create a lightweight tag
- Create an annotated tag
- Push the tags to the remote repository
-**Additional resources**
-
-<https://git-scm.com/book/en/Git-Basics-Tagging>
-
-# Commands
-
```sh
git checkout master
@@ -36,6 +30,10 @@ git tag
git push origin --tags
```
+**Additional resources**
+
+<https://git-scm.com/book/en/Git-Basics-Tagging>
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/university/training/topics/unstage.md b/doc/university/training/topics/unstage.md
index af16afdc5d1..d7482bf2bd5 100644
--- a/doc/university/training/topics/unstage.md
+++ b/doc/university/training/topics/unstage.md
@@ -4,29 +4,27 @@ comments: false
# Unstage
-## Unstage
+- To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This will unstage the file but maintain the modifications.
-- To remove files from stage use reset HEAD where HEAD is the last commit of the current branch. This will unstage the file but maintain the modifications.
-
- ```bash
- git reset HEAD <file>
- ```
+ ```bash
+ git reset HEAD <file>
+ ```
- To revert the file back to the state it was in before the changes we can use:
- ```bash
- git checkout -- <file>
- ```
+ ```bash
+ git checkout -- <file>
+ ```
- To remove a file from disk and repo use 'git rm' and to rm a dir use the '-r' flag:
- ```sh
- git rm '*.txt'
- git rm -r <dirname>
- ```
+ ```sh
+ git rm '*.txt'
+ git rm -r <dirname>
+ ```
- If we want to remove a file from the repository but keep it on disk, say we forgot to add it to our `.gitignore` file then use `--cache`:
- ```sh
- git rm <filename> --cache
- ```
+ ```sh
+ git rm <filename> --cache
+ ```
diff --git a/doc/update/README.md b/doc/update/README.md
index 974982da5d0..42c43110a19 100644
--- a/doc/update/README.md
+++ b/doc/update/README.md
@@ -135,6 +135,30 @@ If you need to downgrade your Enterprise Edition installation back to Community
Edition, you can follow [this guide][ee-ce] to make the process as smooth as
possible.
+## Version specific upgrading instructions
+
+### 12.2.0
+
+In 12.2.0, we enabled Rails' authenticated cookie encryption. Old sessions are
+automatically upgraded.
+
+However, session cookie downgrades are not supported. So after upgrading to 12.2.0,
+any downgrades would result to all sessions being invalidated and users are logged out.
+
+### 12.0.0
+
+In 12.0.0 we made various database related changes. These changes require that
+users first upgrade to the latest 11.11 patch release. Once upgraded to 11.11.x,
+users can upgrade to 12.x. Failure to do so may result in database migrations
+not being applied, which could lead to application errors.
+
+Example 1: you are currently using GitLab 11.11.3, which is the latest patch
+release for 11.11.x. You can upgrade as usual to 12.0.0, 12.1.0, etc.
+
+Example 2: you are currently using a version of GitLab 10.x. To upgrade, first
+upgrade to 11.11.3. Once upgraded to 11.11.3 you can safely upgrade to 12.0.0
+or future versions.
+
## Miscellaneous
- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 1e424134242..f6a1b6abdbf 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -72,15 +72,15 @@ need to enable the bundled PostgreSQL:
1. Stop GitLab:
- ```bash
- sudo gitlab-ctl stop
- ```
+ ```bash
+ sudo gitlab-ctl stop
+ ```
1. Edit `/etc/gitlab/gitlab.rb` to enable bundled PostgreSQL:
- ```
- postgresql['enable'] = true
- ```
+ ```
+ postgresql['enable'] = true
+ ```
1. Edit `/etc/gitlab/gitlab.rb` to use the bundled PostgreSQL. Please check
all the settings beginning with `db_`, such as `gitlab_rails['db_adapter']`
@@ -91,22 +91,22 @@ need to enable the bundled PostgreSQL:
for the changes to take effect.
1. Start Unicorn and PostgreSQL so that we can prepare the schema:
- ```bash
- sudo gitlab-ctl start unicorn
- sudo gitlab-ctl start postgresql
- ```
+ ```bash
+ sudo gitlab-ctl start unicorn
+ sudo gitlab-ctl start postgresql
+ ```
1. Run the following commands to prepare the schema:
- ```bash
- sudo gitlab-rake db:create db:migrate
- ```
+ ```bash
+ sudo gitlab-rake db:create db:migrate
+ ```
1. Stop Unicorn to prevent other database access from interfering with the loading of data:
- ```bash
- sudo gitlab-ctl stop unicorn
- ```
+ ```bash
+ sudo gitlab-ctl stop unicorn
+ ```
After these steps, you'll have a fresh PostgreSQL database with up-to-date schema.
@@ -116,57 +116,57 @@ new PostgreSQL one:
1. Save the following snippet in a `commands.load` file, and edit with your
MySQL database `username`, `password` and `host`:
- ```
- LOAD DATABASE
- FROM mysql://username:password@host/gitlabhq_production
- INTO postgresql://gitlab-psql@unix://var/opt/gitlab/postgresql:/gitlabhq_production
+ ```
+ LOAD DATABASE
+ FROM mysql://username:password@host/gitlabhq_production
+ INTO postgresql://gitlab-psql@unix://var/opt/gitlab/postgresql:/gitlabhq_production
- WITH include no drop, truncate, disable triggers, create no tables,
- create no indexes, preserve index names, no foreign keys,
- data only
+ WITH include no drop, truncate, disable triggers, create no tables,
+ create no indexes, preserve index names, no foreign keys,
+ data only
- ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public'
+ ALTER SCHEMA 'gitlabhq_production' RENAME TO 'public'
- ;
- ```
+ ;
+ ```
1. Start the migration:
- ```bash
- sudo -u gitlab-psql pgloader commands.load
- ```
+ ```bash
+ sudo -u gitlab-psql pgloader commands.load
+ ```
1. Once the migration finishes, you should see a summary table that looks like
the following:
- ```
- table name read imported errors total time
- ----------------------------------------------- --------- --------- --------- --------------
- fetch meta data 119 119 0 0.388s
- Truncate 119 119 0 1.134s
- ----------------------------------------------- --------- --------- --------- --------------
- public.abuse_reports 0 0 0 0.490s
- public.appearances 0 0 0 0.488s
- .
- .
- .
- public.web_hook_logs 0 0 0 1.080s
- ----------------------------------------------- --------- --------- --------- --------------
- COPY Threads Completion 4 4 0 2.008s
- Reset Sequences 113 113 0 0.304s
- Install Comments 0 0 0 0.000s
- ----------------------------------------------- --------- --------- --------- --------------
- Total import time 1894 1894 0 12.497s
- ```
-
- If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See
- the [troubleshooting guide](#troubleshooting) for more details.
+ ```
+ table name read imported errors total time
+ ----------------------------------------------- --------- --------- --------- --------------
+ fetch meta data 119 119 0 0.388s
+ Truncate 119 119 0 1.134s
+ ----------------------------------------------- --------- --------- --------- --------------
+ public.abuse_reports 0 0 0 0.490s
+ public.appearances 0 0 0 0.488s
+ .
+ .
+ .
+ public.web_hook_logs 0 0 0 1.080s
+ ----------------------------------------------- --------- --------- --------- --------------
+ COPY Threads Completion 4 4 0 2.008s
+ Reset Sequences 113 113 0 0.304s
+ Install Comments 0 0 0 0.000s
+ ----------------------------------------------- --------- --------- --------- --------------
+ Total import time 1894 1894 0 12.497s
+ ```
+
+ If there is no output for more than 30 minutes, it's possible `pgloader` encountered an error. See
+ the [troubleshooting guide](#troubleshooting) for more details.
1. Start GitLab:
- ```bash
- sudo gitlab-ctl start
- ```
+ ```bash
+ sudo gitlab-ctl start
+ ```
You can now verify that everything works as expected by visiting GitLab.
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index d3b0a3c2829..df35638cba2 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -47,8 +47,8 @@ sudo service gitlab stop
### 3. Update Ruby
-NOTE: Beginning in GitLab 11.6, we only support Ruby 2.5 or higher, and dropped
-support for Ruby 2.4. Be sure to upgrade if necessary.
+NOTE: Beginning in GitLab 12.2, we only support Ruby 2.6 or higher, and dropped
+support for Ruby 2.5. Be sure to upgrade if necessary.
You can check which version you are running with `ruby -v`.
@@ -378,20 +378,6 @@ Example:
Additional instructions here.
-->
-### 12.0.0
-
-In 12.0.0 we made various database related changes. These changes require that
-users first upgrade to the latest 11.11 patch release. Once upgraded to 11.11.x,
-users can upgrade to 12.x. Failure to do so may result in database migrations
-not being applied, which could lead to application errors.
-
-Example 1: you are currently using GitLab 11.11.3, which is the latest patch
-release for 11.11.x. You can upgrade as usual to 12.0.0, 12.1.0, etc.
-
-Example 2: you are currently using a version of GitLab 10.x. To upgrade, first
-upgrade to 11.11.3. Once upgraded to 11.11.3 you can safely upgrade to 12.0.0
-or future versions.
-
## Things went south? Revert to previous version
### 1. Revert the code to the previous version
diff --git a/doc/user/abuse_reports.md b/doc/user/abuse_reports.md
index 41ee7e62b2c..e6c86cc8f2e 100644
--- a/doc/user/abuse_reports.md
+++ b/doc/user/abuse_reports.md
@@ -1,6 +1,12 @@
# Abuse reports
-Report abuse from users to GitLab administrators.
+You can report abuse from other GitLab users to GitLab administrators.
+
+A GitLab administrator [can then choose](admin_area/abuse_reports.md) to:
+
+- Remove the user, which deletes them from the instance.
+- Block the user, which denies them access to the instance.
+- Or remove the report, which retains the users access to the instance.
You can report a user through their:
@@ -12,7 +18,8 @@ You can report a user through their:
To report abuse from a user's profile page:
-1. Click on the exclamation point report abuse button at the top right of the user's profile.
+1. Click on the exclamation point report abuse button at the top right of the
+ user's profile.
1. Complete an abuse report.
1. Click the **Send report** button.
@@ -26,15 +33,18 @@ To report abuse from a user's comment:
1. Click the **Send report** button.
NOTE: **Note:**
-A URL to the reported user's comment will be
-pre-filled in the abuse report's **Message** field.
+A URL to the reported user's comment will be pre-filled in the abuse report's
+**Message** field.
## Reporting abuse through a user's issue or merge request
The **Report abuse** button is displayed at the top right of the issue or merge request:
-- When **Report abuse** is selected from the menu that appears when the **Close issue** or **Close merge request** button is clicked, for users that have permission to close the issue or merge request.
-- When viewing the issue or merge request, for users that don't have permission to close the issue or merge request.
+- When **Report abuse** is selected from the menu that appears when the
+ **Close issue** or **Close merge request** button is clicked, for users that
+ have permission to close the issue or merge request.
+- When viewing the issue or merge request, for users that don't have permission
+ to close the issue or merge request.
With the **Report abuse** button displayed, to submit an abuse report:
diff --git a/doc/user/admin_area/abuse_reports.md b/doc/user/admin_area/abuse_reports.md
index 01c2d9607f5..0c5d2f81e25 100644
--- a/doc/user/admin_area/abuse_reports.md
+++ b/doc/user/admin_area/abuse_reports.md
@@ -1,31 +1,77 @@
+---
+type: reference, howto
+---
+
# Abuse reports
View and resolve abuse reports from GitLab users.
-Admins can view abuse reports in the admin area and are able to
-resolve the reports by removing the reported user, blocking the reported user, or removing the report.
+GitLab administrators can view and [resolve](#resolving-abuse-reports) abuse
+reports in the Admin Area.
## Reporting abuse
-To find out more about reporting abuse, see [abuse reports user documentation](../abuse_reports.md).
+To find out more about reporting abuse, see [abuse reports user
+documentation](../abuse_reports.md).
## Resolving abuse reports
-To access abuse reports, go to **Admin area > Abuse Reports**.
+To access abuse reports, go to **Admin Area > Abuse Reports**.
There are 3 ways to resolve an abuse report, with a button for each method:
-- Remove user & report: [Deletes the reported user](../profile/account/delete_account.md) from the instance and removes the abuse report from the list.
-- Block user: Blocks the reported user from the instance and does not remove the abuse report from the list.
-- Remove report: Removes the abuse report from the list and does not restrict the access for the reported user.
+- Remove user & report. This will:
+ - [Delete the reported user](../profile/account/delete_account.md) from the
+ instance.
+ - Remove the abuse report from the list.
+- [Block user](#blocking-users).
+- Remove report. This will:
+ - Remove the abuse report from the list.
+ - Remove access restrictions for the reported user.
+
+The following is an example of the **Abuse Reports** page:
![abuse-reports-page-image](img/abuse_reports_page.png)
-## Blocked users
+### Blocking users
+
+A blocked user cannot log in or access any repositories, but all of their data
+remains.
+
+Blocking a user:
+
+- Leaves them in the abuse report list.
+- Changes the **Block user** button to a disabled **Already blocked** button.
+
+The user will be notified with the
+[following message](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/workers/email_receiver_worker.rb#L38):
-Blocking a user will not remove the abuse report from the list.
+```text
+Your account has been blocked. If you believe this is in error, contact a staff member.
+```
-Instead, the block button will be disabled and explain that the user is "Already blocked".
-You are still able to remove the user and/or report if necessary.
+After blocking, you can still either:
+
+- Remove the user and report if necessary.
+- Remove the report.
+
+The following is an example of a blocked user listed on the **Abuse Reports**
+page:
![abuse-report-blocked-user-image](img/abuse_report_blocked_user.png)
+
+NOTE: **Note:**
+Users can be [blocked](../../api/users.md#block-user) and
+[unblocked](../../api/users.md#unblock-user) using the GitLab API.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/broadcast_messages.md b/doc/user/admin_area/broadcast_messages.md
index 02445abdb37..01b6558bdbe 100644
--- a/doc/user/admin_area/broadcast_messages.md
+++ b/doc/user/admin_area/broadcast_messages.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Broadcast Messages
GitLab can display messages to all users of a GitLab instance in a banner that appears in the UI.
@@ -51,3 +55,15 @@ Once deleted, the broadcast message is removed from the list of broadcast messag
NOTE: **Note:**
Broadcast messages can be deleted while active.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/custom_project_templates.md b/doc/user/admin_area/custom_project_templates.md
index 427f3103cfc..02c2efaa4f3 100644
--- a/doc/user/admin_area/custom_project_templates.md
+++ b/doc/user/admin_area/custom_project_templates.md
@@ -1,26 +1,49 @@
+---
+type: reference
+---
+
# Custom instance-level project templates **(PREMIUM ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6860) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.2.
-When you create a new [project](../project/index.md), creating it based on custom project templates is
-a convenient bootstrap option.
+GitLab administrators can configure the group where all the custom project
+templates are sourced.
-GitLab administrators can configure a GitLab group that serves as template
-source for an entire GitLab instance under **Admin area > Settings > Custom project templates**.
+Every project directly under the group namespace will be
+available to the user if they have access to them. For example:
+
+- Public project in the group will be available to every logged in user.
+- Private projects will be available only if the user is a member of the project.
+
+Repository and database information that are copied over to each new project are
+identical to the data exported with
+[GitLab's Project Import/Export](../project/settings/import_export.md).
NOTE: **Note:**
To set project templates at a group level,
see [Custom group-level project templates](../group/custom_project_templates.md).
-Within this section, you can configure the group where all the custom project
-templates are sourced. Every project directly under the group namespace will be
-available to the user if they have access to them. For example, every public
-project in the group will be available to every logged in user.
+## Configuring
-However, private projects will be available only if the user is a member of the project.
+GitLab administrators can configure a GitLab group that serves as template
+source for an entire GitLab instance by:
+
+1. Navigating to **Admin area > Settings > Templates**.
+1. Expanding **Custom project templates**.
+1. Selecting a group to use.
+1. Pressing **Save changes**.
NOTE: **Note:**
Projects below subgroups of the template group are **not** supported.
-Repository and database information that are copied over to each new project are
-identical to the data exported with [GitLab's Project Import/Export](../project/settings/import_export.md).
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/img/abuse_report_blocked_user.png b/doc/user/admin_area/img/abuse_report_blocked_user.png
index 0cb4c7bb8ac..435d8dfe821 100644
--- a/doc/user/admin_area/img/abuse_report_blocked_user.png
+++ b/doc/user/admin_area/img/abuse_report_blocked_user.png
Binary files differ
diff --git a/doc/user/admin_area/img/abuse_reports_page.png b/doc/user/admin_area/img/abuse_reports_page.png
index 81dbe976cda..30e932211cb 100644
--- a/doc/user/admin_area/img/abuse_reports_page.png
+++ b/doc/user/admin_area/img/abuse_reports_page.png
Binary files differ
diff --git a/doc/user/admin_area/img/broadcast_messages.png b/doc/user/admin_area/img/broadcast_messages.png
index 926d38ae049..f0ae92f8c17 100644
--- a/doc/user/admin_area/img/broadcast_messages.png
+++ b/doc/user/admin_area/img/broadcast_messages.png
Binary files differ
diff --git a/doc/user/admin_area/img/license_details.png b/doc/user/admin_area/img/license_details.png
index 2085bb437ad..3e980d9316d 100644
--- a/doc/user/admin_area/img/license_details.png
+++ b/doc/user/admin_area/img/license_details.png
Binary files differ
diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md
index f5e6bff67c5..9bc0f64b68d 100644
--- a/doc/user/admin_area/index.md
+++ b/doc/user/admin_area/index.md
@@ -272,7 +272,7 @@ The **Logs** page provides access to the following log files:
| Log file | Contents |
| :---------------------- | :------- |
| `application.log` | GitLab user activity |
-| `githost.log` | Failed GitLab interaction with Git repositories |
+| `git_json.log` | Failed GitLab interaction with Git repositories |
| `production.log` | Requests received from Unicorn, and the actions taken to serve those requests |
| `sidekiq.log` | Background jobs |
| `repocheck.log` | Repository activity |
diff --git a/doc/user/admin_area/license.md b/doc/user/admin_area/license.md
index bbd04146eb2..f5864e1f828 100644
--- a/doc/user/admin_area/license.md
+++ b/doc/user/admin_area/license.md
@@ -30,22 +30,22 @@ Otherwise, you can:
1. Navigate manually to the **Admin Area** by clicking the wrench icon in the menu bar.
- ![Admin area icon](img/admin_wrench.png)
+ ![Admin area icon](img/admin_wrench.png)
1. And then going to the **License** tab and click on **Upload New License**.
- ![License admin area](img/license_admin_area.png)
+ ![License admin area](img/license_admin_area.png)
1. If you've received a `.gitlab-license` file, you should have already downloaded
it in your local machine. You can then upload it directly by choosing the
license file and clicking the **Upload license** button. In the image below,
you can see that the selected license file is named `GitLab.gitlab-license`.
- ![Upload license](img/license_upload.png)
+ ![Upload license](img/license_upload.png)
- If you've received your license as plain text, you need to select the
- "Enter license key" option, copy the license, paste it into the "License key"
- field and click **Upload license**.
+ If you've received your license as plain text, you need to select the
+ "Enter license key" option, copy the license, paste it into the "License key"
+ field and click **Upload license**.
## Add your license at install time
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 7fbb4d84cfc..6faab685b26 100644
--- a/doc/user/admin_area/settings/account_and_limit_settings.md
+++ b/doc/user/admin_area/settings/account_and_limit_settings.md
@@ -4,6 +4,17 @@ type: reference
# Account and limit settings
+## Max attachment size
+
+You can change the maximum file size for attachments in comments and replies in GitLab.
+Navigate to **Admin Area (wrench icon) > Settings > General**, then expand **Account and Limit**.
+From here, you can increase or decrease by changing the value in `Maximum attachment size (MB)`.
+
+NOTE: **Note:**
+If you choose a size larger than what is currently configured for the web server,
+you will likely get errors. See the [troubleshooting section](#troubleshooting) for more
+details.
+
## Repository size limit **(STARTER)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/740) in [GitLab Enterprise Edition 8.12](https://about.gitlab.com/2016/09/22/gitlab-8-12-released/#limit-project-size-ee).
@@ -51,14 +62,18 @@ For details on manually purging files, see [reducing the repository size using G
NOTE: **Note:**
GitLab.com repository size [is set by GitLab](../../gitlab_com/index.md#repository-size-limit).
-<!-- ## Troubleshooting
+## Troubleshooting
+
+### 413 Request Entity Too Large
+
+If you are attaching a file to a comment or reply in GitLab and receive the `413 Request Entity Too Large`
+error, it is likely caused by having a [max attachment size](#max-attachment-size)
+larger than what the web server is configured to allow.
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+If you wanted to increase the max attachment size to 200m in a GitLab
+[Omnibus](https://docs.gitlab.com/omnibus/) install, for example, you might need to
+add the line below to `/etc/gitlab/gitlab.rb` before increasing the max attachment size:
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+```
+nginx['client_max_body_size'] = "200m"
+```
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index e56acac527f..bd76b052422 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -94,49 +94,6 @@ a group in the **Usage Quotas** page available to the group page settings list.
![Group pipelines quota](img/group_pipelines_quota.png)
-## Extra Shared Runners pipeline minutes quota **(FREE ONLY)**
-
-If you're using GitLab.com, you can purchase additional CI minutes so your
-pipelines will not be blocked after you have used all your CI minutes from your
-main quota.
-
-In order to purchase additional minutes, you should follow these steps:
-
-1. Go to **Group > Settings > Pipelines quota**. Once you are on that page, click on **Buy additional minutes**.
-
- ![Buy additional minutes](img/buy_btn.png)
-
-1. Locate the subscription card that is linked to your group on GitLab.com,
- click on **Buy more CI minutes**, and complete the details about the transaction.
-
- ![Buy additional minutes](img/buy_minutes_card.png)
-
-1. Once we have processed your payment, the extra CI minutes
- will be synced to your Group and you can visualize it from the
- **Group > Settings > Pipelines quota** page:
-
- ![Additional minutes](img/additional_minutes.png)
-
-Be aware that:
-
-1. If you have purchased extra CI minutes before the purchase of a paid plan,
- we will calculate a pro-rated charge for your paid plan. That means you may
- be charged for less than one year since your subscription was previously
- created with the extra CI minutes.
-1. Once the extra CI minutes has been assigned to a Group they cannot be transferred
- to a different Group.
-1. If you have some minutes used over your default quota, these minutes will
- be deducted from your Additional Minutes quota immediately after your purchase of additional
- minutes.
-
-## What happens when my CI minutes quota run out
-
-When the CI minutes quota run out, an email is sent automatically to notifies the owner(s) of the group/namespace which
-includes a link to [purchase more minutes](https://customers.gitlab.com/plans).
-
-If you are not the owner of the group, you will need to contact them to let them know they need to
-[purchase more minutes](https://customers.gitlab.com/plans).
-
## Archive jobs **(CORE ONLY)**
Archiving jobs is useful for reducing the CI/CD footprint on the system by
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 1f07a4dfdc6..490163c1816 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -17,11 +17,10 @@ The logo in the header of some emails can be customized, see the [logo customiza
The additional text will appear at the bottom of any email and can be used for
legal/auditing/compliance reasons.
-1. Go to **Admin area > Settings** (`/admin/application_settings`).
-1. Under the **Email** section, change the **Additional text** field.
-1. Hit **Save** for the changes to take effect.
-
-![Admin email settings](img/email_settings.png)
+1. Go to **Admin Area > Settings > Preferences** (`/admin/application_settings/preferences`).
+1. Expand the **Email** section.
+1. Enter your text in the **Additional text** field.
+1. Click **Save**.
[ee-5031]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5031
[eep]: https://about.gitlab.com/pricing/
@@ -35,11 +34,13 @@ This configuration option sets the email hostname for [private commit emails](..
In order to change this option:
-1. Go to **Admin area > Settings** (`/admin/application_settings`).
-1. Under the **Email** section, change the **Custom hostname (for private commit emails)** field.
-1. Hit **Save** for the changes to take effect.
+1. Go to **Admin Area > Settings > Preferences** (`/admin/application_settings/preferences`).
+1. Expand the **Email** section.
+1. Enter the desire hostname in the **Custom hostname (for private commit emails)** field.
+1. Click **Save changes**.
-NOTE: **Note**: Once the hostname gets configured, every private commit email using the previous hostname, will not get
+NOTE: **Note:**
+Once the hostname gets configured, every private commit email using the previous hostname, will not get
recognized by GitLab. This can directly conflict with certain [Push rules](../../../push_rules/push_rules.md) such as
`Check whether author is a GitLab user` and `Check whether committer is the current authenticated user`.
@@ -53,4 +54,4 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/admin_area/settings/img/additional_minutes.png b/doc/user/admin_area/settings/img/additional_minutes.png
deleted file mode 100644
index d148ed79b92..00000000000
--- a/doc/user/admin_area/settings/img/additional_minutes.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/admin_required_pipeline.png b/doc/user/admin_area/settings/img/admin_required_pipeline.png
index 58488674d51..501b1e3ba0a 100644
--- a/doc/user/admin_area/settings/img/admin_required_pipeline.png
+++ b/doc/user/admin_area/settings/img/admin_required_pipeline.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/buy_btn.png b/doc/user/admin_area/settings/img/buy_btn.png
deleted file mode 100644
index 0cc88b8a48f..00000000000
--- a/doc/user/admin_area/settings/img/buy_btn.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/buy_minutes_card.png b/doc/user/admin_area/settings/img/buy_minutes_card.png
deleted file mode 100644
index cf4ad34ead7..00000000000
--- a/doc/user/admin_area/settings/img/buy_minutes_card.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/email_confirmation.png b/doc/user/admin_area/settings/img/email_confirmation.png
new file mode 100644
index 00000000000..987aa10c3ce
--- /dev/null
+++ b/doc/user/admin_area/settings/img/email_confirmation.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/email_settings.png b/doc/user/admin_area/settings/img/email_settings.png
deleted file mode 100644
index ed0a80d10ce..00000000000
--- a/doc/user/admin_area/settings/img/email_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png b/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png
new file mode 100644
index 00000000000..c32eb93c8a8
--- /dev/null
+++ b/doc/user/admin_area/settings/img/rate_limits_on_raw_endpoints.png
Binary files differ
diff --git a/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png
new file mode 100644
index 00000000000..53dc0e4ac87
--- /dev/null
+++ b/doc/user/admin_area/settings/img/user_and_ip_rate_limits.png
Binary files differ
diff --git a/doc/user/admin_area/settings/index.md b/doc/user/admin_area/settings/index.md
index 5427d04cd7d..2a12614e325 100644
--- a/doc/user/admin_area/settings/index.md
+++ b/doc/user/admin_area/settings/index.md
@@ -18,6 +18,7 @@ include:
- [Third party offers](third_party_offers.md)
- [Usage statistics](usage_statistics.md)
- [Visibility and access controls](visibility_and_access_controls.md)
+- [User and IP rate limits](user_and_ip_rate_limits.md)
- [Custom templates repository](instance_template_repository.md) **(PREMIUM)**
NOTE: **Note:**
diff --git a/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
new file mode 100644
index 00000000000..8e53a6995fb
--- /dev/null
+++ b/doc/user/admin_area/settings/rate_limits_on_raw_endpoints.md
@@ -0,0 +1,22 @@
+---
+type: reference
+---
+
+# Rate limits on raw endpoints **(CORE ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30829) in GitLab 12.2.
+
+This setting allows you to rate limit the requests to raw endpoints, defaults to `300` requests per minute.
+It can be modified in **Admin Area > Network > Performance Optimization**.
+
+For example, requests over `300` per minute to `https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/controllers/application_controller.rb` will be blocked. Access to the raw file will be released after 1 minute.
+
+![Rate limits on raw endpoints](img/rate_limits_on_raw_endpoints.png)
+
+This limit is:
+
+- Applied independently per project, per commit and per file path.
+- Not applied per IP address.
+- Active by default. To disable, set the option to `0`.
+
+Requests over the rate limit are logged into `auth.log`.
diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md
index d77e91156f8..1b1bcbcd6e8 100644
--- a/doc/user/admin_area/settings/sign_up_restrictions.md
+++ b/doc/user/admin_area/settings/sign_up_restrictions.md
@@ -4,19 +4,26 @@ type: reference
# Sign-up restrictions
-By implementing sign-up restrictions, you can blacklist or whitelist email addresses
-belonging to specific domains.
+You can use sign-up restrictions to require user email confirmation, as well as
+to blacklist or whitelist email addresses belonging to specific domains.
>**Note**: These restrictions are only applied during sign-up. An admin is
able to add a user through the admin panel with a disallowed domain. Also
note that the users can change their email addresses after signup to
disallowed domains.
+## Require email confirmation
+
+You can send confirmation emails during sign-up and require that users confirm
+their email address before they are allowed to sign in.
+
+![Email confirmation](img/email_confirmation.png)
+
## Whitelist email domains
> [Introduced][ce-598] in GitLab 7.11.0
-You can restrict users to only signup using email addresses matching the given
+You can restrict users to only sign up using email addresses matching the given
domains list.
## Blacklist email domains
@@ -24,7 +31,9 @@ domains list.
> [Introduced][ce-5259] in GitLab 8.10.
With this feature enabled, you can block email addresses of a specific domain
-from creating an account on your GitLab server. This is particularly useful to prevent spam. Disposable email addresses are usually used by malicious users to create dummy accounts and spam issues.
+from creating an account on your GitLab server. This is particularly useful
+to prevent malicious users from creating spam accounts with disposable email
+addresses.
## Settings
@@ -33,10 +42,10 @@ To access this feature:
1. Navigate to the **Settings > General** in the Admin area.
1. Expand the **Sign-up restrictions** section.
-For the:
+For the blacklist, you can enter the list manually or upload a `.txt` file that
+contains list entries.
-- Blacklist, you can enter the list manually, or upload a `.txt` file with it.
-- Whitelist you must enter the list manually.
+For the whitelist, you must enter the list manually.
Both the whitelist and blacklist accept wildcards. For example, you can use
`*.company.com` to accept every `company.com` subdomain, or `*.io` to block all
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index f698e0a1608..516600c9d99 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -52,8 +52,8 @@ You can view the exact JSON payload in the administration panel. To view the pay
1. Expand **Settings** in the left sidebar and click on **Metrics and profiling**.
1. Expand **Usage statistics** and click on the **Preview payload** button.
-You can see how [the usage ping data maps to different stages of the product](https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv).
-
+You can see how [the usage ping data maps to different stages of the product](https://gitlab.com/gitlab-data/analytics/blob/master/transform/snowflake-dbt/data/ping_metrics_to_stage_mapping_data.csv).
+
### Deactivate the usage ping
The usage ping is opt-out. If you want to deactivate this feature, go to
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
new file mode 100644
index 00000000000..e3a495750f2
--- /dev/null
+++ b/doc/user/admin_area/settings/user_and_ip_rate_limits.md
@@ -0,0 +1,32 @@
+---
+type: reference
+---
+
+# User and IP rate limits
+
+Rate limiting is a common technique used to improve the security and durability
+of a web application. For more details, see
+[Rate limits](../../../security/rate_limits.md).
+
+The following limits can be enforced in **Admin Area > Network > User and
+IP rate limits**:
+
+- Unauthenticated requests
+- Authenticated API requests
+- Authenticated web requests
+
+These limits are disabled by default.
+
+![user-and-ip-rate-limits](img/user_and_ip_rate_limits.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
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/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index da75684a3fe..86491c7d74e 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Container Scanning **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3672)
@@ -47,7 +51,7 @@ To enable Container Scanning in your pipeline, you need:
your Docker image to your project's [Container Registry](../../project/container_registry.md).
The name of the Docker image should match the following scheme:
- ```
+ ```text
$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA
```
@@ -114,7 +118,7 @@ When the GitLab Runner uses the Docker executor and NFS is used
(e.g., `/var/lib/docker` is on an NFS mount), Container Scanning might fail with
an error like the following:
-```
+```text
docker: Error response from daemon: failed to copy xattrs: failed to set xattr "security.selinux" on /path/to/file: operation not supported.
```
diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md
index a0a917c5ebd..fa84f995e58 100644
--- a/doc/user/application_security/dast/index.md
+++ b/doc/user/application_security/dast/index.md
@@ -1,8 +1,17 @@
+---
+type: reference, howto
+---
+
# Dynamic Application Security Testing (DAST) **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/4348)
in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.4.
+NOTE: **4 of the top 6 attacks were application based.**
+Download our whitepaper,
+["A Seismic Shift in Application Security"](https://about.gitlab.com/resources/whitepaper-seismic-shift-application-security/)
+to learn how to protect your organization.
+
Running [static checks](../sast/index.md) on your code is the first step to detect
vulnerabilities that can put the security of your code at risk. Yet, once
deployed, your application is exposed to a new category of possible attacks,
@@ -162,6 +171,28 @@ As the DAST job belongs to a separate `dast` stage that runs after all
[default stages](../../../ci/yaml/README.md#stages),
don't forget to add `stage: dast` when you override the template job definition.
+## Available variables
+
+DAST can be [configured](#customizing-the-dast-settings) using environment variables.
+Since it's a wrapper around the ZAP scanning scripts
+([baseline](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan)
+or [full](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) scan), it
+accepts all arguments those scripts recognize (the arguments are the same).
+The choice of the scan type depends on the `DAST_FULL_SCAN_ENABLED` environment
+variable value.
+
+| Environment variable | Required | Description |
+|-----------------------------| ----------|--------------------------------------------------------------------------------|
+| `DAST_WEBSITE` | yes | The URL of the website to scan. |
+| `DAST_AUTH_URL` | no | The authentication URL of the website to scan. |
+| `DAST_USERNAME` | no | The username to authenticate to in the website. |
+| `DAST_PASSWORD` | no | The password to authenticate to in the website. |
+| `DAST_USERNAME_FIELD` | no | The name of username field at the sign-in HTML form. |
+| `DAST_PASSWORD_FIELD` | no | The name of password field at the sign-in HTML form. |
+| `DAST_AUTH_EXCLUDE_URLS` | no | The URLs to skip during the authenticated scan; comma-separated, no spaces in between. |
+| `DAST_TARGET_AVAILABILITY_TIMEOUT` | no | Time limit in seconds to wait for target availability. Scan is attempted nevertheless if it runs out. Integer. Defaults to `60`. |
+| `DAST_FULL_SCAN_ENABLED` | no | Switches the tool to execute [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Boolean. `true`, `True`, or `1` are considered as true value, otherwise false. Defaults to `false`. |
+
## Security Dashboard
The Security Dashboard is a good place to get an overview of all the security
@@ -177,3 +208,15 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png b/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png
new file mode 100644
index 00000000000..af9cee08d71
--- /dev/null
+++ b/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png
Binary files differ
diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md
new file mode 100644
index 00000000000..38c38bbd8a9
--- /dev/null
+++ b/doc/user/application_security/dependency_list/index.md
@@ -0,0 +1,49 @@
+# Dependency List **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
+The Dependency list allows you to see your project's dependencies, and key
+details about them, including their known vulnerabilities. To see it,
+navigate to **Security & Compliance > Dependency List** in your project's
+sidebar.
+
+## Requirements
+
+1. The [Dependency Scanning](../dependency_scanning/index.md) CI job must be
+ configured for your project.
+1. Your project uses at least one of the
+ [languages and package managers](../dependency_scanning/index.md#supported-languages-and-package-managers)
+ supported by Gemnasium.
+
+## Viewing dependencies
+
+![Dependency List](img/dependency_list_v12_2.png)
+
+Dependencies are displayed with the following information:
+
+| Field | Description |
+| --------- | ----------- |
+| Status | Displays whether or not the dependency has any known vulnerabilities |
+| Component | The dependency's name |
+| Version | The exact locked version of the dependency your project uses |
+| Packager | The packager used to install the depedency |
+| Location | A link to the packager-specific lockfile in your project that declared the dependency |
+
+Dependencies shown are initially sorted by their names. They can also be sorted
+by the packager they were installed by, or by the severity of their known
+vulnerabilities.
+
+There is a second list under the `Vulnerable components` tab displaying only
+those dependencies with known vulnerabilities. If there are none, this tab is
+disabled.
+
+### Vulnerabilities
+
+If a dependency has known vulnerabilities, they can be viewed by clicking on the
+`Status` cell of that dependency. The severity and description of each
+vulnerability will then be displayed below it.
+
+## Downloading the Dependency List
+
+Your project's full list of dependencies and their details can be downloaded in
+`JSON` format by clicking on the download button.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 09bd306363c..3148ec63c79 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Dependency Scanning **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5105)
@@ -48,14 +52,15 @@ The following languages and dependency managers are supported.
| Language (package managers) | Supported | Scan tool(s) |
|----------------------------- | --------- | ------------ |
+| Java ([Gradle](https://gradle.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/13075 "Dependency Scanning for Gradle" )) | not available |
+| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| JavaScript ([npm](https://www.npmjs.com/), [yarn](https://yarnpkg.com/en/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [Retire.js](https://retirejs.github.io/retire.js) |
+| Go ([Golang](https://golang.org/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
+| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
| Python ([pip](https://pip.pypa.io/en/stable/)) (only `requirements.txt` supported) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
+| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
+| Python ([poetry](https://poetry.eustace.io/)) | not currently ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
| Ruby ([gem](https://rubygems.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium), [bundler-audit](https://github.com/rubysec/bundler-audit) |
-| Java ([Maven](https://maven.apache.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
-| PHP ([Composer](https://getcomposer.org/)) | yes | [gemnasium](https://gitlab.com/gitlab-org/security-products/gemnasium) |
-| Python ([poetry](https://poetry.eustace.io/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7006 "Support Poetry in Dependency Scanning")) | not available |
-| Python ([Pipfile](https://docs.pipenv.org/en/latest/basics/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/11756 "Pipfile.lock support for Dependency Scanning"))| not available |
-| Go ([Golang](https://golang.org/)) | no ([issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/7132 "Dependency Scanning for Go")) | not available |
## Remote checks
@@ -142,6 +147,7 @@ using environment variables.
| `DS_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the official default images (proxy). Read more about [customizing analyzers](analyzers.md). |
| `DS_ANALYZER_IMAGE_TAG` | Override the Docker tag of the official default images. Read more about [customizing analyzers](analyzers.md). |
| `DS_PYTHON_VERSION` | Version of Python. If set to 2, dependencies are installed using Python 2.7 instead of Python 3.6. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12296) in GitLab 12.1)|
+| `DS_PIP_DEPENDENCY_PATH` | Path to load Python pip dependencies from. ([Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12412) in GitLab 12.2) |
| `DS_DEFAULT_ANALYZERS` | Override the names of the official default images. Read more about [customizing analyzers](analyzers.md). |
| `DS_DISABLE_REMOTE_CHECKS` | Do not send any data to GitLab. Used in the [Gemnasium analyzer](#remote-checks). |
| `DS_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to `0` to disable). |
@@ -149,7 +155,7 @@ using environment variables.
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
-| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). |
+| `PIP_INDEX_URL` | Base URL of Python Package Index (default `https://pypi.org/simple`). |
| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
## Reports JSON format
@@ -321,16 +327,11 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
-## Dependency List
+## Dependency List **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
-
-An additional benefit of Dependency Scanning is the ability to get a list of your
-project's dependencies with their versions. This list can be generated only for
-[languages and package managers](#supported-languages-and-package-managers)
-supported by Gemnasium.
-
-To see the generated dependency list, navigate to your project's **Project > Dependency List**.
+An additional benefit of Dependency Scanning is the ability to view your
+project's dependencies and their known vulnerabilities. Read more about
+the [Dependency List](../dependency_list/index.md).
## Versioning and release process
@@ -341,3 +342,15 @@ Please check the [Release Process documentation](https://gitlab.com/gitlab-org/s
You can search the [gemnasium-db](https://gitlab.com/gitlab-org/security-products/gemnasium-db) project
to find a vulnerability in the Gemnasium database.
You can also [submit new vulnerabilities](https://gitlab.com/gitlab-org/security-products/gemnasium-db/blob/master/CONTRIBUTING.md).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/img/create_issue_with_list_hover.png b/doc/user/application_security/img/create_issue_with_list_hover.png
index 7d70e8299f5..4c38862e68f 100644
--- a/doc/user/application_security/img/create_issue_with_list_hover.png
+++ b/doc/user/application_security/img/create_issue_with_list_hover.png
Binary files differ
diff --git a/doc/user/application_security/img/dismissed_info.png b/doc/user/application_security/img/dismissed_info.png
index 64d5cf26ed2..b4470b664d2 100644
--- a/doc/user/application_security/img/dismissed_info.png
+++ b/doc/user/application_security/img/dismissed_info.png
Binary files differ
diff --git a/doc/user/application_security/img/interactive_reports.png b/doc/user/application_security/img/interactive_reports.png
index 373b39104db..1b2ef0d3da9 100644
--- a/doc/user/application_security/img/interactive_reports.png
+++ b/doc/user/application_security/img/interactive_reports.png
Binary files differ
diff --git a/doc/user/application_security/img/vulnerability_solution.png b/doc/user/application_security/img/vulnerability_solution.png
index 7443b9b6eea..d86b89a5f99 100644
--- a/doc/user/application_security/img/vulnerability_solution.png
+++ b/doc/user/application_security/img/vulnerability_solution.png
Binary files differ
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 91e79f6c23b..83ea0ea3386 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -1,10 +1,22 @@
+---
+type: reference, howto
+---
+
# GitLab Secure **(ULTIMATE)**
-Check your application for security vulnerabilities that may lead to unauthorized access,
-data leaks, and denial of services. GitLab will perform static and dynamic tests on the
-code of your application, looking for known flaws and report them in the merge request
-so you can fix them before merging. Security teams can use dashboards to get a
-high-level view on projects and groups, and start remediation processes when needed.
+Check your application for security vulnerabilities that may lead to
+unauthorized access, data leaks, and denial of services.
+
+GitLab will perform static and dynamic tests on the code of your application,
+looking for known flaws and report them in the merge request so you can fix
+them before merging.
+
+Security teams can use dashboards to get a high-level view on projects and
+groups, and start remediation processes when needed.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview of application security with GitLab, see
+[Security Deep Dive](https://www.youtube.com/watch?v=k4vEJnGYy84).
## Security scanning tools
@@ -13,6 +25,7 @@ GitLab can scan and report any vulnerabilities found in your project.
| Secure scanning tool | Description |
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](container_scanning/index.md) **(ULTIMATE)** | Scan Docker containers for known vulnerabilities. |
+| [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. |
@@ -54,7 +67,7 @@ Each security vulnerability in the merge request report or the
entry, a detailed information will pop up with different possible options:
- [Dismiss vulnerability](#dismissing-a-vulnerability): Dismissing a vulnerability
- will place a <s>strikethrough</s> styling on it.
+ will place a ~~strikethrough~~ styling on it.
- [Create issue](#creating-an-issue-for-a-vulnerability): The new issue will
have the title and description pre-populated with the information from the
vulnerability report and will be created as [confidential](../project/issues/confidential_issues.md) by default.
@@ -115,16 +128,16 @@ generated by GitLab. To apply the fix:
1. Click on the vulnerability.
1. Download and review the patch file `remediation.patch`.
-2. Ensure your local project has the same commit checked out that was used to generate the patch.
-3. Run `git apply remediation.patch`.
-4. Verify and commit the changes to your branch.
+1. Ensure your local project has the same commit checked out that was used to generate the patch.
+1. Run `git apply remediation.patch`.
+1. Verify and commit the changes to your branch.
![Apply patch for dependency scanning](img/vulnerability_solution.png)
#### Creating a merge request from a vulnerability
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9224) in
- [GitLab Ultimate](https://about.gitlab.com/pricing) 11.9.
+> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.9.
In certain cases, GitLab will allow you to create a merge request that will
automatically remediate the vulnerability. Any vulnerability that has a
@@ -135,3 +148,47 @@ If this action is available there will be a **Create merge request** button in t
Clicking on this button will create a merge request to apply the solution onto the source branch.
![Create merge request from vulnerability](img/create_issue_with_list_hover.png)
+
+## Security approvals in merge requests **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9928) in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
+
+Merge Request Approvals can be configured to require approval from a member
+of your security team when a vulnerability would be introduced by a merge request.
+
+This threshold is defined as `high`, `critical`, or `unknown`
+severity. When any vulnerabilities are present within a merge request, an
+approval will be required from the `Vulnerability-Check` approver group.
+
+### Enabling Security Approvals within a project
+
+To enable Security Approvals, a [project approval rule](../project/merge_requests/merge_request_approvals.md#multiple-approval-rules-premium)
+must be created with the case-sensitive name `Vulnerability-Check`. This approval
+group must be set with an "Approvals required" count greater than zero.
+
+Once this group has been added to your project, the approval rule will be enabled
+for all Merge Requests.
+
+Any code changes made will cause the count of approvals required to reset.
+
+An approval will be required when a security report:
+
+- Contains a new vulnerability of `high`, `critical`, or `unknown` severity.
+- Is not generated during pipeline execution.
+
+An approval will be optional when a security report:
+
+- Contains no new vulnerabilities.
+- Contains only new vulnerabilities of `low` or `medium` severity.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/license_management/img/license_management_add_license.png b/doc/user/application_security/license_management/img/license_management_add_license.png
index 1e1a698515b..c9a5dc14c57 100644
--- a/doc/user/application_security/license_management/img/license_management_add_license.png
+++ b/doc/user/application_security/license_management/img/license_management_add_license.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_decision.png b/doc/user/application_security/license_management/img/license_management_decision.png
index 0763130c375..fbf90bec7fd 100644
--- a/doc/user/application_security/license_management/img/license_management_decision.png
+++ b/doc/user/application_security/license_management/img/license_management_decision.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_search.png b/doc/user/application_security/license_management/img/license_management_search.png
index 7b6006cef9d..b3ffd8d95a1 100644
--- a/doc/user/application_security/license_management/img/license_management_search.png
+++ b/doc/user/application_security/license_management/img/license_management_search.png
Binary files differ
diff --git a/doc/user/application_security/license_management/img/license_management_settings.png b/doc/user/application_security/license_management/img/license_management_settings.png
index 1a2bfa78a03..2e3e8888e93 100644
--- a/doc/user/application_security/license_management/img/license_management_settings.png
+++ b/doc/user/application_security/license_management/img/license_management_settings.png
Binary files differ
diff --git a/doc/user/application_security/license_management/index.md b/doc/user/application_security/license_management/index.md
index b0eb753938b..c324848c703 100644
--- a/doc/user/application_security/license_management/index.md
+++ b/doc/user/application_security/license_management/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# License Management **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5483)
@@ -227,3 +231,15 @@ pipeline ID that has a `license_management` job to see the Licenses tab with the
licenses (if any).
![License Management Pipeline Tab](img/license_management_pipeline_tab.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/sast/analyzers.md b/doc/user/application_security/sast/analyzers.md
new file mode 100644
index 00000000000..f730a25a9fc
--- /dev/null
+++ b/doc/user/application_security/sast/analyzers.md
@@ -0,0 +1,144 @@
+---
+table_display_block: true
+---
+
+# SAST Analyzers **(ULTIMATE)**
+
+SAST relies on underlying third party tools that are wrapped into what we call
+"Analyzers". An analyzer is a
+[dedicated project](https://gitlab.com/gitlab-org/security-products/analyzers)
+that wraps a particular tool to:
+
+- Expose its detection logic.
+- Handle its execution.
+- Convert its output to the common format.
+
+This is achieved by implementing the [common API](https://gitlab.com/gitlab-org/security-products/analyzers/common).
+
+SAST supports the following official analyzers:
+
+- [`bandit`](https://gitlab.com/gitlab-org/security-products/analyzers/bandit) (Bandit)
+- [`brakeman`](https://gitlab.com/gitlab-org/security-products/analyzers/brakeman) (Brakeman)
+- [`eslint`](https://gitlab.com/gitlab-org/security-products/analyzers/eslint) (ESLint (Javascript))
+- [`flawfinder`](https://gitlab.com/gitlab-org/security-products/analyzers/flawfinder) (Flawfinder)
+- [`gosec`](https://gitlab.com/gitlab-org/security-products/analyzers/gosec) (Gosec)
+- [`nodejs-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/nodejs-scan) (NodeJsScan)
+- [`phpcs-security-audit`](https://gitlab.com/gitlab-org/security-products/analyzers/phpcs-security-audit) (PHP CS security-audit)
+- [`pmd-apex`](https://gitlab.com/gitlab-org/security-products/analyzers/pmd-apex) (PMD (Apex only))
+- [`secrets`](https://gitlab.com/gitlab-org/security-products/analyzers/secrets) (Secrets (Gitleaks, TruffleHog & Diffence secret detectors))
+- [`security-code-scan`](https://gitlab.com/gitlab-org/security-products/analyzers/security-code-scan) (Security Code Scan (.NET))
+- [`sobelow`](https://gitlab.com/gitlab-org/security-products/analyzers/sobelow) (Sobelow (Elixir Phoenix))
+- [`spotbugs`](https://gitlab.com/gitlab-org/security-products/analyzers/spotbugs) (SpotBugs with the Find Sec Bugs plugin (Ant, Gradle and wrapper, Grails, Maven and wrapper, SBT))
+- [`tslint`](https://gitlab.com/gitlab-org/security-products/analyzers/tslint) (TSLint (Typescript))
+
+The analyzers are published as Docker images that SAST will use to launch
+dedicated containers for each analysis.
+
+SAST is pre-configured with a set of **default images** that are maintained by
+GitLab, but users can also integrate their own **custom images**.
+
+## Official default analyzers
+
+Any custom change to the official analyzers can be achieved by using an
+[environment variable in your `.gitlab-ci.yml`](index.md#customizing-the-sast-settings).
+
+### Using a custom Docker mirror
+
+You can switch to a custom Docker registry that provides the official analyzer
+images under a different prefix. For instance, the following instructs
+SAST to pull `my-docker-registry/gl-images/bandit`
+instead of `registry.gitlab.com/gitlab-org/security-products/analyzers/bandit`.
+In `.gitlab-ci.yml` define:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+variables:
+ SAST_ANALYZER_IMAGE_PREFIX: my-docker-registry/gl-images
+```
+
+This configuration requires that your custom registry provides images for all
+the official analyzers.
+
+### Selecting specific analyzers
+
+You can select the official analyzers you want to run. Here's how to enable
+`bandit` and `flawfinder` while disabling all the other default ones.
+In `.gitlab-ci.yml` define:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+variables:
+ SAST_DEFAULT_ANALYZERS: "bandit,flawfinder"
+```
+
+`bandit` runs first. When merging the reports, SAST will
+remove the duplicates and will keep the `bandit` entries.
+
+### Disabling default analyzers
+
+Setting `SAST_DEFAULT_ANALYZERS` to an empty string will disable all the official
+default analyzers. In `.gitlab-ci.yml` define:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+variables:
+ SAST_DEFAULT_ANALYZERS: ""
+```
+
+That's needed when one totally relies on [custom analyzers](#custom-analyzers).
+
+## Custom Analyzers
+
+You can provide your own analyzers as a comma separated list of Docker images.
+Here's how to add `analyzers/csharp` and `analyzers/perl` to the default images:
+In `.gitlab-ci.yml` define:
+
+```yaml
+include:
+ template: SAST.gitlab-ci.yml
+
+variables:
+ SAST_ANALYZER_IMAGES: "my-docker-registry/analyzers/csharp,amy-docker-registry/analyzers/perl"
+```
+
+The values must be the full path to the container registry images,
+like what you would feed to the `docker pull` command.
+
+NOTE: **Note:**
+This configuration doesn't benefit from the integrated detection step.
+SAST has to fetch and spawn each Docker image to establish whether the
+custom analyzer can scan the source code.
+
+## Analyzers Data
+
+| Property \ Tool | Apex | Bandit | Brakeman | ESLint security | Find Sec Bugs | Flawfinder | Go AST Scanner | NodeJsScan | Php CS Security Audit | Security code Scan (.NET) | TSLint Security | Sobelow |
+| --------------------------------------- | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :------------------: | :---------------------: | :-------------------------: | :-------------: | :----------------: |
+| Severity | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | 𐄂 |
+| Title | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Description | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | ✓ | 𐄂 | 𐄂 | ✓ | ✓ |
+| File | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| Start line | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
+| End line | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
+| Start column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 |
+| End column | ✓ | 𐄂 | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ | 𐄂 |
+| External id (e.g. CVE) | 𐄂 | 𐄂 | ⚠ | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| URLs | ✓ | 𐄂 | ✓ | 𐄂 | ⚠ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| Internal doc/explanation | ✓ | ⚠ | ✓ | 𐄂 | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
+| Solution | ✓ | 𐄂 | 𐄂 | 𐄂 | ⚠ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| Confidence | 𐄂 | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | ✓ |
+| Affected item (e.g. class or package) | ✓ | 𐄂 | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| Source code extract | 𐄂 | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | 𐄂 | 𐄂 | 𐄂 | 𐄂 | 𐄂 |
+| Internal ID | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | 𐄂 | ✓ | ✓ | ✓ | ✓ |
+
+- ✓ => we have that data
+- ⚠ => we have that data but it's partially reliable, or we need to extract it from unstructured content
+- 𐄂 => we don't have that data or it would need to develop specific or inefficient/unreliable logic to obtain it.
+
+The values provided by these tools are heterogeneous so they are sometimes
+normalized into common values (e.g., `severity`, `confidence`, etc).
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index 1f9fd9d4e18..2f15d997b5b 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Static Application Security Testing (SAST) **(ULTIMATE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3775)
@@ -55,6 +59,7 @@ The following table shows which languages, package managers and frameworks are s
|-----------------------------------------------------------------------------|----------------------------------------------------------------------------------------|------------------------------|
| .NET | [Security Code Scan](https://security-code-scan.github.io) | 11.0 |
| Any | [Gitleaks](https://github.com/zricethezav/gitleaks) and [TruffleHog](https://github.com/dxa4481/truffleHog) | 11.9 |
+| Apex (Salesforce) | [pmd](https://pmd.github.io/pmd/index.html) | 12.1 |
| C/C++ | [Flawfinder](https://www.dwheeler.com/flawfinder/) | 10.7 |
| Elixir (Phoenix) | [Sobelow](https://github.com/nccgroup/sobelow) | 11.10 |
| Go | [Gosec](https://github.com/securego/gosec) | 10.7 |
@@ -135,6 +140,58 @@ sast:
CI_DEBUG_TRACE: "true"
```
+### Available variables
+
+SAST can be [configured](#customizing-the-sast-settings) using environment variables.
+
+#### Docker images
+
+The following are Docker image-related variables.
+
+| Environment variable | Description |
+|-------------------------------|--------------------------------------------------------------------------------|
+| `SAST_ANALYZER_IMAGES` | Comma separated list of custom images. Default images are still enabled. Read more about [customizing analyzers](analyzers.md). |
+| `SAST_ANALYZER_IMAGE_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). |
+| `SAST_ANALYZER_IMAGE_TAG` | Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md). |
+| `SAST_DEFAULT_ANALYZERS` | Override the names of default images. Read more about [customizing analyzers](analyzers.md). |
+| `SAST_PULL_ANALYZER_IMAGES` | Pull the images from the Docker registry (set to 0 to disable). Read more about [customizing analyzers](analyzers.md). |
+
+### Vulnerability filters
+
+Some analyzers make it possible to filter out vulnerabilities under a given threshold.
+
+| `SAST_BANDIT_EXCLUDED_PATHS` | - | comma-separated list of paths to exclude from scan. Uses Python's [`fnmatch` syntax](https://docs.python.org/2/library/fnmatch.html) |
+| `SAST_BRAKEMAN_LEVEL` | 1 | Ignore Brakeman vulnerabilities under given confidence level. Integer, 1=Low 3=High. |
+| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
+| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
+| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
+| `SAST_EXCLUDED_PATHS` | - | Exclude vulnerabilities from output based on the paths. This is a comma-separated list of patterns. Patterns can be globs, file or folder paths. Parent directories will also match patterns. |
+
+### Timeouts
+
+The following variables configure timeouts.
+
+| `SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | 2m | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m". |
+| `SAST_PULL_ANALYZER_IMAGE_TIMEOUT` | 5m | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m". |
+| `SAST_RUN_ANALYZER_TIMEOUT` | 20m | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". For example, "300ms", "1.5h" or "2h45m".|
+
+### Analyzer settings
+
+Some analyzers can be customized with environment variables.
+
+| Environment variable | Analyzer | Description |
+|-------------------------|----------|----------|
+| `ANT_HOME` | spotbugs | The `ANT_HOME` environment variable. |
+| `ANT_PATH` | spotbugs | Path to the `ant` executable. |
+| `GRADLE_PATH` | spotbugs | Path to the `gradle` executable. |
+| `JAVA_OPTS` | spotbugs | Additional arguments for the `java` executable. |
+| `JAVA_PATH` | spotbugs | Path to the `java` executable. |
+| `MAVEN_CLI_OPTS` | spotbugs | Additional arguments for the `mvn` or `mvnw` executable. |
+| `MAVEN_PATH` | spotbugs | Path to the `mvn` executable. |
+| `MAVEN_REPO_PATH` | spotbugs | Path to the Maven local repository (shortcut for the `maven.repo.local` property). |
+| `SBT_PATH` | spotbugs | Path to the `sbt` executable. |
+| `FAIL_NEVER` | spotbugs | Set to `1` to ignore compilation failure. |
+
## Reports JSON format
CAUTION: **Caution:**
@@ -282,3 +339,15 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/application_security/security_dashboard/img/dashboard.png b/doc/user/application_security/security_dashboard/img/dashboard.png
deleted file mode 100644
index a75168b1ce4..00000000000
--- a/doc/user/application_security/security_dashboard/img/dashboard.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/group_security_dashboard.png b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
new file mode 100644
index 00000000000..85ab124f74c
--- /dev/null
+++ b/doc/user/application_security/security_dashboard/img/group_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
index f0dad6c54d0..baa136fd885 100644
--- a/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
+++ b/doc/user/application_security/security_dashboard/img/project_security_dashboard.png
Binary files differ
diff --git a/doc/user/application_security/security_dashboard/index.md b/doc/user/application_security/security_dashboard/index.md
index 3b01fe66e03..ac8c1ac0354 100644
--- a/doc/user/application_security/security_dashboard/index.md
+++ b/doc/user/application_security/security_dashboard/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# GitLab Security Dashboard **(ULTIMATE)**
The Security Dashboard is a good place to get an overview of all the security
@@ -16,9 +20,9 @@ To benefit from the Security Dashboard you must first configure one of the
The Security Dashboard supports the following reports:
- [Container Scanning](../container_scanning/index.md)
-- [DAST](../dast/index.md)
+- [Dynamic Application Security Testing](../dast/index.md)
- [Dependency Scanning](../dependency_scanning/index.md)
-- [SAST](../sast/index.md)
+- [Static Application Security Testing](../sast/index.md)
## Requirements
@@ -26,8 +30,8 @@ To use the project or group security dashboard:
1. At least one project inside a group must be configured with at least one of
the [supported reports](#supported-reports).
-2. The configured jobs must use the [new `reports` syntax](../../../ci/yaml/README.md#artifactsreports).
-3. [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or newer must be used.
+1. The configured jobs must use the [new `reports` syntax](../../../ci/yaml/README.md#artifactsreports).
+1. [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or newer must be used.
If you're using the shared Runners on GitLab.com, this is already the case.
## Project Security Dashboard
@@ -43,13 +47,13 @@ for your project. Use it to find and fix vulnerabilities affecting the
## Group Security Dashboard
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/6709) in
- [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
+> [GitLab Ultimate](https://about.gitlab.com/pricing) 11.5.
The group Security Dashboard gives an overview of the vulnerabilities of all the
projects in a group and its subgroups.
First, navigate to the Security Dashboard found under your group's
-**Overview > Security Dashboard**.
+**Security** tab.
Once you're on the dashboard, at the top you should see a series of filters for:
@@ -58,7 +62,7 @@ Once you're on the dashboard, at the top you should see a series of filters for:
- Report type
- Project
-![dashboard with action buttons and metrics](img/dashboard.png)
+![dashboard with action buttons and metrics](img/group_security_dashboard.png)
Selecting one or more filters will filter the results in this page.
The first section is an overview of all the vulnerabilities, grouped by severity.
@@ -102,3 +106,15 @@ That way, reports are created even if no code change happens.
When using [Auto DevOps](../../../topics/autodevops/index.md), use
[special environment variables](../../../topics/autodevops/index.md#environment-variables)
to configure daily security scans.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/asciidoc.md b/doc/user/asciidoc.md
index df86b2a1cbe..862316b57da 100644
--- a/doc/user/asciidoc.md
+++ b/doc/user/asciidoc.md
@@ -277,11 +277,11 @@ source - a listing that is embellished with (colorized) syntax highlighting
----
```
-````asciidoc
+~~~asciidoc
\```language
fenced code - a shorthand syntax for the source block
\```
-````
+~~~
```asciidoc
[,attribution,citetitle]
diff --git a/doc/user/clusters/applications.md b/doc/user/clusters/applications.md
index 6956086c382..096730f800c 100644
--- a/doc/user/clusters/applications.md
+++ b/doc/user/clusters/applications.md
@@ -1,15 +1,18 @@
# GitLab Managed Apps
GitLab provides **GitLab Managed Apps**, a one-click install for various applications which can
-be added directly to your configured cluster. These applications are
-needed for [Review Apps](../../ci/review_apps/index.md) and
-[deployments](../../ci/environments.md) when using [Auto DevOps](../../topics/autodevops/index.md).
+be added directly to your configured cluster.
+
+These applications are needed for [Review Apps](../../ci/review_apps/index.md)
+and [deployments](../../ci/environments.md) when using [Auto DevOps](../../topics/autodevops/index.md).
+
You can install them after you
-[create a cluster](../project/clusters/index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+[create a cluster](../project/clusters/index.md#adding-and-removing-clusters).
## Installing applications
Applications managed by GitLab will be installed onto the `gitlab-managed-apps` namespace.
+
This namespace:
- Is different from the namespace used for project deployments.
@@ -19,8 +22,10 @@ This namespace:
To see a list of available applications to install:
1. For a:
- - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
- - Group-level cluster, navigate to your group's **Kubernetes** page.
+ - [Project-level cluster](../project/clusters/index.md),
+ navigate to your project's **Operations > Kubernetes**.
+ - [Group-level cluster](../group/clusters/index.md),
+ navigate to your group's **Kubernetes** page.
Install Helm first as it's used to install other applications.
@@ -94,7 +99,7 @@ CI/CD](../../ci/README.md), the open-source continuous integration
service included with GitLab that coordinates the jobs. When installing
the GitLab Runner via the applications, it will run in **privileged
mode** by default. Make sure you read the [security
-implications](../project/clusters/index.md/#security-implications) before doing so.
+implications](../project/clusters/index.md#security-implications) before doing so.
NOTE: **Note:**
The
@@ -159,9 +164,9 @@ file.
When installing JupyterHub onto your Kubernetes cluster, [JupyterLab's Git extension](https://github.com/jupyterlab/jupyterlab-git)
is automatically provisioned and configured using the authenticated user's:
-- Name
-- Email
-- Newly created access token
+- Name.
+- Email.
+- Newly created access token.
JupyterLab's Git extension enables full version control of your notebooks as well as issuance of Git commands within Jupyter.
Git commands can be issued via the **Git** tab on the left panel or via Jupyter's command line prompt.
@@ -232,8 +237,10 @@ The applications below can be upgraded.
To upgrade an application:
1. For a:
- - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
- - Group-level cluster, navigate to your group's **Kubernetes** page.
+ - [Project-level cluster](../project/clusters/index.md),
+ navigate to your project's **Operations > Kubernetes**.
+ - [Group-level cluster](../group/clusters/index.md),
+ navigate to your group's **Kubernetes** page.
1. Select your cluster.
1. If an upgrade is available, the **Upgrade** button is displayed. Click the button to upgrade.
@@ -251,16 +258,21 @@ The applications below can be uninstalled.
| Application | GitLab version | Notes |
| ----------- | -------------- | ----- |
+| Cert-Manager | 12.2+ | The associated private key will be deleted and cannot be restored. Deployed applications will continue to use HTTPS, but certificates will not be renewed. Before uninstalling, you may wish to [back up your configuration](https://docs.cert-manager.io/en/latest/tasks/backup-restore-crds.html) or [revoke your certificates](https://letsencrypt.org/docs/revoking/) |
| GitLab Runner | 12.2+ | Any running pipelines will be canceled. |
+| Helm | 12.2+ | The associated Tiller pod will be deleted and cannot be restored. |
| Ingress | 12.1+ | The associated load balancer and IP will be deleted and cannot be restored. Furthermore, it can only be uninstalled if JupyterHub is not installed. |
| JupyterHub | 12.1+ | All data not committed to GitLab will be deleted and cannot be restored. |
+| Knative | 12.1+ | The associated IP will be deleted and cannot be restored. |
| Prometheus | 11.11+ | All data will be deleted and cannot be restored. |
To uninstall an application:
1. For a:
- - Project-level cluster, navigate to your project's **Operations > Kubernetes**.
- - Group-level cluster, navigate to your group's **Kubernetes** page.
+ - [Project-level cluster](../project/clusters/index.md),
+ navigate to your project's **Operations > Kubernetes**.
+ - [Group-level cluster](../group/clusters/index.md),
+ navigate to your group's **Kubernetes** page.
1. Select your cluster.
1. Click the **Uninstall** button for the application.
diff --git a/doc/user/clusters/img/jupyter-gitclone.png b/doc/user/clusters/img/jupyter-gitclone.png
index 41d467f806a..aff194dea43 100644
--- a/doc/user/clusters/img/jupyter-gitclone.png
+++ b/doc/user/clusters/img/jupyter-gitclone.png
Binary files differ
diff --git a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
index d31216a7e2e..a6fc4b0aef1 100644
--- a/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
+++ b/doc/user/discussions/img/automatically_resolve_outdated_discussions.png
Binary files differ
diff --git a/doc/user/discussions/img/btn_new_issue_for_all_threads.png b/doc/user/discussions/img/btn_new_issue_for_all_threads.png
index f24c84a2348..5d86e0ca016 100644
--- a/doc/user/discussions/img/btn_new_issue_for_all_threads.png
+++ b/doc/user/discussions/img/btn_new_issue_for_all_threads.png
Binary files differ
diff --git a/doc/user/discussions/img/commit_comment_mr_context.png b/doc/user/discussions/img/commit_comment_mr_context.png
index 7b87d6e44d7..68f58a57757 100644
--- a/doc/user/discussions/img/commit_comment_mr_context.png
+++ b/doc/user/discussions/img/commit_comment_mr_context.png
Binary files differ
diff --git a/doc/user/discussions/img/commit_comment_mr_discussions_tab.png b/doc/user/discussions/img/commit_comment_mr_discussions_tab.png
index 4798ff4b658..d88f08eae26 100644
--- a/doc/user/discussions/img/commit_comment_mr_discussions_tab.png
+++ b/doc/user/discussions/img/commit_comment_mr_discussions_tab.png
Binary files differ
diff --git a/doc/user/discussions/img/discussion_comment.png b/doc/user/discussions/img/discussion_comment.png
index 685c30e5004..3fec5962363 100644
--- a/doc/user/discussions/img/discussion_comment.png
+++ b/doc/user/discussions/img/discussion_comment.png
Binary files differ
diff --git a/doc/user/discussions/img/image_resolved_discussion.png b/doc/user/discussions/img/image_resolved_discussion.png
index 9feded27c92..f6e5a3b66ae 100644
--- a/doc/user/discussions/img/image_resolved_discussion.png
+++ b/doc/user/discussions/img/image_resolved_discussion.png
Binary files differ
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/merge_request_commits_tab.png b/doc/user/discussions/img/merge_request_commits_tab.png
index 065d4be61f0..267f3a720dc 100644
--- a/doc/user/discussions/img/merge_request_commits_tab.png
+++ b/doc/user/discussions/img/merge_request_commits_tab.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_resolve.png b/doc/user/discussions/img/mr_review_resolve.png
index fc6299961a5..ced33682459 100644
--- a/doc/user/discussions/img/mr_review_resolve.png
+++ b/doc/user/discussions/img/mr_review_resolve.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_resolve2.png b/doc/user/discussions/img/mr_review_resolve2.png
index 1794b682911..2f0be3b6d06 100644
--- a/doc/user/discussions/img/mr_review_resolve2.png
+++ b/doc/user/discussions/img/mr_review_resolve2.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_second_comment.png b/doc/user/discussions/img/mr_review_second_comment.png
index 204cc840d9e..f345c52e941 100644
--- a/doc/user/discussions/img/mr_review_second_comment.png
+++ b/doc/user/discussions/img/mr_review_second_comment.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_second_comment_added.png b/doc/user/discussions/img/mr_review_second_comment_added.png
index aa15ca7fb98..61b45431c9e 100644
--- a/doc/user/discussions/img/mr_review_second_comment_added.png
+++ b/doc/user/discussions/img/mr_review_second_comment_added.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_start.png b/doc/user/discussions/img/mr_review_start.png
index 0f52bee7d89..08b4c6bb82b 100644
--- a/doc/user/discussions/img/mr_review_start.png
+++ b/doc/user/discussions/img/mr_review_start.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_unresolve.png b/doc/user/discussions/img/mr_review_unresolve.png
index 3441efc1572..4bef38f7808 100644
--- a/doc/user/discussions/img/mr_review_unresolve.png
+++ b/doc/user/discussions/img/mr_review_unresolve.png
Binary files differ
diff --git a/doc/user/discussions/img/mr_review_unresolve2.png b/doc/user/discussions/img/mr_review_unresolve2.png
index a824b806e4a..79da61bb556 100644
--- a/doc/user/discussions/img/mr_review_unresolve2.png
+++ b/doc/user/discussions/img/mr_review_unresolve2.png
Binary files differ
diff --git a/doc/user/discussions/img/multi-line-suggestion-preview.png b/doc/user/discussions/img/multi-line-suggestion-preview.png
index 4288d0ba034..476c50b9098 100644
--- a/doc/user/discussions/img/multi-line-suggestion-preview.png
+++ b/doc/user/discussions/img/multi-line-suggestion-preview.png
Binary files differ
diff --git a/doc/user/discussions/img/multi-line-suggestion-syntax.png b/doc/user/discussions/img/multi-line-suggestion-syntax.png
index df0c99b84ef..80424d1f056 100644
--- a/doc/user/discussions/img/multi-line-suggestion-syntax.png
+++ b/doc/user/discussions/img/multi-line-suggestion-syntax.png
Binary files differ
diff --git a/doc/user/discussions/img/new_issue_for_thread.png b/doc/user/discussions/img/new_issue_for_thread.png
index 2264da0b5b5..28b76adf7fe 100644
--- a/doc/user/discussions/img/new_issue_for_thread.png
+++ b/doc/user/discussions/img/new_issue_for_thread.png
Binary files differ
diff --git a/doc/user/discussions/img/onion_skin_view.png b/doc/user/discussions/img/onion_skin_view.png
index 9bb4428184e..81bb4a2c85a 100644
--- a/doc/user/discussions/img/onion_skin_view.png
+++ b/doc/user/discussions/img/onion_skin_view.png
Binary files differ
diff --git a/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png b/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png
index 9314e3a6490..bd0aaca79b2 100644
--- a/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png
+++ b/doc/user/discussions/img/only_allow_merge_if_all_threads_are_resolved.png
Binary files differ
diff --git a/doc/user/discussions/img/pending_review_comment.png b/doc/user/discussions/img/pending_review_comment.png
index 812e4ac966a..70a66b3f4f0 100644
--- a/doc/user/discussions/img/pending_review_comment.png
+++ b/doc/user/discussions/img/pending_review_comment.png
Binary files differ
diff --git a/doc/user/discussions/img/preview_issue_for_thread.png b/doc/user/discussions/img/preview_issue_for_thread.png
index 1517902c61c..a9d7ab598be 100644
--- a/doc/user/discussions/img/preview_issue_for_thread.png
+++ b/doc/user/discussions/img/preview_issue_for_thread.png
Binary files differ
diff --git a/doc/user/discussions/img/preview_issue_for_threads.png b/doc/user/discussions/img/preview_issue_for_threads.png
index 8359ab3143c..8757decb178 100644
--- a/doc/user/discussions/img/preview_issue_for_threads.png
+++ b/doc/user/discussions/img/preview_issue_for_threads.png
Binary files differ
diff --git a/doc/user/discussions/img/reply_to_comment_button.png b/doc/user/discussions/img/reply_to_comment_button.png
index d362b19785c..d327d1c3e27 100644
--- a/doc/user/discussions/img/reply_to_comment_button.png
+++ b/doc/user/discussions/img/reply_to_comment_button.png
Binary files differ
diff --git a/doc/user/discussions/img/resolve_comment_button.png b/doc/user/discussions/img/resolve_comment_button.png
index 0319ec999fd..0a3ed03a69c 100644
--- a/doc/user/discussions/img/resolve_comment_button.png
+++ b/doc/user/discussions/img/resolve_comment_button.png
Binary files differ
diff --git a/doc/user/discussions/img/resolve_thread_button.png b/doc/user/discussions/img/resolve_thread_button.png
index 873c302f570..ca0a3e50550 100644
--- a/doc/user/discussions/img/resolve_thread_button.png
+++ b/doc/user/discussions/img/resolve_thread_button.png
Binary files differ
diff --git a/doc/user/discussions/img/resolve_thread_issue_notice.png b/doc/user/discussions/img/resolve_thread_issue_notice.png
index c2a8fdebee7..30a65b8fbd4 100644
--- a/doc/user/discussions/img/resolve_thread_issue_notice.png
+++ b/doc/user/discussions/img/resolve_thread_issue_notice.png
Binary files differ
diff --git a/doc/user/discussions/img/resolve_thread_open_issue.png b/doc/user/discussions/img/resolve_thread_open_issue.png
index be2a4365297..2dd4ea3cb1b 100644
--- a/doc/user/discussions/img/resolve_thread_open_issue.png
+++ b/doc/user/discussions/img/resolve_thread_open_issue.png
Binary files differ
diff --git a/doc/user/discussions/img/review_comment_quickactions.png b/doc/user/discussions/img/review_comment_quickactions.png
index df5c4dd0fcc..276def6381f 100644
--- a/doc/user/discussions/img/review_comment_quickactions.png
+++ b/doc/user/discussions/img/review_comment_quickactions.png
Binary files differ
diff --git a/doc/user/discussions/img/review_preview.png b/doc/user/discussions/img/review_preview.png
index 1b91506b477..e69a58dbb91 100644
--- a/doc/user/discussions/img/review_preview.png
+++ b/doc/user/discussions/img/review_preview.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/discussions/img/swipe_view.png b/doc/user/discussions/img/swipe_view.png
index 287d52a0811..e6f5e5053af 100644
--- a/doc/user/discussions/img/swipe_view.png
+++ b/doc/user/discussions/img/swipe_view.png
Binary files differ
diff --git a/doc/user/discussions/img/thread_view.png b/doc/user/discussions/img/thread_view.png
index 8c1fd9d5acf..e2db44aa604 100644
--- a/doc/user/discussions/img/thread_view.png
+++ b/doc/user/discussions/img/thread_view.png
Binary files differ
diff --git a/doc/user/discussions/img/threads_resolved.png b/doc/user/discussions/img/threads_resolved.png
index 6ac815cf874..ffb1233f2ee 100644
--- a/doc/user/discussions/img/threads_resolved.png
+++ b/doc/user/discussions/img/threads_resolved.png
Binary files differ
diff --git a/doc/user/discussions/img/two_up_view.png b/doc/user/discussions/img/two_up_view.png
index 062a96723dd..3b6ddfbe1be 100644
--- a/doc/user/discussions/img/two_up_view.png
+++ b/doc/user/discussions/img/two_up_view.png
Binary files differ
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 3cb765c0463..6891682141c 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -22,7 +22,7 @@ higher can also edit a comment made by someone else.
You can also reply to a comment notification email to reply to the comment if
[Reply by email] is configured for your GitLab instance. Replying to a standard comment
creates another standard comment. Replying to a threaded comment creates a reply in the thread. Email replies support
- [Markdown] and [quick actions], just as if you replied from the web.
+[Markdown] and [quick actions], just as if you replied from the web.
## Resolvable comments and threads
@@ -58,17 +58,17 @@ To create a commit diff thread:
1. Navigate to the merge request **Commits** tab. A list of commits that
constitute the merge request will be shown.
- ![Merge request commits tab](img/merge_request_commits_tab.png)
+ ![Merge request commits tab](img/merge_request_commits_tab.png)
1. Navigate to a specific commit, click on the **Changes** tab (where you
will only be presented diffs from the selected commit), and leave a comment.
- ![Commit diff discussion in merge request context](img/commit_comment_mr_context.png)
+ ![Commit diff discussion in merge request context](img/commit_comment_mr_context.png)
1. Any threads created this way will be shown in the merge request's
**Discussions** tab and are resolvable.
- ![Merge request Discussions tab](img/commit_comment_mr_discussions_tab.png)
+ ![Merge request Discussions tab](img/commit_comment_mr_discussions_tab.png)
Threads created this way will only appear in the original merge request
and not when navigating to that commit under your project's
@@ -88,6 +88,11 @@ Jump button next to the Reply field on a thread.
You can also jump to the first unresolved thread from the button next to the
resolved threads tracker.
+You can also use keyboard shortcuts to navigate among threads:
+
+- Use <kbd>n</kbd> to jump to the next unresolved thread.
+- Use <kbd>p</kbd> to jump to the previous unresolved thread.
+
!["8/9 threads resolved"](img/threads_resolved.png)
### Marking a comment or thread as resolved
@@ -338,8 +343,8 @@ bottom of the screen with two buttons:
- **Discard**: Discards all comments that have not been submitted.
- **Finish review**: Opens a list of comments ready to be submitted for review.
- Clicking **Submit review** will publish all comments. Any quick actions
- submitted are performed at this time.
+ Clicking **Submit review** will publish all comments. Any quick actions
+ submitted are performed at this time.
Alternatively, every pending comment has a button to finish the entire review.
@@ -384,18 +389,18 @@ the Merge Request authored by the user that applied them.
1. Choose a line of code to be changed, add a new comment, then click
on the **Insert suggestion** icon in the toolbar:
- ![Add a new comment](img/insert_suggestion.png)
+ ![Add a new comment](img/insert_suggestion.png)
1. In the comment, add your suggestion to the pre-populated code block:
- ![Add a suggestion into a code block tagged properly](img/make_suggestion.png)
+ ![Add a suggestion into a code block tagged properly](img/make_suggestion.png)
1. Click **Comment**.
- The suggestions in the comment can be applied by the merge request author
- directly from the merge request:
+ The suggestions in the comment can be applied by the merge request author
+ directly from the merge request:
- ![Apply suggestions](img/suggestion.png)
+ ![Apply suggestions](img/suggestion.png)
Once the author applies a suggestion, it will be marked with the **Applied** label,
the thread will be automatically resolved, and GitLab will create a new commit
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 7858c419e04..d21a325d401 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -81,13 +81,12 @@ IP based firewall can be configured by looking up all
## Shared Runners
-Shared Runners on GitLab.com run in [autoscale mode] and powered by
-Google Cloud Platform. Autoscaling means reduced
-waiting times to spin up CI/CD jobs, and isolated VMs for each project,
-thus maximizing security.
-They're free to use for public open source projects and limited to 2000 CI
-minutes per month per group for private projects. Read about all
-[GitLab.com plans](https://about.gitlab.com/pricing/).
+Shared Runners on GitLab.com run in [autoscale mode] and powered by Google Cloud Platform.
+Autoscaling means reduced waiting times to spin up CI/CD jobs, and isolated VMs for each project,
+thus maximizing security. They're free to use for public open source projects and limited
+to 2000 CI minutes per month per group for private projects. More minutes
+[can be purchased](../../subscriptions/index.md#extra-shared-runners-pipeline-minutes), if
+needed. Read about all [GitLab.com plans](https://about.gitlab.com/pricing/).
All your CI/CD jobs run on [n1-standard-1 instances](https://cloud.google.com/compute/docs/machine-types) with 3.75GB of RAM, CoreOS and the latest Docker Engine
installed. Instances provide 1 vCPU and 25GB of HDD disk space. The default
@@ -113,57 +112,6 @@ Below are the shared Runners settings.
The full contents of our `config.toml` are:
-**DigitalOcean**
-
-```toml
-concurrent = X
-check_interval = 1
-metrics_server = "X"
-sentry_dsn = "X"
-
-[[runners]]
- name = "docker-auto-scale"
- request_concurrency = X
- url = "https://gitlab.com/"
- token = "SHARED_RUNNER_TOKEN"
- executor = "docker+machine"
- environment = [
- "DOCKER_DRIVER=overlay2"
- ]
- limit = X
- [runners.docker]
- image = "ruby:2.5"
- privileged = true
- [runners.machine]
- IdleCount = 20
- IdleTime = 1800
- OffPeakPeriods = ["* * * * * sat,sun *"]
- OffPeakTimezone = "UTC"
- OffPeakIdleCount = 5
- OffPeakIdleTime = 1800
- MaxBuilds = 1
- MachineName = "srm-%s"
- MachineDriver = "digitalocean"
- MachineOptions = [
- "digitalocean-image=X",
- "digitalocean-ssh-user=core",
- "digitalocean-region=nyc1",
- "digitalocean-size=s-2vcpu-2gb",
- "digitalocean-private-networking",
- "digitalocean-tags=shared_runners,gitlab_com",
- "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR",
- "digitalocean-access-token=DIGITAL_OCEAN_ACCESS_TOKEN",
- ]
- [runners.cache]
- Type = "s3"
- BucketName = "runner"
- Insecure = true
- Shared = true
- ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
- AccessKey = "ACCESS_KEY"
- SecretKey = "ACCESS_SECRET_KEY"
-```
-
**Google Cloud Platform**
```toml
@@ -179,20 +127,25 @@ sentry_dsn = "X"
token = "SHARED_RUNNER_TOKEN"
executor = "docker+machine"
environment = [
- "DOCKER_DRIVER=overlay2"
+ "DOCKER_DRIVER=overlay2",
+ "DOCKER_TLS_CERTDIR="
]
limit = X
[runners.docker]
image = "ruby:2.5"
privileged = true
+ volumes = [
+ "/certs/client",
+ "/dummy-sys-class-dmi-id:/sys/class/dmi/id:ro" # Make kaniko builds work on GCP.
+ ]
[runners.machine]
- IdleCount = 20
- IdleTime = 1800
+ IdleCount = 50
+ IdleTime = 3600
OffPeakPeriods = ["* * * * * sat,sun *"]
OffPeakTimezone = "UTC"
- OffPeakIdleCount = 5
- OffPeakIdleTime = 1800
- MaxBuilds = 1
+ OffPeakIdleCount = 15
+ OffPeakIdleTime = 3600
+ MaxBuilds = 1 # For security reasons we delete the VM after job has finished so it's not reused.
MachineName = "srm-%s"
MachineDriver = "google"
MachineOptions = [
@@ -203,17 +156,18 @@ sentry_dsn = "X"
"google-tags=gitlab-com,srm",
"google-use-internal-ip",
"google-zone=us-east1-d",
+ "engine-opt=mtu=1460", # Set MTU for container interface, for more information check https://gitlab.com/gitlab-org/gitlab-runner/issues/3214#note_82892928
"google-machine-image=PROJECT/global/images/IMAGE",
- "engine-registry-mirror=http://INTERNAL_IP_OF_OUR_REGISTRY_MIRROR"
+ "engine-opt=ipv6", # This will create IPv6 interfaces in the containers.
+ "engine-opt=fixed-cidr-v6=fc00::/7",
+ "google-operation-backoff-initial-interval=2" # Custom flag from forked docker-machine, for more information check https://github.com/docker/machine/pull/4600
]
[runners.cache]
- Type = "s3"
- BucketName = "runner"
- Insecure = true
+ Type = "gcs"
Shared = true
- ServerAddress = "INTERNAL_IP_OF_OUR_CACHE_SERVER"
- AccessKey = "ACCESS_KEY"
- SecretKey = "ACCESS_SECRET_KEY"
+ [runners.cache.gcs]
+ CredentialsFile = "/path/to/file"
+ BucketName = "bucket-name"
```
## Sidekiq
@@ -223,7 +177,7 @@ and the following environment variables:
| Setting | GitLab.com | Default |
|-------- |----------- |-------- |
-| `SIDEKIQ_MEMORY_KILLER_MAX_RSS` | `1000000` | `1000000` |
+| `SIDEKIQ_MEMORY_KILLER_MAX_RSS` | `1000000` | `2000000` |
| `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL` | `SIGKILL` | - |
| `SIDEKIQ_LOG_ARGUMENTS` | `1` | - |
@@ -298,6 +252,82 @@ Web front-ends:
- `memory_limit_min` = 1024MiB
- `memory_limit_max` = 1280MiB
+## GitLab.com-specific rate limits
+
+NOTE: **Note:**
+See [Rate limits](../../security/rate_limits.md) for administrator
+documentation.
+
+IP blocks usually happen when GitLab.com receives unusual traffic from a single
+IP address that the system views as potentially malicious based on rate limit
+settings. After the unusual traffic ceases, the IP address will be automatically
+released depending on the type of block, as described below.
+
+If you receive a `403 Forbidden` error for all requests to GitLab.com, please
+check for any automated processes that may be triggering a block. For
+assistance, contact [GitLab Support](https://support.gitlab.com)
+with details, such as the affected IP address.
+
+### HAProxy API throttle
+
+GitLab.com responds with HTTP status code `429` to API requests that exceed 10
+requests
+per second per IP address.
+
+The following example headers are included for all API requests:
+
+```
+RateLimit-Limit: 600
+RateLimit-Observed: 6
+RateLimit-Remaining: 594
+RateLimit-Reset: 1563325137
+RateLimit-ResetTime: Wed, 17 Jul 2019 00:58:57 GMT
+```
+
+Source:
+
+- Search for `rate_limit_http_rate_per_minute` and `rate_limit_sessions_per_second` in [GitLab.com's current HAProxy settings](https://gitlab.com/gitlab-cookbooks/gitlab-haproxy/blob/master/attributes/default.rb).
+
+### Rack Attack initializer
+
+Details of rate limits enforced by [Rack Attack](../../security/rack_attack.md).
+
+#### Protected paths throttle
+
+GitLab.com responds with HTTP status code `429` to POST requests at protected
+paths that exceed 10 requests per **minute** per IP address.
+
+See the source below for which paths are protected. This includes user creation,
+user confirmation, user sign in, and password reset.
+
+This header is included in responses to blocked requests:
+
+```
+Retry-After: 60
+```
+
+Source:
+
+- Search for `rate_limit_requests_per_period`, `rate_limit_period`, and `rack_attack_protected_paths` in [GitLab.com's current Rails app settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb).
+
+#### Git and container registry failed authentication ban
+
+GitLab.com responds with HTTP status code 403 for 1 hour, if 30 failed
+authentication requests were received in a 3-minute period from a single IP address.
+
+This applies only to Git requests and container registry (`/jwt/auth`) requests
+(combined).
+
+This limit is reset by requests that authenticate successfully. For example, 29
+failed authentication requests followed by 1 successful request, followed by 29
+more failed authentication requests would not trigger a ban.
+
+No response headers are provided.
+
+### Admin Area settings
+
+GitLab.com does not currently use these settings.
+
## GitLab.com at scale
In addition to the GitLab Enterprise Edition Omnibus install, GitLab.com uses
diff --git a/doc/user/group/bulk_editing/img/bulk-editing.png b/doc/user/group/bulk_editing/img/bulk-editing.png
new file mode 100644
index 00000000000..ca1662a781b
--- /dev/null
+++ b/doc/user/group/bulk_editing/img/bulk-editing.png
Binary files differ
diff --git a/doc/user/group/bulk_editing/index.md b/doc/user/group/bulk_editing/index.md
new file mode 100644
index 00000000000..ea48b0b9fef
--- /dev/null
+++ b/doc/user/group/bulk_editing/index.md
@@ -0,0 +1,31 @@
+# Bulk editing issues, merge requests, and epics at the group level **(PREMIUM)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7249) for issues in [GitLab Premium](https://about.gitlab.com/pricing/) 12.1.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12719) for merge requests in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7250) for epics in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+
+## Editing milestones and labels
+
+> **Notes:**
+>
+> - A permission level of `Reporter` or higher is required in order to manage issues.
+> - A permission level of `Developer` or higher is required in order to manage merge requests.
+> - A permission level of `Reporter` or higher is required in order to manage epics.
+
+By using the bulk editing feature:
+
+- Milestones can be updated simultaneously across multiple issues or merge requests.
+- Labels can be updated simultaneously across multiple issues, merge requests, or epics.
+
+![Bulk editing](img/bulk-editing.png)
+
+To bulk update group issues, merge requests, or epics:
+
+1. Navigate to the issues, merge requests, or epics list.
+1. Click **Edit issues**, **Edit merge requests**, or **Edit epics**.
+ - This will open a sidebar on the right-hand side where editable fields
+ for milestones and labels will be displayed.
+ - Checkboxes will also appear beside each issue, merge request, or epic.
+1. Check the checkbox beside each issue, merge request, or epic to be edited.
+1. Select the desired new values from the sidebar.
+1. Click **Update all**.
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 0dffc216f8e..d41f44f85cc 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -5,7 +5,6 @@ type: reference
# Group-level Kubernetes clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in GitLab 11.6.
-> Group Cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga).
## Overview
@@ -87,7 +86,7 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
When adding more than one Kubernetes cluster to your project, you need to differentiate
them with an environment scope. The environment scope associates clusters with
[environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium)
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables)
work.
While evaluating which environment matches the environment scope of a
@@ -138,6 +137,13 @@ The result will then be:
- The Staging cluster will be used for the `deploy to staging` job.
- The Production cluster will be used for the `deploy to production` job.
+## Security of Runners
+
+For important information about securely configuring GitLab Runners, see
+[Security of
+Runners](../../project/clusters/index.md#security-of-gitlab-runners)
+documentation for project-level clusters.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/group/contribution_analytics/index.md b/doc/user/group/contribution_analytics/index.md
index 2d37fc375db..a0dd5cd497d 100644
--- a/doc/user/group/contribution_analytics/index.md
+++ b/doc/user/group/contribution_analytics/index.md
@@ -4,7 +4,8 @@ type: reference
# Contribution Analytics **(STARTER)**
-> Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+> - Introduced in [GitLab Starter](https://about.gitlab.com/pricing/) 8.3.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/3090) for subgroups in GitLab 12.2.
## Overview
diff --git a/doc/user/group/epics/img/bulk_editing.png b/doc/user/group/epics/img/bulk_editing.png
new file mode 100644
index 00000000000..85610bef83e
--- /dev/null
+++ b/doc/user/group/epics/img/bulk_editing.png
Binary files differ
diff --git a/doc/user/group/epics/img/button_reopen_epic.png b/doc/user/group/epics/img/button_reopen_epic.png
index 7a953189270..9d1be88549d 100644
--- a/doc/user/group/epics/img/button_reopen_epic.png
+++ b/doc/user/group/epics/img/button_reopen_epic.png
Binary files differ
diff --git a/doc/user/group/epics/img/containing_epic.png b/doc/user/group/epics/img/containing_epic.png
index 5ed693e6d6a..dc13d55e2bc 100644
--- a/doc/user/group/epics/img/containing_epic.png
+++ b/doc/user/group/epics/img/containing_epic.png
Binary files differ
diff --git a/doc/user/group/epics/img/epic_view.png b/doc/user/group/epics/img/epic_view.png
index dbda98e4351..c55d302ec29 100644
--- a/doc/user/group/epics/img/epic_view.png
+++ b/doc/user/group/epics/img/epic_view.png
Binary files differ
diff --git a/doc/user/group/epics/index.md b/doc/user/group/epics/index.md
index 4ab562b655f..5968b91c9b7 100644
--- a/doc/user/group/epics/index.md
+++ b/doc/user/group/epics/index.md
@@ -97,6 +97,22 @@ have a [start or due date](#start-date-and-due-date), then you can see a
Drag and drop to reorder issues and child epics. New issues and child epics added to an epic appear at the top of the list.
+## Updating epics
+
+### Using bulk editing
+
+To apply labels across multiple epics:
+
+1. Go to the Epics list.
+1. Click **Edit epics**.
+ - Checkboxes will appear beside each epic.
+ - A sidebar on the right-hand side will appear, with an editable field for labels.
+1. Check the checkbox beside each epic to be edited.
+1. Select the desired labels.
+1. Click **Update all**.
+
+![bulk editing](img/bulk_editing.png)
+
## Deleting an epic
NOTE: **Note:**
diff --git a/doc/user/group/img/group_file_template_dropdown.png b/doc/user/group/img/group_file_template_dropdown.png
index d9caae1a223..f0586772218 100644
--- a/doc/user/group/img/group_file_template_dropdown.png
+++ b/doc/user/group/img/group_file_template_dropdown.png
Binary files differ
diff --git a/doc/user/group/img/group_file_template_settings.png b/doc/user/group/img/group_file_template_settings.png
index ca42f7726db..5e07974bc86 100644
--- a/doc/user/group/img/group_file_template_settings.png
+++ b/doc/user/group/img/group_file_template_settings.png
Binary files differ
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index db348c678eb..d1d4f3740b0 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -78,6 +78,10 @@ Issues and merge requests are part of projects. For a given group, you can view
[issues](../project/issues/index.md#issues-list) and [merge requests](../project/merge_requests/index.md#merge-requests-per-group) across all projects in that group,
together in a single list view.
+### Bulk editing issues and merge requests
+
+For details, see [bulk editing issues and merge requests](../group/bulk_editing/index.md).
+
## Create a new group
> For a list of words that are not allowed to be used as group names see the
@@ -87,11 +91,11 @@ To create a new Group, either:
- In the top menu, click **Groups** and then **Your Groups**, and click the green button **New group**.
- ![new group from groups page](img/new_group_from_groups.png)
+ ![new group from groups page](img/new_group_from_groups.png)
- Or, in the top menu, expand the `plus` sign and choose **New group**.
- ![new group from elsewhere](img/new_group_from_other_pages.png)
+ ![new group from elsewhere](img/new_group_from_other_pages.png)
Add the following information:
@@ -100,15 +104,15 @@ Add the following information:
1. The **Group name** will automatically populate the URL. Optionally, you can change it.
This is the name that displays in group views.
The name can contain only:
- - Alphanumeric characters
- - Underscores
- - Dashes and dots
- - Spaces
+ - Alphanumeric characters
+ - Underscores
+ - Dashes and dots
+ - Spaces
1. The **Group URL** is the namespace under which your projects will be hosted.
The URL can contain only:
- - Alphanumeric characters
- - Underscores
- - Dashes and dots (it cannot start with dashes or end in a dot)
+ - Alphanumeric characters
+ - Underscores
+ - Dashes and dots (it cannot start with dashes or end in a dot)
1. Optionally, you can add a brief description to tell others
what this group is about.
1. Optionally, choose an avatar for your group.
@@ -162,12 +166,12 @@ There are two different ways to add a new project to a group:
- Select a group, and then click **New project**. You can then continue [creating your project](../../gitlab-basics/create-project.md).
- ![New project](img/create_new_project_from_group.png)
+ ![New project](img/create_new_project_from_group.png)
- While you are creating a project, select a group namespace
you've already created from the dropdown menu.
- ![Select group](img/select_group_dropdown.png)
+ ![Select group](img/select_group_dropdown.png)
### Default project-creation level
@@ -327,7 +331,7 @@ This will disable the option for all users who previously had permissions to
operate project memberships, so no new users can be added. Furthermore, any
request to add a new user to a project through API will not be possible.
-#### IP access restriction **(ULTIMATE)**
+#### IP access restriction **(ULTIMATE ONLY)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/1985) in
[GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
@@ -346,6 +350,38 @@ Restriction currently applies to UI, API access is not restricted.
To avoid accidental lock-out, admins and group owners are are able to access
the group regardless of the IP restriction.
+#### Allowed domain restriction **(PREMIUM ONLY)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7297) in
+[GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+
+You can restrict access to groups and their underlying projects by
+allowing only users with email addresses in particular domains to be added to the group.
+
+Add email domains you want to whitelist and users with emails from different
+domains won't be allowed to be added to this group.
+
+Some domains cannot be restricted. These are the most popular public email domains, such as:
+
+- `gmail.com`
+- `yahoo.com`
+- `hotmail.com`
+- `aol.com`
+- `msn.com`
+- `hotmail.co.uk`
+- `hotmail.fr`
+- `live.com`
+- `outlook.com`
+- `icloud.com`
+
+To enable this feature:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and enter domain name into **Restrict membership by email** field.
+1. Click **Save changes**.
+
+This will enable the domain-checking for all new users added to the group from this moment on.
+
#### Group file templates **(PREMIUM)**
Group file templates allow you to share a set of templates for common file
@@ -375,6 +411,17 @@ To enable this feature, navigate to the group settings page, expand the
Define project templates at a group level by setting a group as the template source.
[Learn more about group-level project templates](custom_project_templates.md).
+#### Disabling email notifications
+
+You can disable all email notifications related to the group, which also includes
+it's subgroups and projects.
+
+To enable this feature:
+
+1. Navigate to the group's **Settings > General** page.
+1. Expand the **Permissions, LFS, 2FA** section, and select **Disable email notifications**.
+1. Click **Save changes**.
+
### Advanced settings
- **Projects**: View all projects within that group, add members to each project,
diff --git a/doc/user/group/insights/img/insights_example_stacked_bar_chart.png b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png
index 791d0e4bcdf..0e338b99e4c 100644
--- a/doc/user/group/insights/img/insights_example_stacked_bar_chart.png
+++ b/doc/user/group/insights/img/insights_example_stacked_bar_chart.png
Binary files differ
diff --git a/doc/user/group/insights/img/insights_group_configuration.png b/doc/user/group/insights/img/insights_group_configuration.png
index 0af0073e448..d181a1e94c3 100644
--- a/doc/user/group/insights/img/insights_group_configuration.png
+++ b/doc/user/group/insights/img/insights_group_configuration.png
Binary files differ
diff --git a/doc/user/group/insights/img/insights_sidebar_link.png b/doc/user/group/insights/img/insights_sidebar_link.png
index 64bbd1fc4d3..f7b0c2daae3 100644
--- a/doc/user/group/insights/img/insights_sidebar_link.png
+++ b/doc/user/group/insights/img/insights_sidebar_link.png
Binary files differ
diff --git a/doc/user/group/roadmap/img/roadmap_view.png b/doc/user/group/roadmap/img/roadmap_view.png
index ff41a2e0441..2be3849ba1b 100644
--- a/doc/user/group/roadmap/img/roadmap_view.png
+++ b/doc/user/group/roadmap/img/roadmap_view.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/group_saml_configuration_information.png b/doc/user/group/saml_sso/img/group_saml_configuration_information.png
index 98b83d0cb0f..e03c50ceb01 100644
--- a/doc/user/group/saml_sso/img/group_saml_configuration_information.png
+++ b/doc/user/group/saml_sso/img/group_saml_configuration_information.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/group_saml_settings.png b/doc/user/group/saml_sso/img/group_saml_settings.png
index d95acb5075f..487be4faeba 100644
--- a/doc/user/group/saml_sso/img/group_saml_settings.png
+++ b/doc/user/group/saml_sso/img/group_saml_settings.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_advanced.png b/doc/user/group/saml_sso/img/scim_advanced.png
index 3b70e3fbe83..c9e095dc89a 100644
--- a/doc/user/group/saml_sso/img/scim_advanced.png
+++ b/doc/user/group/saml_sso/img/scim_advanced.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_attribute_mapping.png b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
index c9f6b71f5b0..933d8fb6f36 100644
--- a/doc/user/group/saml_sso/img/scim_attribute_mapping.png
+++ b/doc/user/group/saml_sso/img/scim_attribute_mapping.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png
new file mode 100644
index 00000000000..f9c63970f16
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_name_identifier_mapping.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_provisioning_status.png b/doc/user/group/saml_sso/img/scim_provisioning_status.png
new file mode 100644
index 00000000000..41466ec9276
--- /dev/null
+++ b/doc/user/group/saml_sso/img/scim_provisioning_status.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/scim_token.png b/doc/user/group/saml_sso/img/scim_token.png
index 7eb52bf6ea2..bf9347716e9 100644
--- a/doc/user/group/saml_sso/img/scim_token.png
+++ b/doc/user/group/saml_sso/img/scim_token.png
Binary files differ
diff --git a/doc/user/group/saml_sso/img/unlink_group_saml.png b/doc/user/group/saml_sso/img/unlink_group_saml.png
index 0561443b5f4..9d53a9bf407 100644
--- a/doc/user/group/saml_sso/img/unlink_group_saml.png
+++ b/doc/user/group/saml_sso/img/unlink_group_saml.png
Binary files differ
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 08e516141da..df02761ced4 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -90,7 +90,7 @@ Once you've set up your identity provider to work with GitLab, you'll need to co
1. Navigate to the group's **Settings > SAML SSO**.
1. Find the SSO URL from your Identity Provider and enter it the **Identity provider single sign on URL** field.
1. Find and enter the fingerprint for the SAML token signing certificate in the **Certificate** field.
-1. Check the **Enable SAML authentication for this group** checkbox.
+1. Click the **Enable SAML authentication for this group** toggle switch.
1. Click the **Save changes** button.
![Group SAML Settings for GitLab.com](img/group_saml_settings.png)
diff --git a/doc/user/group/saml_sso/scim_setup.md b/doc/user/group/saml_sso/scim_setup.md
index 55c5a18db7d..5d136ad62da 100644
--- a/doc/user/group/saml_sso/scim_setup.md
+++ b/doc/user/group/saml_sso/scim_setup.md
@@ -45,7 +45,7 @@ The following identity providers are supported:
Feature.enable(:group_scim, group)
```
-### GitLab configuration
+## GitLab configuration
Once [Single sign-on](index.md) has been configured, we can:
@@ -55,41 +55,48 @@ Once [Single sign-on](index.md) has been configured, we can:
![SCIM token configuration](img/scim_token.png)
-## SCIM IdP configuration
+## Identity Provider configuration
-### Configuration on Azure
+### Azure
-In the [Single sign-on](index.md) configuration for the group, make sure
-that the **Name identifier value** (NameID) points to a unique identifier, such
-as the `user.objectid`. 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.
-The GitLab app in Azure needs to be configured following
-[Azure's SCIM setup](https://docs.microsoft.com/en-us/azure/active-directory/manage-apps/use-scim-to-provision-users-and-groups#getting-started).
+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.
-Note the following:
+ ![Name identifier value mapping](img/scim_name_identifier_mapping.png)
+
+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:
- The `Tenant URL` and `secret token` are the ones retrieved in the
[previous step](#gitlab-configuration).
- Should there be any problems with the availability of GitLab or similar
errors, the notification email set will get those.
+- It is recommended to set a notification email and check the **Send an email notification when a failure occurs** checkbox.
- For mappings, we will only leave `Synchronize Azure Active Directory Users to AppName` enabled.
-You can then test the connection clicking on `Test Connection`.
+You can then test the connection by clicking on **Test Connection**. If the connection is successful, be sure to save your configuration before moving on. See below for [troubleshooting](#troubleshooting).
-### Synchronize Azure Active Directory users
+#### Configure attribute mapping
-1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure
- the attribute mapping.
-1. Select the unique identifier (in the example `objectId`) as the `id` and `externalId`,
- and enable the `Create`, `Update`, and `Delete` actions.
-1. Map the `userPricipalName` to `emails[type eq "work"].value` and `mailNickname` to
- `userName`.
+1. Click on `Synchronize Azure Active Directory Users to AppName`, to configure the attribute mapping.
+1. Click **Delete** next to the `mail` mapping.
+1. Map `userPrincipalName` to `emails[type eq "work"].value` and change it's **Matching precedence** to `2`.
+1. Map `mailNickname` to `userName`.
+1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, **Target attribute** to `id`, **Match objects using this attribute** to `Yes`, and **Matching precedence** to `1`.
+1. Create a new mapping by clicking **Add New Mapping** then set **Source attribute** to `objectId`, and **Target attribute** to `externalId`.
+1. Click the `userPrincipalName` mapping and change **Match objects using this attribute** to `No`.
- Example configuration:
+ Save your changes and you should have the following configuration:
![Azure's attribute mapping configuration](img/scim_attribute_mapping.png)
-1. Click on **Show advanced options > Edit attribute list for AppName**.
+ 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:**
@@ -99,24 +106,33 @@ You can then test the connection clicking on `Test Connection`.
![Azure's attribute advanced configuration](img/scim_advanced.png)
1. Save all the screens and, in the **Provisioning** step, set
- the `Provisioning Status` to `ON`.
+ the `Provisioning Status` to `On`.
+
+ ![Provisioning status toggle switch](img/scim_provisioning_status.png)
NOTE: **Note:**
You can control what is actually synced by selecting the `Scope`. For example,
`Sync only assigned users and groups` will only sync the users assigned to
- the application (`Users and groups`), otherwise it will sync the whole Active Directory.
+ the application (`Users and groups`), otherwise, it will sync the whole Active Directory.
Once enabled, the synchronization details and any errors will appear on the
bottom of the **Provisioning** screen, together with a link to the audit logs.
-<!-- ## Troubleshooting
+## Troubleshooting
+
+### Testing Azure connection: invalid credentials
+
+When testing the connection, you may encounter an error: **You appear to have entered invalid credentials. Please confirm you are using the correct information for an administrative account**. If `Tenant URL` and `secret token` are correct, check whether your group path contains characters that may be considered invalid JSON primitives (such as `.`). Removing such characters from the group path typically resolves the error.
+
+### Azure: (Field) can't be blank sync error
+
+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.
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
-one might have when setting this up, or when something is changed, or on upgrading, it's
-important to describe those, too. Think of things that may go wrong and include them here.
-This is important to minimize requests for support, and to avoid doc comments with
-questions that you know someone might ask.
+As a workaround, try an alternate mapping:
-Each scenario can be a third-level heading, e.g. `### Getting error message X`.
-If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. -->
+1. Follow the Azure mapping instructions from above.
+1. Delete the `name.formatted` target attribute entry.
+1. Change the `displayName` source attribute to have `name.formatted` target attribute.
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 1c6cca049c5..2eb3fae1a85 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -28,39 +28,39 @@ only 1 parent group. It resembles a directory behavior or a nested items list:
- Group 1
- Group 1.1
- Group 1.2
- - Group 1.2.1
- - Group 1.2.2
- - Group 1.2.2.1
+ - Group 1.2.1
+ - Group 1.2.2
+ - Group 1.2.2.1
In a real world example, imagine maintaining a GNU/Linux distribution with the
first group being the name of the distribution, and subsequent groups split as follows:
- Organization Group - GNU/Linux distro
- Category Subgroup - Packages
- - (project) Package01
- - (project) Package02
+ - (project) Package01
+ - (project) Package02
- Category Subgroup - Software
- - (project) Core
- - (project) CLI
- - (project) Android app
- - (project) iOS app
+ - (project) Core
+ - (project) CLI
+ - (project) Android app
+ - (project) iOS app
- Category Subgroup - Infra tools
- - (project) Ansible playbooks
+ - (project) Ansible playbooks
Another example of GitLab as a company would be the following:
- Organization Group - GitLab
- Category Subgroup - Marketing
- - (project) Design
- - (project) General
+ - (project) Design
+ - (project) General
- Category Subgroup - Software
- - (project) GitLab CE
- - (project) GitLab EE
- - (project) Omnibus GitLab
- - (project) GitLab Runner
- - (project) GitLab Pages daemon
+ - (project) GitLab CE
+ - (project) GitLab EE
+ - (project) Omnibus GitLab
+ - (project) GitLab Runner
+ - (project) GitLab Pages daemon
- Category Subgroup - Infra tools
- - (project) Chef cookbooks
+ - (project) Chef cookbooks
- Category Subgroup - Executive team
---
@@ -74,27 +74,37 @@ structure.
## Creating a subgroup
-NOTE: **Note:**
-You must be an Owner of a group to create a subgroup. For
-more information check the [permissions table](../../permissions.md#group-members-permissions).
-For a list of words that are not allowed to be used as group names see the
+To create a subgroup you must either be an Owner or a Maintainer of the
+group, depending on the group's setting.
+
+By default, groups created in:
+
+- GitLab 12.2 or later allow both Owners and Maintainers to create subgroups.
+- GitLab 12.1 or earlier only allow Owners to create subgroups.
+
+This setting can be for any group by an Owner or Administrator.
+
+For more information check the
+[permissions table](../../permissions.md#group-members-permissions). For a list
+of words that are not allowed to be used as group names see the
[reserved names](../../reserved_names.md).
-Users can always create subgroups if they are explicitly added as an Owner to
-a parent group, even if group creation is disabled by an administrator in their
-settings.
+
+Users can always create subgroups if they are explicitly added as an Owner (or
+Maintainer, if that setting is enabled) to a parent group, even if group
+creation is disabled by an administrator in their settings.
To create a subgroup:
1. In the group's dashboard expand the **New project** split button, select
**New subgroup** and click the **New subgroup** button.
- ![Subgroups page](img/create_subgroup_button.png)
+ ![Subgroups page](img/create_subgroup_button.png)
1. Create a new group like you would normally do. Notice that the parent group
namespace is fixed under **Group path**. The visibility level can differ from
the parent group.
- ![Subgroups page](img/create_new_group.png)
+ ![Subgroups page](img/create_new_group.png)
1. Click the **Create group** button and you will be taken to the new group's
dashboard page.
diff --git a/doc/user/index.md b/doc/user/index.md
index db2759727a5..c93f64cd528 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -2,7 +2,7 @@
description: 'Read through the GitLab User documentation to learn how to use, configure, and customize GitLab and GitLab.com to your own needs.'
---
-# User documentation
+# User Docs
Welcome to GitLab! We're glad to have you here!
diff --git a/doc/user/instance/clusters/index.md b/doc/user/instance/clusters/index.md
index 894f83d3c75..f557dcf4b3c 100644
--- a/doc/user/instance/clusters/index.md
+++ b/doc/user/instance/clusters/index.md
@@ -1,7 +1,6 @@
# Instance-level Kubernetes clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840) in GitLab 11.11.
-> Instance-level cluster integration is currently in [Beta](https://about.gitlab.com/handbook/product/#alpha-beta-ga).
## Overview
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 96e74125801..6cfc8b6429b 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -136,26 +136,26 @@ Supported formats (named colors are not supported):
Color written inside backticks will be followed by a color "chip":
```markdown
-`#F00`
-`#F00A`
-`#FF0000`
-`#FF0000AA`
-`RGB(0,255,0)`
-`RGB(0%,100%,0%)`
-`RGBA(0,255,0,0.3)`
-`HSL(540,70%,50%)`
-`HSLA(540,70%,50%,0.3)`
-```
-
-`#F00`
-`#F00A`
-`#FF0000`
-`#FF0000AA`
-`RGB(0,255,0)`
-`RGB(0%,100%,0%)`
-`RGBA(0,255,0,0.3)`
-`HSL(540,70%,50%)`
-`HSLA(540,70%,50%,0.3)`
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.3)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.3)`
+```
+
+`#F00`
+`#F00A`
+`#FF0000`
+`#FF0000AA`
+`RGB(0,255,0)`
+`RGB(0%,100%,0%)`
+`RGBA(0,255,0,0.3)`
+`HSL(540,70%,50%)`
+`HSLA(540,70%,50%,0.3)`
### Diagrams and flowcharts using Mermaid
@@ -185,6 +185,49 @@ graph TD;
C-->D;
```
+#### Subgraphs
+
+NOTE: **Note:** GitLab 12.1 and up now [requires quotes around subgraph
+titles that contain multiple words](https://github.com/knsv/mermaid/pull/845).
+
+Subgraphs can also be included:
+
+~~~
+```mermaid
+graph TB
+
+ SubGraph1 --> SubGraph1Flow
+ subgraph "SubGraph 1 Flow"
+ SubGraph1Flow(SubNode 1)
+ SubGraph1Flow -- Choice1 --> DoChoice1
+ SubGraph1Flow -- Choice2 --> DoChoice2
+ end
+
+ subgraph "Main Graph"
+ Node1[Node 1] --> Node2[Node 2]
+ Node2 --> SubGraph1[Jump to SubGraph1]
+ SubGraph1 --> FinalThing[Final Thing]
+end
+```
+~~~
+
+```mermaid
+graph TB
+
+ SubGraph1 --> SubGraph1Flow
+ subgraph "SubGraph 1 Flow"
+ SubGraph1Flow(SubNode 1)
+ SubGraph1Flow -- Choice1 --> DoChoice1
+ SubGraph1Flow -- Choice2 --> DoChoice2
+ end
+
+ subgraph "Main Graph"
+ Node1[Node 1] --> Node2[Node 2]
+ Node2 --> SubGraph1[Jump to SubGraph1]
+ SubGraph1 --> FinalThing[Final Thing]
+end
+```
+
### Emoji
> If this is not rendered correctly, [view it in GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#emoji).
@@ -397,6 +440,7 @@ unordered or ordered lists:
- [ ] Sub-task 1
- [x] Sub-task 2
- [ ] Sub-task 3
+
1. [x] Completed task
1. [ ] Incomplete task
1. [ ] Sub-task 1
@@ -408,6 +452,7 @@ unordered or ordered lists:
- [ ] Sub-task 1
- [x] Sub-task 2
- [ ] Sub-task 3
+
1. [x] Completed task
1. [ ] Incomplete task
1. [ ] Sub-task 1
@@ -488,6 +533,10 @@ This snippet links to `<wiki_root>/miscellaneous.md`:
[Link to Related Page](/miscellaneous.md)
```
+### Embedding metrics in GitLab Flavored Markdown
+
+Metric charts can be embedded within GitLab Flavored Markdown. See [Embedding Metrics within GitLab flavored Markdown](../user/project/integrations/prometheus.md#embedding-metric-charts-within-gitlab-flavored-markdown) for more details.
+
## Standard markdown and extensions in GitLab
All standard markdown formatting should work as expected within GitLab. Some standard
@@ -976,7 +1025,7 @@ after the `</summary>` tag and before the `</details>` tag, as shown in the exam
These details _will_ remain **hidden** until expanded.
- PASTE LOGS HERE
+PASTE LOGS HERE
</details>
```
@@ -988,7 +1037,7 @@ These details _will_ remain **hidden** until expanded.
These details <em>will</em> remain <b>hidden</b> until expanded.
- PASTE LOGS HERE
+PASTE LOGS HERE
</details>
@@ -1047,14 +1096,14 @@ A new line due to the previous backslash.
First paragraph.
Another line in the same paragraph.
-A third line in the same paragraph, but this time ending with two spaces.
+A third line in the same paragraph, but this time ending with two spaces.
A new line directly under the first paragraph.
<!-- (Do *NOT* remove the two ending whitespaces in the second line) -->
<!-- (They are needed for the Markdown text to render correctly on docs.gitlab.com, the backslash works fine inside GitLab itself) -->
Second paragraph.
-Another line, this time ending with a backslash.
+Another line, this time ending with a backslash.
A new line due to the previous backslash.
### Links
@@ -1135,13 +1184,13 @@ GFM will autolink almost any URL you put into your text:
### Lists
Ordered and unordered lists can be easily created. Add the number you want the list
-to start with, like `1. ` (with a space) at the start of each line for ordered lists.
+to start with, like `1.`, followed by a space, at the start of each line for ordered lists.
After the first number, it does not matter what number you use, ordered lists will be
-numbered automatically by vertical order, so repeating `1. ` for all items in the
-same list is common. If you start with a number other than `1. `, it will use that as the first
+numbered automatically by vertical order, so repeating `1.` for all items in the
+same list is common. If you start with a number other than `1.`, it will use that as the first
number, and count up from there.
-Add a `* `, `- ` or `+ ` (with a space) at the start of each line for unordered lists, but
+Add a `*`, `-` or `+`, followed by a space, at the start of each line for unordered lists, but
you should not use a mix of them.
Examples:
@@ -1156,21 +1205,27 @@ Examples:
4. And another item.
* Unordered lists can use asterisks
+
- Or minuses
+
+ Or pluses
```
+<!-- The "2." and "4." in the example above are changed to "1." below, only to match the standards on docs.gitlab.com -->
+
1. First ordered list item
-2. Another item
+1. Another item
- Unordered sub-list.
1. Actual numbers don't matter, just that it's a number
1. Ordered sub-list
1. Next ordered sub-list item
-4. And another item.
+1. And another item.
+
+- Unordered lists can use asterisks
-* Unordered lists can use asterisks
- Or minuses
-+ Or pluses
+
+- Or pluses
---
@@ -1184,14 +1239,14 @@ Example:
Second paragraph of first item.
-2. Another item
+1. Another item
```
1. First ordered list item
Second paragraph of first item.
-2. Another item
+1. Another item
---
@@ -1205,14 +1260,14 @@ Example:
Paragraph of first item.
-2. Another item
+1. Another item
```
1. First ordered list item
Paragraph of first item.
-2. Another item
+1. Another item
### Superscripts / Subscripts
diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
index 9d6a509ea72..d4db5e88672 100644
--- a/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
+++ b/doc/user/operations_dashboard/img/index_operations_dashboard_top_bar_icon.png
Binary files differ
diff --git a/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png
index ee33eee8134..7dbec81c18a 100644
--- a/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png
+++ b/doc/user/operations_dashboard/img/index_operations_dashboard_with_projects.png
Binary files differ
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 619cf34b5c3..9ecc8a80b3a 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -47,6 +47,8 @@ The following table depicts the various user permission levels in a project.
| View approved/blacklisted licenses **(ULTIMATE)** | ✓ | ✓ | ✓ | ✓ | ✓ |
| View license management reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View Security reports **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View Dependency list **(ULTIMATE)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
+| View [Design Management](project/issues/design_management.md) pages **(PREMIUM)** | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| Pull project code | ✓ (*1*) | ✓ | ✓ | ✓ | ✓ |
| View GitLab Pages protected by [access control](project/pages/introduction.md#gitlab-pages-access-control-core-only) | ✓ | ✓ | ✓ | ✓ | ✓ |
@@ -63,7 +65,6 @@ The following table depicts the various user permission levels in a project.
| Lock issue threads | | ✓ | ✓ | ✓ | ✓ |
| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
| Manage related issues **(STARTER)** | | ✓ | ✓ | ✓ | ✓ |
-| Create issue from vulnerability **(ULTIMATE)** | | ✓ | ✓ | ✓ | ✓ |
| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
| See a commit status | | ✓ | ✓ | ✓ | ✓ |
@@ -74,6 +75,7 @@ The following table depicts the various user permission levels in a project.
| View Error Tracking list | | ✓ | ✓ | ✓ | ✓ |
| Pull from [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | ✓ | ✓ | ✓ | ✓ |
| Publish to [Maven repository](project/packages/maven_repository.md) or [NPM registry](project/packages/npm_registry.md) **(PREMIUM)** | | | ✓ | ✓ | ✓ ||
+| Upload [Design Management](project/issues/design_management.md) files **(PREMIUM)** | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
@@ -92,6 +94,8 @@ The following table depicts the various user permission levels in a project.
| Remove a container registry image | | | ✓ | ✓ | ✓ |
| Create/edit/delete project milestones | | | ✓ | ✓ | ✓ |
| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
+| View vulnerabilities in Dependency list **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
+| Create issue from vulnerability **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Dismiss vulnerability **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
| Apply code change suggestions | | | ✓ | ✓ | ✓ |
| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
@@ -122,6 +126,7 @@ The following table depicts the various user permission levels in a project.
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
| Delete issues | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
| Force push to protected branches (*4*) | | | | | |
| Remove protected branches (*4*) | | | | | |
@@ -157,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
@@ -209,12 +214,17 @@ group.
| Create project in group | | | ✓ | ✓ | ✓ |
| Create/edit/delete group milestones | | | ✓ | ✓ | ✓ |
| Enable/disable a dependency proxy **(PREMIUM)** | | | ✓ | ✓ | ✓ |
+| Use security dashboard **(ULTIMATE)** | | | ✓ | ✓ | ✓ |
+| Create subgroup | | | | ✓ (1) | ✓ |
| Edit group | | | | | ✓ |
-| Create subgroup | | | | | ✓ |
| Manage group members | | | | | ✓ |
| Remove group | | | | | ✓ |
| Delete group epic **(ULTIMATE)** | | | | | ✓ |
| View group Audit Events | | | | | ✓ |
+| Disable notification emails | | | | | ✓ |
+
+- (1): Groups can be set to [allow either Owners or Owners and
+ Maintainers to create subgroups](group/subgroups/index.md#creating-a-subgroup)
### Subgroup permissions
@@ -225,6 +235,19 @@ nested groups if you have membership in one of its parents.
To learn more, read through the documentation on
[subgroups memberships](group/subgroups/index.md#membership).
+## Guest User
+
+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
In cases where it is desired that a user has access only to some internal or
diff --git a/doc/user/profile/account/delete_account.md b/doc/user/profile/account/delete_account.md
index 304a7984191..5b0954195f8 100644
--- a/doc/user/profile/account/delete_account.md
+++ b/doc/user/profile/account/delete_account.md
@@ -1,37 +1,87 @@
-# Deleting a User Account
+---
+type: howto
+---
+
+# Deleting a User account
+
+Users can be deleted from a GitLab instance, either by:
+
+- The user themselves.
+- An administrator.
NOTE: **Note:**
Deleting a user will delete all projects in that user namespace.
-- As a user, you can delete your own account by navigating to **Settings** > **Account** and selecting **Delete account**
-- As an admin, you can delete a user account by navigating to the **Admin Area**, selecting the **Users** tab, selecting a user, and clicking on **Delete user**
+## As a user
+
+As a user, you can delete your own account by:
+
+1. Clicking on your avatar.
+1. Navigating to **Settings > Account**.
+1. Selecting **Delete account**.
+
+## As an administrator
+
+As an administrator, you can delete a user account by:
+
+1. Navigating to **Admin Area > Overview > Users**.
+1. Selecting a user.
+1. Under the **Account** tab, clicking:
+ - **Delete user** to delete only the user but maintaining their
+ [associated records](#associated-records).
+ - **Delete user and contributions** to delete the user and
+ their associated records.
+
+### Blocking a user
+
+In addition to blocking a user
+[via an abuse report](../../admin_area/abuse_reports.md#blocking-users),
+a user can be blocked directly from the Admin area. To do this:
+
+1. Navigate to **Admin Area > Overview > Users**.
+1. Selecting a user.
+1. Under the **Account** tab, click **Block user**.
## Associated Records
-> Introduced for issues in [GitLab 9.0][ce-7393], and for merge requests, award
- emoji, notes, and abuse reports in [GitLab 9.1][ce-10467].
- Hard deletion from abuse reports and spam logs was introduced in
- [GitLab 9.1][ce-10273], and from the API in [GitLab 9.3][ce-11853].
+> - Introduced for issues in
+> [GitLab 9.0](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7393).
+> - Introduced for merge requests, award emoji, notes, and abuse reports in
+> [GitLab 9.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467).
+> - Hard deletion from abuse reports and spam logs was introduced in
+> [GitLab 9.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273),
+> and from the API in
+> [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853).
When a user account is deleted, not all associated records are deleted with it.
Here's a list of things that will **not** be deleted:
-- Issues that the user created
-- Merge requests that the user created
-- Notes that the user created
-- Abuse reports that the user reported
-- Award emoji that the user created
+- Issues that the user created.
+- Merge requests that the user created.
+- Notes that the user created.
+- Abuse reports that the user reported.
+- Award emoji that the user created.
Instead of being deleted, these records will be moved to a system-wide
-user with the username "Ghost User", whose sole purpose is to act as a container for such records. Any commits made by a deleted user will still display the username of the original user.
+user with the username "Ghost User", whose sole purpose is to act as a container
+for such records. Any commits made by a deleted user will still display the
+username of the original user.
-When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md) or spam log, these associated
+When a user is deleted from an [abuse report](../../admin_area/abuse_reports.md)
+or spam log, these associated
records are not ghosted and will be removed, along with any groups the user
-is a sole owner of. Administrators can also request this behaviour when
+is a sole owner of. Administrators can also request this behavior when
deleting users from the [API](../../../api/users.md#user-deletion) or the
-admin area.
+Admin Area.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
-[ce-7393]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7393
-[ce-10273]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10273
-[ce-10467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10467
-[ce-11853]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11853
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index d3a634c7b9d..e2b1a20a605 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Two-Factor Authentication
Two-factor Authentication (2FA) provides an additional level of security to your
@@ -15,7 +19,7 @@ When you enable 2FA, don't forget to back up your [recovery codes](#recovery-cod
In addition to time-based one time passwords (TOTP), GitLab supports U2F
(universal 2nd factor) devices as the second factor of authentication. Once
-enabled, in addition to supplying your username and password to login, you'll
+enabled, in addition to supplying your username and password to log in, you'll
be prompted to activate your U2F device (usually by pressing a button on it),
and it will perform secure authentication on your behalf.
@@ -238,3 +242,15 @@ Sign in and re-enable two-factor authentication as soon as possible.
- The user logs out and attempts to log in via `first.host.xyz` - U2F authentication succeeds.
- The user logs out and attempts to log in via `second.host.xyz` - U2F authentication fails, because
the U2F key has only been registered on `first.host.xyz`.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/active_sessions.md b/doc/user/profile/active_sessions.md
index 28e3f4904a9..2d7bd25fc27 100644
--- a/doc/user/profile/active_sessions.md
+++ b/doc/user/profile/active_sessions.md
@@ -1,14 +1,31 @@
+---
+type: howto
+---
+
# Active Sessions
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17867)
> in GitLab 10.8.
GitLab lists all devices that have logged into your account. This allows you to
-review the sessions.
+review the sessions, and revoke any you don't recognize.
## Listing all active sessions
-1. On the upper right corner, click on your avatar and go to your **Settings**.
-1. Navigate to the **Active Sessions** tab.
+1. Click your avatar.
+1. Select **Settings**.
+1. Click **Active Sessions** in the sidebar.
![Active sessions list](img/active_sessions_list.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/img/active_sessions_list.png b/doc/user/profile/img/active_sessions_list.png
index 1e242ac4710..41173c7eee5 100644
--- a/doc/user/profile/img/active_sessions_list.png
+++ b/doc/user/profile/img/active_sessions_list.png
Binary files differ
diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md
index 61a30a775b0..e5ccc8ee758 100644
--- a/doc/user/profile/index.md
+++ b/doc/user/profile/index.md
@@ -1,7 +1,12 @@
+---
+type: index, howto
+---
+
# User account
-When signed into their GitLab account, users can customize their
-experience according to the best approach to their cases.
+Each GitLab account has a user profile, and settings. Your [profile](#user-profile)
+contains information about you, and your GitLab activity. Your [settings](#profile-settings)
+allow you to customize some aspects of GitLab to suit yourself.
## Signing in
@@ -10,8 +15,10 @@ See the [authentication topic](../../topics/authentication/index.md) for more de
## User profile
-Your profile is available from the up-right corner menu bar (user's avatar) > **Profile**,
-or from `https://example.gitlab.com/username`.
+To access your profile:
+
+1. Click on your avatar.
+1. Select **Profile**.
On your profile page, you will see the following information:
@@ -20,12 +27,15 @@ On your profile page, you will see the following information:
- Groups: [groups](../group/index.md) you're a member of
- Contributed projects: [projects](../project/index.md) you contributed to
- Personal projects: your personal projects (respecting the project's visibility level)
+- Starred projects: projects you starred
- Snippets: your personal code [snippets](../snippets.md#personal-snippets)
## Profile settings
-You can edit your account settings by navigating from the up-right corner menu bar
-(user's avatar) > **Settings**, or visiting `https://example.gitlab.com/profile`.
+To access your profile settings:
+
+1. Click on your avatar.
+1. Select **Settings**.
From there, you can:
@@ -56,8 +66,8 @@ before proceeding.
To change your `username`:
1. Navigate to your [profile's](#profile-settings) **Settings > Account**.
-1. Enter a new username under "Change username".
-1. Hit **Update username**.
+1. Enter a new username under **Change username**.
+1. Click **Update username**.
CAUTION: **Caution:**
It is currently not possible to change your username if it contains a
@@ -82,16 +92,20 @@ The following information will be hidden from the user profile page (`https://gi
- Groups tab
- Contributed projects tab
- Personal projects tab
+- Starred projects tab
- Snippets tab
To enable private profile:
-1. Navigate to your personal [profile settings](#profile-settings).
-1. Check the "Private profile" option.
-1. Hit **Update profile settings**.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Check the **Private profile** option in the **Main settings** section.
+1. Click **Update profile settings**.
NOTE: **Note:**
-You and GitLab admins can see your the abovementioned information on your profile even if it is private.
+All your profile information can be seen by yourself, and GitLab admins, even if
+the **Private profile** option is enabled.
## Add details of external accounts
@@ -99,9 +113,15 @@ GitLab allows you to add links to certain other external accounts you might have
To add links to other accounts:
-1. Navigate to your **User Settings > Profile**.
-1. In the **Main settings** section, locate and fill out fields for links to external accounts like Skype and Twitter.
-1. Click the **Update profile settings** button.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Complete the desired fields for external accounts, in the **Main settings**
+ section:
+ - Skype
+ - Twitter
+ - LinkedIn
+1. Click **Update profile settings**.
## Private contributions
@@ -111,9 +131,11 @@ Enabling private contributions will include contributions to private projects, i
To enable private contributions:
-1. Navigate to your personal [profile settings](#profile-settings).
-1. Check the "Private contributions" option.
-1. Hit **Update profile settings**.
+1. Click on your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Check the **Private contributions** option.
+1. Click **Update profile settings**.
## Current status
@@ -124,22 +146,24 @@ This may be helpful when you are out of office or otherwise not available.
Other users can then take your status into consideration when responding to your issues or assigning work to you.
Please be aware that your status is publicly visible even if your [profile is private](#private-profile).
+Status messages are restricted to 100 characters of plain text.
+They may however contain emoji codes such as `I'm on vacation :palm_tree:`.
+
To set your current status:
-1. Open the user menu in the top-right corner of the navigation bar.
-1. Hit **Set status**, or **Edit status** if you have already set a status.
-1. Set the emoji and/or status message to your liking.
-1. Hit **Set status**. Alternatively, you can also hit **Remove status** to remove your user status entirely.
+1. Click your avatar.
+1. Click **Set status**, or **Edit status** if you have already set a status.
+1. Set the desired emoji and/or status message.
+1. Click **Set status**. Alternatively, you can click **Remove status** to remove your user status entirely.
or
-1. Navigate to your personal [profile settings](#profile-settings).
-1. In the text field below `Your status`, enter your status message.
-1. Select an emoji from the dropdown if you like.
-1. Hit **Update profile settings**.
-
-Status messages are restricted to 100 characters of plain text.
-They may however contain emoji codes such as `I'm on vacation :palm_tree:`.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Enter your status message in the **Your status** text field.
+1. Click **Add status emoji** (smiley face), and select the desired emoji.
+1. Click **Update profile settings**.
You can also set your current status [using the API](../../api/users.md#user-status).
@@ -147,39 +171,42 @@ You can also set your current status [using the API](../../api/users.md#user-sta
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21598) in GitLab 11.4.
-A commit email, is the email that will be displayed in every Git-related action done through the
-GitLab interface.
+A commit email is an email address displayed in every Git-related action carried out through the GitLab interface.
-You are able to select from the list of your own verified emails which email you want to use as the commit email.
+Any of your own verified email addresses can be used as the commit email.
-To change it:
+To change your commit email:
-1. Open the user menu in the top-right corner of the navigation bar.
-1. Hit **Commit email** selection box.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Click **Commit email** dropdown.
1. Select any of the verified emails.
-1. Hit **Update profile settings**.
+1. Click **Update profile settings**.
### Private commit email
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22560) in GitLab 11.5.
GitLab provides the user with an automatically generated private commit email option,
-which allows the user to not make their email information public.
+which allows the user to keep their email information private.
To enable this option:
-1. Open the user menu in the top-right corner of the navigation bar.
-1. Hit **Commit email** selection box.
+1. Click your avatar.
+1. Select **Profile**.
+1. Click **Edit profile** (pencil icon).
+1. Click **Commit email** dropdown.
1. Select **Use a private email** option.
-1. Hit **Update profile settings**.
+1. Click **Update profile settings**.
Once this option is enabled, every Git-related action will be performed using the private commit email.
-In order to stay fully annonymous, you can also copy this private commit email
+To stay fully anonymous, you can also copy this private commit email
and configure it on your local machine using the following command:
-```
-git config --global user.email "YOUR_PRIVATE_COMMIT_EMAIL"
+```sh
+git config --global user.email <your email address>
```
## Troubleshooting
@@ -202,3 +229,15 @@ to get you a new `_gitlab_session` and keep you signed in through browser restar
After your `remember_user_token` expires and your `_gitlab_session` is cleared/expired,
you will be asked to sign in again to verify your identity (which is for security reasons).
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 0b224fc7e01..d556daa3460 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Personal access tokens
> [Introduced][ce-3749] in GitLab 8.8.
@@ -40,7 +44,7 @@ the following table.
| Scope | Introduced in | Description |
| ------------------ | ------------- | ----------- |
| `read_user` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Allows access to the read-only endpoints under `/users`. Essentially, any of the `GET` requests in the [Users API][users] are allowed. |
-| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete access to the API and Container Registry (read/write). |
+| `api` | [GitLab 8.15](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5951) | Grants complete read/write access to the API, including all groups and projects, the container registry, and the package registry. |
| `read_registry` | [GitLab 9.3](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/11845) | Allows to read (pull) [container registry] images if a project is private and authorization is required. |
| `sudo` | [GitLab 10.2](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14838) | Allows performing API actions as any user in the system (if the authenticated user is an admin). |
| `read_repository` | [GitLab 10.7](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17894) | Allows read-only access (pull) to the repository through git clone. |
@@ -52,3 +56,15 @@ the following table.
[container registry]: ../project/container_registry.md
[users]: ../../api/users.md
[usage]: ../../api/README.md#personal-access-tokens
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index b1fde3b577b..82a6d2b3703 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -1,11 +1,17 @@
+---
+type: concepts, howto
+---
+
# Profile preferences
A user's profile preferences page allows the user to customize various aspects
of GitLab to their liking.
-To navigate to your profile's preferences, click your avatar icon in the top
-right corner, select **Settings** and then choose **Preferences** from the
-left sidebar.
+To navigate to your profile's preferences:
+
+1. Click your avatar.
+1. Select **Settings**.
+1. Click **Preferences** in the sidebar.
## Navigation theme
@@ -15,7 +21,7 @@ and left side navigation.
Using individual color themes might help you differentiate between your different
GitLab instances.
-The default palette is Indigo. You can choose between 10 different themes:
+The default theme is Indigo. You can choose between 10 themes:
- Indigo
- Light Indigo
@@ -39,7 +45,7 @@ for syntax highlighting. For a list of supported languages visit the rouge websi
Changing this setting allows you to customize the color theme when viewing any
syntax highlighted code on GitLab.
-The default syntax theme is White, and you can choose among 5 different colors:
+The default syntax theme is White, and you can choose among 5 different themes:
- White
- Dark
@@ -81,6 +87,16 @@ You have 8 options here that you can use for your default dashboard view:
- Assigned Merge Requests
- Operations Dashboard **(PREMIUM)**
+### Group overview content
+
+The **Group overview content** dropdown allows you to choose what information is
+displayed on a group’s home page.
+
+You can choose between 2 options:
+
+- Details (default)
+- [Security dashboard](../application_security/security_dashboard/index.md) **(ULTIMATE)**
+
### Project overview content
The project overview content setting allows you to choose what content you want to
@@ -102,7 +118,7 @@ Select your preferred language from a list of supported languages.
### First day of the week
-The first day of the week can be customised for calendar views and date pickers.
+The first day of the week can be customized for calendar views and date pickers.
You can choose one of the following options as the first day of the week:
@@ -111,3 +127,15 @@ You can choose one of the following options as the first day of the week:
- Monday
If you select **System Default**, the system-wide default setting will be used.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
index 8849dd2d684..cd3e5f5a63c 100644
--- a/doc/user/project/badges.md
+++ b/doc/user/project/badges.md
@@ -17,10 +17,10 @@ If you find that you have to add the same badges to several projects, you may wa
To add a new badge to a project:
-1. Navigate to your project's **Settings > General > Badges**.
-1. Under "Link", enter the URL that the badges should point to and under
- "Badge image URL" the URL of the image that should be displayed.
-1. Submit the badge by clicking the **Add badge** button.
+1. Navigate to your project's **Settings > General > Badges**.
+1. Under "Link", enter the URL that the badges should point to and under
+ "Badge image URL" the URL of the image that should be displayed.
+1. Submit the badge by clicking the **Add badge** button.
After adding a badge to a project, you can see it in the list below the form.
You can edit it by clicking on the pen icon next to it or to delete it by
@@ -39,10 +39,10 @@ project, consider adding them on the [project level](#project-badges) or use
To add a new badge to a group:
-1. Navigate to your group's **Settings > General > Badges**.
-1. Under "Link", enter the URL that the badges should point to and under
- "Badge image URL" the URL of the image that should be displayed.
-1. Submit the badge by clicking the **Add badge** button.
+1. Navigate to your group's **Settings > General > Badges**.
+1. Under "Link", enter the URL that the badges should point to and under
+ "Badge image URL" the URL of the image that should be displayed.
+1. Submit the badge by clicking the **Add badge** button.
After adding a badge to a group, you can see it in the list below the form.
You can edit the badge by clicking on the pen icon next to it or to delete it
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index d0c7daf4692..f4733620640 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -13,13 +13,20 @@ by using the bulk editing feature.
![Bulk editing](img/bulk-editing.png)
NOTE: **Note:**
-Bulk editing of issues and merge requests is only available at the project level.
+Bulk editing issues and merge requests is also available at the group level.
+For more details, see [bulk editing group issues and merge requests](../group/bulk_editing/index.md).
-To update multiple project issues or merge requests at the same time, navigate to
-their respective lists and click **Edit issues** or **Edit merge requests** available
-in the tab bar. This will open a sidebar on the right-hand side of your screen
-where editable fields will be displayed. Checkboxes will also appear to the left-hand
-side of eachissue or merge request for you to select the items you want to update.
+To update multiple project issues or merge requests at the same time:
-Once you have selected all relevant items, choose the appropriate fields and their
-values from the sidebar and click **Update all** to apply your changes.
+1. Navigate to their respective list.
+
+1. Click **Edit issues** or **Edit merge requests**.
+
+ - This will open a sidebar on the right-hand side of your screen
+ where editable fields will be displayed.
+
+ - Checkboxes will also appear beside each issue or merge request.
+
+1. Check the checkboxes of each items to be edited.
+1. Choose the appropriate fields and their values from the sidebar.
+1. Click **Update all**.
diff --git a/doc/user/project/clusters/eks_and_gitlab/index.md b/doc/user/project/clusters/eks_and_gitlab/index.md
index 55a9fbabf98..28f3420de35 100644
--- a/doc/user/project/clusters/eks_and_gitlab/index.md
+++ b/doc/user/project/clusters/eks_and_gitlab/index.md
@@ -253,7 +253,7 @@ With RBAC disabled and services deployed,
[Auto DevOps](../../../../topics/autodevops/index.md) can now be leveraged
to build, test, and deploy the app.
-[Enable Auto DevOps](../../../../topics/autodevops/index.md#enablingdisabling-auto-devops-at-the-project-level)
+[Enable Auto DevOps](../../../../topics/autodevops/index.md#at-the-project-level)
if not already enabled. If a wildcard DNS entry was created resolving to the
Load Balancer, enter it in the `domain` field under the Auto DevOps settings.
Otherwise, the deployed app will not be externally available outside of the cluster.
diff --git a/doc/user/project/clusters/img/k8s_cluster_monitoring.png b/doc/user/project/clusters/img/k8s_cluster_monitoring.png
index e449893a606..0a8c5043c65 100644
--- a/doc/user/project/clusters/img/k8s_cluster_monitoring.png
+++ b/doc/user/project/clusters/img/k8s_cluster_monitoring.png
Binary files differ
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index 4c247691757..cf3a3fef79f 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -1,33 +1,113 @@
-# Connecting GitLab with a Kubernetes cluster
+# Kubernetes clusters
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in GitLab 10.1.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35954) in GitLab 10.1 for projects.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) in
+> GitLab 11.6 for [groups](../../group/clusters/index.md).
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840) in
+> GitLab 11.11 for [instances](../../instance/clusters/index.md).
-Connect your project to Google Kubernetes Engine (GKE) or an existing Kubernetes
-cluster in a few steps.
+GitLab provides many features with a Kubernetes integration. Kubernetes can be
+integrated with projects, but also:
+
+- [Groups](../../group/clusters/index.md).
+- [Instances](../../instance/clusters/index.md).
NOTE: **Scalable app deployment with GitLab and Google Cloud Platform**
[Watch the webcast](https://about.gitlab.com/webcast/scalable-app-deploy/) and learn how to spin up a Kubernetes cluster managed by Google Cloud Platform (GCP) in a few clicks.
## Overview
-With one or more Kubernetes clusters associated to your project, you can use
-[Review Apps](../../../ci/review_apps/index.md), deploy your applications, run
-your pipelines, use it with [Auto DevOps](../../../topics/autodevops/index.md),
-and much more, all from within GitLab.
+Using the GitLab project Kubernetes integration, you can:
+
+- Use [Review Apps](../../../ci/review_apps/index.md).
+- Run [pipelines](../../../ci/pipelines.md).
+- [Deploy](#deploying-to-a-kubernetes-cluster) your applications.
+- Detect and [monitor Kubernetes](#kubernetes-monitoring).
+- Use it with [Auto DevOps](#auto-devops).
+- Use [Web terminals](#web-terminals).
+- Use [Deploy Boards](#deploy-boards-premium). **(PREMIUM)**
+- Use [Canary Deployments](#canary-deployments-premium). **(PREMIUM)**
+- View [Pod logs](#pod-logs-ultimate). **(ULTIMATE)**
+
+You can also:
+
+- Connect and deploy to an [Amazon EKS cluster](eks_and_gitlab/index.html).
+- Run serverless workloads on [Kubernetes with Knative](serverless/index.md).
+
+### Deploy Boards **(PREMIUM)**
+
+GitLab's Deploy Boards offer a consolidated view of the current health and
+status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
+displaying the status of the pods in the deployment. Developers and other
+teammates can view the progress and status of a rollout, pod by pod, in the
+workflow they already use without any need to access Kubernetes.
+
+[Read more about Deploy Boards](../deploy_boards.md)
+
+### Canary Deployments **(PREMIUM)**
+
+Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
+and visualize your canary deployments right inside the Deploy Board, without
+the need to leave GitLab.
+
+[Read more about Canary Deployments](../canary_deployments.md)
+
+### Pod logs **(ULTIMATE)**
+
+GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
+
+[Read more about Kubernetes pod logs](kubernetes_pod_logs.md)
+
+### Kubernetes monitoring
+
+Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
+[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
+
+[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
+
+### Auto DevOps
+
+Auto DevOps automatically detects, builds, tests, deploys, and monitors your
+applications.
+
+To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
+you will need the Kubernetes project integration enabled.
+
+[Read more about Auto DevOps](../../../topics/autodevops/index.md)
+
+NOTE: **Note**
+Kubernetes clusters can be used without Auto DevOps.
-There are two options when adding a new cluster to your project; either associate
-your account with Google Kubernetes Engine (GKE) so that you can [create new
-clusters](#adding-and-creating-a-new-gke-cluster-via-gitlab) from within GitLab,
-or provide the credentials to an [existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster).
+### Web terminals
NOTE: **Note:**
-From [GitLab 11.6](https://gitlab.com/gitlab-org/gitlab-ce/issues/34758) you
-can also associate a Kubernetes cluster to your groups and from
-[GitLab 11.11](https://gitlab.com/gitlab-org/gitlab-ce/issues/39840),
-to the GitLab instance. Learn more about [group-level](../../group/clusters/index.md)
-and [instance-level](../../instance/clusters/index.md) Kubernetes clusters.
+Introduced in GitLab 8.15. You must be the project owner or have `maintainer` permissions
+to use terminals. Support is limited to the first container in the
+first pod of your environment.
-## Adding and creating a new GKE cluster via GitLab
+When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
+support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
+Docker and Kubernetes, so you get a new shell session within your existing
+containers. To use this integration, you should deploy to Kubernetes using
+the deployment variables above, ensuring any deployments, replica sets, and
+pods are annotated with:
+
+- `app.gitlab.com/env: $CI_ENVIRONMENT_SLUG`
+- `app.gitlab.com/app: $CI_PROJECT_PATH_SLUG`
+
+`$CI_ENVIRONMENT_SLUG` and `$CI_PROJECT_PATH_SLUG` are the values of
+the CI variables.
+
+## Adding and removing clusters
+
+There are two options when adding a new cluster to your project:
+
+- Associate your account with Google Kubernetes Engine (GKE) to
+ [create new clusters](#add-new-gke-cluster) from within GitLab.
+- Provide credentials to an
+ [existing Kubernetes cluster](#add-existing-kubernetes-cluster).
+
+### Add new GKE cluster
TIP: **Tip:**
Every new Google Cloud Platform (GCP) account receives [$300 in credit upon sign up](https://console.cloud.google.com/freetrial),
@@ -39,7 +119,7 @@ The [Google authentication integration](../../../integration/google.md) must
be enabled in GitLab at the instance level. If that's not the case, ask your
GitLab administrator to enable it. On GitLab.com, this is enabled.
-### Requirements
+#### Requirements
Before creating your first cluster on Google Kubernetes Engine with GitLab's
integration, make sure the following requirements are met:
@@ -49,7 +129,7 @@ integration, make sure the following requirements are met:
- The Kubernetes Engine API and related service are enabled. It should work immediately but may take up to 10 minutes after you create a project. For more information see the
["Before you begin" section of the Kubernetes Engine docs](https://cloud.google.com/kubernetes-engine/docs/quickstart#before-you-begin).
-### Creating the cluster
+#### Creating the cluster
If all of the above requirements are met, you can proceed to create and add a
new Kubernetes cluster to your project:
@@ -57,7 +137,7 @@ new Kubernetes cluster to your project:
1. Navigate to your project's **Operations > Kubernetes** page.
NOTE: **Note:**
- You need Maintainer [permissions] and above to access the Kubernetes page.
+ You need Maintainer [permissions](../../permissions.md) and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Create with Google Kubernetes Engine**.
@@ -91,14 +171,17 @@ client certificate is enabled.
NOTE: **Note:**
Starting from [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/issues/55902), all GKE clusters created by GitLab are RBAC enabled. Take a look at the [RBAC section](#rbac-cluster-resources) for more information.
-## Adding an existing Kubernetes cluster
+### Add existing Kubernetes cluster
+
+NOTE: **Note:**
+Kubernetes integration is not supported for arm64 clusters. See the issue [Helm Tiller fails to install on arm64 cluster](https://gitlab.com/gitlab-org/gitlab-ce/issues/64044) for details.
To add an existing Kubernetes cluster to your project:
1. Navigate to your project's **Operations > Kubernetes** page.
NOTE: **Note:**
- You need Maintainer [permissions] and above to access the Kubernetes page.
+ You need Maintainer [permissions](../../permissions.md) and above to access the Kubernetes page.
1. Click **Add Kubernetes cluster**.
1. Click **Add an existing Kubernetes cluster** and fill in the details:
@@ -216,7 +299,36 @@ To add an existing Kubernetes cluster to your project:
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
-## Security implications
+### Enabling or disabling integration
+
+After you have successfully added your cluster information, you can enable the
+Kubernetes cluster integration:
+
+1. Click the **Enabled/Disabled** switch
+1. Hit **Save** for the changes to take effect
+
+To disable the Kubernetes cluster integration, follow the same procedure.
+
+### Removing integration
+
+NOTE: **Note:**
+You need Maintainer [permissions](../../permissions.md) and above to remove a Kubernetes cluster integration.
+
+NOTE: **Note:**
+When you remove a cluster, you only remove its relation to GitLab, not the
+cluster itself. To remove the cluster, you can do so by visiting the GKE
+dashboard or using `kubectl`.
+
+To remove the Kubernetes cluster integration from your project, simply click the
+**Remove integration** button. You will then be able to follow the procedure
+and add a Kubernetes cluster again.
+
+## Cluster configuration
+
+This section covers important considerations for configuring Kubernetes
+clusters with GitLab.
+
+### Security implications
CAUTION: **Important:**
The whole cluster security is based on a model where [developers](../../permissions.md)
@@ -227,7 +339,7 @@ functionalities needed to successfully build and deploy a containerized
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
-## GitLab-managed clusters
+### GitLab-managed clusters
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5.
> Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11.
@@ -246,7 +358,7 @@ NOTE: **Note:**
If you [install applications](#installing-applications) on your cluster, GitLab will create
the resources required to run these even if you have chosen to manage your own cluster.
-## Base domain
+### Base domain
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
@@ -264,7 +376,7 @@ you can either:
- Create an `A` record that points to the Ingress IP address with your domain provider.
- Enter a wildcard DNS address using a service such as nip.io or xip.io. For example, `192.168.1.1.xip.io`.
-## Access controls
+### Access controls
When creating a cluster in GitLab, you will be asked if you would like to create either:
@@ -275,13 +387,9 @@ NOTE: **Note:**
[RBAC](#rbac-cluster-resources) is recommended and the GitLab default.
GitLab creates the necessary service accounts and privileges to install and run
-[GitLab managed applications](#installing-applications). When GitLab creates the cluster:
-
-- A `gitlab` service account with `cluster-admin` privileges is created in the `default` namespace
- to manage the newly created cluster.
-- A project service account with [`edit`
- privileges](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
- is created in the GitLab-created project namespace for [deployment jobs](#deployment-variables).
+[GitLab managed applications](#installing-applications). When GitLab creates the cluster,
+a `gitlab` service account with `cluster-admin` privileges is created in the `default` namespace
+to manage the newly created cluster.
NOTE: **Note:**
Restricted service account for deployment was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/51716) in GitLab 11.5.
@@ -294,47 +402,53 @@ Helm will also create additional service accounts and other resources for each
installed application. Consult the documentation of the Helm charts for each application
for details.
-If you are [adding an existing Kubernetes cluster](#adding-an-existing-kubernetes-cluster),
+If you are [adding an existing Kubernetes cluster](#add-existing-kubernetes-cluster),
ensure the token of the account has administrator privileges for the cluster.
The resources created by GitLab differ depending on the type of cluster.
-### ABAC cluster resources
+#### ABAC cluster resources
GitLab creates the following resources for ABAC clusters.
-| Name | Type | Details | Created when |
-|:------------------|:---------------------|:----------------------------------|:---------------------------|
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
+| Name | Type | Details | Created when |
+|:----------------------|:---------------------|:-------------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Environment namespace | `Namespace` | Contains all environment-specific resources | Deploying to a cluster |
+| Environment namespace | `ServiceAccount` | Uses namespace of environment | Deploying to a cluster |
+| Environment namespace | `Secret` | Token for environment ServiceAccount | Deploying to a cluster |
-### RBAC cluster resources
+#### RBAC cluster resources
GitLab creates the following resources for RBAC clusters.
-| Name | Type | Details | Created when |
-|:------------------|:---------------------|:-----------------------------------------------------------------------------------------------------------|:---------------------------|
-| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
-| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
-| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
-| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
-| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
-| Project namespace | `ServiceAccount` | Uses namespace of Project | Deploying to a cluster |
-| Project namespace | `Secret` | Token for project ServiceAccount | Deploying to a cluster |
-| Project namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Deploying to a cluster |
+| Name | Type | Details | Created when |
+|:----------------------|:---------------------|:-----------------------------------------------------------------------------------------------------------|:---------------------------|
+| `gitlab` | `ServiceAccount` | `default` namespace | Creating a new GKE Cluster |
+| `gitlab-admin` | `ClusterRoleBinding` | [`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Creating a new GKE Cluster |
+| `gitlab-token` | `Secret` | Token for `gitlab` ServiceAccount | Creating a new GKE Cluster |
+| `tiller` | `ServiceAccount` | `gitlab-managed-apps` namespace | Installing Helm Tiller |
+| `tiller-admin` | `ClusterRoleBinding` | `cluster-admin` roleRef | Installing Helm Tiller |
+| Environment namespace | `Namespace` | Contains all environment-specific resources | Deploying to a cluster |
+| Environment namespace | `ServiceAccount` | Uses namespace of environment | Deploying to a cluster |
+| Environment namespace | `Secret` | Token for environment ServiceAccount | Deploying to a cluster |
+| Environment namespace | `RoleBinding` | [`edit`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) roleRef | Deploying to a cluster |
+
+NOTE: **Note:**
+Environment-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters).
NOTE: **Note:**
-Project-specific resources are only created if your cluster is [managed by GitLab](#gitlab-managed-clusters).
+If your cluster was created before GitLab 12.2, it will use a single namespace for all project environments.
-### Security of GitLab Runners
+#### Security of GitLab Runners
GitLab Runners have the [privileged mode](https://docs.gitlab.com/runner/executors/docker.html#the-privileged-mode)
enabled by default, which allows them to execute special commands and running
-Docker in Docker. This functionality is needed to run some of the [Auto DevOps]
+Docker in Docker. This functionality is needed to run some of the
+[Auto DevOps](../../../topics/autodevops/index.md)
jobs. This implies the containers are running in privileged mode and you should,
therefore, be aware of some important details.
@@ -343,10 +457,81 @@ turn can do almost everything that the host can do. Be aware of the
inherent security risk associated with performing `docker run` operations on
arbitrary images as they effectively have root access.
-If you don't want to use GitLab Runner in privileged mode, first make sure that
-you don't have it installed via the applications, and then use the
-[Runner's Helm chart](../../../install/kubernetes/gitlab_runner_chart.md) to
-install it manually.
+If you don't want to use GitLab Runner in privileged mode, either:
+
+- Use shared Runners on GitLab.com. They don't have this security issue.
+- Set up your own Runners using configuration described at
+ [Shared Runners](../../gitlab_com/index.md#shared-runners). This involves:
+ 1. Making sure that you don't have it installed via
+ [the applications](#installing-applications).
+ 1. Installing a Runner
+ [using `docker+machine`](https://docs.gitlab.com/runner/executors/docker_machine.html).
+
+### Setting the environment scope **(PREMIUM)**
+
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables) work.
+
+The default environment scope is `*`, which means all jobs, regardless of their
+environment, will use that cluster. Each scope can only be used by a single
+cluster in a project, and a validation error will occur if otherwise.
+Also, jobs that don't have an environment keyword set will not be able to access any cluster.
+
+For example, let's say the following Kubernetes clusters exist in a project:
+
+| Cluster | Environment scope |
+| ----------- | ----------------- |
+| Development | `*` |
+| Production | `production` |
+
+And the following environments are set in
+[`.gitlab-ci.yml`](../../../ci/yaml/README.md):
+
+```yaml
+stages:
+- test
+- deploy
+
+test:
+ stage: test
+ script: sh test
+
+deploy to staging:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: staging
+ url: https://staging.example.com/
+
+deploy to production:
+ stage: deploy
+ script: make deploy
+ environment:
+ name: production
+ url: https://example.com/
+```
+
+The result will then be:
+
+- The Development cluster details will be available in the `deploy to staging`
+ job.
+- The production cluster details will be available in the `deploy to production`
+ job.
+- No cluster details will be available in the `test` job because it doesn't
+ define any environment.
+
+### Multiple Kubernetes clusters **(PREMIUM)**
+
+> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.3.
+
+With GitLab Premium, you can associate more than one Kubernetes cluster to your
+project. That way you can have different clusters for different environments,
+like dev, staging, production, etc.
+
+Simply add another cluster, like you did the first time, and make sure to
+[set an environment scope](#setting-the-environment-scope-premium) that will
+differentiate the new cluster with the rest.
## Installing applications
@@ -355,7 +540,7 @@ cluster. For more information on installing, upgrading, uninstalling,
and troubleshooting applications for your project cluster, see
[Gitlab Managed Apps](../../clusters/applications.md).
-## Getting the external endpoint
+### Getting the external endpoint
NOTE: **Note:**
With the following procedure, a load balancer must be installed in your cluster
@@ -366,7 +551,7 @@ to obtain the endpoint. You can use either
In order to publish your web application, you first need to find the endpoint which will be either an IP
address or a hostname associated with your load balancer.
-### Automatically determining the external endpoint
+#### Automatically determining the external endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17052) in GitLab 10.6.
@@ -381,7 +566,7 @@ and your cluster runs on Google Kubernetes Engine:
If GitLab is still unable to determine the endpoint of your Ingress or Knative application, you can
manually determine it by following the steps below.
-### Manually determining the external endpoint
+#### Manually determining the external endpoint
If the cluster is on GKE, click the **Google Kubernetes Engine** link in the
**Advanced settings**, or go directly to the
@@ -420,7 +605,7 @@ Otherwise, you can list the IP addresses of all load balancers:
kubectl get svc --all-namespaces -o jsonpath='{range.items[?(@.status.loadBalancer.ingress)]}{.status.loadBalancer.ingress[*].ip} '
```
-### Using a static IP
+#### Using a static IP
By default, an ephemeral external IP address is associated to the cluster's load
balancer. If you associate the ephemeral IP with your DNS and the IP changes,
@@ -430,79 +615,27 @@ reserved IP.
Read how to [promote an ephemeral external IP address in GKE](https://cloud.google.com/compute/docs/ip-addresses/reserve-static-external-ip-address#promote_ephemeral_ip).
-### Pointing your DNS at the external endpoint
+#### Pointing your DNS at the external endpoint
Once you've set up the external endpoint, you should associate it with a [wildcard DNS
record](https://en.wikipedia.org/wiki/Wildcard_DNS_record) such as `*.example.com.`
in order to be able to reach your apps. If your external endpoint is an IP address,
use an A record. If your external endpoint is a hostname, use a CNAME record.
-## Multiple Kubernetes clusters **(PREMIUM)**
-
-> Introduced in [GitLab Premium][ee] 10.3.
-
-With GitLab Premium, you can associate more than one Kubernetes clusters to your
-project. That way you can have different clusters for different environments,
-like dev, staging, production, etc.
-
-Simply add another cluster, like you did the first time, and make sure to
-[set an environment scope](#setting-the-environment-scope-premium) that will
-differentiate the new cluster with the rest.
-
-## Setting the environment scope **(PREMIUM)**
+## Deploying to a Kubernetes cluster
-When adding more than one Kubernetes cluster to your project, you need to differentiate
-them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
-[environment-specific variables](../../../ci/variables/README.md#limiting-environment-scopes-of-environment-variables-premium) work.
+A Kubernetes cluster can be the destination for a deployment job. If
-The default environment scope is `*`, which means all jobs, regardless of their
-environment, will use that cluster. Each scope can only be used by a single
-cluster in a project, and a validation error will occur if otherwise.
-Also, jobs that don't have an environment keyword set will not be able to access any cluster.
+- The cluster is integrated with GitLab, special
+ [deployment variables](#deployment-variables) are made available to your job
+ and configuration is not required. You can immediately begin interacting with
+ the cluster from your jobs using tools such as `kubectl` or `helm`.
+- You don't use GitLab's cluster integration you can still deploy to your
+ cluster. However, you will need configure Kubernetes tools yourself
+ using [environment variables](../../../ci/variables/README.md#creating-a-custom-environment-variable)
+ before you can interact with the cluster from your jobs.
----
-
-For example, let's say the following Kubernetes clusters exist in a project:
-
-| Cluster | Environment scope |
-| ----------- | ----------------- |
-| Development | `*` |
-| Staging | `staging` |
-| Production | `production` |
-
-And the following environments are set in [`.gitlab-ci.yml`](../../../ci/yaml/README.md):
-
-```yaml
-stages:
-- test
-- deploy
-
-test:
- stage: test
- script: sh test
-
-deploy to staging:
- stage: deploy
- script: make deploy
- environment:
- name: staging
- url: https://staging.example.com/
-
-deploy to production:
- stage: deploy
- script: make deploy
- environment:
- name: production
- url: https://example.com/
-```
-
-The result will then be:
-
-- The development cluster will be used for the "test" job.
-- The staging cluster will be used for the "deploy to staging" job.
-- The production cluster will be used for the "deploy to production" job.
-
-## Deployment variables
+### Deployment variables
The Kubernetes cluster integration exposes the following
[deployment variables](../../../ci/variables/README.md#deployment-environment-variables) in the
@@ -511,8 +644,8 @@ GitLab CI/CD build environment.
| Variable | Description |
| -------- | ----------- |
| `KUBE_URL` | Equal to the API URL. |
-| `KUBE_TOKEN` | The Kubernetes token of the [project service account](#access-controls). |
-| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. |
+| `KUBE_TOKEN` | The Kubernetes token of the [environment service account](#access-controls). |
+| `KUBE_NAMESPACE` | The Kubernetes namespace is auto-generated if not specified. The default value is `<project_name>-<project_id>-<environment>`. You can overwrite it to use different one if needed, otherwise the `KUBE_NAMESPACE` variable will receive the default value. |
| `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. |
| `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. |
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. |
@@ -522,7 +655,10 @@ NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
service account of the cluster integration.
-### Troubleshooting failed deployment jobs
+NOTE: **Note:**
+If your cluster was created before GitLab 12.2, default `KUBE_NAMESPACE` will be set to `<project_name>-<project_id>`.
+
+### Troubleshooting
Before the deployment jobs starts, GitLab creates the following specifically for
the deployment job:
@@ -554,105 +690,8 @@ namespaces and service accounts yourself.
## Monitoring your Kubernetes cluster **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate][ee] 10.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4701) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6.
When [Prometheus is deployed](#installing-applications), GitLab will automatically monitor the cluster's health. At the top of the cluster settings page, CPU and Memory utilization is displayed, along with the total amount available. Keeping an eye on cluster resources can be important, if the cluster runs out of memory pods may be shutdown or fail to start.
![Cluster Monitoring](img/k8s_cluster_monitoring.png)
-
-## Enabling or disabling the Kubernetes cluster integration
-
-After you have successfully added your cluster information, you can enable the
-Kubernetes cluster integration:
-
-1. Click the **Enabled/Disabled** switch
-1. Hit **Save** for the changes to take effect
-
-You can now start using your Kubernetes cluster for your deployments.
-
-To disable the Kubernetes cluster integration, follow the same procedure.
-
-## Removing the Kubernetes cluster integration
-
-NOTE: **Note:**
-You need Maintainer [permissions] and above to remove a Kubernetes cluster integration.
-
-NOTE: **Note:**
-When you remove a cluster, you only remove its relation to GitLab, not the
-cluster itself. To remove the cluster, you can do so by visiting the GKE
-dashboard or using `kubectl`.
-
-To remove the Kubernetes cluster integration from your project, simply click the
-**Remove integration** button. You will then be able to follow the procedure
-and add a Kubernetes cluster again.
-
-## What you can get with the Kubernetes integration
-
-Here's what you can do with GitLab if you enable the Kubernetes integration.
-
-### Deploy Boards **(PREMIUM)**
-
-GitLab's Deploy Boards offer a consolidated view of the current health and
-status of each CI [environment](../../../ci/environments.md) running on Kubernetes,
-displaying the status of the pods in the deployment. Developers and other
-teammates can view the progress and status of a rollout, pod by pod, in the
-workflow they already use without any need to access Kubernetes.
-
-[Read more about Deploy Boards](../deploy_boards.md)
-
-### Canary Deployments **(PREMIUM)**
-
-Leverage [Kubernetes' Canary deployments](https://kubernetes.io/docs/concepts/cluster-administration/manage-deployment/#canary-deployments)
-and visualize your canary deployments right inside the Deploy Board, without
-the need to leave GitLab.
-
-[Read more about Canary Deployments](../canary_deployments.md)
-
-### Pod logs **(ULTIMATE)**
-
-GitLab makes it easy to view the logs of running pods in connected Kubernetes clusters. By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
-
-[Read more about Kubernetes pod logs](kubernetes_pod_logs.md)
-
-### Kubernetes monitoring
-
-Automatically detect and monitor Kubernetes metrics. Automatic monitoring of
-[NGINX ingress](../integrations/prometheus_library/nginx.md) is also supported.
-
-[Read more about Kubernetes monitoring](../integrations/prometheus_library/kubernetes.md)
-
-### Auto DevOps
-
-Auto DevOps automatically detects, builds, tests, deploys, and monitors your
-applications.
-
-To make full use of Auto DevOps(Auto Deploy, Auto Review Apps, and Auto Monitoring)
-you will need the Kubernetes project integration enabled.
-
-[Read more about Auto DevOps](../../../topics/autodevops/index.md)
-
-### Web terminals
-
-NOTE: **Note:**
-Introduced in GitLab 8.15. You must be the project owner or have `maintainer` permissions
-to use terminals. Support is limited to the first container in the
-first pod of your environment.
-
-When enabled, the Kubernetes service adds [web terminal](../../../ci/environments.md#web-terminals)
-support to your [environments](../../../ci/environments.md). This is based on the `exec` functionality found in
-Docker and Kubernetes, so you get a new shell session within your existing
-containers. To use this integration, you should deploy to Kubernetes using
-the deployment variables above, ensuring any pods you create are labelled with
-`app=$CI_ENVIRONMENT_SLUG`. GitLab will do the rest!
-
-### Integrating Amazon EKS cluster with GitLab
-
-- Learn how to [connect and deploy to an Amazon EKS cluster](eks_and_gitlab/index.md).
-
-### Serverless
-
-- [Run serverless workloads on Kubernetes with Knative.](serverless/index.md)
-
-[permissions]: ../../permissions.md
-[ee]: https://about.gitlab.com/pricing/
-[Auto DevOps]: ../../../topics/autodevops/index.md
diff --git a/doc/user/project/clusters/kubernetes_pod_logs.md b/doc/user/project/clusters/kubernetes_pod_logs.md
index 864cd75823c..163f3befeee 100644
--- a/doc/user/project/clusters/kubernetes_pod_logs.md
+++ b/doc/user/project/clusters/kubernetes_pod_logs.md
@@ -5,6 +5,10 @@
GitLab makes it easy to view the logs of running pods in [connected Kubernetes clusters](index.md).
By displaying the logs directly in GitLab, developers can avoid having to manage console tools or jump to a different interface.
+NOTE: **Kubernetes + GitLab**
+Everything you need to build, test, deploy, and run your app at scale.
+[Learn more](https://about.gitlab.com/solutions/kubernetes/).
+
## Overview
[Kubernetes](https://kubernetes.io) pod logs can be viewed directly within GitLab. Logs can be displayed by clicking on a specific pod from [Deploy Boards](../deploy_boards.md):
diff --git a/doc/user/project/clusters/runbooks/index.md b/doc/user/project/clusters/runbooks/index.md
index c021d059d30..8df27976662 100644
--- a/doc/user/project/clusters/runbooks/index.md
+++ b/doc/user/project/clusters/runbooks/index.md
@@ -35,7 +35,7 @@ for an overview of how this is accomplished in GitLab!**
To create an executable runbook, you will need:
1. **Kubernetes** - A Kubernetes cluster is required to deploy the rest of the applications.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#add-new-gke-cluster).
1. **Helm Tiller** - Helm is a package manager for Kubernetes and is required to install
all the other applications. It is installed in its own pod inside the cluster which
can run the helm CLI in a safe environment.
@@ -60,7 +60,7 @@ the components outlined above and the preloaded demo runbook.
### 1. Add a Kubernetes cluster
-Follow the steps outlined in [Adding and creating a new GKE cluster via GitLab](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab)
+Follow the steps outlined in [Add new GKE cluster](../index.md#add-new-gke-cluster)
to add a Kubernetes cluster to your project.
### 2. Install Helm Tiller, Ingress, and JupyterHub
diff --git a/doc/user/project/clusters/serverless/img/dns-entry.png b/doc/user/project/clusters/serverless/img/dns-entry.png
index 351e40b77d5..7b5d6497f0e 100644
--- a/doc/user/project/clusters/serverless/img/dns-entry.png
+++ b/doc/user/project/clusters/serverless/img/dns-entry.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/function-details-loaded.png b/doc/user/project/clusters/serverless/img/function-details-loaded.png
index 34465c5c087..2f0d61f8032 100644
--- a/doc/user/project/clusters/serverless/img/function-details-loaded.png
+++ b/doc/user/project/clusters/serverless/img/function-details-loaded.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/function-endpoint.png b/doc/user/project/clusters/serverless/img/function-endpoint.png
index f3c7ae7a00d..a38fe2cb6c2 100644
--- a/doc/user/project/clusters/serverless/img/function-endpoint.png
+++ b/doc/user/project/clusters/serverless/img/function-endpoint.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/function-execution.png b/doc/user/project/clusters/serverless/img/function-execution.png
index 93b0b6d802d..f60dd277081 100644
--- a/doc/user/project/clusters/serverless/img/function-execution.png
+++ b/doc/user/project/clusters/serverless/img/function-execution.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/install-knative.png b/doc/user/project/clusters/serverless/img/install-knative.png
index ecc2f8fb481..1dc830848f2 100644
--- a/doc/user/project/clusters/serverless/img/install-knative.png
+++ b/doc/user/project/clusters/serverless/img/install-knative.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/img/serverless-page.png b/doc/user/project/clusters/serverless/img/serverless-page.png
index a872fda7740..8dce3cb1f70 100644
--- a/doc/user/project/clusters/serverless/img/serverless-page.png
+++ b/doc/user/project/clusters/serverless/img/serverless-page.png
Binary files differ
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index 14ee6303bf9..bcf9a677a40 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -28,7 +28,7 @@ To run Knative on Gitlab, you will need:
- If you are planning on deploying a serverless application, clone the sample [Knative Ruby App](https://gitlab.com/knative-examples/knative-ruby-app) to get started.
1. **Kubernetes Cluster:** An RBAC-enabled Kubernetes cluster is required to deploy Knative.
- The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#adding-and-creating-a-new-gke-cluster-via-gitlab).
+ The simplest way to get started is to add a cluster using [GitLab's GKE integration](../index.md#add-new-gke-cluster).
The set of minimum recommended cluster specifications to run Knative is 3 nodes, 6 vCPUs, and 22.50 GB memory.
1. **Helm Tiller:** Helm is a package manager for Kubernetes and is required to install
Knative.
@@ -41,8 +41,7 @@ To run Knative on Gitlab, you will need:
external IP address or hostname for that domain.
1. **`.gitlab-ci.yml`:** GitLab uses [Kaniko](https://github.com/GoogleContainerTools/kaniko)
to build the application. We also use [gitlabktl](https://gitlab.com/gitlab-org/gitlabktl)
- and [TriggerMesh CLI](https://github.com/triggermesh/tm) CLIs to simplify the
- deployment of services and functions to Knative.
+ CLI to simplify the deployment of services and functions to Knative.
1. **`serverless.yml`** (for [functions only](#deploying-functions)): When using serverless to deploy functions, the `serverless.yml` file
will contain the information for all the functions being hosted in the repository as well as a reference to the
runtime being used.
@@ -97,7 +96,8 @@ cluster which already has Knative installed.
You must do the following:
1. Follow the steps to
- [add an existing Kubernetes cluster](../index.md#adding-an-existing-kubernetes-cluster).
+ [add an existing Kubernetes
+ cluster](../index.md#add-existing-kubernetes-cluster).
1. Ensure GitLab can manage Knative:
- For a non-GitLab managed cluster, ensure that the service account for the token
@@ -249,7 +249,7 @@ Explanation of the fields used above:
| Parameter | Description |
|-----------|-------------|
-| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh `tm` CLI. |
+| `name` | Indicates which provider is used to execute the `serverless.yml` file. In this case, the TriggerMesh middleware. |
| `environment` | Includes the environment variables to be passed as part of function execution for **all** functions in the file, where `FOO` is the variable name and `BAR` are he variable contents. You may replace this with you own variables. |
### `functions`
@@ -343,27 +343,23 @@ Go to the **CI/CD > Pipelines** and click on the pipeline that deployed your app
The output will look like this:
```bash
-Running with gitlab-runner 11.5.0~beta.844.g96d88322 (96d88322)
- on docker-auto-scale 72989761
-Using Docker executor with image gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Pulling docker image gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Using docker image sha256:6b3f6590a9b30bd7aafb9573f047d930c70066e43955b4beb18a1eee175f6de1 for gcr.io/triggermesh/tm@sha256:e3ee74db94d215bd297738d93577481f3e4db38013326c90d57f873df7ab41d5 ...
-Running on runner-72989761-project-4342902-concurrent-0 via runner-72989761-stg-srm-1541795796-27929c96...
-Cloning repository...
-Cloning into '/builds/danielgruesso/knative'...
-Checking out 8671ad20 as master...
-Skipping Git submodules setup
-$ echo "$CI_REGISTRY_IMAGE"
-registry.staging.gitlab.com/danielgruesso/knative
-$ tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
-Deployment started. Run "tm -n knative-4342902 describe service knative" to see the details
-Waiting for ready state.......
-Service domain: knative.knative-4342902.example.com
+Running with gitlab-runner 12.1.0-rc1 (6da35412)
+ on prm-com-gitlab-org ae3bfce3
+Using Docker executor with image registry.gitlab.com/gitlab-org/gitlabktl:latest ...
+Running on runner-ae3bfc-concurrent-0 via runner-ae3bfc ...
+Fetching changes...
+Authenticating with credentials from job payload (GitLab Registry)
+$ /usr/bin/gitlabktl application deploy
+Welcome to gitlabktl tool
+time="2019-07-15T10:51:07Z" level=info msg="deploying registry credentials"
+Creating app-hello function
+Waiting for app-hello ready state
+Service app-hello URL: http://app-hello.serverless.example.com
Job succeeded
```
-The second to last line, labeled **Service domain** contains the URL for the deployment. Copy and paste the domain into your
-browser to see the app live.
+The second to last line, labeled **Service domain** contains the URL for the
+deployment. Copy and paste the domain into your browser to see the app live.
![knative app](img/knative-app.png)
@@ -438,7 +434,7 @@ The instructions below relate to installing and running Certbot on a Linux serve
./certbot-auto certonly --manual --preferred-challenges dns -d '*.<namespace>.example.com'
```
- Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<projectname+id>`) and
+ Where `<namespace>` is the namespace created by GitLab for your serverless project (composed of `<project_name>-<project_id>-<environment>`) and
`example.com` is the domain being used for your project. If you are unsure what the namespace of your project is, navigate
to the **Operations > Serverless** page of your project and inspect
the endpoint provided for your function/app.
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index 9368c56004d..c9eb81b990c 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -1,13 +1,8 @@
# GitLab Container Registry
-> **Notes:**
->
> - [Introduced][ce-4040] in GitLab 8.8.
> - Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
> versions earlier than 1.10.
-> - This document is about the user guide. To learn how to enable GitLab Container
-> Registry across your GitLab instance, visit the
-> [administrator documentation](../../administration/container_registry.md).
> - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
> login to GitLab's Container Registry.
@@ -16,28 +11,33 @@
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
+This document is the user guide. To learn how to enable GitLab Container
+Registry across your GitLab instance, visit the
+[administrator documentation](../../administration/container_registry.md).
+
You can read more about Docker Registry at <https://docs.docker.com/registry/introduction/>.
## Enable the Container Registry for your project
-NOTE: **Note:**
-If you cannot find the Container Registry entry under your project's settings,
-that means that it is not enabled in your GitLab instance. Ask your administrator
-to enable it.
-
-1. First, ask your system administrator to enable GitLab Container Registry
- following the [administration documentation](../../administration/container_registry.md).
- If you are using GitLab.com, this is enabled by default so you can start using
- the Registry immediately. Currently there is a soft (10GB) size restriction for
- registry on GitLab.com, as part of the [repository size limit](repository/index.md).
-1. Go to your [project's General settings](settings/index.md#sharing-and-permissions)
+If you cannot find the **Packages > Container Registry** entry under your
+project's sidebar, it is not enabled in your GitLab instance. Ask your
+administrator to enable GitLab Container Registry following the
+[administration documentation](../../administration/container_registry.md).
+
+If you are using GitLab.com, this is enabled by default so you can start using
+the Registry immediately. Currently there is a soft (10GB) size restriction for
+registry on GitLab.com, as part of the [repository size limit](repository/index.md).
+
+Once enabled for your GitLab instance, to enable Container Registry for your
+project:
+
+1. Go to your project's **Settings > General** page.
+1. Expand the **Visibility, project features, permissions** section
and enable the **Container Registry** feature on your project. For new
projects this might be enabled by default. For existing projects
(prior GitLab 8.8), you will have to explicitly enable it.
-1. Hit **Save changes** for the changes to take effect. You should now be able
- to see the **Registry** link in the sidebar.
-
-![Container Registry](img/container_registry.png)
+1. Press **Save changes** for the changes to take effect. You should now be able
+ to see the **Packages > Container Registry** link in the sidebar.
## Build and push images
@@ -49,14 +49,14 @@ to enable it.
> - To move or rename a repository with a container registry you will have to
> delete all existing images.
-If you visit the **Registry** link under your project's menu, you can see the
-explicit instructions to login to the Container Registry using your GitLab
-credentials.
+If you visit the **Packages > Container Registry** link under your project's
+menu, you can see the explicit instructions to login to the Container Registry
+using your GitLab credentials.
For example if the Registry's URL is `registry.example.com`, then you should be
able to login with:
-```
+```sh
docker login registry.example.com
```
@@ -64,14 +64,14 @@ Building and publishing images should be a straightforward process. Just make
sure that you are using the Registry URL with the namespace and project name
that is hosted on GitLab:
-```
+```sh
docker build -t registry.example.com/group/project/image .
docker push registry.example.com/group/project/image
```
Your image will be named after the following scheme:
-```
+```text
<registry URL>/<namespace>/<project>/<image>
```
@@ -79,7 +79,7 @@ GitLab supports up to three levels of image repository names.
Following examples of image tags are valid:
-```
+```text
registry.example.com/group/project:some-tag
registry.example.com/group/project/image:latest
registry.example.com/group/project/my/image:rc1
@@ -90,7 +90,7 @@ registry.example.com/group/project/my/image:rc1
To download and run a container from images hosted in GitLab Container Registry,
use `docker run`:
-```
+```sh
docker run [options] registry.example.com/group/project/image [arguments]
```
@@ -100,7 +100,7 @@ For more information on running Docker containers, visit the
## Control Container Registry from within GitLab
GitLab offers a simple Container Registry management panel. Go to your project
-and click **Registry** in the project menu.
+and click **Packages > Container Registry** in the project menu.
This view will show you all tags in your project and will easily allow you to
delete them.
@@ -173,9 +173,9 @@ curl localhost:5001/debug/vars
A Docker connection error can occur when there are special characters in either the group,
project or branch name. Special characters can include:
-* Leading underscore
-* Trailing hyphen/dash
-* Double hyphen/dash
+- Leading underscore
+- Trailing hyphen/dash
+- Double hyphen/dash
To get around this, you can [change the group path](../group/index.md#changing-a-groups-path),
[change the project path](../project/settings/index.md#renaming-a-repository) or chanage the branch
@@ -183,7 +183,8 @@ name.
### Advanced Troubleshooting
->**NOTE:** The following section is only recommended for experts.
+NOTE: **Note:**
+The following section is only recommended for experts.
Sometimes it's not obvious what is wrong, and you may need to dive deeper into
the communication between the Docker client and the Registry to find out
@@ -195,7 +196,7 @@ diagnose a problem with the S3 setup.
A user attempted to enable an S3-backed Registry. The `docker login` step went
fine. However, when pushing an image, the output showed:
-```
+```text
The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test/docker-image]
dc5e59c14160: Pushing [==================================================>] 14.85 kB
03c20c1a019a: Pushing [==================================================>] 2.048 kB
@@ -218,7 +219,7 @@ at the communication between the client and the Registry.
The REST API between the Docker client and Registry is [described
here](https://docs.docker.com/registry/spec/api/). Normally, one would just
use Wireshark or tcpdump to capture the traffic and see where things went
-wrong. However, since all communications between Docker clients and servers
+wrong. However, since all communications between Docker clients and servers
are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even
if you know the private key. What can we do instead?
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 5d36e1d4be3..87577c9ec88 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -1,161 +1,5 @@
-# Cycle 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
-
-You can find the Cycle Analytics page under your project's **Project ➔ Cycle
-Analytics** tab.
-
-![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 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.
-
-## 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/deploy_tokens/img/deploy_tokens.png b/doc/user/project/deploy_tokens/img/deploy_tokens.png
index 421aa1ab3e5..493de8e0fce 100644
--- a/doc/user/project/deploy_tokens/img/deploy_tokens.png
+++ b/doc/user/project/deploy_tokens/img/deploy_tokens.png
Binary files differ
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 196874fdc86..f53dc056010 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -55,7 +55,7 @@ changes you made after picking the template and return it to its initial status.
![Description templates](img/description_templates.png)
-## Setting a default template for issues and merge requests **(STARTER)**
+## Setting a default template for merge requests and issues **(STARTER)**
> **Notes:**
>
@@ -66,20 +66,20 @@ changes you made after picking the template and return it to its initial status.
> - Templates for merge requests were [introduced][ee-7478ece] in GitLab EE 6.9.
The visibility of issues and/or merge requests should be set to either "Everyone
-with access" or "Only Project Members" in your project's **Settings** otherwise the
+with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the
template text areas won't show. This is the default behavior so in most cases
you should be fine.
-Go to your project's **Settings** and fill in the "Default description template
-for issues" and "Default description template for merge requests" text areas
-for issues and merge requests respectively. Since GitLab issues and merge
-request support [Markdown](../markdown.md), you can use special markup like
+Go to your project's **Settings** and under the **Merge requests** header, click *Expand* and fill in the "Default description template
+for merge requests" text area. Under the **Default issue template**, click *Expand* and fill in "Default description template for issues" text area. Since GitLab merge request and issues
+ support [Markdown](../markdown.md), you can use special markup like
headings, lists, etc.
-![Default description templates](img/description_templates_default_settings.png)
+![Default merge request description templates](img/description_templates_merge_request_settings.png)
+![Default issue description templates](img/description_templates_issue_settings.png)
After you add the description, hit **Save changes** for the settings to take
-effect. Now, every time a new issue or merge request is created, it will be
+effect. Now, every time a new merge request or issue is created, it will be
pre-filled with the text you entered in the template(s).
## Description template example
diff --git a/doc/user/project/file_lock.md b/doc/user/project/file_lock.md
index dec679fc975..dce8c3a97bc 100644
--- a/doc/user/project/file_lock.md
+++ b/doc/user/project/file_lock.md
@@ -40,7 +40,7 @@ To lock a file:
1. Pick the file you want to lock.
1. Click the "Lock" button.
- ![Locking file](img/file_lock.png)
+ ![Locking file](img/file_lock.png)
To lock an entire directory, look for the "Lock" link next to "History".
diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
index 9c6fa923b80..9c6fa923b80 100755..100644
--- a/doc/user/project/img/autocomplete_characters_example1_v12_0.png
+++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
Binary files differ
diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
index b2e8a782a0b..b2e8a782a0b 100755..100644
--- a/doc/user/project/img/autocomplete_characters_example2_v12_0.png
+++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
Binary files differ
diff --git a/doc/user/project/img/container_registry.png b/doc/user/project/img/container_registry.png
deleted file mode 100644
index abbaf838538..00000000000
--- a/doc/user/project/img/container_registry.png
+++ /dev/null
Binary files differ
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 cf46098b9a4..00000000000
--- a/doc/user/project/img/cycle_analytics_landing_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/deploy_boards_kubernetes_label.png b/doc/user/project/img/deploy_boards_kubernetes_label.png
index 19785c74d07..130ff2caa8a 100644
--- a/doc/user/project/img/deploy_boards_kubernetes_label.png
+++ b/doc/user/project/img/deploy_boards_kubernetes_label.png
Binary files differ
diff --git a/doc/user/project/img/description_templates_default_settings.png b/doc/user/project/img/description_templates_default_settings.png
deleted file mode 100644
index ab314e83d06..00000000000
--- a/doc/user/project/img/description_templates_default_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/description_templates_issue_settings.png b/doc/user/project/img/description_templates_issue_settings.png
new file mode 100644
index 00000000000..53328108835
--- /dev/null
+++ b/doc/user/project/img/description_templates_issue_settings.png
Binary files differ
diff --git a/doc/user/project/img/description_templates_merge_request_settings.png b/doc/user/project/img/description_templates_merge_request_settings.png
new file mode 100644
index 00000000000..eda264f7f37
--- /dev/null
+++ b/doc/user/project/img/description_templates_merge_request_settings.png
Binary files differ
diff --git a/doc/user/project/img/file_lock.png b/doc/user/project/img/file_lock.png
index 82699a12ffd..e881442630b 100644
--- a/doc/user/project/img/file_lock.png
+++ b/doc/user/project/img/file_lock.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_merge_request_error_message.png b/doc/user/project/img/file_lock_merge_request_error_message.png
index 4ef04b15bef..64bcc86ac0d 100644
--- a/doc/user/project/img/file_lock_merge_request_error_message.png
+++ b/doc/user/project/img/file_lock_merge_request_error_message.png
Binary files differ
diff --git a/doc/user/project/img/file_lock_repository_view.png b/doc/user/project/img/file_lock_repository_view.png
index a2cab0decab..ced14198da9 100644
--- a/doc/user/project/img/file_lock_repository_view.png
+++ b/doc/user/project/img/file_lock_repository_view.png
Binary files differ
diff --git a/doc/user/project/img/issue_boards_multiple.png b/doc/user/project/img/issue_boards_multiple.png
index 4b1a8356dc9..e6183360610 100644
--- a/doc/user/project/img/issue_boards_multiple.png
+++ b/doc/user/project/img/issue_boards_multiple.png
Binary files differ
diff --git a/doc/user/project/img/key_value_labels.png b/doc/user/project/img/key_value_labels.png
deleted file mode 100644
index bec901f127f..00000000000
--- a/doc/user/project/img/key_value_labels.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_default.png b/doc/user/project/img/labels_default.png
deleted file mode 100644
index 221c5cf55a2..00000000000
--- a/doc/user/project/img/labels_default.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_default_v12_1.png b/doc/user/project/img/labels_default_v12_1.png
new file mode 100644
index 00000000000..b36b5dac80b
--- /dev/null
+++ b/doc/user/project/img/labels_default_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_delete_v12_1.png b/doc/user/project/img/labels_delete_v12_1.png
new file mode 100644
index 00000000000..566e0519fbe
--- /dev/null
+++ b/doc/user/project/img/labels_delete_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_drag_priority_v12_1.gif b/doc/user/project/img/labels_drag_priority_v12_1.gif
new file mode 100644
index 00000000000..a568490da5f
--- /dev/null
+++ b/doc/user/project/img/labels_drag_priority_v12_1.gif
Binary files differ
diff --git a/doc/user/project/img/labels_epic_sidebar.png b/doc/user/project/img/labels_epic_sidebar.png
deleted file mode 100644
index f8162d89e9d..00000000000
--- a/doc/user/project/img/labels_epic_sidebar.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_epic_sidebar_v12_1.png b/doc/user/project/img/labels_epic_sidebar_v12_1.png
new file mode 100644
index 00000000000..13fbab33e26
--- /dev/null
+++ b/doc/user/project/img/labels_epic_sidebar_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_generate_default.png b/doc/user/project/img/labels_generate_default.png
deleted file mode 100644
index 982a4df999c..00000000000
--- a/doc/user/project/img/labels_generate_default.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_generate_default_v12_1.png b/doc/user/project/img/labels_generate_default_v12_1.png
new file mode 100644
index 00000000000..cbdda2ab4dd
--- /dev/null
+++ b/doc/user/project/img/labels_generate_default_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_group_issues.png b/doc/user/project/img/labels_group_issues.png
deleted file mode 100644
index cea1d304d31..00000000000
--- a/doc/user/project/img/labels_group_issues.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_group_issues_v12_1.png b/doc/user/project/img/labels_group_issues_v12_1.png
new file mode 100644
index 00000000000..3f8f93c1dfd
--- /dev/null
+++ b/doc/user/project/img/labels_group_issues_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_key_value_v12_1.png b/doc/user/project/img/labels_key_value_v12_1.png
new file mode 100644
index 00000000000..52906b7b458
--- /dev/null
+++ b/doc/user/project/img/labels_key_value_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_list.png b/doc/user/project/img/labels_list.png
deleted file mode 100644
index 6940dde68bf..00000000000
--- a/doc/user/project/img/labels_list.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_list_v12_1.png b/doc/user/project/img/labels_list_v12_1.png
new file mode 100644
index 00000000000..47359d05f7f
--- /dev/null
+++ b/doc/user/project/img/labels_list_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/new_label_from_sidebar.gif b/doc/user/project/img/labels_new_label_from_sidebar.gif
index 572b29a86e1..572b29a86e1 100644
--- a/doc/user/project/img/new_label_from_sidebar.gif
+++ b/doc/user/project/img/labels_new_label_from_sidebar.gif
Binary files differ
diff --git a/doc/user/project/img/labels_prioritized.png b/doc/user/project/img/labels_prioritized.png
deleted file mode 100644
index 7ce2d08b38c..00000000000
--- a/doc/user/project/img/labels_prioritized.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_prioritized_v12_1.png b/doc/user/project/img/labels_prioritized_v12_1.png
new file mode 100644
index 00000000000..512c5d59a5a
--- /dev/null
+++ b/doc/user/project/img/labels_prioritized_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_promotion.png b/doc/user/project/img/labels_promotion.png
deleted file mode 100644
index 762a3773692..00000000000
--- a/doc/user/project/img/labels_promotion.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_promotion_v12_1.png b/doc/user/project/img/labels_promotion_v12_1.png
new file mode 100644
index 00000000000..7cd8ff24e02
--- /dev/null
+++ b/doc/user/project/img/labels_promotion_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/labels_subscriptions.png b/doc/user/project/img/labels_subscriptions.png
deleted file mode 100644
index f3c4235d051..00000000000
--- a/doc/user/project/img/labels_subscriptions.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/labels_subscriptions_v12_1.png b/doc/user/project/img/labels_subscriptions_v12_1.png
new file mode 100644
index 00000000000..fa83b7db414
--- /dev/null
+++ b/doc/user/project/img/labels_subscriptions_v12_1.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_disabled.png b/doc/user/project/img/service_desk_disabled.png
index 3ae64dcbe96..edae7e76986 100644
--- a/doc/user/project/img/service_desk_disabled.png
+++ b/doc/user/project/img/service_desk_disabled.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_enabled.png b/doc/user/project/img/service_desk_enabled.png
index 329348e4b52..9c143ff58cd 100644
--- a/doc/user/project/img/service_desk_enabled.png
+++ b/doc/user/project/img/service_desk_enabled.png
Binary files differ
diff --git a/doc/user/project/img/service_desk_issue_tracker.png b/doc/user/project/img/service_desk_issue_tracker.png
index 485751fab0a..02d18c9debb 100644
--- a/doc/user/project/img/service_desk_issue_tracker.png
+++ b/doc/user/project/img/service_desk_issue_tracker.png
Binary files differ
diff --git a/doc/user/project/import/bitbucket.md b/doc/user/project/import/bitbucket.md
index 1cc3bd3363d..e509e333313 100644
--- a/doc/user/project/import/bitbucket.md
+++ b/doc/user/project/import/bitbucket.md
@@ -31,12 +31,12 @@ to enable this if not already.
## How it works
When issues/pull requests are being imported, the Bitbucket importer tries to find
-the Bitbucket author/assignee in GitLab's database using the Bitbucket ID. For this
-to work, the Bitbucket author/assignee should have signed in beforehand in GitLab
-and **associated their Bitbucket account**. If the user is not
-found in GitLab's database, the project creator (most of the times the current
-user that started the import process) is set as the author, but a reference on
-the issue about the original Bitbucket author is kept.
+the Bitbucket author/assignee in GitLab's database using the Bitbucket `nickname`.
+For this to work, the Bitbucket author/assignee should have signed in beforehand in GitLab
+and **associated their Bitbucket account**. Their `nickname` must also match their Bitbucket
+`username.`. If the user is not found in GitLab's database, the project creator
+(most of the times the current user that started the import process) is set as the author,
+but a reference on the issue about the original Bitbucket author is kept.
The importer will create any new namespaces (groups) if they don't exist or in
the case the namespace is taken, the repository will be imported under the user's
@@ -49,17 +49,17 @@ namespace that started the import process.
1. Click on the "Bitbucket Cloud" button.
- ![Bitbucket](img/import_projects_from_new_project_page.png)
+ ![Bitbucket](img/import_projects_from_new_project_page.png)
1. Grant GitLab access to your Bitbucket account
- ![Grant access](img/bitbucket_import_grant_access.png)
+ ![Grant access](img/bitbucket_import_grant_access.png)
1. Click on the projects that you'd like to import or **Import all projects**.
You can also select the namespace under which each project will be
imported.
- ![Import projects](img/bitbucket_import_select_project.png)
+ ![Import projects](img/bitbucket_import_select_project.png)
[bb-import]: ../../../integration/bitbucket.md
[social sign-in]: ../../profile/account/social_sign_in.md
diff --git a/doc/user/project/import/bitbucket_server.md b/doc/user/project/import/bitbucket_server.md
index e4eb1a9a392..28e211ee2ba 100644
--- a/doc/user/project/import/bitbucket_server.md
+++ b/doc/user/project/import/bitbucket_server.md
@@ -32,6 +32,8 @@ Import your projects from Bitbucket Server to GitLab with minimal effort.
1. Attachments in Markdown are currently not imported.
1. Task lists are not imported.
1. Emoji reactions are not imported
+1. Project filtering does not support fuzzy search (only `starts with` or `full
+ match strings` are currently supported)
## How it works
@@ -59,16 +61,16 @@ namespace that started the import process.
1. Sign in to GitLab and go to your dashboard.
1. Click on **New project**.
1. Click on the "Bitbucket Server" button. If the button is not present, enable the importer in
- **Admin > Application Settings > Visibility and access controls > Import sources**.
+ **Admin > Application Settings > Visibility and access controls > Import sources**.
- ![Bitbucket](img/import_projects_from_new_project_page.png)
+ ![Bitbucket](img/import_projects_from_new_project_page.png)
1. Enter your Bitbucket Server credentials.
- ![Grant access](img/bitbucket_server_import_credentials.png)
+ ![Grant access](img/bitbucket_server_import_credentials.png)
1. Click on the projects that you'd like to import or **Import all projects**.
- You can also select the namespace under which each project will be
+ You can also filter projects by name and select the namespace under which each project will be
imported.
- ![Import projects](img/bitbucket_server_import_select_project.png)
+ ![Import projects](img/bitbucket_server_import_select_project_v12_3.png)
diff --git a/doc/user/project/import/gemnasium.md b/doc/user/project/import/gemnasium.md
index 0afa32e4133..cf48189fa6e 100644
--- a/doc/user/project/import/gemnasium.md
+++ b/doc/user/project/import/gemnasium.md
@@ -5,13 +5,17 @@ instance or GitLab.com.
## Why is Gemnasium.com closed?
-Gemnasium has been [acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html)
+Gemnasium was [acquired by GitLab](https://about.gitlab.com/press/releases/2018-01-30-gemnasium-acquisition.html)
in January 2018. Since May 15, 2018, the services provided by Gemnasium are no longer available.
The team behind Gemnasium has joined GitLab as the new Security Products team
-and is working on a wider range of tools than just Dependency Scanning:
-[SAST](../../application_security/sast/index.md),
-[DAST](../../application_security/dast/index.md),
-[Container Scanning](../../application_security/container_scanning/index.md) and more.
+and is working on a [wide range of tools](../../application_security/index.md),
+including:
+
+- [Dependency Scanning](../../application_security/dependency_scanning/index.md)
+- [SAST](../../application_security/sast/index.md)
+- [DAST](../../application_security/dast/index.md)
+- [Container Scanning](../../application_security/container_scanning/index.md)
+
If you want to continue monitoring your dependencies, see the
[Migrating to GitLab](#migrating-to-gitlab) section below.
@@ -57,27 +61,27 @@ back to both GitLab and GitHub when completed.
1. Create a new project, and select the "CI/CD for external repo" tab:
- ![Create new Project](img/gemnasium/create_project.png)
+ ![Create new Project](img/gemnasium/create_project.png)
1. Use the "GitHub" button to connect your repositories.
- ![Connect from GitHub](img/gemnasium/connect_github.png)
+ ![Connect from GitHub](img/gemnasium/connect_github.png)
1. Select the project(s) to be set up with GitLab CI/CD and chose "Connect".
- ![Select projects](img/gemnasium/select_project.png)
+ ![Select projects](img/gemnasium/select_project.png)
- Once the configuration is done, you may click on your new
- project on GitLab.
+ Once the configuration is done, you may click on your new
+ project on GitLab.
- ![click on connected project](img/gemnasium/project_connected.png)
+ ![click on connected project](img/gemnasium/project_connected.png)
- Your project is now mirrored on GitLab, where the Runners will be able to access
- your source code and run your tests.
+ Your project is now mirrored on GitLab, where the Runners will be able to access
+ your source code and run your tests.
- Optional step: If you set this up on GitLab.com, make sure the project is
- public (in the project settings) if your GitHub project is public, since
- the security feature is available only for [GitLab Ultimate](https://about.gitlab.com/pricing).
+ Optional step: If you set this up on GitLab.com, make sure the project is
+ public (in the project settings) if your GitHub project is public, since
+ the security feature is available only for [GitLab Ultimate](https://about.gitlab.com/pricing).
1. To set up the dependency scanning job, corresponding to what Gemnasium was
doing, you must create a `.gitlab-ci.yml` file, or update it according to
@@ -85,16 +89,16 @@ back to both GitLab and GitHub when completed.
The mirroring is pull-only by default, so you may create or update the file on
GitHub:
- ![Edit gitlab-ci.yml file](img/gemnasium/edit_gitlab-ci.png)
+ ![Edit gitlab-ci.yml file](img/gemnasium/edit_gitlab-ci.png)
1. Once your file has been committed, a new pipeline will be automatically
triggered if your file is valid:
- ![pipeline](img/gemnasium/pipeline.png)
+ ![pipeline](img/gemnasium/pipeline.png)
1. The result of the job will be visible directly from the pipeline view:
- ![security report](img/gemnasium/report.png)
+ ![security report](img/gemnasium/report.png)
NOTE: **Note:**
If you don't commit very often to your project, you may want to use
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/bitbucket_server_import_select_project.png b/doc/user/project/import/img/bitbucket_server_import_select_project.png
deleted file mode 100644
index e7fddef9955..00000000000
--- a/doc/user/project/import/img/bitbucket_server_import_select_project.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/import/img/bitbucket_server_import_select_project_v12_3.png b/doc/user/project/import/img/bitbucket_server_import_select_project_v12_3.png
new file mode 100644
index 00000000000..1c344853cc8
--- /dev/null
+++ b/doc/user/project/import/img/bitbucket_server_import_select_project_v12_3.png
Binary files differ
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/import/index.md b/doc/user/project/import/index.md
index 9d7463416d8..ecd491d8e5b 100644
--- a/doc/user/project/import/index.md
+++ b/doc/user/project/import/index.md
@@ -10,7 +10,7 @@
1. [From Gitea](gitea.md)
1. [From Perforce](perforce.md)
1. [From SVN](svn.md)
-1. [From TFS](tfs.md)
+1. [From TFVC](tfvc.md)
1. [From repo by URL](repo_by_url.md)
1. [By uploading a manifest file (AOSP)](manifest.md)
1. [From Gemnasium](gemnasium.md)
diff --git a/doc/user/project/import/manifest.md b/doc/user/project/import/manifest.md
index baf410d9c9e..161a4163e42 100644
--- a/doc/user/project/import/manifest.md
+++ b/doc/user/project/import/manifest.md
@@ -1,7 +1,6 @@
# Import multiple repositories by uploading a manifest file
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28811) in
-GitLab 11.2.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28811) in GitLab 11.2.
GitLab allows you to import all the required Git repositories
based on a manifest file like the one used by the
@@ -56,4 +55,4 @@ You can start the import with:
to the import status page with projects list based on the manifest file.
1. Check the list and click **Import all repositories** to start the import.
- ![Manifest status](img/manifest_status.png)
+ ![Manifest status](img/manifest_status.png)
diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md
index 7359487e1bf..d175ee87f26 100644
--- a/doc/user/project/import/svn.md
+++ b/doc/user/project/import/svn.md
@@ -9,13 +9,13 @@ between the two, for more information consult your favorite search engine.
There are two approaches to SVN to Git migration:
1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which:
- - Makes the GitLab repository to mirror the SVN project.
- - Git and SVN repositories are kept in sync; you can use either one.
- - Smoothens the migration process and allows to manage migration risks.
+ - Makes the GitLab repository to mirror the SVN project.
+ - Git and SVN repositories are kept in sync; you can use either one.
+ - Smoothens the migration process and allows to manage migration risks.
1. [Cut over migration](#cut-over-migration-with-svn2git) which:
- - Translates and imports the existing data and history from SVN to Git.
- - Is a fire and forget approach, good for smaller teams.
+ - Translates and imports the existing data and history from SVN to Git.
+ - Is a fire and forget approach, good for smaller teams.
## Smooth migration with a Git/SVN mirror using SubGit
diff --git a/doc/user/project/import/tfs.md b/doc/user/project/import/tfs.md
index b4597a4da60..7b3b11b9519 100644
--- a/doc/user/project/import/tfs.md
+++ b/doc/user/project/import/tfs.md
@@ -1,42 +1,5 @@
-# Migrating from TFS
+---
+redirect_to: 'tfvc.md'
+---
-[TFS](https://visualstudio.microsoft.com/tfs/) is a set of tools developed by Microsoft
-which also includes a centralized version control system (TFVC) similar to Git.
-
-In this document, we emphasize on the TFVC to Git migration.
-
-## TFVC vs Git
-
-The following list illustrates the main differences between TFVC and Git:
-
-- **Git is distributed** whereas TFVC is centralized using a client-server
- architecture. This translates to Git having a more flexible workflow since
- your working area is a copy of the entire repository. This decreases the
- overhead when switching branches or merging for example, since you don't have
- to communicate with a remote server.
-- **Storage method.** Changes in CVS are per file (changeset), while in Git
- a committed file(s) is stored in its entirety (snapshot). That means that's
- very easy in Git to revert or undo a whole change.
-
-Check also Microsoft's documentation on the
-[comparison of Git and TFVC](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/comparison-git-tfvc?view=azure-devops)
-and the Wikipedia
-[comparison of version control software](https://en.wikipedia.org/wiki/Comparison_of_version_control_software).
-
-## Why migrate
-
-Migrating to Git/GitLab there is:
-
-- **No licensing costs**, Git is GPL while TFVC is proprietary.
-- **Shorter learning curve**, Git has a big community and a vast number of
- tutorials to get you started (see our [Git topic](../../../topics/git/index.md)).
-- **Integration with modern tools**, migrating to Git and GitLab you can have
- an open source end-to-end software development platform with built-in version
- control, issue tracking, code review, CI/CD, and more.
-
-## How to migrate
-
-The best option to migrate from TFVC to Git is to use the
-[`git-tfs`](https://github.com/git-tfs/git-tfs) tool. A specific guide for the
-migration exists:
-[Migrate TFS to Git](https://github.com/git-tfs/git-tfs/blob/master/doc/usecases/migrate_tfs_to_git.md).
+This document was moved to [another location](tfvc.md).
diff --git a/doc/user/project/import/tfvc.md b/doc/user/project/import/tfvc.md
new file mode 100644
index 00000000000..375522b77d0
--- /dev/null
+++ b/doc/user/project/import/tfvc.md
@@ -0,0 +1,46 @@
+---
+type: concepts
+---
+
+# Migrating from TFVC to Git
+
+Team Foundation Server (TFS), renamed [Azure DevOps Server](https://azure.microsoft.com/en-us/services/devops/server/)
+in 2019, is a set of tools developed by Microsoft which also includes
+[Team Foundation Version Control](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/overview)
+(TFVC), a centralized version control system similar to Git.
+
+In this document, we focus on the TFVC to Git migration.
+
+## TFVC vs Git
+
+The main differences between TFVC and Git are:
+
+- **Git is distributed:** While TFVC is centralized using a client-server architecture,
+ Git is distributed. This translates to Git having a more flexible workflow since
+ you work with a copy of the entire repository. This allows you to quickly
+ switch branches or merge, for example, without needing to communicate with a remote server.
+- **Storage:** Changes in a centralized version control system are per file (changeset),
+ while in Git a committed file is stored in its entirety (snapshot). That means that it is
+ very easy to revert or undo a whole change in Git.
+
+For more information, see:
+
+- Microsoft's [comparison of Git and TFVC](https://docs.microsoft.com/en-us/azure/devops/repos/tfvc/comparison-git-tfvc?view=azure-devops).
+- The Wikipedia [comparison of version control software](https://en.wikipedia.org/wiki/Comparison_of_version_control_software).
+
+## Why migrate
+
+Advantages of migrating to Git/GitLab:
+
+- **No licensing costs:** Git is open source, while TFVC is proprietary.
+- **Shorter learning curve:** Git has a big community and a vast number of
+ tutorials to get you started (see our [Git topic](../../../topics/git/index.md)).
+- **Integration with modern tools:** After migrating to Git and GitLab, you will have
+ an open source, end-to-end software development platform with built-in version
+ control, issue tracking, code review, CI/CD, and more.
+
+## How to migrate
+
+The best option to migrate from TFVC to Git is to use the [`git-tfs`](https://github.com/git-tfs/git-tfs)
+tool. Read the [Migrate TFS to Git](https://github.com/git-tfs/git-tfs/blob/master/doc/usecases/migrate_tfs_to_git.md)
+guide for more details.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 7307c5b8991..30ff0e9ff07 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -66,15 +66,15 @@ When you create a project in GitLab, you'll have access to a large number of
to automatically set up your app's deployment
- [Enable and disable GitLab CI](../../ci/enable_or_disable_ci.md)
- [Pipelines](../../ci/pipelines.md): Configure and visualize
- your GitLab CI/CD pipelines from the UI
- - [Scheduled Pipelines](pipelines/schedules.md): Schedule a pipeline
- to start at a chosen time
- - [Pipeline Graphs](../../ci/pipelines.md#visualizing-pipelines): View your
- entire pipeline from the UI
- - [Job artifacts](pipelines/job_artifacts.md): Define,
- browse, and download job artifacts
- - [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
- timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
+ your GitLab CI/CD pipelines from the UI
+ - [Scheduled Pipelines](pipelines/schedules.md): Schedule a pipeline
+ to start at a chosen time
+ - [Pipeline Graphs](../../ci/pipelines.md#visualizing-pipelines): View your
+ entire pipeline from the UI
+ - [Job artifacts](pipelines/job_artifacts.md): Define,
+ browse, and download job artifacts
+ - [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
+ timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
with a Kubernetes cluster
- [Feature Flags](operations/feature_flags.md): Feature flags allow you to ship a project in
@@ -99,6 +99,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [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)**
+- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
### Project integrations
diff --git a/doc/user/project/insights/img/insights_example_bar_chart.png b/doc/user/project/insights/img/insights_example_bar_chart.png
index eb96eb4a90f..5fd24265b32 100644
--- a/doc/user/project/insights/img/insights_example_bar_chart.png
+++ b/doc/user/project/insights/img/insights_example_bar_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_bar_time_series_chart.png b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png
index 28aa81939d8..2e32df3e2b1 100644
--- a/doc/user/project/insights/img/insights_example_bar_time_series_chart.png
+++ b/doc/user/project/insights/img/insights_example_bar_time_series_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_line_chart.png b/doc/user/project/insights/img/insights_example_line_chart.png
index 131dbedd35e..9460b104339 100644
--- a/doc/user/project/insights/img/insights_example_line_chart.png
+++ b/doc/user/project/insights/img/insights_example_line_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_pie_chart.png b/doc/user/project/insights/img/insights_example_pie_chart.png
index 518ed7338f9..3480bce6738 100644
--- a/doc/user/project/insights/img/insights_example_pie_chart.png
+++ b/doc/user/project/insights/img/insights_example_pie_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_example_stacked_bar_chart.png b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png
index aafec4b394e..a3fb50488f5 100644
--- a/doc/user/project/insights/img/insights_example_stacked_bar_chart.png
+++ b/doc/user/project/insights/img/insights_example_stacked_bar_chart.png
Binary files differ
diff --git a/doc/user/project/insights/img/insights_sidebar_link.png b/doc/user/project/insights/img/insights_sidebar_link.png
index aadb5745992..9fc449baf68 100644
--- a/doc/user/project/insights/img/insights_sidebar_link.png
+++ b/doc/user/project/insights/img/insights_sidebar_link.png
Binary files differ
diff --git a/doc/user/project/insights/img/project_insights.png b/doc/user/project/insights/img/project_insights.png
index 2d0292dda54..83674c94110 100644
--- a/doc/user/project/insights/img/project_insights.png
+++ b/doc/user/project/insights/img/project_insights.png
Binary files differ
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/embed_metrics.png b/doc/user/project/integrations/img/embed_metrics.png
new file mode 100644
index 00000000000..6f9660c9aec
--- /dev/null
+++ b/doc/user/project/integrations/img/embed_metrics.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/github_configuration.png b/doc/user/project/integrations/img/github_configuration.png
index 9f2d47921c7..5798b826681 100644
--- a/doc/user/project/integrations/img/github_configuration.png
+++ b/doc/user/project/integrations/img/github_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_add_user_to_group.png b/doc/user/project/integrations/img/jira_add_user_to_group.png
index d8cf541a81e..b63a851a987 100644
--- a/doc/user/project/integrations/img/jira_add_user_to_group.png
+++ b/doc/user/project/integrations/img/jira_add_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_added_user_to_group.png b/doc/user/project/integrations/img/jira_added_user_to_group.png
index b3e29a65d6e..f5120a8d42e 100644
--- a/doc/user/project/integrations/img/jira_added_user_to_group.png
+++ b/doc/user/project/integrations/img/jira_added_user_to_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_api_token.png b/doc/user/project/integrations/img/jira_api_token.png
index 29689271bf7..d9d37713a4d 100644
--- a/doc/user/project/integrations/img/jira_api_token.png
+++ b/doc/user/project/integrations/img/jira_api_token.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_api_token_menu.png b/doc/user/project/integrations/img/jira_api_token_menu.png
index 1aca1d78f36..a10a59a243d 100644
--- a/doc/user/project/integrations/img/jira_api_token_menu.png
+++ b/doc/user/project/integrations/img/jira_api_token_menu.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_group.png b/doc/user/project/integrations/img/jira_create_new_group.png
index 84be3a94a45..4ab7a5eae4e 100644
--- a/doc/user/project/integrations/img/jira_create_new_group.png
+++ b/doc/user/project/integrations/img/jira_create_new_group.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_create_new_user.png b/doc/user/project/integrations/img/jira_create_new_user.png
index 8460dc98ef9..c74933298e3 100644
--- a/doc/user/project/integrations/img/jira_create_new_user.png
+++ b/doc/user/project/integrations/img/jira_create_new_user.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_group_access.png b/doc/user/project/integrations/img/jira_group_access.png
index 58cf114bd55..e33f2eed242 100644
--- a/doc/user/project/integrations/img/jira_group_access.png
+++ b/doc/user/project/integrations/img/jira_group_access.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_issue_reference.png b/doc/user/project/integrations/img/jira_issue_reference.png
index a3e80c1b054..db8bc4f0bb9 100644
--- a/doc/user/project/integrations/img/jira_issue_reference.png
+++ b/doc/user/project/integrations/img/jira_issue_reference.png
Binary files differ
diff --git a/doc/user/project/integrations/img/jira_merge_request_close.png b/doc/user/project/integrations/img/jira_merge_request_close.png
index 1c089c94207..9a176daf5f4 100644
--- a/doc/user/project/integrations/img/jira_merge_request_close.png
+++ b/doc/user/project/integrations/img/jira_merge_request_close.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 80dd65ea24e..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/img/jira_user_management_link.png b/doc/user/project/integrations/img/jira_user_management_link.png
index 43ef18da6c8..caecd1d71fc 100644
--- a/doc/user/project/integrations/img/jira_user_management_link.png
+++ b/doc/user/project/integrations/img/jira_user_management_link.png
Binary files differ
diff --git a/doc/user/project/integrations/img/mattermost_configuration.png b/doc/user/project/integrations/img/mattermost_configuration.png
index 75ef0310f2d..6abf5c5c8d6 100644
--- a/doc/user/project/integrations/img/mattermost_configuration.png
+++ b/doc/user/project/integrations/img/mattermost_configuration.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png b/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png
new file mode 100644
index 00000000000..9fdb6dfddac
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_dashboard_area_panel_type.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png b/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png
new file mode 100644
index 00000000000..2d7dfb27b49
--- /dev/null
+++ b/doc/user/project/integrations/img/prometheus_dashboard_single_stat_panel_type.png
Binary files differ
diff --git a/doc/user/project/integrations/img/prometheus_service_alerts.png b/doc/user/project/integrations/img/prometheus_service_alerts.png
index a81dfe7da14..609c5e5196c 100644
--- a/doc/user/project/integrations/img/prometheus_service_alerts.png
+++ b/doc/user/project/integrations/img/prometheus_service_alerts.png
Binary files differ
diff --git a/doc/user/project/integrations/img/slack_configuration.png b/doc/user/project/integrations/img/slack_configuration.png
index a14d2969488..10d2cda6dc7 100644
--- a/doc/user/project/integrations/img/slack_configuration.png
+++ b/doc/user/project/integrations/img/slack_configuration.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/jira_cloud_configuration.md b/doc/user/project/integrations/jira_cloud_configuration.md
index 5a5ba2dd168..1d5a4a3d4c7 100644
--- a/doc/user/project/integrations/jira_cloud_configuration.md
+++ b/doc/user/project/integrations/jira_cloud_configuration.md
@@ -9,7 +9,7 @@ below to create one:
It is important that the user associated with this email address has *write* access
to projects in Jira.
-2. Click **Create API token**.
+1. Click **Create API token**.
![Jira API token](img/jira_api_token_menu.png)
diff --git a/doc/user/project/integrations/jira_server_configuration.md b/doc/user/project/integrations/jira_server_configuration.md
index 5116cbfe9ac..1efd0ff3944 100644
--- a/doc/user/project/integrations/jira_server_configuration.md
+++ b/doc/user/project/integrations/jira_server_configuration.md
@@ -3,8 +3,8 @@
We need to create a user in Jira which will have access to all projects that
need to integrate with GitLab.
-As an example, we'll create a user named `gitlab` and add it to the `Jira-developers`
-group.
+As an example, we'll create a user named `gitlab` and add it to a new group
+named `gitlab-developers`.
NOTE: **Note**
It is important that the Jira user created for the integration is given 'write'
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index ea58a08e127..6e0f39956d3 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -14,7 +14,7 @@ To enable Mattermost integration you must create an incoming webhook integration
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
-it on `https://mattermost.example/admin_console/integrations/custom`.
+it on **Mattermost System Console > Integrations > Integration Management**, or on **Mattermost System Console > Integrations > Custom Integrations** in Mattermost versions 5.11 and earlier.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
diff --git a/doc/user/project/integrations/mattermost_slash_commands.md b/doc/user/project/integrations/mattermost_slash_commands.md
index 41be26c1d30..a3a2568445e 100644
--- a/doc/user/project/integrations/mattermost_slash_commands.md
+++ b/doc/user/project/integrations/mattermost_slash_commands.md
@@ -40,17 +40,13 @@ the administrator console.
1. Log in with an account that has admin privileges and navigate to the system
console.
- ![Mattermost go to console](img/mattermost_goto_console.png)
-
- ---
+ ![Mattermost go to console](img/mattermost_goto_console.png)
1. Click **Custom integrations** and set **Enable Custom Slash Commands**,
**Enable custom integrations to override usernames**, and **Override
custom integrations to override profile picture icons** to true
- ![Mattermost console](img/mattermost_console_integrations.png)
-
- ---
+ ![Mattermost console](img/mattermost_console_integrations.png)
1. Click **Save** at the bottom to save the changes.
@@ -62,14 +58,12 @@ the administrator console.
A screen will appear with all the values you need to copy in Mattermost as
described in the next step. Leave the window open.
- >**Note:**
- GitLab will propose some values for the Mattermost settings. The only one
- required to copy-paste as-is is the **Request URL**, all the others are just
- suggestions.
-
- ![Mattermost setup instructions](img/mattermost_config_help.png)
+ NOTE: **Note:**
+ GitLab will propose some values for the Mattermost settings. The only one
+ required to copy-paste as-is is the **Request URL**, all the others are just
+ suggestions.
- ---
+ ![Mattermost setup instructions](img/mattermost_config_help.png)
1. Proceed to the next step and create a slash command in Mattermost with the
above values.
@@ -83,44 +77,38 @@ in a new slash command.
1. Back to Mattermost, under your team page settings, you should see the
**Integrations** option.
- ![Mattermost team integrations](img/mattermost_team_integrations.png)
-
- ---
+ ![Mattermost team integrations](img/mattermost_team_integrations.png)
1. Go to the **Slash Commands** integration and add a new one by clicking the
**Add Slash Command** button.
- ![Mattermost add command](img/mattermost_add_slash_command.png)
-
- ---
+ ![Mattermost add command](img/mattermost_add_slash_command.png)
1. Fill in the options for the custom command as described in
[step 2](#step-2-open-the-mattermost-slash-commands-service-in-gitlab).
- >**Note:**
- If you plan on connecting multiple projects, pick a slash command trigger
- word that relates to your projects such as `/gitlab-project-name` or even
- just `/project-name`. Only use `/gitlab` if you will only connect a single
- project to your Mattermost team.
+ NOTE: **Note:**
+ If you plan on connecting multiple projects, pick a slash command trigger
+ word that relates to your projects such as `/gitlab-project-name` or even
+ just `/project-name`. Only use `/gitlab` if you will only connect a single
+ project to your Mattermost team.
- ![Mattermost add command configuration](img/mattermost_slash_command_configuration.png)
+ ![Mattermost add command configuration](img/mattermost_slash_command_configuration.png)
1. After you set up all the values, copy the token (we will use it below) and
click **Done**.
- ![Mattermost slash command token](img/mattermost_slash_command_token.png)
+ ![Mattermost slash command token](img/mattermost_slash_command_token.png)
### Step 4. Copy the Mattermost token into the Mattermost slash command service
1. In GitLab, paste the Mattermost token you copied in the previous step and
check the **Active** checkbox.
- ![Mattermost copy token to GitLab](img/mattermost_gitlab_token.png)
+ ![Mattermost copy token to GitLab](img/mattermost_gitlab_token.png)
1. Click **Save changes** for the changes to take effect.
----
-
You are now set to start using slash commands in Mattermost that talk to the
GitLab project you configured.
diff --git a/doc/user/project/integrations/mock_ci.md b/doc/user/project/integrations/mock_ci.md
index 1c64b275d6e..886094a6531 100644
--- a/doc/user/project/integrations/mock_ci.md
+++ b/doc/user/project/integrations/mock_ci.md
@@ -5,9 +5,9 @@
To set up the mock CI service server, respond to the following endpoints
- `commit_status`: `#{project.namespace.path}/#{project.path}/status/#{sha}.json`
- - Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success-with-warnings'|'skipped'|'not_found'] }`
+ - Have your service return `200 { status: ['failed'|'canceled'|'running'|'pending'|'success'|'success-with-warnings'|'skipped'|'not_found'] }`
- If the service returns a 404, it is interpreted as `pending`
- `build_page`: `#{project.namespace.path}/#{project.path}/status/#{sha}`
- - Just where the build is linked to, doesn't matter if implemented
+ - Just where the build is linked to, doesn't matter if implemented
For an example of a mock CI server, see [`gitlab-org/gitlab-mock-ci-service`](https://gitlab.com/gitlab-org/gitlab-mock-ci-service)
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 72f12972596..e2f45b8a32f 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -98,7 +98,10 @@ You can view the performance dashboard for an environment by [clicking on the mo
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/3799) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.6.
-Additional metrics can be monitored by adding them on the Prometheus integration page. Once saved, they will be displayed on the environment performance dashboard.
+Custom metrics can be monitored by adding them on the Prometheus integration page. Once saved, they will be displayed on the environment performance dashboard provided that either:
+
+- A [connected Kubernetes cluster](../clusters/index.md#adding-and-removing-clusters) with the environment scope of `*` is used and [Prometheus installed on the cluster](#enabling-prometheus-integration), or
+- Prometheus is [manually configured](#manual-configuration-of-prometheus).
![Add New Metric](img/prometheus_add_metric.png)
@@ -191,7 +194,7 @@ The following tables outline the details of expected properties.
| Property | Type | Required | Description |
| ------ | ------ | ------ | ------- |
-| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use. Only `area-chart` is currently supported. |
+| `type` | enum | no, defaults to `area-chart` | Specifies the chart type to use. |
| `title` | string | yes | Heading for the panel. |
| `y_label` | string | no, but highly encouraged | Y-Axis label for the panel. |
| `weight` | number | no, defaults to order in file | Order to appear within the grouping. Lower number means higher priority, which will be higher on the page. Numbers do not need to be consecutive. |
@@ -207,6 +210,71 @@ The following tables outline the details of expected properties.
| `query` | string | yes if `query_range` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
| `query_range` | string | yes if `query` is not defined | Defines the Prometheus query to be used to populate the chart/panel. If defined, the `query_range` endpoint of the [Prometheus API](https://prometheus.io/docs/prometheus/latest/querying/api/) will be utilized. |
+#### Panel types for dashboards
+
+The below panel types are supported in monitoring dashboards.
+
+##### Area
+
+To add an area panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - type: area-chart
+ title: "Chart Title"
+ y_label: "Y-Axis"
+ metrics:
+ - id: 10
+ query_range: 'http_requests_total'
+ label: "Metric of Ages"
+ unit: "count"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | no | Type of panel to be rendered. Optional for area panel types |
+| query_range | yes | required | For area panel types, you must use a [range query](https://prometheus.io/docs/prometheus/latest/querying/api/#range-queries) |
+
+![area panel type](img/prometheus_dashboard_area_panel_type.png)
+
+##### Single Stat
+
+To add a single stat panel type to a dashboard, look at the following sample dashboard file:
+
+```yaml
+dashboard: 'Dashboard Title'
+panel_groups:
+ - group: 'Group Title'
+ panels:
+ - title: "Single Stat"
+ type: "single-stat"
+ metrics:
+ - id: 10
+ query: 'max(go_memstats_alloc_bytes{job="prometheus"})'
+ unit: MB
+ label: "Total"
+```
+
+Note the following properties:
+
+| Property | Type | Required | Description |
+| ------ | ------ | ------ | ------ |
+| type | string | yes | Type of panel to be rendered. For single stat panel types, set to `single-stat` |
+| query | string | yes | For single stat panel types, you must use an [instant query](https://prometheus.io/docs/prometheus/latest/querying/api/#instant-queries) |
+
+![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
@@ -264,8 +332,11 @@ Once enabled, an issue will be opened automatically when an alert is triggered w
- `starts_at`: Alert start time via `startsAt`
- `full_query`: Alert query extracted from `generatorURL`
- Optional list of attached annotations extracted from `annotations/*`
+- Alert [GFM](../../markdown.md): GitLab Flavored Markdown from `annotations/gitlab_incident_markdown`
-To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template.
+To further customize the issue, you can add labels, mentions, or any other supported [quick action](../quick_actions.md) in the selected issue template, which will apply to all incidents. To limit quick actions or other information to only specific types of alerts, use the `annotations/gitlab_incident_markdown` field.
+
+Since [version 12.2](https://gitlab.com/gitlab-org/gitlab-ce/issues/63373), GitLab will tag each incident issue with the `incident` label automatically. If the label does not yet exist, it will be created automatically as well.
If the metric exceeds the threshold of the alert for over 5 minutes, an email will be sent to all [Maintainers and Owners](../../permissions.md#project-members-permissions) of the project.
@@ -289,6 +360,31 @@ Prometheus server.
![Merge Request with Performance Impact](img/merge_request_performance.png)
+## Embedding metric charts within Gitlab Flavored Markdown
+
+> [Introduced][ce-29691] in GitLab 12.2.
+> Requires [Kubernetes](prometheus_library/kubernetes.md) metrics.
+
+It is possible to display metrics charts within [GitLab Flavored Markdown](../../markdown.md#gitlab-flavored-markdown-gfm).
+
+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.
+- Prometheus must be monitoring the environment.
+- The GitLab instance must be configured to receive data from the environment.
+- The user must be allowed access to the monitoring dashboard for the environment ([Reporter or higher](../../permissions.md)).
+- The dashboard must have data within the last 8 hours.
+
+ If all of the above are true, then the metric will unfurl as seen below:
+
+![Embedded Metrics](img/embed_metrics.png)
+
## Troubleshooting
If the "No data found" screen continues to appear, it could be due to:
@@ -311,4 +407,5 @@ If the "No data found" screen continues to appear, it could be due to:
[ci-environment-slug]: ../../../ci/variables/#predefined-environment-variables
[ce-8935]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8935
[ce-10408]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10408
+[ce-29691]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29691
[promgldocs]: ../../../administration/monitoring/prometheus/index.md
diff --git a/doc/user/project/integrations/prometheus_library/haproxy.md b/doc/user/project/integrations/prometheus_library/haproxy.md
index 6be8fc82431..90a725f271b 100644
--- a/doc/user/project/integrations/prometheus_library/haproxy.md
+++ b/doc/user/project/integrations/prometheus_library/haproxy.md
@@ -1,4 +1,5 @@
# Monitoring HAProxy
+
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12621) in GitLab 9.4
GitLab has support for automatically detecting and monitoring HAProxy. This is provided by leveraging the [HAProxy Exporter](https://github.com/prometheus/haproxy_exporter), which translates HAProxy statistics into a Prometheus readable form.
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index bac7eecfce4..25b000b2753 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -5,18 +5,18 @@
the **Redmine** service, and fill in the required details on the page as described
in the table below.
- | Field | Description |
- | ----- | ----------- |
- | `description` | A name for the issue tracker (to differentiate between instances, for example) |
- | `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
- | `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
- | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** |
+ | Field | Description |
+ | ----- | ----------- |
+ | `description` | A name for the issue tracker (to differentiate between instances, for example) |
+ | `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
+ | `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+ | `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project. **This is currently not being used and will be removed in a future release.** |
- Once you have configured and enabled Redmine you'll see the Redmine link on the GitLab project pages that takes you to the appropriate Redmine project.
+ Once you have configured and enabled Redmine you'll see the Redmine link on the GitLab project pages that takes you to the appropriate Redmine project.
- As an example, below is a configuration for a project named gitlab-ci.
+ As an example, below is a configuration for a project named gitlab-ci.
- ![Redmine configuration](img/redmine_configuration.png)
+ ![Redmine configuration](img/redmine_configuration.png)
1. To disable the internal issue tracking system in a project, navigate to the General page, expand the [permissions](../settings/index.md#sharing-and-permissions) section and switch the **Issues** toggle to disabled.
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index 30940b65454..84adb9637fc 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -58,13 +58,13 @@ Navigate to the webhooks page by going to your project's
If you are writing your own endpoint (web server) that will receive
GitLab webhooks keep in mind the following things:
-- Your endpoint should send its HTTP response as fast as possible. If
- you wait too long, GitLab may decide the hook failed and retry it.
-- Your endpoint should ALWAYS return a valid HTTP response. If you do
- not do this then GitLab will think the hook failed and retry it.
- Most HTTP libraries take care of this for you automatically but if
- you are writing a low-level hook this is important to remember.
-- GitLab ignores the HTTP status code returned by your endpoint.
+- Your endpoint should send its HTTP response as fast as possible. If
+ you wait too long, GitLab may decide the hook failed and retry it.
+- Your endpoint should ALWAYS return a valid HTTP response. If you do
+ not do this then GitLab will think the hook failed and retry it.
+ Most HTTP libraries take care of this for you automatically but if
+ you are writing a low-level hook this is important to remember.
+- GitLab ignores the HTTP status code returned by your endpoint.
## Secret token
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 3eb5581912a..eaca5f8cfb8 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -143,10 +143,10 @@ Create lists for each of your team members and quickly drag-and-drop issues onto
- **Issue Board** - Each board represents a unique view for your issues. It can have multiple lists with each list consisting of issues represented by cards.
- **List** - A column on the issue board that displays issues matching certain attributes. In addition to the default lists of 'Open' and 'Closed' issue, each additional list will show issues matching your chosen label or assignee. On the top of that list you can see the number of issues that belong to it.
- - **Label list**: a list based on a label. It shows all opened issues with that label.
- - **Assignee list**: a list which includes all issues assigned to a user.
- - **Open** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
- - **Closed** (default): shows all closed issues. Always appears as the rightmost list.
+ - **Label list**: a list based on a label. It shows all opened issues with that label.
+ - **Assignee list**: a list which includes all issues assigned to a user.
+ - **Open** (default): shows all open issues that do not belong to one of the other lists. Always appears as the leftmost list.
+ - **Closed** (default): shows all closed issues. Always appears as the rightmost list.
- **Card** - A box in the list that represents an individual issue. The information you can see on a card consists of the issue number, the issue title, the assignee, and the labels associated with the issue. You can drag cards from one list to another to change their label or assignee from that of the source list to that of the destination list.
## Permissions
@@ -386,6 +386,10 @@ a given board inside your GitLab instance, any time those two issues are subsequ
loaded in any board in the same instance (could be a different project board or a different group board, for example),
that ordering will be maintained.
+This ordering also affects [issue lists](issues/sorting_issue_lists.md).
+Changing the order in an issue board changes the ordering in an issue list,
+and vice versa.
+
### Filtering issues
You should be able to use the filters on top of your Issue Board to show only
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 2c755e0fb4d..c10ef564f0d 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -77,3 +77,49 @@ project's search results respectively.
| Maintainer access | Guest access |
| :-----------: | :----------: |
| ![Confidential issues search master](img/confidential_issues_search_master.png) | ![Confidential issues search guest](img/confidential_issues_search_guest.png) |
+
+## Merge Requests for Confidential Issues
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/58583) in GitLab 12.1.
+
+To help prevent confidential information being leaked from a public project
+in the process of resolving a confidential issue, confidential issues can be
+resolved by creating a merge request from a private fork.
+
+The merge request created will target the default branch of the private fork,
+not the default branch of the public upstream project. This prevents the merge
+request, branch, and commits entering the public repository, and revealing
+confidential information prematurely. When the confidential commits are ready
+to be made public, this can be done by opening a merge request from the private
+fork to the public upstream project.
+
+TIP: **Best practice:**
+If you create a long-lived private fork in the same group or in a sub-group of
+the original upstream, all the users with Developer membership to the public
+project will also have the same permissions in the private project. This way,
+all the Developers, who have access to view confidential issues, will have a
+streamlined workflow for fixing them.
+
+### How it works
+
+On a confidential issue, a **Create confidential merge request** button is
+available. Clicking on it will open a dropdown where you can choose to
+**Create confidential merge request and branch** or **Create branch**:
+
+| Create confidential merge request | Create branch |
+| :-------------------------------: | :-----------: |
+| ![Create Confidential Merge Request Dropdown](img/confidential_mr_dropdown_v12_1.png) | ![Create Confidential Branch Dropdown](img/confidential_mr_branch_dropdown_v12_1.png) |
+
+The **Project** dropdown includes the list of private forks the user is a member
+of as at least a Developer and merge requests are enabled.
+
+Whenever the **Branch name** and **Source (branch or tag)** fields change, the
+availability of the target or source branch will be checked. Both branches should
+be available in the private fork selected.
+
+By clicking the **Create confidential merge request** button, GitLab will create
+the branch and merge request in the private fork. When you choose
+**Create branch**, GitLab will only create the branch.
+
+Once the branch is created in the private fork, developers can now push code to
+that branch to fix the confidential issue.
diff --git a/doc/user/project/issues/csv_export.md b/doc/user/project/issues/csv_export.md
index d3f53c09fb3..27ce3a814f9 100644
--- a/doc/user/project/issues/csv_export.md
+++ b/doc/user/project/issues/csv_export.md
@@ -72,4 +72,4 @@ Data will be encoded with a comma as the column delimiter, with `"` used to quot
## Limitations
-As the issues will be sent as an email attachment, there is a limit on how much data can be exported. Currently this limit is 20MB to ensure successful delivery across a range of email providers. If this limit is reached we suggest narrowing the search before export, perhaps by exporting open and closed issues separately.
+As the issues will be sent as an email attachment, there is a limit on how much data can be exported. Currently this limit is 15MB to ensure successful delivery across a range of email providers. If this limit is reached we suggest narrowing the search before export, perhaps by exporting open and closed issues separately.
diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md
new file mode 100644
index 00000000000..1324a90e00b
--- /dev/null
+++ b/doc/user/project/issues/design_management.md
@@ -0,0 +1,79 @@
+# Design Management **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/660) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2.
+
+CAUTION: **Warning:**
+This an **alpha** feature and is subject to change at any time without
+prior notice.
+
+## Overview
+
+Design Management allows you to upload design assets (wireframes, mockups, etc.)
+to GitLab issues and keep them stored in one single place, accessed by the Design
+Management's page within an issue, giving product designers, product managers, and engineers a
+way to collaborate on designs over one single source of truth.
+
+You can easily share mock-ups of designs with your team, or visual regressions can be easily
+viewed and addressed.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, see the video [Design Management (GitLab 12.2)](https://www.youtube.com/watch?v=CCMtCqdK_aM).
+
+## Requirements
+
+Design Management requires
+[Large File Storage (LFS)](../../../workflow/lfs/manage_large_binaries_with_git_lfs.md)
+to be enabled:
+
+- For GitLab.com, LFS is already enabled.
+- For self-managed instances, a GitLab administrator must have
+ [enabled LFS globally](../../../workflow/lfs/lfs_administration.md).
+- For both GitLab.com and self-managed instances: LFS must be enabled for the project itself.
+ If enabled globally, LFS will be enabled by default to all projects. To enable LFS on the
+ project level, navigate to your project's **Settings > General**, expand **Visibility, project features, permissions**
+ and enable **Git Large File Storage**.
+
+## 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).
+- 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 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
+
+Navigate to the **Design Management** page from any issue by clicking the **Designs** tab:
+
+![Designs tab](img/design_management_v12_2.png)
+
+## Adding designs
+
+To upload design images, click the **Upload Designs** button and select images to upload.
+
+Designs with the same filename as an existing uploaded design will create a new version
+of the design, and will replace the previous version.
+
+## Viewing designs
+
+Images on the Design Management page can be enlarged by clicking on them.
+
+## Adding annotations to designs
+
+When a design image is displayed, you can add annotations to it by clicking on
+the image. A badge is added to the image and a form is displayed to start a new
+discussion. For example:
+
+![Starting a new discussion on design](img/adding_note_to_design_1.png)
+
+When submitted, the form saves a badge linked to the discussion on the image. Different discussions have different badge numbers. For example:
+
+![Discussions on design annotations](img/adding_note_to_design_2.png)
diff --git a/doc/user/project/issues/img/adding_note_to_design_1.png b/doc/user/project/issues/img/adding_note_to_design_1.png
new file mode 100644
index 00000000000..aa50bbb69ce
--- /dev/null
+++ b/doc/user/project/issues/img/adding_note_to_design_1.png
Binary files differ
diff --git a/doc/user/project/issues/img/adding_note_to_design_2.png b/doc/user/project/issues/img/adding_note_to_design_2.png
new file mode 100644
index 00000000000..37cefeb1a15
--- /dev/null
+++ b/doc/user/project/issues/img/adding_note_to_design_2.png
Binary files differ
diff --git a/doc/user/project/issues/img/comment-or-discussion.png b/doc/user/project/issues/img/comment-or-discussion.png
index ccecc9fa39f..a29014c984c 100644
--- a/doc/user/project/issues/img/comment-or-discussion.png
+++ b/doc/user/project/issues/img/comment-or-discussion.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png
new file mode 100644
index 00000000000..1f4ad5c42bb
--- /dev/null
+++ b/doc/user/project/issues/img/confidential_mr_branch_dropdown_v12_1.png
Binary files differ
diff --git a/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png
new file mode 100644
index 00000000000..7b7bd599a71
--- /dev/null
+++ b/doc/user/project/issues/img/confidential_mr_dropdown_v12_1.png
Binary files differ
diff --git a/doc/user/project/issues/img/create_mr_from_issue.png b/doc/user/project/issues/img/create_mr_from_issue.png
index 680c42b5df0..d05a678cd17 100644
--- a/doc/user/project/issues/img/create_mr_from_issue.png
+++ b/doc/user/project/issues/img/create_mr_from_issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/design_management_v12_2.png b/doc/user/project/issues/img/design_management_v12_2.png
new file mode 100644
index 00000000000..ad803df4e63
--- /dev/null
+++ b/doc/user/project/issues/img/design_management_v12_2.png
Binary files differ
diff --git a/doc/user/project/issues/img/issues_main_view_numbered.png b/doc/user/project/issues/img/issues_main_view_numbered.png
index 16cb6b497b0..92b9df44972 100644
--- a/doc/user/project/issues/img/issues_main_view_numbered.png
+++ b/doc/user/project/issues/img/issues_main_view_numbered.png
Binary files differ
diff --git a/doc/user/project/issues/img/reopen-issue.png b/doc/user/project/issues/img/reopen-issue.png
index 1749be31239..fc48742afe0 100644
--- a/doc/user/project/issues/img/reopen-issue.png
+++ b/doc/user/project/issues/img/reopen-issue.png
Binary files differ
diff --git a/doc/user/project/issues/img/report-abuse.png b/doc/user/project/issues/img/report-abuse.png
index f189cbf1d36..f8cef22da03 100644
--- a/doc/user/project/issues/img/report-abuse.png
+++ b/doc/user/project/issues/img/report-abuse.png
Binary files differ
diff --git a/doc/user/project/issues/img/show-all-activity.png b/doc/user/project/issues/img/show-all-activity.png
index c43ba75ce25..55c6f5ab5db 100644
--- a/doc/user/project/issues/img/show-all-activity.png
+++ b/doc/user/project/issues/img/show-all-activity.png
Binary files differ
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index e917697e973..bf04ed2d2d0 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -89,7 +89,7 @@ Key actions for Issues include:
![Issue view](img/issues_main_view.png)
On an issue's page, you can view [all aspects of the issue](issue_data_and_actions.md),
-and modify them if you you have the necessary [permissions](../../permissions.md).
+and modify them if you have the necessary [permissions](../../permissions.md).
### Issues list
@@ -104,7 +104,8 @@ view, you can also make certain changes [in bulk](../bulk_editing.md) to the dis
For more information, see the [Issue Data and Actions](issue_data_and_actions.md) page
for a rundown of all the fields and information in an issue.
-For sorting by issue priority, see [Label Priority](../labels.md#label-priority).
+You can sort a list of issues several ways, including by issue creation date, milestone due date,
+etc. For more information, see the [Sorting and Ordering Issue Lists](sorting_issue_lists.md) page.
### Issue boards
@@ -119,6 +120,12 @@ associated label or assignee will change to match that of the new column. The en
board can also be filtered to only include issues from a certain milestone or an overarching
label.
+### Design Management **(PREMIUM)**
+
+With [Design Management](design_management.md), you can upload design
+assets to issues and view them all together to easily share and
+collaborate with your team.
+
### Epics **(ULTIMATE)**
[Epics](../../group/epics/index.md) let you manage your portfolio of projects more
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 7b031f83cb1..d7d168710ef 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -50,7 +50,12 @@ The button to do this has a different label depending on whether the issue is al
#### 3. Assignee
-An issue can be assigned to yourself, another person, or [many people](#31-multiple-assignees-STARTER).
+An issue can be assigned to:
+
+- Yourself.
+- Another person.
+- [Many people](#31-multiple-assignees-STARTER). **(STARTER)**
+
The assignee(s) can be changed as often as needed. The idea is that the assignees are
responsible for that issue until it's reassigned to someone else to take it from there.
When assigned to someone, it will appear in their assigned issues list.
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index 709588959c1..e9b4e591384 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -7,7 +7,9 @@ you can do with issues.
## Create a new Issue
-When you create a new issue, you'll be prompted to fill in the [data and fields of the issue](issue_data_and_actions.md#parts-of-an-issue), as illustrated below.
+When you create a new issue, you'll be prompted to fill in the [data and fields of the issue](issue_data_and_actions.md#parts-of-an-issue), as illustrated below. If you know
+the values you want to assign to an issue, you can use the [Quick actions](../quick_actions.md)
+feature to input values, instead of selecting them from lists.
![New issue from the issues list](img/new_issue.png)
diff --git a/doc/user/project/issues/related_issues.md b/doc/user/project/issues/related_issues.md
index 9c72fe33d0d..d7178506b64 100644
--- a/doc/user/project/issues/related_issues.md
+++ b/doc/user/project/issues/related_issues.md
@@ -19,7 +19,7 @@ Issues from a different project require additional information like the
group and the project name. For example:
- same project: `#44`
-- same group: `project#44 `
+- same group: `project#44`
- different group: `group/project#44`
Valid references will be added to a temporary list that you can review.
diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md
new file mode 100644
index 00000000000..0fe86e6f410
--- /dev/null
+++ b/doc/user/project/issues/sorting_issue_lists.md
@@ -0,0 +1,30 @@
+# Sorting and Ordering Issue Lists
+
+You can sort a list of issues several ways, including by issue creation date, milestone due date,
+etc. The available sorting options can change based on the context of the list.
+For sorting by issue priority, see [Label Priority](../labels.md#label-priority).
+
+In group and project issue lists, it is also possible to order issues manually,
+similar to [issue boards](../issue_board.md#issue-ordering-in-a-list).
+
+## Manual sorting
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/62178) in GitLab 12.2.
+
+When you select **Manual** sorting, you can change
+the order by dragging and dropping the issues. The changed order will persist. Everyone who visits the same list will see the reordered list, with some exceptions.
+
+Each issue is assigned a relative order value, representing its relative
+order with respect to the other issues in the list. When you drag-and-drop reorder
+an issue, its relative order value changes accordingly.
+
+In addition, any time that issue appears in a manually sorted list,
+the updated relative order value will be used for the ordering. This means that
+if issue `A` is drag-and-drop reordered to be above issue `B` by any user in
+a given list inside your GitLab instance, any time those two issues are subsequently
+loaded in any list in the same instance (could be a different project issue list or a
+different group issue list, for example), that ordering will be maintained.
+
+This ordering also affects [issue boards](../issue_board.md#issue-ordering-in-a-list).
+Changing the order in an issue list changes the ordering in an issue board,
+and vice versa.
diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md
index fdfeb4aa4cd..cc1274faa2c 100644
--- a/doc/user/project/labels.md
+++ b/doc/user/project/labels.md
@@ -2,43 +2,52 @@
## Overview
-Labels allow you to categorize issues or merge requests using descriptive titles like `bug`, `feature request`, or `docs`. Each label also has a customizable color. They allow you to quickly and dynamically filter and manage issues or merge requests you care about, and are visible throughout GitLab in most places where issues and merge requests are located.
+Labels allow you to categorize issues or merge requests using descriptive titles like
+`bug`, `feature request`, or `docs`. Each label also has a customizable color. They
+allow you to quickly and dynamically filter and manage issues or merge requests you
+care about, and are visible throughout GitLab in most places where issues and merge
+requests are located.
## Project labels and group labels
In GitLab, you can create project and group labels:
- **Project labels** can be assigned to issues or merge requests in that project only.
-- **Group labels** can be assigned to any issue or merge request of any project in that group or any subgroups of the group.
+- **Group labels** can be assigned to any issue or merge request in any project in
+ that group, or any subgroups of the group.
## Scoped labels **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9175) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10.
-Scoped labels allow teams to use the simple and familiar feature of labels to
+Scoped labels allow teams to use the simple and familiar label feature to
annotate their issues, merge requests, and epics to achieve custom fields and
custom workflow states by leveraging a special label title syntax.
-A scoped label is a kind of label defined only by a special double-colon syntax
+A scoped label is a kind of label defined by a special double-colon syntax
in the label’s title, using the format `key::value`. For example:
-![A sample scoped label](img/key_value_labels.png)
+![A sample scoped label](img/labels_key_value_v12_1.png)
An issue, epic, or merge request cannot have two scoped labels with the same key.
-For example, if an issue is already labeled `priority::3` and you apply the label `priority::2` to it,
-`priority::3` is automatically removed.
+For example, if an issue is already labeled `priority::3`, and then you apply the
+`priority::2` label to it, `priority::3` is automatically removed.
This functionality is demonstrated in a video titled [Use scoped labels in GitLab 11.10 for custom fields and custom workflows](https://www.youtube.com/watch?v=4BCBby6du3c).
### Labels with multiple colon pairs
-If labels have multiple instances of `::`, the longest path from left to right, until the last `::`, is considered the "key" or the "scope".
+If labels have multiple instances of `::`, the longest path from left to right, until
+the last `::`, is considered the "key" or the "scope".
-For example, `nested::key1::value1` and `nested::key1::value2` cannot both exist on the same issue. Adding the latter label will automatically remove the former due to the shared scope of `nested::key1`.
+For example, `nested::key1::value1` and `nested::key1::value2` cannot both exist on
+the same issue. Adding the latter label will automatically remove the former due to
+the shared scope of `nested::key1`.
-`nested::key1::value1` and `nested::key2::value1` can both exist on the same issue, as these are considered to use two different label scopes, `nested::key1` and `nested::key2`.
+`nested::key1::value1` and `nested::key2::value1` can both exist on the same issue,
+as these are considered to use two different label scopes, `nested::key1` and `nested::key2`.
-### Workflows with scoped labels **(PREMIUM)**
+### Workflows with scoped labels
Suppose you wanted a custom field in issues to track the platform operating system
that your features target, where each issue should only target one platform. You
@@ -58,101 +67,156 @@ be able to advance workflow states consistently in issues themselves.
## Creating labels
->**Note:**
+NOTE: **Note:**
A permission level of Reporter or higher is required to create labels.
### New project label
To create a **project label**, navigate to **Issues > Labels** in the project.
-This page only shows project labels in this project and group labels of this project's parent group.
+This page only shows the project labels in this project, and the group labels of the
+project's parent group.
-Click the **New label** button. Enter the title, an optional description, and the background color. Click **Create label** to create the label.
+Click the **New label** button. Enter the title, an optional description, and the
+background color. Click **Create label** to create the label.
-If a project has no labels, you can generate a default set of project labels from its empty label list page:
+If a project has no labels, you can generate a default set of project labels from
+its empty label list page:
-![Labels generate default](img/labels_generate_default.png)
+![Labels generate default](img/labels_generate_default_v12_1.png)
GitLab will add the following default labels to the project:
-![Labels default](img/labels_default.png)
+![Labels default](img/labels_default_v12_1.png)
### New group label
-To create a **group label**, follow similar steps from above to project labels. Navigate to **Issues > Labels** in the group and create it from there.
-This page only shows group labels in this group.
-Alternatively, you can create group labels also from Epic sidebar. Please note that the created label will belong to the immediate group to which epic belongs.
+To create a **group label**, navigate to **Issues > Labels** in the **group** and create
+it from there. This page only shows group labels in this group.
+
+Alternatively, you can create group labels from the Epic sidebar. **(ULTIMATE)**
-![Create Labels from Epic](img/labels_epic_sidebar.png)
+Please note that the created label will belong to the immediate group to which the
+epic belongs.
+
+![Create Labels from Epic](img/labels_epic_sidebar_v12_1.png)
Group labels appear in every label list page of the group's child projects.
-![Labels list](img/labels_list.png)
+![Labels list](img/labels_list_v12_1.png)
### New project label from sidebar
-From the sidebar of an issue or a merge request, you can create a new **project label** inline immediately, instead of navigating to the project label list page.
+From the sidebar of an issue or a merge request, you can create a new **project label**
+inline immediately, instead of navigating to the project label list page.
-![Labels inline](img/new_label_from_sidebar.gif)
+![Labels inline](img/labels_new_label_from_sidebar.gif)
## Editing labels
NOTE: **Note:**
A permission level of Reporter or higher is required to edit labels.
-You can update a label by navigating to **Issues > Labels** in the project or group and clicking the pencil icon.
+To update a label, navigate to **Issues > Labels** in the project or group
+and click the pencil icon. The title, description and color can be changed.
+
+To delete a label, click the three dots next to the `Subscribe` button, and select
+**Delete**.
-You can delete a label by clicking the trash icon.
+![Delete label](img/labels_delete_v12_1.png)
### Promoting project labels to group labels
-If you are expanding from a few projects to a larger number of projects within the same group, you may want to share the same label among multiple projects in the same group. If you previously created a project label and now want to make it available for other projects, you can promote it to a group label.
+If you are expanding from a few projects to a larger number of projects within the
+same group, you may want to share the same label among multiple projects in the same
+group. If you previously created a project label and now want to make it available
+for other projects, you can promote it to a group label.
-From the project label list page, you can promote a project label to a group label. This will merge all project labels across all projects in this group with the same name into a single group label. All issues and merge requests that previously were assigned one of these project labels will now be assigned the new group label. This action cannot be reversed and the changes are permanent.
+From the project label list page, you can promote a project label to a group label.
+This will merge all project labels with the same name into a single group label, across
+all projects in this group. All issues and merge requests that were previously
+assigned one of these project labels will now be assigned the new group label. This
+action cannot be reversed and the changes are permanent.
-![Labels promotion](img/labels_promotion.png)
+![Labels promotion](img/labels_promotion_v12_1.png)
## Assigning labels from the sidebar
-Every issue and merge request can be assigned any number of labels. The labels are visible on every issue and merge request page, in the sidebar. They are also visible in the issue board. From the sidebar, you can assign or unassign a label to the object (i.e. label or unlabel it). You can also perform this as a [quick action](quick_actions.md) in a comment.
+Every issue and merge request can be assigned any number of labels. The labels are
+visible on every issue and merge request page, in the sidebar. They are also visible on:
+
+- Every issue and merge request page in the sidebar.
+- The issue board.
+
+From the sidebar, you can assign or unassign a label to the object (i.e. label or
+unlabel it). You can also perform this as a [quick action](quick_actions.md),
+in a comment.
| View labels in sidebar | Assign labels from sidebar |
-|:---:|:---:|
+|:----------------------:|:--------------------------:|
| ![Labels sidebar](img/labels_sidebar.png) | ![Labels sidebar assign](img/labels_sidebar_assign.png) |
## Searching for project labels
-You can search for project labels by navigating from the left sidebar to your
-project's **Issues > Labels** and entering your query to the search bar on the
-top-right:
+To search for project labels, go to **Issues > Labels** in the left sidebar, and enter
+your search query in the **Filter** field.
![Labels project list search](img/labels_project_list_search.png)
-GitLab will consider the label title and description for the search.
+GitLab will check both the label titles and descriptions for the search.
+
+## Filtering by label
-## Filtering issues, merge requests and epics by label
+The following can be filtered labels:
+
+- Issue lists
+- Merge Request lists
+- Epic lists **(ULTIMATE)**
+- Issue Boards
### Filtering in list pages
-From the project issue list page and the project merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group (including subgroup ancestors) labels and project labels.
+- From the project issue list page and the project merge request list page, you can
+ [filter](../search/index.md#issues-and-merge-requests) by:
+ - Group labels (including subgroup ancestors)
+ - Project labels
-From the group issue list page and the group merge request list page, you can [filter](../search/index.md#issues-and-merge-requests) by both group labels (including subgroup ancestors and subgroup descendants) and project labels.
+- From the group issue list page and the group merge request list page, you can
+ [filter](../search/index.md#issues-and-merge-requests) by:
+ - Group labels (including subgroup ancestors and subgroup descendants)
+ - Project labels
-From the group epic list page, you can [filter](../search/index.md#issues-and-merge-requests) by both current group labels as well as descendant group labels.
+- You can [filter](../search/index.md#issues-and-merge-requests) the group epic list
+ page by: **(ULTIMATE)**
+ - Current group labels
+ - Descendant group labels
-![Labels group issues](img/labels_group_issues.png)
+![Labels group issues](img/labels_group_issues_v12_1.png)
### Filtering in issue boards
-- From [project boards](issue_board.md), you can filter by both group labels and project labels in the [search and filter bar](../search/index.md#issue-boards).
-- From [group issue boards](issue_board.md#group-issue-boards-premium), you can filter by only group labels in the [search and filter bar](../search/index.md#issue-boards). **(PREMIUM)**
-- From [project boards](issue_board.md), you can filter by both group labels and project labels in the [issue board configuration](issue_board.md#configurable-issue-boards-starter). **(STARTER)**
-- From [group issue boards](issue_board.md#group-issue-boards-premium), you can filter by only group labels in the [issue board configuration](issue_board.md#configurable-issue-boards-starter). **(STARTER)**
+- From [project boards](issue_board.md), you can use the [search and filter bar](../search/index.md#issue-boards)
+ to filter by:
+ - Group labels
+ - Project labels
+
+- From [group issue boards](issue_board.md#group-issue-boards-premium), you can use the
+ [search and filter bar](../search/index.md#issue-boards) to filter by group labels only. **(PREMIUM)**
+
+- From [project boards](issue_board.md), in the [issue board configuration](issue_board.md#configurable-issue-boards-starter),
+ you can filter by: **(STARTER)**
+ - Group labels
+ - Project labels
+
+- From [group issue boards](issue_board.md#group-issue-boards-premium), in the [issue board configuration](issue_board.md#configurable-issue-boards-starter),
+ you can filter by group labels only. **(STARTER)**
## Subscribing to labels
-From the project label list page and the group label list page, you can subscribe to [notifications](../../workflow/notifications.md) of a given label, to alert you that the label has been assigned to an issue or merge request.
+From the project label list page and the group label list page, you can subscribe
+to [notifications](../../workflow/notifications.md) of a given label, to alert you
+that the label has been assigned to an issue or merge request.
-![Labels subscriptions](img/labels_subscriptions.png)
+![Labels subscriptions](img/labels_subscriptions_v12_1.png)
## Label priority
@@ -161,26 +225,43 @@ From the project label list page and the group label list page, you can subscrib
> - Introduced in GitLab 8.9.
> - Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554) considers changing this.
-Labels can have relative priorities, which are used in the "Label priority" and "Priority" sort orders of the issue and merge request list pages.
+Labels can have relative priorities, which are used in the "Label priority" and
+"Priority" sort orders of the issue and merge request list pages.
+
+From the project label list page, star a label to indicate that it has a priority.
+
+![Labels prioritized](img/labels_prioritized_v12_1.png)
+
+Drag starred labels up and down the list to change their priority. Higher in the list
+means higher priority. Prioritization happens at the project level, only on the project
+label list page, and not on the group label list page.
-From the project label list page, star a label to indicate that it has a priority. Drag starred labels up and down to change their priority. Higher means higher priority. Prioritization happens at the project level, only on the project label list page, and not on the group label list page. However, both project and group labels can be prioritized on the project label list page since both types are displayed on the project label list page.
+However, both project and group
+labels can be prioritized on the project label list page since both types are displayed
+on the project label list page.
-![Labels prioritized](img/labels_prioritized.png)
+![Drag to change label priority](img/labels_drag_priority_v12_1.gif)
-On the project and group issue and merge request list pages, you can sort by `Label priority` and `Priority`, which account for objects (issues and merge requests) that have prioritized labels assigned to them.
+On the merge request and issue pages, for both groups and projects, you can sort by `Label priority`
+and `Priority`, which account for objects (issues and merge requests) that have prioritized
+labels assigned to them.
If you sort by `Label priority`, GitLab considers this sort comparison order:
- Object with a higher priority prioritized label.
- Object without a prioritized label.
-Ties are broken arbitrarily. (Note that we _only_ consider the highest prioritized label in an object, and not any of the lower prioritized labels. [This discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554) considers changing this.)
+Ties are broken arbitrarily. Note that we _only_ consider the highest prioritized label
+in an object, and not any of the lower prioritized labels. [This discussion](https://gitlab.com/gitlab-org/gitlab-ce/issues/18554)
+considers changing this.
![Labels sort label priority](img/labels_sort_label_priority.png)
If you sort by `Priority`, GitLab considers this sort comparison order:
-- Object's assigned [milestone](milestones/index.md)'s due date is sooner, provided the object has a milestone and the milestone has a due date. If this isn't the case, consider the object having a due date in the infinite future.
+- Due date of the assigned [milestone](milestones/index.md) is sooner, provided
+ the object has a milestone and the milestone has a due date. If this isn't the case,
+ consider the object having a due date in the infinite future.
- Object with a higher priority prioritized label.
- Object without a prioritized label.
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/members/share_project_with_groups.md b/doc/user/project/members/share_project_with_groups.md
index 611ff0e6bfb..9340d239677 100644
--- a/doc/user/project/members/share_project_with_groups.md
+++ b/doc/user/project/members/share_project_with_groups.md
@@ -20,18 +20,18 @@ To share 'Project Acme' with the 'Engineering' group:
1. For 'Project Acme' use the left navigation menu to go to **Settings > Members**
- ![share project with groups](img/share_project_with_groups.png)
+ ![share project with groups](img/share_project_with_groups.png)
1. Select the 'Share with group' tab
1. Add the 'Engineering' group with the maximum access level of your choice
1. Click **Share** to share it
- ![share project with groups tab](img/share_project_with_groups_tab.png)
+ ![share project with groups tab](img/share_project_with_groups_tab.png)
1. After sharing 'Project Acme' with 'Engineering', the project will be listed
on the group dashboard
- !['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
+ !['Project Acme' is listed as a shared project for 'Engineering'](img/other_group_sees_shared_project.png)
Note that you can only share a project with:
diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md
index ad1d79ae5b1..3a409bab19d 100644
--- a/doc/user/project/merge_requests/code_quality.md
+++ b/doc/user/project/merge_requests/code_quality.md
@@ -1,5 +1,6 @@
---
type: reference, howto
+disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/code_quality_diff.html'
---
# Code Quality **(STARTER)**
@@ -9,8 +10,18 @@ in [GitLab Starter](https://about.gitlab.com/pricing/) 9.3.
With the help of [GitLab CI/CD](../../../ci/README.md), you can analyze your
source code quality using GitLab Code Quality.
-Code Quality uses [Code Climate Engines](https://codeclimate.com), which are
-free and open source. Code Quality doesn't require a Code Climate subscription.
+
+Code Quality:
+
+- Uses [Code Climate Engines](https://codeclimate.com), which are
+ free and open source. Code Quality doesn't require a Code Climate
+ subscription.
+- Runs in [pipelines](../../../ci/pipelines.md) using an Docker image built in
+ [GitLab Code
+ Quality](https://gitlab.com/gitlab-org/security-products/codequality) project.
+- Can make use of a [template](#template-and-examples).
+- Is available with [Auto
+ DevOps](../../../topics/autodevops/index.md#auto-code-quality-starter).
Going a step further, GitLab can show the Code Quality report right
in the merge request widget area:
@@ -21,22 +32,48 @@ in the merge request widget area:
For instance, consider the following workflow:
-1. Your backend team member starts a new implementation for making a certain feature in your app faster
-1. With Code Quality reports, they analyze how their implementation is impacting the code quality
-1. The metrics show that their code degrade the quality in 10 points
-1. You ask a co-worker to help them with this modification
-1. They both work on the changes until Code Quality report displays no degradations, only improvements
-1. You approve the merge request and authorize its deployment to staging
-1. Once verified, their changes are deployed to production
+1. Your backend team member starts a new implementation for making a certain
+ feature in your app faster.
+1. With Code Quality reports, they analyze how their implementation is impacting
+ the code quality.
+1. The metrics show that their code degrade the quality in 10 points.
+1. You ask a co-worker to help them with this modification.
+1. They both work on the changes until Code Quality report displays no
+ degradations, only improvements.
+1. You approve the merge request and authorize its deployment to staging.
+1. Once verified, their changes are deployed to production.
+
+## Template and examples
+
+For most GitLab instances, the supplied template is the preferred method of
+implementing Code Quality. See
+[Analyze your project's Code Quality](../../../ci/examples/code_quality.md) for:
+
+- Information on the builtin GitLab Code Quality template.
+- Examples of manual GitLab configuration for earlier GitLab versions.
-## How it works
+## Configuring jobs using variables
-First of all, you need to define a job in your `.gitlab-ci.yml` file that generates the
-[Code Quality report artifact](../../../ci/yaml/README.md#artifactsreportscodequality-starter).
+The Code Quality job supports environment variables that users can set to
+configure job execution at runtime.
-The Code Quality report artifact is a subset of the
-[Code Climate spec](https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types).
-It must be a JSON file containing an array of objects with the following properties:
+For a list of available environment variables, see
+[Environment variables](https://gitlab.com/gitlab-org/security-products/codequality/blob/master/README.md#environment-variables).
+
+## Implementing a custom tool
+
+It's possible to have a custom tool provide Code Quality reports in GitLab. To
+do this:
+
+1. Define a job in your `.gitlab-ci.yml` file that generates the
+ [Code Quality report
+ artifact](../../../ci/yaml/README.md#artifactsreportscodequality-starter).
+1. Configure your tool to generate the Code Quality report artifact as a JSON
+ file that implements subset of the [Code Climate
+ spec](https://github.com/codeclimate/spec/blob/master/SPEC.md#data-types).
+
+The Code Quality report artifact JSON file must contain an array of objects
+with the following properties:
| Name | Description |
| ---------------------- | -------------------------------------------------------------------------------------- |
@@ -63,13 +100,16 @@ Example:
```
NOTE: **Note:**
-Although the Code Climate spec supports more properties, those are ignored by GitLab.
+Although the Code Climate spec supports more properties, those are ignored by
+GitLab.
+
+## Code Quality reports
-For more information on what the Code Quality job should look like, check the
-example on [analyzing a project's code quality](../../../ci/examples/code_quality.md).
+Once the Code Quality job has completed, GitLab:
-GitLab then checks this report, compares the metrics between the source and target
-branches, and shows the information right on the merge request.
+- Checks the generated report.
+- Compares the metrics between the source and target branches.
+- Shows the information right on the merge request.
If multiple jobs in a pipeline generate a code quality artifact, only the artifact from
the last created job (the job with the largest job ID) is used. To avoid confusion,
diff --git a/doc/user/project/merge_requests/code_quality_diff.md b/doc/user/project/merge_requests/code_quality_diff.md
index ccc694672a6..0aa108a2e73 100644
--- a/doc/user/project/merge_requests/code_quality_diff.md
+++ b/doc/user/project/merge_requests/code_quality_diff.md
@@ -1,5 +1,4 @@
---
-redirect_from: 'code_quality_diff.md'
redirect_to: 'code_quality.md'
---
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/allow_collaboration.png b/doc/user/project/merge_requests/img/allow_collaboration.png
index e40e8a6b11c..cc13493646d 100644
--- a/doc/user/project/merge_requests/img/allow_collaboration.png
+++ b/doc/user/project/merge_requests/img/allow_collaboration.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
index 4ba4c84c8c5..bc7678b21ec 100644
--- a/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
+++ b/doc/user/project/merge_requests/img/allow_collaboration_after_save.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
index b6dc86f312e..2598cc71c33 100644
--- a/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
+++ b/doc/user/project/merge_requests/img/approvals_premium_mr_widget.png
Binary files differ
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
index b6f6188b9cd..6a09412533f 100644
--- a/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
+++ b/doc/user/project/merge_requests/img/approvals_premium_project_edit.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_edit.png b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png
index 868b9d58740..4c554d846cc 100644
--- a/doc/user/project/merge_requests/img/approvals_starter_project_edit.png
+++ b/doc/user/project/merge_requests/img/approvals_starter_project_edit.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approvals_starter_project_empty.png b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png
index 7375820224c..fc88a59a745 100644
--- a/doc/user/project/merge_requests/img/approvals_starter_project_empty.png
+++ b/doc/user/project/merge_requests/img/approvals_starter_project_empty.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approve.png b/doc/user/project/merge_requests/img/approve.png
index e68259ac5c2..e2641f48c7a 100644
--- a/doc/user/project/merge_requests/img/approve.png
+++ b/doc/user/project/merge_requests/img/approve.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/approve_additionally.png b/doc/user/project/merge_requests/img/approve_additionally.png
index 3db5a9159e5..bab0cd4e041 100644
--- a/doc/user/project/merge_requests/img/approve_additionally.png
+++ b/doc/user/project/merge_requests/img/approve_additionally.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/create_from_email.png b/doc/user/project/merge_requests/img/create_from_email.png
index 5cb2afaf976..14eef473e27 100644
--- a/doc/user/project/merge_requests/img/create_from_email.png
+++ b/doc/user/project/merge_requests/img/create_from_email.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.png b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_inaccessible_v12_2.png
new file mode 100644
index 00000000000..2dc02634fd8
--- /dev/null
+++ 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_v12_2.png b/doc/user/project/merge_requests/img/cross_project_dependencies_edit_v12_2.png
new file mode 100644
index 00000000000..362e7e0ead2
--- /dev/null
+++ 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_v12_2.png b/doc/user/project/merge_requests/img/cross_project_dependencies_view_v12_2.png
new file mode 100644
index 00000000000..e00231c839b
--- /dev/null
+++ 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/filter_approver_merge_requests.png b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png
index 9c386391a4f..4c28ee17f47 100644
--- a/doc/user/project/merge_requests/img/filter_approver_merge_requests.png
+++ b/doc/user/project/merge_requests/img/filter_approver_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
index 8df6a3c9a29..0989b41e2a4 100644
--- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
+++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.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/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
index ea3aff59aa1..ed374b11fbd 100644
--- a/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
+++ b/doc/user/project/merge_requests/img/merge_when_pipeline_succeeds_only_if_succeeds_settings.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
index 9ae6e350798..dde2680ed74 100644
--- a/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
+++ b/doc/user/project/merge_requests/img/multiple_assignees_for_merge_requests_sidebar.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/remove_approval.png b/doc/user/project/merge_requests/img/remove_approval.png
index 6083e1745ef..b178d26cf85 100644
--- a/doc/user/project/merge_requests/img/remove_approval.png
+++ b/doc/user/project/merge_requests/img/remove_approval.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/squash_mr_message.png b/doc/user/project/merge_requests/img/squash_mr_message.png
index 8734cab29aa..8c7dc7886f7 100644
--- a/doc/user/project/merge_requests/img/squash_mr_message.png
+++ b/doc/user/project/merge_requests/img/squash_mr_message.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
index b6d38d85165..ab2c8425b83 100644
--- a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
+++ b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
Binary files differ
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index f593046fa8b..2794cbc0f39 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -47,6 +47,7 @@ With **[GitLab Enterprise Edition][ee]**, you can also:
- Analyze your dependencies for vulnerabilities with [Dependency Scanning](../../application_security/dependency_scanning/index.md) **(ULTIMATE)**
- Analyze your Docker images for vulnerabilities with [Container Scanning](../../application_security/container_scanning/index.md) **(ULTIMATE)**
- Determine the performance impact of changes with [Browser Performance Testing](#browser-performance-testing-premium) **(PREMIUM)**
+- Specify merge order dependencies with [Cross-project Merge Request Dependencies](#cross-project-merge-request-dependencies-premium) **(PREMIUM)**
## Use cases
@@ -285,6 +286,9 @@ as pushing changes:
- Create a new merge request for the pushed branch.
- Set the target of the merge request to a particular branch.
- Set the merge request to merge when its pipeline succeeds.
+- Set the merge request to remove the source branch when it's merged.
+- Set the title of the merge request to a particular title.
+- Set the description of the merge request to a particular description.
### Create a new merge request using git push options
@@ -328,6 +332,49 @@ pipeline succeeds at the same time using a `-o` flag per push option:
git push -o merge_request.create -o merge_request.merge_when_pipeline_succeeds
```
+### Set removing the source branch using git push options
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
+To set an existing merge request to remove the source branch when the
+merge request is merged, the
+`merge_request.remove_source_branch` push option can be used:
+
+```sh
+git push -o merge_request.remove_source_branch
+```
+
+You can also use this push option in addition to the
+`merge_request.create` push option.
+
+### Set merge request title using git push options
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
+To set the title of an existing merge request, use
+the `merge_request.title` push option:
+
+```sh
+git push -o merge_request.title="The title I want"
+```
+
+You can also use this push option in addition to the
+`merge_request.create` push option.
+
+### Set merge request description using git push options
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
+To set the description of an existing merge request, use
+the `merge_request.description` push option:
+
+```sh
+git push -o merge_request.description="The description I want"
+```
+
+You can also use this push option in addition to the
+`merge_request.create` push option.
+
## Find the merge request that introduced a change
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383) in GitLab 10.5.
@@ -365,6 +412,11 @@ have been marked as a **Work In Progress**.
[Learn more about setting a merge request as "Work In Progress".](work_in_progress_merge_requests.md)
+## Merge Requests for Confidential Issues
+
+Create [merge requests to resolve confidential issues](../issues/confidential_issues.md#merge-requests-for-confidential-issues)
+for preventing leakage or early release of sentive data through regular merge requests.
+
## Merge request approvals **(STARTER)**
> Included in [GitLab Starter][products].
@@ -386,6 +438,17 @@ can show the Code Climate report right in the merge request widget area.
[Read more about Code Quality reports.](code_quality.md)
+## Metrics Reports **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9788) in [GitLab Premium][products] 11.10.
+Requires GitLab Runner 11.10 and above.
+
+If you are using [GitLab CI][ci], you can configure your job to output custom
+metrics and GitLab will display the Metrics Report on the merge request so
+that it's fast and easy to identify changes to important metrics.
+
+[Read more about Metrics Report](../../../ci/metrics_reports.md).
+
## Browser Performance Testing **(PREMIUM)**
> Introduced in [GitLab Premium][products] 10.3.
@@ -396,6 +459,21 @@ GitLab runs the [Sitespeed.io container][sitespeed-container] and displays the d
[Read more about Browser Performance Testing.](browser_performance_testing.md)
+## Cross-project Merge Request Dependencies **(PREMIUM)**
+
+> Introduced in [GitLab Premium][products] 12.2.
+
+A single logical change may be split across several merge requests, across
+several projects. When this happens, the order in which MRs are merged is
+important.
+
+GitLab allows you to specify that a merge request depends on other MRs. With
+this relationship in place, the merge request cannot be merged until all of its
+dependencies have also been merged, helping to maintain the consistency of a
+single logical change.
+
+[Read more about cross-project merge request dependencies.](merge_request_dependencies.md)
+
## Security reports **(ULTIMATE)**
GitLab can scan and report any vulnerabilities found in your project.
@@ -410,15 +488,6 @@ without having to check the entire job log.
[Read more about JUnit test reports](../../../ci/junit_test_reports.md).
-## Live preview with Review Apps
-
-If you configured [Review Apps](https://about.gitlab.com/features/review-apps/) for your project,
-you can preview the changes submitted to a feature-branch through a merge request
-in a per-branch basis. No need to checkout the branch, install and preview locally;
-all your changes will be available to preview by anyone with the Review Apps link.
-
-[Read more about Review Apps.](../../../ci/review_apps/index.md)
-
## Merge request diff file navigation
When reviewing changes in the **Changes** tab the diff can be navigated using
@@ -428,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 220795d6f15..5161b25de99 100644
--- a/doc/user/project/merge_requests/merge_request_approvals.md
+++ b/doc/user/project/merge_requests/merge_request_approvals.md
@@ -44,7 +44,7 @@ To edit the merge request approvals:
1. Navigate to your project's **Settings > General** and expand
**Merge request approvals**.
- ![Approvals starter project empty](img/approvals_starter_project_empty.png)
+ ![Approvals starter project empty](img/approvals_starter_project_empty.png)
1. Click **Edit**.
1. Search for users or groups that will be [eligible to approve](#eligible-approvers)
@@ -54,7 +54,7 @@ To edit the merge request approvals:
box. Note: the minimum can be 0.
1. Click **Update approvers**.
- ![Approvals starter project edit](img/approvals_starter_project_edit.png)
+ ![Approvals starter project edit](img/approvals_starter_project_edit.png)
The steps above are the minimum required to get approvals working in your
merge requests, but there are a couple more options available that might be
@@ -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.png)
## Multiple approval rules **(PREMIUM)**
@@ -144,14 +144,17 @@ the following is possible:
- If the required number of approvals has _not_ been yet met, they can approve
it by clicking the displayed **Approve** button.
- ![Approve](img/approve.png)
+
+ ![Approve](img/approve.png)
+
- If the required number of approvals has already been met, they can still
approve it by clicking the displayed **Approve additionally** button.
- ![Add approval](img/approve_additionally.png)
+
+ ![Add approval](img/approve_additionally.png)
- **They have already approved this merge request**: They can remove their approval.
- ![Remove approval](img/remove_approval.png)
+ ![Remove approval](img/remove_approval.png)
NOTE: **Note:**
The merge request author is only allowed to approve their own merge request
@@ -159,7 +162,7 @@ if [**Prevent author approval**](#allowing-merge-request-authors-to-approve-thei
For a given merge request, if the approval restrictions have been satisfied,
the merge request is unblocked and can be merged.
-Note, that meeting the required number of approvals is a necessary, but not
+Note that meeting the required number of approvals is a necessary, but not
sufficient condition for unblocking a merge request from being merged. There
are other conditions that may block it, such as merge conflicts,
[pending discussions](../../discussions/index.md#only-allow-merge-requests-to-be-merged-if-all-threads-are-resolved)
@@ -203,7 +206,7 @@ First, you have to enable this option in the project's settings:
1. Tick the "Can override approvers and approvals required per merge request"
checkbox
- ![Approvals can override](img/approvals_can_override.png)
+ ![Approvals can override](img/approvals_can_override.png)
1. Click **Save changes**
@@ -226,12 +229,6 @@ The default approval settings can now be overridden when creating a
in the **No. approvals required** box.
1. Click **Update approvers**.
-There are however some restrictions:
-
-- The amount of required approvals, if changed, must be greater than the default
- set at the project level. This ensures that you're not forced to adjust settings
- when someone is unavailable for approval, yet the process is still enforced.
-
NOTE: **Note:**
If you are contributing to a forked project, things are a little different.
Read what happens when the
@@ -239,14 +236,9 @@ Read what happens when the
## Overriding merge request approvals default settings **(PREMIUM)**
-In GitLab Premium, when the approval rules are [set at the project level](#editing-approvals-premium), and
-**Can override approvers and approvals required per merge request** is checked, there are a few more
-restrictions (compared to [GitLab Starter](#overriding-the-merge-request-approvals-default-settings)):
-
-- Approval rules can be added to an MR with no restriction.
-- For project sourced approval rules, editing and removing approvers is not allowed.
-- The approvals required of all approval rules is configurable, but if a rule is backed by a project rule, then it is restricted
- to the minimum approvals required set in the project's corresponding rule.
+In GitLab Premium, when the approval rules are [set at the project level](#editing-approvals-premium),
+and **Can override approvers and approvals required per merge request** is checked,
+approval rules can be added to an MR with no restriction.
## Resetting approvals on push
@@ -259,7 +251,7 @@ new commits are pushed to the source branch of the merge request:
1. Tick the "Remove all approvals in a merge request when new commits are pushed to its source branch"
checkbox
- ![Approvals remove on push](img/approvals_remove_on_push.png)
+ ![Approvals remove on push](img/approvals_remove_on_push.png)
1. Click **Save changes**
@@ -331,6 +323,16 @@ the dropdown) `approver` and select the user.
![Filter MRs by an approver](img/filter_approver_merge_requests.png)
+## Security approvals in merge requests **(ULTIMATE)**
+
+> Introduced in [GitLab Ultimate](https://about.gitlab.com/pricing) 12.2.
+
+Merge Request Approvals can be configured to require approval from a member
+of your security team when a vulnerability would be introduced by a merge request.
+
+For more information, see
+[Security approvals in merge requests](../../application_security/index.md#security-approvals-in-merge-requests-ultimate).
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/project/merge_requests/merge_request_dependencies.md b/doc/user/project/merge_requests/merge_request_dependencies.md
new file mode 100644
index 00000000000..b30e24b2386
--- /dev/null
+++ b/doc/user/project/merge_requests/merge_request_dependencies.md
@@ -0,0 +1,142 @@
+---
+type: reference, concepts
+---
+
+# Cross-project Merge Request dependencies **(PREMIUM)**
+
+> [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
+merge request "depends on" another, then it cannot be merged until its
+dependency is itself merged.
+
+NOTE: **Note:**
+Merge requests dependencies are a **PREMIUM** feature, but this restriction is
+only enforced for the dependent merge request. A merge request in a **CORE** or
+**STARTER** project can be a dependency of a **PREMIUM** merge request, but not
+vice-versa.
+
+NOTE: **Note:**
+A merge request can only depend on merge requests in a different project. Two
+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.
+- Prevent a documentation-only merge request from being merged before the merge request
+ 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.
+
+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
+significant.
+
+For example, given a project `mycorp/awesome-project` that imports a library
+at `myfriend/awesome-lib`, adding a feature in `awesome-project` may **also**
+require changes to `awesome-lib`, and so necessitate two merge requests. Merging
+the `awesome-project` merge request before the `awesome-lib` one would
+break the `master`branch.
+
+The `awesome-project` merge request could be [marked as
+WIP](work_in_progress_merge_requests.md),
+and the reason for the WIP stated included in the comments. However, this
+requires the state of the `awesome-lib` merge request to be manually
+tracked, and doesn't scale well if the `awesome-project` merge request
+depends on changes to **several** other projects.
+
+By making the `awesome-project` merge request depend on the
+`awesome-lib` merge request instead, this relationship is
+automatically tracked by GitLab, and the WIP state can be used to
+communicate the readiness of the code in each individual merge request
+instead.
+
+## Configuration
+
+To continue the above example, you can configure a dependency when creating the
+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_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.
+
+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_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.
+
+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_v12_2.png)
+
+Until all dependencies have, themselves, been merged, the **Merge**
+button will be disabled for the dependent merge request. In
+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.
+
+If a merge request has been closed **and** the dependency is no longer relevant,
+it must be removed as a dependency, following the instructions above, before
+merge.
+
+## Limitations
+
+- API support: [gitlab-ee#12551](https://gitlab.com/gitlab-org/gitlab-ee/issues/12551)
+- Dependencies are not preserved across project export/import: [gitlab-ee#12549](https://gitlab.com/gitlab-org/gitlab-ee/issues/12549)
+- Complex merge order dependencies are not supported: [gitlab-ee#11393](https://gitlab.com/gitlab-org/gitlab-ee/issues/11393)
+
+The last item merits a little more explanation. Dependencies between merge
+requests can be described as a graph of relationships. The simplest possible
+graph has one merge request that depends upon another:
+
+```mermaid
+graph LR;
+ myfriend/awesome-lib!10-->mycorp/awesome-project!100;
+```
+
+A more complex (and still supported) graph might have one merge request that
+directly depends upon several others:
+
+```mermaid
+graph LR;
+ myfriend/awesome-lib!10-->mycorp/awesome-project!100;
+ herfriend/another-lib!1-->mycorp/awesome-project!100;
+```
+
+Several different merge requests can also directly depend upon the
+same merge request:
+
+```mermaid
+graph LR;
+ herfriend/another-lib!1-->myfriend/awesome-lib!10;
+ herfriend/another-lib!1-->mycorp/awesome-project!100;
+```
+
+What is **not** supported is a "deep", or "nested" graph of dependencies, e.g.:
+
+```mermaid
+graph LR;
+ herfriend/another-lib!1-->myfriend/awesome-lib!10;
+ myfriend/awesome-lib!10-->mycorp/awesome-project!100;
+```
+
+In this example, `myfriend/awesome-lib!10` depends on `herfriend/another-lib!1`,
+and is itself a dependent of `mycorp/awesome-project!100`. This means that
+`myfriend/awesome-lib!10` becomes an **indirect** dependency of
+`mycorp/awesome-project!100`, which is not yet supported.
diff --git a/doc/user/project/milestones/burndown_charts.md b/doc/user/project/milestones/burndown_charts.md
index 84e08b4eeb8..d4dd9c7d4c3 100644
--- a/doc/user/project/milestones/burndown_charts.md
+++ b/doc/user/project/milestones/burndown_charts.md
@@ -1,6 +1,9 @@
+---
+type: reference
+---
+
# Burndown Charts **(STARTER)**
-> **Notes:**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1540) in [GitLab Starter](https://about.gitlab.com/pricing/) 9.1 for project milestones.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/5354) in [GitLab Premium](https://about.gitlab.com/pricing/) 10.8 for group milestones.
> - [Added](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/6495) to [GitLab Starter](https://about.gitlab.com/pricing/) 11.2 for group milestones.
@@ -20,7 +23,8 @@ yourself to have the same sense of progress.
GitLab Starter plots it for you and presents it in a clear and beautiful chart.
-For an overview, check the video demonstration on [Mapping Work Versus Time, With Burndown Charts](https://about.gitlab.com/2017/04/25/mapping-work-to-do-versus-time-with-burndown-charts/).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, check the video demonstration on [Mapping work versus time with Burndown Charts](https://www.youtube.com/watch?v=zJU2MuRChzs).
## Use cases
@@ -68,3 +72,15 @@ The Burndown Chart can also be toggled to display the cumulative open issue
weight for a given day. When using this feature, make sure issue weights have
been properly assigned, since an open issue with no weight adds zero to the
cumulative value.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index 142462e1879..453983fa882 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -1,3 +1,7 @@
+---
+type: index, reference
+---
+
# Milestones
## Overview
@@ -145,3 +149,15 @@ The milestone sidebar on the milestone view shows the following:
For project milestones only, the milestone sidebar shows the total issue weight of all issues that have the milestone assigned.
![Project milestone page](img/milestones_project_milestone_page.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index 494dd539da7..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,13 +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. To migrate trigger to use new permission
-model use **Take ownership**.
-
## Before GitLab 8.12
In versions before GitLab 8.12, all CI jobs would use the CI Runner's token
@@ -204,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
@@ -228,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 fdc1e978291..75b0623e6b0 100644
--- a/doc/user/project/operations/feature_flags.md
+++ b/doc/user/project/operations/feature_flags.md
@@ -74,9 +74,9 @@ To define specs for each environment:
1. Navigate to your project's **Operations > Feature Flags**.
1. Click on the **New Feature Flag** button or edit an existing flag.
-1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). This status will be used for _all_ environments.
+1. Set the status of the default [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) (`*`). Choose a rollout strategy. This status and rollout strategy combination will be used for _all_ environments.
1. If you want to enable/disable the feature on a specific environment, create a new [spec](../../../ci/environments.md#scoping-environments-with-specs-premium) and type the environment name.
-1. Set the status of the additional spec. This status takes precedence over the default spec's status since we always use the most specific match available.
+1. Set the status and rollout strategy of the additional spec. This status and rollout strategy combination takes precedence over the default spec since we always use the most specific match available.
1. Click **Create feature flag** or **Update feature flag**.
![Feature flag specs list](img/specs_list.png)
@@ -85,6 +85,46 @@ NOTE: **NOTE**
We'd highly recommend you to use the [Environment](../../../ci/environments.md)
feature in order to quickly assess which flag is enabled per environment.
+## Rollout Strategy
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8240) in GitLab 12.2.
+
+The selected rollout strategy affects which users will experience the feature enabled.
+
+The status of an environment spec ultimately determines whether or not a feature is enabled at all.
+For instance, a feature will always be disabled for every user if the matching environment spec has a disabled status, regardless of the chosen rollout strategy.
+However, a feature will be enabled for 50% of logged-in users if the matching environment spec has an enabled status along with a **Percent rollout (logged in users)** strategy set to 50%.
+
+### All users
+
+Enables the feature for all users.
+
+**All users** is implemented using the Unleash [default](https://unleash.github.io/docs/activation_strategy#default) activation strategy.
+
+### Percent rollout (logged in users)
+
+**Percent rollout (logged in users)** enables the feature for a percentage of authenticated users. Set a value of 15%, for example, to enable the feature for 15% of authenticated users.
+
+A rollout percentage may be between 0% and 100%.
+
+CAUTION: **Caution:**
+If this strategy is selected, then the Unleash client **must** be given a user id for the feature to be enabled. See the [Ruby example](#ruby-application-example) below.
+
+**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
@@ -175,3 +215,34 @@ func main() {
log.Fatal(http.ListenAndServe(":8080", nil))
}
```
+
+### Ruby application example
+
+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 or a list of **Target Users**.
+
+```ruby
+#!/usr/bin/env ruby
+
+require 'unleash'
+require 'unleash/context'
+
+unleash = Unleash::Client.new({
+ url: 'http://gitlab.com/api/v4/feature_flags/unleash/42',
+ app_name: 'production',
+ instance_id: '29QmjsW6KngPR5JNPMWx'
+})
+
+unleash_context = Unleash::Context.new
+# Replace "123" with the id of an authenticated user.
+# Note that the context's user id must be a string:
+# https://unleash.github.io/docs/unleash_context
+unleash_context.user_id = "123"
+
+if unleash.is_enabled?("my_feature_name", unleash_context)
+ puts "Feature enabled"
+else
+ puts "hello, world!"
+end
+```
diff --git a/doc/user/project/operations/img/error_tracking_list.png b/doc/user/project/operations/img/error_tracking_list.png
index aa0f9867fdb..194c7b440a4 100644
--- a/doc/user/project/operations/img/error_tracking_list.png
+++ b/doc/user/project/operations/img/error_tracking_list.png
Binary files differ
diff --git a/doc/user/project/operations/img/external_dashboard_link.png b/doc/user/project/operations/img/external_dashboard_link.png
index 4fb8bce7cd0..82c5e05e467 100644
--- a/doc/user/project/operations/img/external_dashboard_link.png
+++ b/doc/user/project/operations/img/external_dashboard_link.png
Binary files differ
diff --git a/doc/user/project/operations/img/external_dashboard_settings.png b/doc/user/project/operations/img/external_dashboard_settings.png
index 8dc380f01e2..e1b7fa56a1c 100644
--- a/doc/user/project/operations/img/external_dashboard_settings.png
+++ b/doc/user/project/operations/img/external_dashboard_settings.png
Binary files differ
diff --git a/doc/user/project/operations/img/feature_flags_list.png b/doc/user/project/operations/img/feature_flags_list.png
index 5313a163fec..f3e85b9ce44 100644
--- a/doc/user/project/operations/img/feature_flags_list.png
+++ b/doc/user/project/operations/img/feature_flags_list.png
Binary files differ
diff --git a/doc/user/project/operations/img/specs_list.png b/doc/user/project/operations/img/specs_list.png
index 9630c907cfc..43d069c09ce 100644
--- a/doc/user/project/operations/img/specs_list.png
+++ b/doc/user/project/operations/img/specs_list.png
Binary files differ
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/packages/img/npm_package_view.png b/doc/user/project/packages/img/npm_package_view.png
index 8baf7d0ef9f..e0634718c02 100644
--- a/doc/user/project/packages/img/npm_package_view.png
+++ b/doc/user/project/packages/img/npm_package_view.png
Binary files differ
diff --git a/doc/user/project/packages/npm_registry.md b/doc/user/project/packages/npm_registry.md
index 481b1ce0337..e01bac6b26f 100644
--- a/doc/user/project/packages/npm_registry.md
+++ b/doc/user/project/packages/npm_registry.md
@@ -49,41 +49,42 @@ Registry.
## Authenticating to the GitLab NPM Registry
If a project is private or you want to upload an NPM package to GitLab,
-credentials will need to be provided for authentication. Support is available
-only for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow).
+credentials will need to be provided for authentication. Support is available for [OAuth tokens](../../../api/oauth2.md#resource-owner-password-credentials-flow) or [personal access tokens](../../profile/personal_access_tokens.md).
-CAUTION: **2FA not supported:**
-Authentication for personal access tokens is not yet supported
-([#9140](https://gitlab.com/gitlab-org/gitlab-ee/issues/9140)). If you have 2FA
-enabled, you won't be able to authenticate to the GitLab NPM Registry.
+CAUTION: **2FA is only supported with personal access tokens:**
+If you have 2FA enabled, you need to use a [personal access token](../../profile/personal_access_tokens.md) with OAuth headers. Standard OAuth tokens won't be able to authenticate to the GitLab NPM Registry.
### Authenticating with an OAuth token
-To authenticate with an [OAuth token](../../../api/oauth2.md#resource-owner-password-credentials-flow),
-add a corresponding section to your `.npmrc` file:
+To authenticate with an [OAuth token](../../../api/oauth2.md#resource-owner-password-credentials-flow)
+or [personal access token](../../profile/personal_access_tokens.md), add a corresponding section to your `.npmrc` file:
```ini
; Set URL for your scoped packages.
; For example package with name `@foo/bar` will use this URL for download
@foo:registry=https://gitlab.com/api/v4/packages/npm/
-; Add the OAuth token for the scoped packages URL. This will allow you to download
+; Add the token for the scoped packages URL. This will allow you to download
; `@foo/` packages from private projects.
-//gitlab.com/api/v4/packages/npm/:_authToken=<your_oauth_token>
+//gitlab.com/api/v4/packages/npm/:_authToken=<your_token>
-; Add OAuth token for uploading to the registry. Replace <your_project_id>
+; Add token for uploading to the registry. Replace <your_project_id>
; with the project you want your package to be uploaded to.
-//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_oauth_token>
+//gitlab.com/api/v4/projects/<your_project_id>/packages/npm/:_authToken=<your_token>
```
Replace `<your_project_id>` with your project ID which can be found on the home page
-of your project and `<your_oauth_token>` with your OAuth token.
+of your project and `<your_token>` with your OAuth or personal access token.
If you have a self-hosted GitLab installation, replace `gitlab.com` with your
domain name.
You should now be able to download and upload NPM packages to your project.
+NOTE: **Note:**
+If you encounter an error message with [Yarn](https://yarnpkg.com/en/), see the
+[troubleshooting section](#troubleshooting).
+
## Uploading packages
Before you will be able to upload a package, you need to specify the registry
@@ -119,3 +120,29 @@ a given scope, you will receive a `403 Forbidden!` error.
If you upload a package with a same name and version twice, GitLab will show
both packages in the UI, but the GitLab NPM Registry will expose the most recent
one as it supports only one package per version for `npm install`.
+
+## Troubleshooting
+
+### Error running yarn with NPM registry
+
+If you are using [yarn](https://yarnpkg.com/en/) with the NPM registry, you may get
+an error message like:
+
+```sh
+yarn install v1.15.2
+warning package.json: No license field
+info No lockfile found.
+warning XXX: No license field
+[1/4] 🔍 Resolving packages...
+[2/4] 🚚 Fetching packages...
+error An unexpected error occurred: "https://gitlab.com/api/v4/projects/XXX/packages/npm/XXX/XXX/-/XXX/XXX-X.X.X.tgz: Request failed \"404 Not Found\"".
+info If you think this is a bug, please open a bug report with the information provided in "/Users/XXX/gitlab-migration/module-util/yarn-error.log".
+info Visit https://yarnpkg.com/en/docs/cli/install for documentation about this command
+```
+
+In this case, try adding this to your `.npmrc` file (and replace `<your_oauth_token>`
+with your with your OAuth or personal access token):
+
+```text
+//gitlab.com/api/v4/projects/:_authToken=<your_oauth_token>
+```
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png
index b4dcdc9bb60..3b93572a8f1 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/get_domain_verification_code_v12_0.png
Binary files differ
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png
index 2e825e84d92..5eab04a61a9 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png
Binary files differ
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png
index db8f25bc6c3..5f2f100e883 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/retry_domain_verification_v12_0.png
Binary files differ
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index 54ecc42d2b9..1821d954af3 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -1,7 +1,7 @@
---
last_updated: 2019-07-04
type: reference, howto
-redirect_from: 'https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html'
+disqus_identifier: 'https://docs.gitlab.com/ee/user/project/pages/getting_started_part_three.html'
---
# Custom domains and SSL/TLS Certificates
@@ -191,14 +191,14 @@ can use the following setup:
1. In Cloudflare, create a DNS `TXT` record to verify your domain.
1. In GitLab, verify your domain.
1. In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`.
-1. In Cloudflare, add a Page Rule pointing `www.domain,com` to `domain.com`:
- - Navigate to your domain's dashboard and click **Page Rules**
- on the top nav.
- - Click **Create Page Rule**.
- - Enter the domain `www.domain.com` and click **+ Add a Setting**.
- - From the dropdown menu, choose **Forwarding URL**, then select the
- status code **301 - Permanent Redirect**.
- - Enter the destination URL `https://domain.com`.
+1. In Cloudflare, add a Page Rule pointing `www.domain.com` to `domain.com`:
+ - Navigate to your domain's dashboard and click **Page Rules**
+ on the top nav.
+ - Click **Create Page Rule**.
+ - Enter the domain `www.domain.com` and click **+ Add a Setting**.
+ - From the dropdown menu, choose **Forwarding URL**, then select the
+ status code **301 - Permanent Redirect**.
+ - Enter the destination URL `https://domain.com`.
## Adding an SSL/TLS certificate to Pages
@@ -263,7 +263,6 @@ To enable this setting:
1. Navigate to your project's **Settings > Pages**.
1. Tick the checkbox **Force HTTPS (requires valid certificates)**.
-
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index 7675a5dd9d4..255d535645d 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -15,6 +15,12 @@ GitLab does it for you, out-of-the-box.
[Let's Encrypt](https://letsencrypt.org) is a free, automated, and
open source Certificate Authority.
+CAUTION: **Caution:**
+This feature is in **beta** and might present bugs and UX issues
+such as [#64870](https://gitlab.com/gitlab-org/gitlab-ce/issues/64870).
+See all the related issues linked from this [issue's description](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996)
+for more information.
+
## Requirements
Before you can enable automatic provisioning of a SSL certificate for your domain, make sure you have:
@@ -41,7 +47,7 @@ Once you've met the requirements, to enable Let's Encrypt integration:
1. Click **Edit** in the top-right corner.
1. Enable Let's Encrypt integration by switching **Automatic certificate management using Let's Encrypt**:
- ![Enable Let's Encrypt](img/lets_encrypt_integration_v12_1.png)
+ ![Enable Let's Encrypt](img/lets_encrypt_integration_v12_1.png)
1. Click **Save changes**.
@@ -55,9 +61,22 @@ associated Pages domain. It will be also renewed automatically by GitLab.
> - If you already have SSL certificate in domain settings it
> will continue to work until it will be replaced by Let's Encrypt's certificate.
-<!-- ## Troubleshooting
+## Troubleshooting
+
+### Error "Certificate misses intermediates"
+
+If you get an error **Certificate misses intermediates** while trying to enable Let's Encrypt integration for your domain, follow the steps below:
+
+1. Go to your project's **Settings > Pages**.
+1. Turn off **Force HTTPS** if it's turned on.
+1. Click **Details** on your domain.
+1. Click the **Edit** button in the top right corner of domain details page.
+1. Enable Let's Encrypt integration.
+1. Click **Save**.
+1. Go to your project's **Settings > Pages**.
+1. Turn on **Force HTTPS**.
-Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+<!-- Include any troubleshooting steps that you can foresee. If you know beforehand what issues
one might have when setting this up, or when something is changed, or on upgrading, it's
important to describe those, too. Think of things that may go wrong and include them here.
This is important to minimize requests for support, and to avoid doc comments with
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
index 2470e78819f..ee0550bfca2 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/ssl_tls_concepts.md
@@ -67,7 +67,7 @@ There are some certificate authorities that
offer free certificates, aiming to make the internet more secure
to everyone. The most popular is [Let's Encrypt](https://letsencrypt.org/),
which issues certificates trusted by most of browsers, it's open
-source, and free to use. See our tutorial on [how to secure your GitLab Pages website with Let's Encrypt](../lets_encrypt_for_gitlab_pages.md).
+source, and free to use. See [GitLab Pages integration with Let's Encrypt](../custom_domains_ssl_tls_certification/lets_encrypt_integration.md) to enable HTTPS on your custom domain.
Similarly popular are [certificates issued by CloudFlare](https://www.cloudflare.com/ssl/),
which also offers a [free CDN service](https://blog.cloudflare.com/cloudflares-free-cdn-and-you/).
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 743c360f075..3bab652ca39 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -86,17 +86,17 @@ You can also take some **optional** further steps:
- _Remove the fork relationship._ The fork relationship is necessary to contribute back to the project you originally forked from. If you don't have any intentions to do so, you can remove it. To do so, navigate to your project's **Settings**, expand **Advanced settings**, and scroll down to **Remove fork relationship**:
- ![remove fork relationship](img/remove_fork_relationship.png)
+ ![remove fork relationship](img/remove_fork_relationship.png)
- _Make it a user or group website._ To turn a **project website** forked
from the Pages group into a **user/group** website, you'll need to:
- - Rename it to `namespace.gitlab.io`: go to your project's
- **Settings > General** and expand **Advanced**. Scroll down to
- **Rename repository** and change the path to `namespace.gitlab.io`.
- - Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to
- `""`. This setting will be at a different place for each SSG, as each of them
- have their own structure and file tree. Most likely, it will be in the SSG's
- config file.
+ - Rename it to `namespace.gitlab.io`: go to your project's
+ **Settings > General** and expand **Advanced**. Scroll down to
+ **Rename repository** and change the path to `namespace.gitlab.io`.
+ - Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to
+ `""`. This setting will be at a different place for each SSG, as each of them
+ have their own structure and file tree. Most likely, it will be in the SSG's
+ config file.
### Create a project from scratch
@@ -107,12 +107,12 @@ You can also take some **optional** further steps:
files to your project, add, commit and push to GitLab.
1. From the your **Project**'s page, click **Set up CI/CD**:
- ![setup GitLab CI/CD](img/setup_ci.png)
+ ![setup GitLab CI/CD](img/setup_ci.png)
1. Choose one of the templates from the dropbox menu.
Pick up the template corresponding to the SSG you're using (or plain HTML).
- ![gitlab-ci templates](img/choose_ci_template.png)
+ ![gitlab-ci templates](img/choose_ci_template.png)
Once you have both site files and `.gitlab-ci.yml` in your project's
root, GitLab CI/CD will build your site and deploy it with Pages.
@@ -123,20 +123,20 @@ where you'll find its default URL.
> **Notes:**
>
> - GitLab Pages [supports any SSG](https://about.gitlab.com/2016/06/17/ssg-overview-gitlab-pages-part-3-examples-ci/), but,
- if you don't find yours among the templates, you'll need
- to configure your own `.gitlab-ci.yml`. To do that, please
- read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
- the [example projects](https://gitlab.com/pages). If you set
- up a new one, please
- [contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
- to our examples.
+> if you don't find yours among the templates, you'll need
+> to configure your own `.gitlab-ci.yml`. To do that, please
+> read through the article [Creating and Tweaking GitLab CI/CD for GitLab Pages](getting_started_part_four.md). New SSGs are very welcome among
+> the [example projects](https://gitlab.com/pages). If you set
+> up a new one, please
+> [contribute](https://gitlab.com/pages/pages.gitlab.io/blob/master/CONTRIBUTING.md)
+> to our examples.
>
> - The second step _"Clone it to your local computer"_, can be done
- differently, achieving the same results: instead of cloning the bare
- repository to you local computer and moving your site files into it,
- you can run `git init` in your local website directory, add the
- remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
- then add, commit, and push to GitLab.
+> differently, achieving the same results: instead of cloning the bare
+> repository to you local computer and moving your site files into it,
+> you can run `git init` in your local website directory, add the
+> remote URL: `git remote add origin git@gitlab.com:namespace/project-name.git`,
+> then add, commit, and push to GitLab.
## URLs and Baseurls
diff --git a/doc/user/project/pages/img/pages_project_templates_v11_8.png b/doc/user/project/pages/img/pages_project_templates_v11_8.png
index a645d28260b..61cae88b5a8 100644
--- a/doc/user/project/pages/img/pages_project_templates_v11_8.png
+++ b/doc/user/project/pages/img/pages_project_templates_v11_8.png
Binary files differ
diff --git a/doc/user/project/pages/img/remove_pages.png b/doc/user/project/pages/img/remove_pages.png
index 60f76f15f93..d6c37ef30cd 100644
--- a/doc/user/project/pages/img/remove_pages.png
+++ b/doc/user/project/pages/img/remove_pages.png
Binary files differ
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index 25944b029d7..a0bc10b0ed7 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -101,7 +101,7 @@ To get started with GitLab Pages, you can either:
1. Select **Create from Template**.
1. Choose one of the templates starting with **Pages**:
- ![Project templates for Pages](img/pages_project_templates_v11_8.png)
+ ![Project templates for Pages](img/pages_project_templates_v11_8.png)
1. From the left sidebar, navigate to your project's **CI/CD > Pipelines**
and click **Run pipeline** to trigger GitLab CI/CD to build and deploy your
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 9451b5349c0..e8984cb8b9f 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -83,23 +83,23 @@ You can enable Pages access control on your project, so that only
1. Navigate to your project's **Settings > General > Permissions**.
1. Toggle the **Pages** button to enable the access control.
- NOTE: **Note:**
- If you don't see the toggle button, that means that it's not enabled.
- Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
+ NOTE: **Note:**
+ If you don't see the toggle button, that means that it's not enabled.
+ Ask your administrator to [enable it](../../../administration/pages/index.md#access-control).
1. The Pages access control dropdown allows you to set who can view pages hosted
with GitLab Pages, depending on your project's visibility:
- - If your project is private:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is internal:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
- - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
- - If your project is public:
- - **Only project members**: Only project members will be able to browse the website.
- - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is private:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is internal:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone logged into GitLab will be able to browse the website, no matter their project membership.
+ - **Everyone**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
+ - If your project is public:
+ - **Only project members**: Only project members will be able to browse the website.
+ - **Everyone with access**: Everyone, both logged into and logged out of GitLab, will be able to browse the website, no matter their project membership.
1. Click **Save changes**.
diff --git a/doc/user/project/pipelines/img/pipeline_schedule_variables.png b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
index 29846206491..ce3c3dc6af1 100644
--- a/doc/user/project/pipelines/img/pipeline_schedule_variables.png
+++ b/doc/user/project/pipelines/img/pipeline_schedule_variables.png
Binary files differ
diff --git a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
index e135dd51070..993fbf8ca00 100644
--- a/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
+++ b/doc/user/project/pipelines/img/pipeline_schedules_new_form.png
Binary files differ
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 2411744c874..ef90899c512 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -1,24 +1,24 @@
+---
+type: reference, howto
+---
+
# Introduction to job artifacts
-> **Notes:**
->
-> - Since GitLab 8.2 and GitLab Runner 0.7.0, job artifacts that are created by
-> GitLab Runner are uploaded to GitLab and are downloadable as a single archive
-> (`tar.gz`) using the GitLab UI.
-> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format
-> changed to `ZIP`, and it is now possible to browse its contents, with the added
-> ability of downloading the files separately.
-> - Starting with GitLab 8.17, builds are renamed to jobs.
-> - The artifacts browser will be available only for new artifacts that are sent
-> to GitLab using GitLab Runner version 1.0 and up. It will not be possible to
-> browse old artifacts already uploaded to GitLab.
->- This is the user documentation. For the administration guide see
-> [administration/job_artifacts](../../../administration/job_artifacts.md).
-
-Artifacts is a list of files and directories which are attached to a job
-after it finishes. This feature is enabled by default in all
+> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
+> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`, and it is now possible to browse its contents, with the added ability of downloading the files separately.
+> - In GitLab 8.17, builds were renamed to jobs.
+> - The artifacts browser will be available only for new artifacts that are sent to GitLab using GitLab Runner version 1.0 and up. It will not be possible to browse old artifacts already uploaded to GitLab.
+
+Artifacts are a list of files and directories which created by a job
+once it finishes. This feature is [enabled by default](../../../administration/job_artifacts.md) in all
GitLab installations.
+Job artifacts that are created by GitLab Runner are uploaded to GitLab and are downloadable as a single archive using the GitLab UI.
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, watch the video [GitLab CI Pipeline, Artifacts, and Environments](https://www.youtube.com/watch?v=PCKDICEe10s).
+Watch also [GitLab CI pipeline tutorial for beginners](https://www.youtube.com/watch?v=Jav4vbUrqII).
+
## Defining artifacts in `.gitlab-ci.yml`
A simple example of using the artifacts definition in `.gitlab-ci.yml` would be
@@ -50,11 +50,8 @@ For more examples on artifacts, follow the [artifacts reference in
## Browsing artifacts
-> **Note:**
> With GitLab 9.2, PDFs, images, videos and other formats can be previewed
> directly in the job artifacts browser without the need to download them.
->
-> **Note:**
> With [GitLab 10.1][ce-14399], HTML files in a public project can be previewed
> directly in a new tab without the need to download them when
> [GitLab Pages](../../../administration/pages/index.md) is enabled.
@@ -108,7 +105,7 @@ inside GitLab that make that possible.
It is possible to download the latest artifacts of a job via a well known URL
so you can use it for scripting purposes.
->**Note:**
+NOTE: **Note:**
The latest artifacts are considered as the artifacts created by jobs in the
latest pipeline that succeeded for the specific ref.
Artifacts for other pipelines can be accessed with direct access to them.
@@ -169,9 +166,9 @@ https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/master/file/htmlcov/ind
The latest builds are also exposed in the UI in various places. Specifically,
look for the download button in:
-- the main project's page
-- the branches page
-- the tags page
+- The main project's page
+- The branches page
+- The tags page
If the latest job has failed to upload the artifacts, you can see that
information in the UI.
@@ -201,3 +198,15 @@ In order to retrieve a job artifact of a different project, you might need to us
[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in
[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index dd5427ab35a..f3e9c950efd 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -1,10 +1,14 @@
+---
+type: reference, howto
+---
+
# Pipeline schedules
-> **Notes**:
->
> - Introduced in GitLab 9.1 as [Trigger Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10533).
> - [Renamed to Pipeline Schedule](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10853) in GitLab 9.2.
-> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
+
+NOTE: **Note:**
+Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository.
@@ -118,3 +122,15 @@ on the target branch, the schedule will stop creating new pipelines. This can
happen if, for example, the owner is blocked or removed from the project, or
the target branch or tag is protected. In this case, someone with sufficient
privileges must take ownership of the schedule.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index df82daa3da3..456209c2180 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Pipelines settings
To reach the pipelines settings navigate to your project's
@@ -5,6 +9,10 @@ To reach the pipelines settings navigate to your project's
The following settings can be configured per project.
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
+For an overview, watch the video [GitLab CI Pipeline, Artifacts, and Environments](https://www.youtube.com/watch?v=PCKDICEe10s).
+Watch also [GitLab CI pipeline tutorial for beginners](https://www.youtube.com/watch?v=Jav4vbUrqII).
+
## Git strategy
With Git strategy, you can choose the default way your repository is fetched
@@ -68,6 +76,13 @@ Here are some valid examples:
- `my/path/.gitlab-ci.yml`
- `my/path/.my-custom-file.yml`
+The path can be customized at a project level. To customize the path:
+
+1. Go to the project's **Settings > CI / CD**.
+1. Expand the **General pipelines** section.
+1. Provide a value in the **Custom CI config path** field.
+1. Click **Save changes**.
+
## Test coverage parsing
If you use test coverage in your code, GitLab can capture its output in the
@@ -216,3 +231,15 @@ https://example.gitlab.com/<namespace>/<project>/badges/<branch>/coverage.svg?st
## Environment Variables
[Environment variables](../../../ci/variables/README.html#gitlab-cicd-environment-variables) can be set in an environment to be available to a runner.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 20a03dff2da..7a79cdbcaee 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Protected Branches
[Permissions](../permissions.md) in GitLab are fundamentally defined around the
@@ -9,13 +13,13 @@ created protected branches.
By default, a protected branch does four simple things:
-- it prevents its creation, if not already created, from everybody except users
- with Maintainer permission
-- it prevents pushes from everybody except users with Maintainer permission
-- it prevents **anyone** from force pushing to the branch
-- it prevents **anyone** from deleting the branch
+- It prevents its creation, if not already created, from everybody except users
+ with Maintainer permission.
+- It prevents pushes from everybody except users with Maintainer permission.
+- It prevents **anyone** from force pushing to the branch.
+- It prevents **anyone** from deleting the branch.
->**Note**:
+NOTE: **Note:**
A GitLab admin is allowed to push to the protected branches.
See the [Changelog](#changelog) section for changes over time.
@@ -30,11 +34,11 @@ that the `master` branch is protected by default.
1. From the **Branch** dropdown menu, select the branch you want to protect and
click **Protect**. In the screenshot below, we chose the `develop` branch.
- ![Protected branches page](img/protected_branches_page.png)
+ ![Protected branches page](img/protected_branches_page.png)
1. Once done, the protected branch will appear in the "Protected branches" list.
- ![Protected branches list](img/protected_branches_list.png)
+ ![Protected branches list](img/protected_branches_list.png)
## Using the Allowed to merge and Allowed to push settings
@@ -68,7 +72,7 @@ they are set to "Maintainers" by default.
## Restricting push and merge access to certain users **(STARTER)**
-> This feature was [introduced][ce-5081] in [GitLab Starter][ee] 8.11.
+> [Introduced][ce-5081] in [GitLab Starter][ee] 8.11.
With GitLab Enterprise Edition you can restrict access to protected branches
by choosing a role (Maintainers, Developers) as well as certain users. From the
@@ -141,7 +145,7 @@ branches via GitLab's web interface:
1. In order to prevent accidental deletion, an additional confirmation is
required
- ![Delete protected branches](img/protected_branches_delete.png)
+ ![Delete protected branches](img/protected_branches_delete.png)
Deleting a protected branch is only allowed via the web interface, not via Git.
This means that you can't accidentally delete a protected branch from your
@@ -174,12 +178,21 @@ for details about the pipelines security model.
- Allow developers to merge into a protected branch without having push access [gitlab-org/gitlab-ce!4892][ce-4892]
- Allow specifying protected branches using wildcards [gitlab-org/gitlab-ce!4665][ce-4665]
----
-
[ce-4665]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4665 "Allow specifying protected branches using wildcards"
[ce-4892]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4892 "Allow developers to merge into a protected branch without having push access"
[ce-5081]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081 "Allow creating protected branches that can't be pushed to"
[ce-21393]: https://gitlab.com/gitlab-org/gitlab-ce/issues/21393
-[ee-restrict]: https://docs.gitlab.com/ee/user/project/protected_branches.html#restricting-push-and-merge-access-to-certain-users
[perm]: ../permissions.md
[ee]: https://about.gitlab.com/pricing/
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/protected_tags.md b/doc/user/project/protected_tags.md
index 26bec323f02..5cc3b8a7fc3 100644
--- a/doc/user/project/protected_tags.md
+++ b/doc/user/project/protected_tags.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Protected Tags
> [Introduced][ce-10356] in GitLab 9.1.
@@ -14,21 +18,21 @@ Protected tags will prevent anyone from updating or deleting the tag, as and wil
To protect a tag, you need to have at least Maintainer permission level.
-1. Navigate to the project's Settings -> Repository page
+1. Navigate to the project's **Settings > Repository**:
- ![Repository Settings](img/project_repository_settings.png)
+ ![Repository Settings](img/project_repository_settings.png)
-1. From the **Tag** dropdown menu, select the tag you want to protect or type and click `Create wildcard`. In the screenshot below, we chose to protect all tags matching `v*`.
+1. From the **Tag** dropdown menu, select the tag you want to protect or type and click **Create wildcard**. In the screenshot below, we chose to protect all tags matching `v*`:
- ![Protected tags page](img/protected_tags_page.png)
+ ![Protected tags page](img/protected_tags_page.png)
-1. From the `Allowed to create` dropdown, select who will have permission to create matching tags and then click `Protect`.
+1. From the **Allowed to create** dropdown, select who will have permission to create matching tags and then click **Protect**:
- ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown.png)
+ ![Allowed to create tags dropdown](img/protected_tags_permissions_dropdown.png)
-1. Once done, the protected tag will appear in the "Protected tags" list.
+1. Once done, the protected tag will appear in the **Protected tags** list:
- ![Protected tags list](img/protected_tags_list.png)
+ ![Protected tags list](img/protected_tags_list.png)
## Wildcard protected tags
@@ -45,13 +49,23 @@ matching the wildcard. For example:
Two different wildcards can potentially match the same tag. For example,
`*-stable` and `production-*` would both match a `production-stable` tag.
In that case, if _any_ of these protected tags have a setting like
-"Allowed to create", then `production-stable` will also inherit this setting.
+**Allowed to create**, then `production-stable` will also inherit this setting.
If you click on a protected tag's name, you will be presented with a list of
all matching tags:
![Protected tag matches](img/protected_tag_matches.png)
----
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
[ce-10356]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/10356 "Protected Tags"
diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md
index 948ac91a886..647250bd02a 100644
--- a/doc/user/project/quick_actions.md
+++ b/doc/user/project/quick_actions.md
@@ -1,13 +1,18 @@
-# GitLab quick actions
+---
+type: reference
+---
+
+# GitLab Quick Actions
Quick actions are textual shortcuts for common actions on issues, epics, merge requests,
and commits that are usually done by clicking buttons or dropdowns in GitLab's UI.
You can enter these commands while creating a new issue or merge request, or
in comments of issues, epics, merge requests, and commits. Each command should be
on a separate line in order to be properly detected and executed. Once executed,
-the commands are removed from the text body and not visible to anyone else.
-## Quick actions for issues and merge requests
+> From [GitLab 12.1](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26672), an alert is displayed when a quick action is successfully applied.
+
+## Quick Actions for issues and merge requests
The following quick actions are applicable to both issues and merge requests threads,
discussions, and descriptions:
@@ -35,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 | ✓ | |
@@ -59,6 +66,25 @@ discussions, and descriptions:
| `/create_merge_request <branch name>` | Create a new merge request starting from the current issue | ✓ | |
| `/relate #issue1 #issue2` | Mark issues as related **(STARTER)** | ✓ | |
+## Autocomplete characters
+
+Many quick actions require a parameter, for example: username, milestone, and
+label. [Autocomplete characters](autocomplete_characters.md) can make it easier
+to enter a parameter, compared to selecting items from a list.
+
+## Quick actions parameters
+
+The easiest way to set parameters for quick actions is to use autocomplete. If
+you manually enter a parameter, it must be enclosed in double quotation marks
+(`"`), unless it contains only:
+
+1. ASCII letters.
+1. Numerals.
+1. Underscore, hyphen, question mark, dot, and ampersand.
+
+Parameters are also case-sensitive. Autocomplete handles this, and the insertion
+of quotation marks, automatically.
+
## Quick actions for commit messages
The following quick actions are applicable for commit messages:
@@ -86,7 +112,19 @@ 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
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/releases/img/releases.png b/doc/user/project/releases/img/releases.png
index f8b1b7305ad..da5bcd9d913 100644
--- a/doc/user/project/releases/img/releases.png
+++ b/doc/user/project/releases/img/releases.png
Binary files differ
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index 00a4f6c6a6b..aae253467f0 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -1,3 +1,7 @@
+---
+type: reference, howto
+---
+
# Releases
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/41766) in GitLab 11.7.
@@ -60,3 +64,15 @@ Navigate to **Project > Releases** in order to see the list of releases for a gi
project.
![Releases list](img/releases.png)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/img/compare_branches.png b/doc/user/project/repository/branches/img/compare_branches.png
index 52d5c518c45..52d5c518c45 100644
--- a/doc/user/project/repository/img/compare_branches.png
+++ b/doc/user/project/repository/branches/img/compare_branches.png
Binary files differ
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index a81c9197ec1..a864665602e 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -1,17 +1,41 @@
+---
+type: concepts, howto
+---
+
# Branches
-Read through GiLab's branching documentation:
+A branch is a version of a project's working tree. You create a branch for each
+set of related changes you make. This keeps each set of changes separate from
+each other, allowing changes to be made in parallel, without affecting each
+other.
+
+After pushing your changes to a new branch, you can:
+
+- Create a [merge request](../../merge_requests/index.md)
+- Perform inline code review
+- [Discuss](../../../discussions/index.md) your implementation with your team
+- Preview changes submitted to a new branch with [Review Apps](../../../../ci/review_apps/index.md).
+
+With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
+[approval](../../merge_requests/merge_request_approvals.md) from your managers.
+
+For more information on managing branches using the GitLab UI, see:
+
+- [Default branches](#default-branch)
+- [Create a branch](../web_editor.md#create-a-new-branch)
+- [Protected branches](../../protected_branches.md#protected-branches)
+- [Delete merged branches](#delete-merged-branches)
+- [Branch filter search box](#branch-filter-search-box)
+
+You can also manage branches using the
+[command line](../../../../gitlab-basics/start-using-git.md#create-a-branch).
-- [Create a branch](../web_editor.md#create-a-new-branch).
-- [Default branch](#default-branch).
-- [Protected branches](../../protected_branches.md#protected-branches).
-- [Delete merged branches](#delete-merged-branches).
-- [Branch filter search box](#branch-filter-search-box).
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>Watch the video [GitLab Flow](https://www.youtube.com/watch?v=InKNIvky2KE).
See also:
- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
-- [GitLab Flow](../../../../university/training/gitlab_flow.md). Use the best of GitLab for your branching strategies.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md) documentation.
- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
@@ -29,6 +53,17 @@ The default branch is also protected against accidental deletion. Read through
the documentation on [protected branches](../../protected_branches.md#protected-branches)
to learn more.
+## Compare
+
+To compare branches in a repository:
+
+1. Navigate to your project's repository.
+1. Select **Repository > Compare** in the sidebar.
+1. Select branches to compare using the [branch filter search box](#branch-filter-search-box)
+1. Click **Compare** to view the changes inline:
+
+![compare branches](img/compare_branches.png)
+
## Delete merged branches
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6449) in GitLab 8.14.
@@ -57,3 +92,15 @@ Sometimes when you have hundreds of branches you may want a more flexible matchi
- `^feature` will only match branch names that begin with 'feature'.
- `feature$` will only match branch names that end with 'feature'.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md
index 3b0a045ef9c..b929c6a681a 100644
--- a/doc/user/project/repository/gpg_signed_commits/index.md
+++ b/doc/user/project/repository/gpg_signed_commits/index.md
@@ -1,38 +1,39 @@
-# Signing commits with GPG
+---
+type: concepts, howto
+---
-NOTE: **Note:**
-The term GPG is used for all OpenPGP/PGP/GPG related material and
-implementations.
+# Signing commits with GPG
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9546) in GitLab 9.5.
> - Subkeys support was added in GitLab 10.1.
-GitLab can show whether a commit is verified or not when signed with a GPG key.
-All you need to do is upload the public GPG key in your profile settings.
+You can use a GPG key to sign Git commits made in a GitLab repository. Signed
+commits are labeled **Verified** if the identity of the committer can be
+verified. To verify the identity of a committer, GitLab requires their public
+GPG key.
-GPG verified tags are not supported yet.
+NOTE: **Note:**
+The term GPG is used for all OpenPGP/PGP/GPG related material and
+implementations.
-## Getting started with GPG
+GPG verified tags are not supported yet.
-Here are a few guides to get you started with GPG:
-
-- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
-- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
-- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
-- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
+See the [further reading](#further-reading) section for more details on GPG.
## How GitLab handles GPG
GitLab uses its own keyring to verify the GPG signature. It does not access any
public key server.
-In order to have a commit verified on GitLab the corresponding public key needs
-to be uploaded to GitLab. For a signature to be verified three conditions need
-to be met:
+For a commit to be verified by GitLab:
-1. The public key needs to be added your GitLab account
-1. One of the emails in the GPG key matches a **verified** email address you use in GitLab
-1. The committer's email matches the verified email from the gpg key
+- The committer must have a GPG public/private key pair.
+- The committer's public key must have been uploaded to their GitLab
+ account.
+- One of the emails in the GPG key must match a **verified** email address
+ used by the committer in GitLab.
+- The committer's email address must match the verified email address from the
+ GPG key.
## Generating a GPG key
@@ -65,8 +66,7 @@ started:
Your selection? 1
```
-1. The next question is key length. We recommend to choose the highest value
- which is `4096`:
+1. The next question is key length. We recommend you choose `4096`:
```
RSA keys may be between 1024 and 4096 bits long.
@@ -74,8 +74,8 @@ started:
Requested keysize is 4096 bits
```
-1. Next, you need to specify the validity period of your key. This is something
- subjective, and you can use the default value which is to never expire:
+1. Specify the validity period of your key. This is something
+ subjective, and you can use the default value, which is to never expire:
```
Please specify how long the key should be valid.
@@ -94,9 +94,9 @@ started:
Is this correct? (y/N) y
```
-1. Enter you real name, the email address to be associated with this key (should
- match a verified email address you use in GitLab) and an optional comment
- (press <kbd>Enter</kbd> to skip):
+1. Enter your real name, the email address to be associated with this key
+ (should match a verified email address you use in GitLab) and an optional
+ comment (press <kbd>Enter</kbd> to skip):
```
GnuPG needs to construct a user ID to identify your key.
@@ -270,3 +270,24 @@ via [push rules](../../../../push_rules/push_rules.md).
## GPG signing API
Learn how to [get the GPG signature from a commit via API](../../../../api/commits.md#get-gpg-signature-of-a-commit).
+
+## Further reading
+
+For more details about GPG, see:
+
+- [Git Tools - Signing Your Work](https://git-scm.com/book/en/v2/Git-Tools-Signing-Your-Work)
+- [Managing OpenPGP Keys](https://riseup.net/en/security/message-security/openpgp/gpg-keys)
+- [OpenPGP Best Practices](https://riseup.net/en/security/message-security/openpgp/best-practices)
+- [Creating a new GPG key with subkeys](https://www.void.gr/kargig/blog/2013/12/02/creating-a-new-gpg-key-with-subkeys/) (advanced)
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/img/download_source_code.png b/doc/user/project/repository/img/download_source_code.png
index 17f2cb4b3e8..56808061980 100644
--- a/doc/user/project/repository/img/download_source_code.png
+++ b/doc/user/project/repository/img/download_source_code.png
Binary files differ
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 5b5685b3418..bd966185c94 100644
--- a/doc/user/project/repository/index.md
+++ b/doc/user/project/repository/index.md
@@ -1,3 +1,7 @@
+---
+type: concepts, howto
+---
+
# Repository
A [repository](https://git-scm.com/book/en/v2/Git-Basics-Getting-a-Git-Repository)
@@ -111,33 +115,7 @@ GitLab.
## Branches
-When you submit changes in a new [branch](branches/index.md), you create a new version
-of that project's file tree. Your branch contains all the changes
-you are presenting, which are detected by Git line by line.
-
-To continue your workflow, once you pushed your changes to a new branch,
-you can create a [merge request](../merge_requests/index.md), perform
-inline code review, and [discuss](../../discussions/index.md)
-your implementation with your team.
-You can live preview changes submitted to a new branch with
-[Review Apps](../../../ci/review_apps/index.md).
-
-With [GitLab Starter](https://about.gitlab.com/pricing/), you can also request
-[approval](../merge_requests/merge_request_approvals.md) from your managers.
-
-To create, delete, and view [branches](branches/index.md) via GitLab's UI:
-
-- [Default branches](branches/index.md#default-branch)
-- [Create a branch](web_editor.md#create-a-new-branch)
-- [Protected branches](../protected_branches.md#protected-branches)
-- [Delete merged branches](branches/index.md#delete-merged-branches)
-- [Branch filter search box](branches/index.md#branch-filter-search-box)
-
-Alternatively, you can use the
-[command line](../../../gitlab-basics/start-using-git.md#create-a-branch).
-
-To learn more about branching strategies read through the
-[GitLab Flow](../../../university/training/gitlab_flow.md) documentation.
+For details, see [Branches](branches/index.md).
## Commits
@@ -204,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
@@ -213,14 +191,6 @@ detected, add the following to `.gitattributes` in the root of your repository.
> *.proto linguist-detectable=true
-## Compare
-
-Select branches to compare using the [branch filter search box](branches/index.md#branch-filter-search-box), then click the **Compare** button to view the changes inline:
-
-![compare branches](img/compare_branches.png)
-
-Find it under your project's **Repository > Compare**.
-
## Locked files **(PREMIUM)**
Use [File Locking](../file_lock.md) to
@@ -256,3 +226,15 @@ By clicking the download icon, a dropdown will open with links to download the f
`tar`, `tar.gz`, and `tar.bz2`.
- **Artifacts:**
allows users to download the artifacts of the latest CI build.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/reducing_the_repo_size_using_git.md b/doc/user/project/repository/reducing_the_repo_size_using_git.md
index 7c711bc0b3b..3adf66e4b6f 100644
--- a/doc/user/project/repository/reducing_the_repo_size_using_git.md
+++ b/doc/user/project/repository/reducing_the_repo_size_using_git.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# Reducing the repository size using Git
A GitLab Enterprise Edition administrator can set a [repository size limit](../../admin_area/settings/account_and_limit_settings.md)
@@ -139,3 +143,15 @@ purposes!
```
Your repository should now be below the size limit.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md
index 0116f0fe7ca..b5299eaca27 100644
--- a/doc/user/project/repository/web_editor.md
+++ b/doc/user/project/repository/web_editor.md
@@ -1,3 +1,7 @@
+---
+type: howto
+---
+
# GitLab Web Editor
Sometimes it's easier to make quick changes directly from the GitLab interface
@@ -101,7 +105,7 @@ Once you click it, a new branch will be created that diverges from the default
branch of your project, by default `master`. The branch name will be based on
the title of the issue and as a prefix, it will have its internal ID. Thus, the example
screenshot above will yield a branch named
-`2-et-cum-et-sed-expedita-repellat-consequatur-ut-assumenda-numquam-rerum`.
+`23177-add-support-for-rich-references-to-referables`.
Since GitLab 9.0, when you click the `New branch` in an empty repository project, GitLab automatically creates the master branch, commits a blank `README.md` file to it and creates and redirects you to a new branch based on the issue title.
If your [project is already configured with a deployment service][project-services-doc] (e.g. Kubernetes), GitLab takes one step further and prompts you to set up [auto deploy][auto-deploy-doc] by helping you create a `.gitlab-ci.yml` file.
@@ -169,3 +173,15 @@ through the web editor, you can choose to use another of your linked email
addresses from the **User Settings > Edit Profile** page.
[ce-2808]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2808
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/settings/img/general_settings.png b/doc/user/project/settings/img/general_settings.png
index 4ff6fff5ca3..f88a158d2be 100644
--- a/doc/user/project/settings/img/general_settings.png
+++ b/doc/user/project/settings/img/general_settings.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_download_export.png b/doc/user/project/settings/img/import_export_download_export.png
index ab81c87bf5f..c7ab7565fc7 100644
--- a/doc/user/project/settings/img/import_export_download_export.png
+++ b/doc/user/project/settings/img/import_export_download_export.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_export_button.png b/doc/user/project/settings/img/import_export_export_button.png
index 9e368739695..6933e3edfcc 100644
--- a/doc/user/project/settings/img/import_export_export_button.png
+++ b/doc/user/project/settings/img/import_export_export_button.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_mail_link.png b/doc/user/project/settings/img/import_export_mail_link.png
index 985c37650d3..1bd9a071178 100644
--- a/doc/user/project/settings/img/import_export_mail_link.png
+++ b/doc/user/project/settings/img/import_export_mail_link.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_new_project.png b/doc/user/project/settings/img/import_export_new_project.png
index fc1f73c5d6e..0e2365ecb68 100644
--- a/doc/user/project/settings/img/import_export_new_project.png
+++ b/doc/user/project/settings/img/import_export_new_project.png
Binary files differ
diff --git a/doc/user/project/settings/img/import_export_select_file.png b/doc/user/project/settings/img/import_export_select_file.png
index e3e1a5ef980..90a3e8d5c4e 100644
--- a/doc/user/project/settings/img/import_export_select_file.png
+++ b/doc/user/project/settings/img/import_export_select_file.png
Binary files differ
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 7241df613eb..9c1a31fb7c3 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -66,6 +66,7 @@ The following items will be exported:
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities
- LFS objects
+- Issue boards
The following items will NOT be exported:
@@ -116,3 +117,8 @@ For more details on the specific data persisted in a project export, see the
1. Click on **Import project** to begin importing. Your newly imported project
page will appear soon.
+
+NOTE: **Note:**
+If use of the `Internal` visibility level
+[is restricted](../../../public_access/public_access.md#restricting-the-use-of-public-or-internal-projects),
+all imported projects are given the visibility of `Private`.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 17ec9ecb5d1..4e3db95b6d6 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -32,6 +32,12 @@ links will be missing from the sidebar UI.
You can still access them with direct links if you can access Merge Requests. This is deliberate, if you can see
Issues or Merge Requests, both of which use Labels and Milestones, then you shouldn't be denied access to Labels and Milestones pages.
+#### Disabling email notifications
+
+You can disable all email notifications related to the project by selecting the
+**Disable email notifications** checkbox. Only the project owner is allowed to change
+this setting.
+
### Issue settings
Add an [issue description template](../description_templates.md#description-templates) to your project, so that every new issue will start with a custom template.
diff --git a/doc/user/project/web_ide/img/terminal_status.png b/doc/user/project/web_ide/img/terminal_status.png
index c37aa02b07a..91c341a9854 100644
--- a/doc/user/project/web_ide/img/terminal_status.png
+++ b/doc/user/project/web_ide/img/terminal_status.png
Binary files differ
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/user/reserved_names.md b/doc/user/reserved_names.md
index 9e8475d8294..68532ccee65 100644
--- a/doc/user/reserved_names.md
+++ b/doc/user/reserved_names.md
@@ -40,19 +40,18 @@ It is currently not possible to create a project with the following names:
Currently the following names are reserved as top level groups:
-- 503.html
- \-
- .well-known
- 404.html
- 422.html
- 500.html
- 502.html
+- 503.html
- abuse_reports
- admin
- api
- apple-touch-icon-precomposed.png
- apple-touch-icon.png
-- files
- assets
- autocomplete
- ci
@@ -61,12 +60,14 @@ Currently the following names are reserved as top level groups:
- explore
- favicon.ico
- favicon.png
+- files
- groups
- health_check
- help
- import
- invites
- jwt
+- login
- notification_settings
- oauth
- profile
@@ -78,8 +79,6 @@ Currently the following names are reserved as top level groups:
- sent_notifications
- slash-command-logo.png
- snippets
-- u
-- unicorn_test
- unsubscribes
- uploads
- users
@@ -88,22 +87,5 @@ Currently the following names are reserved as top level groups:
These group names are unavailable as subgroup names:
- \-
-- activity
-- analytics
-- audit_events
-- avatar
-- edit
-- group_members
-- hooks
-- issues
-- labels
-- ldap
-- ldap_group_links
-- merge_requests
-- milestones
-- notification_setting
-- pipeline_quota
-- projects
-- subgroups
[reserved]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/path_regex.rb
diff --git a/doc/user/search/advanced_search_syntax.md b/doc/user/search/advanced_search_syntax.md
index ad9f065b19b..c3d22e4fd29 100644
--- a/doc/user/search/advanced_search_syntax.md
+++ b/doc/user/search/advanced_search_syntax.md
@@ -50,9 +50,9 @@ here's a quick guide:
The Advanced Syntax Search also supports the use of filters. The available filters are:
- - filename: Filters by filename. You can use the glob (`*`) operator for fuzzy matching.
- - path: Filters by path. You can use the glob (`*`) operator for fuzzy matching.
- - extension: Filters by extension in the filename. Please write the extension without a leading dot. Exact match only.
+- filename: Filters by filename. You can use the glob (`*`) operator for fuzzy matching.
+- path: Filters by path. You can use the glob (`*`) operator for fuzzy matching.
+- extension: Filters by extension in the filename. Please write the extension without a leading dot. Exact match only.
To use them, simply add them to your query in the format `<filter_name>:<value>` without
any spaces between the colon (`:`) and the value.
diff --git a/doc/user/search/index.md b/doc/user/search/index.md
index c34b9ae3d7e..8d7b4a429aa 100644
--- a/doc/user/search/index.md
+++ b/doc/user/search/index.md
@@ -55,12 +55,12 @@ Selecting **Any** does the opposite. It returns results that have a non-empty va
You can filter issues and merge requests by specific terms included in titles or descriptions.
- Syntax
- - Searches look for all the words in a query, in any order. E.g.: searching
- issues for `display bug` will return all issues matching both those words, in any order.
- - To find the exact term, use double quotes: `"display bug"`
+ - Searches look for all the words in a query, in any order. E.g.: searching
+ issues for `display bug` will return all issues matching both those words, in any order.
+ - To find the exact term, use double quotes: `"display bug"`
- Limitation
- - For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
- issues for `included in titles` is same as `included titles`
+ - For performance reasons, terms shorter than 3 chars are ignored. E.g.: searching
+ issues for `included in titles` is same as `included titles`
![filter issues by specific terms](img/issue_search_by_term.png)
diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md
index 02be0ad191d..869a0a621c3 100644
--- a/doc/workflow/forking_workflow.md
+++ b/doc/workflow/forking_workflow.md
@@ -10,31 +10,25 @@ document more information about using branches to work together.
Forking a project is in most cases a two-step process.
-1. Click on the fork button located in the middle of the page or a project's
- home page right next to the stars button.
+1. Click on the fork button located in the middle of the page or a project's
+ home page right next to the stars button.
- ![Fork button](img/forking_workflow_fork_button.png)
+ ![Fork button](img/forking_workflow_fork_button.png)
- ---
+1. Once you do that, you'll be presented with a screen where you can choose
+ the namespace to fork to. Only namespaces (groups and your own
+ namespace) where you have write access to, will be shown. Click on the
+ namespace to create your fork there.
-1. Once you do that, you'll be presented with a screen where you can choose
- the namespace to fork to. Only namespaces (groups and your own
- namespace) where you have write access to, will be shown. Click on the
- namespace to create your fork there.
+ ![Choose namespace](img/forking_workflow_choose_namespace.png)
- ![Choose namespace](img/forking_workflow_choose_namespace.png)
+ **Note:**
+ If the namespace you chose to fork the project to has another project with
+ the same path name, you will be presented with a warning that the forking
+ could not be completed. Try to resolve the error and repeat the forking
+ process.
- ---
-
- **Note:**
- If the namespace you chose to fork the project to has another project with
- the same path name, you will be presented with a warning that the forking
- could not be completed. Try to resolve the error and repeat the forking
- process.
-
- ![Path taken error](img/forking_workflow_path_taken_error.png)
-
- ---
+ ![Path taken error](img/forking_workflow_path_taken_error.png)
After the forking is done, you can start working on the newly created
repository. There, you will have full [Owner](../user/permissions.md)
diff --git a/doc/workflow/git_annex.md b/doc/workflow/git_annex.md
index 84d25951908..9543859f86f 100644
--- a/doc/workflow/git_annex.md
+++ b/doc/workflow/git_annex.md
@@ -54,13 +54,13 @@ sudo yum install epel-release && sudo yum install git-annex
For omnibus-gitlab packages, only one configuration setting is needed.
The Omnibus package will internally set the correct options in all locations.
-1. In `/etc/gitlab/gitlab.rb` add the following line:
+1. In `/etc/gitlab/gitlab.rb` add the following line:
- ```ruby
- gitlab_shell['git_annex_enabled'] = true
- ```
+ ```ruby
+ gitlab_shell['git_annex_enabled'] = true
+ ```
-1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
### Configuration for installations from source
@@ -69,20 +69,20 @@ There are 2 settings to enable git-annex on your GitLab server.
One is located in `config/gitlab.yml` of the GitLab repository and the other
one is located in `config.yml` of gitlab-shell.
-1. In `config/gitlab.yml` add or edit the following lines:
+1. In `config/gitlab.yml` add or edit the following lines:
- ```yaml
- gitlab_shell:
- git_annex_enabled: true
- ```
+ ```yaml
+ gitlab_shell:
+ git_annex_enabled: true
+ ```
-1. In `config.yml` of gitlab-shell add or edit the following lines:
+1. In `config.yml` of gitlab-shell add or edit the following lines:
- ```yaml
- git_annex_enabled: true
- ```
+ ```yaml
+ git_annex_enabled: true
+ ```
-1. Save the files and [restart GitLab][] for the changes to take effect.
+1. Save the files and [restart GitLab][] for the changes to take effect.
## Using GitLab git-annex
diff --git a/doc/workflow/img/notification_global_settings.png b/doc/workflow/img/notification_global_settings.png
index 72f7418f1f8..699f726c442 100644
--- a/doc/workflow/img/notification_global_settings.png
+++ b/doc/workflow/img/notification_global_settings.png
Binary files differ
diff --git a/doc/workflow/lfs/lfs_administration.md b/doc/workflow/lfs/lfs_administration.md
index 55183408a10..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,6 +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 |
| `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) |
@@ -106,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.
@@ -118,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
@@ -166,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
@@ -202,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 b6bba57049d..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
@@ -84,6 +86,10 @@ that are on the remote repository, eg. for a branch from origin:
git lfs fetch origin master
```
+### Migrate an existing repo to Git LFS
+
+Read the documentation on how to [migrate an existing Git repo with Git LFS](../../topics/git/migrate_to_git_lfs/index.md).
+
## File Locking
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/35856) in GitLab 10.5.
@@ -212,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 71c73e3dffe..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
@@ -46,25 +46,24 @@ need to do (we assume you have [git-annex enabled](../git_annex.md#using-gitlab-
repository and that you have made backups in case something goes wrong).
Fire up a terminal, navigate to your Git repository and:
-
1. Disable `git-annex`:
- ```bash
- git annex sync --content
- git annex direct
- git annex uninit
- git annex indirect
- ```
+ ```bash
+ git annex sync --content
+ git annex direct
+ git annex uninit
+ git annex indirect
+ ```
1. Enable `git-lfs`:
- ```
- git lfs install
- git lfs track <files>
- git add .
- git commit -m "commit message"
- git push
- ```
+ ```
+ git lfs install
+ git lfs track <files>
+ git add .
+ git commit -m "commit message"
+ git push
+ ```
### Disabling Git Annex in your repo
@@ -77,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
@@ -86,72 +85,72 @@ if the server also has Git Annex 6 installed. Read more in the
1. Backup your repository
- ```bash
- cd repository
- git annex sync --content
- cd ..
- git clone repository repository-backup
- cd repository-backup
- git annex get
- cd ..
- ```
+ ```bash
+ cd repository
+ git annex sync --content
+ cd ..
+ git clone repository repository-backup
+ cd repository-backup
+ git annex get
+ cd ..
+ ```
1. Use `annex direct`:
- ```bash
- cd repository
- git annex direct
- ```
+ ```bash
+ cd repository
+ git annex direct
+ ```
- The output should be similar to this:
+ The output should be similar to this:
- ```bash
- commit
- On branch master
- Your branch is up-to-date with 'origin/master'.
- nothing to commit, working tree clean
- ok
- direct debian.iso ok
- direct ok
- ```
+ ```bash
+ commit
+ On branch master
+ Your branch is up-to-date with 'origin/master'.
+ nothing to commit, working tree clean
+ ok
+ direct debian.iso ok
+ direct ok
+ ```
1. Disable Git Annex with [`annex uninit`][uninit]:
- ```bash
- git annex uninit
- ```
+ ```bash
+ git annex uninit
+ ```
- The output should be similar to this:
+ The output should be similar to this:
- ```bash
- unannex debian.iso ok
- Deleted branch git-annex (was 2534d2c).
- ```
+ ```bash
+ unannex debian.iso ok
+ Deleted branch git-annex (was 2534d2c).
+ ```
- This will `unannex` every file in the repository, leaving the original files.
+ This will `unannex` every file in the repository, leaving the original files.
1. Switch back to `indirect` mode:
- ```bash
- git annex indirect
- ```
-
- The output should be similar to this:
+ ```bash
+ git annex indirect
+ ```
- ```bash
- (merging origin/git-annex into git-annex...)
- (recording state in git...)
- commit (recording state in git...)
+ The output should be similar to this:
- ok
- (recording state in git...)
- [master fac3194] commit before switching to indirect mode
- 1 file changed, 1 deletion(-)
- delete mode 120000 alpine-virt-3.4.4-x86_64.iso
- ok
- indirect ok
- ok
- ```
+ ```bash
+ (merging origin/git-annex into git-annex...)
+ (recording state in git...)
+ commit (recording state in git...)
+
+ ok
+ (recording state in git...)
+ [master fac3194] commit before switching to indirect mode
+ 1 file changed, 1 deletion(-)
+ delete mode 120000 alpine-virt-3.4.4-x86_64.iso
+ ok
+ indirect ok
+ ok
+ ```
---
@@ -216,16 +215,16 @@ branches created by Git Annex: `git-annex`, and all under `synced/`.
![repository branches](images/git-annex-branches.png)
-You can also do this on the commandline with:
+You can also do this on the command line with:
- ```bash
- git branch -d synced/master
- git branch -d synced/git-annex
- git push origin :synced/master
- git push origin :synced/git-annex
- git push origin :git-annex
- git remote prune origin
- ```
+```bash
+git branch -d synced/master
+git branch -d synced/git-annex
+git push origin :synced/master
+git push origin :synced/git-annex
+git push origin :git-annex
+git remote prune origin
+```
If there are still some Annex objects inside your repository (`.git/annex/`)
or references inside `.git/config`, run `annex uninit` again:
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index d82f7c6fdc7..ccb8844aea3 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -51,6 +51,10 @@ Organization like this is suitable for users that belong to different groups but
same need for being notified for every group they are member of.
These settings can be configured on group page under the name of the group. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
+The group owner can disable email notifications for a group, which also includes
+it's subgroups and projects. If this is the case, you will not receive any corresponding notifications,
+and the notification button will be disabled with an explanatory tooltip.
+
### Project Settings
![notification settings](img/notification_project_settings.png)
@@ -60,6 +64,10 @@ other setting.
This is suitable for users that have different needs for notifications per project basis.
These settings can be configured on project page under the name of the project. It will be the dropdown with the bell icon. They can also be configured on the user profile notifications dropdown.
+The project owner (or it's group owner) can disable email notifications for the project.
+If this is the case, you will not receive any corresponding notifications, and the notification
+button will be disabled with an explanatory tooltip.
+
## Notification events
Below is the table of events users can be notified of:
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 87ca46e94be..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.
@@ -92,14 +90,13 @@ The repository will push soon. To force a push, click the appropriate button.
1. On the destination GitLab instance, create a [personal access token](../user/profile/personal_access_tokens.md) with `API` scope.
1. On the source GitLab instance:
- 1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
- 1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance.
- 1. Click the **Mirror repository** button.
+ 1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
+ 1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance.
+ 1. Click the **Mirror repository** 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.
@@ -118,9 +115,9 @@ To configure mirror pulling for an existing project:
1. Select **Pull** from the **Mirror direction** dropdown.
1. Select an authentication method from the **Authentication method** dropdown, if necessary.
1. If necessary, check the following boxes:
- - **Overwrite diverged branches**.
- - **Trigger pipelines for mirror updates**.
- - **Only mirror protected branches**.
+ - **Overwrite diverged branches**.
+ - **Trigger pipelines for mirror updates**.
+ - **Only mirror protected branches**.
1. Click the **Mirror repository** button to save the configuration.
![Repository mirroring pull settings screen - upper part](img/repository_mirroring_pull_settings_upper.png)
@@ -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/shortcuts.md b/doc/workflow/shortcuts.md
index fd67ea8ce87..5d08bf5e77d 100644
--- a/doc/workflow/shortcuts.md
+++ b/doc/workflow/shortcuts.md
@@ -35,11 +35,11 @@ You can see GitLab's keyboard shortcuts by using <kbd>shift</kbd> + <kbd>?</kbd>
| Keyboard Shortcut | Description |
| ----------------- | ----------- |
-| <kbd>g</kbd> + <kbd>a</kbd> | Go to the activity feed |
-| <kbd>g</kbd> + <kbd>p</kbd> | Go to projects |
-| <kbd>g</kbd> + <kbd>i</kbd> | Go to issues |
-| <kbd>g</kbd> + <kbd>m</kbd> | Go to merge requests |
-| <kbd>g</kbd> + <kbd>t</kbd> | Go to todos |
+| <kbd>Shift</kbd> + <kbd>a</kbd> | Go to the activity feed |
+| <kbd>Shift</kbd> + <kbd>p</kbd> | Go to projects |
+| <kbd>Shift</kbd> + <kbd>i</kbd> | Go to issues |
+| <kbd>Shift</kbd> + <kbd>m</kbd> | Go to merge requests |
+| <kbd>Shift</kbd> + <kbd>t</kbd> | Go to todos |
## Project
@@ -84,6 +84,8 @@ You can see GitLab's keyboard shortcuts by using <kbd>shift</kbd> + <kbd>?</kbd>
| <kbd>l</kbd> | Change label |
| <kbd>]</kbd> or <kbd>j</kbd> | Move to next file |
| <kbd>[</kbd> or <kbd>k</kbd> | Move to previous file |
+| <kbd>n</kbd> | Move to next unresolved discussion |
+| <kbd>p</kbd> | Move to previous unresolved discussion |
## Epics **(ULTIMATE)**
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index b55c6b2e3df..fad853f8a44 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -22,8 +22,8 @@ below.
## How to enter data
-Time Tracking uses two [quick actions] that GitLab introduced with this new
-feature: `/spend` and `/estimate`.
+Time Tracking uses two [quick actions](../user/project/quick_actions.md)
+that GitLab introduced with this new feature: `/spend` and `/estimate`.
Quick actions can be used in the body of an issue or a merge request, but also
in a comment in both an issue or a merge request.
@@ -73,16 +73,15 @@ The following time units are available:
Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
-### Limit displayed units to hours
+### Limit displayed units to hours **(CORE ONLY)**
-> Introduced in GitLab 12.1.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/29469/) in GitLab 12.1.
-The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'.
+In GitLab self-managed instances, the display of time units can be limited to
+hours through the option in **Admin Area > Settings > Preferences** under **Localization**.
With this option enabled, `75h` is displayed instead of `1w 4d 3h`.
## Other interesting links
- [Time Tracking landing page on about.gitlab.com](https://about.gitlab.com/solutions/time-tracking/)
-
-[quick actions]: ../user/project/quick_actions.md
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/doc/workflow/workflow.md b/doc/workflow/workflow.md
index f70e41df842..7fac41c3b6f 100644
--- a/doc/workflow/workflow.md
+++ b/doc/workflow/workflow.md
@@ -1,31 +1,31 @@
# Feature branch workflow
-1. Clone project:
+1. Clone project:
- ```bash
- git clone git@example.com:project-name.git
- ```
+ ```bash
+ git clone git@example.com:project-name.git
+ ```
-1. Create branch with your feature:
+1. Create branch with your feature:
- ```bash
- git checkout -b $feature_name
- ```
+ ```bash
+ git checkout -b $feature_name
+ ```
-1. Write code. Commit changes:
+1. Write code. Commit changes:
- ```bash
- git commit -am "My feature is ready"
- ```
+ ```bash
+ git commit -am "My feature is ready"
+ ```
-1. Push your branch to GitLab:
+1. Push your branch to GitLab:
- ```bash
- git push origin $feature_name
- ```
+ ```bash
+ git push origin $feature_name
+ ```
-1. Review your code on commits page.
+1. Review your code on commits page.
-1. Create a merge request.
+1. Create a merge request.
-1. Your team lead will review the code &amp; merge it to the main branch.
+1. Your team lead will review the code &amp; merge it to the main branch.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 223ae13bd2d..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,
@@ -104,7 +104,6 @@ module API
mount ::API::BroadcastMessages
mount ::API::Commits
mount ::API::CommitStatuses
- mount ::API::ContainerRegistry
mount ::API::DeployKeys
mount ::API::Deployments
mount ::API::Environments
@@ -116,6 +115,7 @@ module API
mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::Groups
+ mount ::API::GroupContainerRepositories
mount ::API::GroupVariables
mount ::API::ImportGithub
mount ::API::Internal
@@ -138,6 +138,7 @@ module API
mount ::API::Pipelines
mount ::API::PipelineSchedules
mount ::API::ProjectClusters
+ mount ::API::ProjectContainerRepositories
mount ::API::ProjectEvents
mount ::API::ProjectExport
mount ::API::ProjectImport
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/commit_statuses.rb b/lib/api/commit_statuses.rb
index 08b4f8db8b0..d58a5e214ed 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -52,6 +52,7 @@ module API
optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"'
optional :coverage, type: Float, desc: 'The total code coverage'
+ optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered'
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/statuses/:sha' do
@@ -73,7 +74,8 @@ module API
name = params[:name] || params[:context] || 'default'
- pipeline = @project.pipeline_for(ref, commit.sha)
+ pipeline = @project.pipeline_for(ref, commit.sha, params[:pipeline_id])
+
unless pipeline
pipeline = @project.ci_pipelines.create!(
source: :external,
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index c414ad75d9d..a2f3e87ebd2 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -43,7 +43,7 @@ module API
path = params[:path]
before = params[:until]
after = params[:since]
- ref = params[:ref_name] || user_project.try(:default_branch) || 'master' unless params[:all]
+ ref = params[:ref_name].presence || user_project.try(:default_branch) || 'master' unless params[:all]
offset = (params[:page] - 1) * params[:per_page]
all = params[:all]
with_stats = params[:with_stats]
@@ -76,7 +76,7 @@ module API
detail 'This feature was introduced in GitLab 8.13'
end
params do
- requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide `start_branch`.', allow_blank: false
+ requires :branch, type: String, desc: 'Name of the branch to commit into. To create a new branch, also provide either `start_branch` or `start_sha`, and optionally `start_project`.', allow_blank: false
requires :commit_message, type: String, desc: 'Commit message'
requires :actions, type: Array, desc: 'Actions to perform in commit' do
requires :action, type: String, desc: 'The action to perform, `create`, `delete`, `move`, `update`, `chmod`', values: %w[create update move delete chmod].freeze
@@ -98,12 +98,16 @@ module API
requires :execute_filemode, type: Boolean, desc: 'When `true/false` enables/disables the execute flag on the file.'
end
end
- optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from'
- optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the commit from'
+
+ optional :start_branch, type: String, desc: 'Name of the branch to start the new branch from'
+ optional :start_sha, type: String, desc: 'SHA of the commit to start the new branch from'
+ mutually_exclusive :start_branch, :start_sha
+
+ optional :start_project, types: [Integer, String], desc: 'The ID or path of the project to start the new branch from'
optional :author_email, type: String, desc: 'Author email for commit'
optional :author_name, type: String, desc: 'Author name for commit'
optional :stats, type: Boolean, default: true, desc: 'Include commit stats'
- optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch`'
+ optional :force, type: Boolean, default: false, desc: 'When `true` overwrites the target branch with a new commit based on the `start_branch` or `start_sha`'
end
post ':id/repository/commits' do
if params[:start_project]
@@ -118,7 +122,7 @@ module API
attrs = declared_params
attrs[:branch_name] = attrs.delete(:branch)
- attrs[:start_branch] ||= attrs[:branch_name]
+ attrs[:start_branch] ||= attrs[:branch_name] unless attrs[:start_sha]
attrs[:start_project] = start_project if start_project
result = ::Files::MultiService.new(user_project, current_user, attrs).execute
@@ -126,7 +130,7 @@ module API
if result[:status] == :success
commit_detail = user_project.repository.commit(result[:result])
- Gitlab::UsageDataCounters::WebIdeCommitsCounter.increment if find_user_from_warden
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_commits_count if find_user_from_warden
present commit_detail, with: Entities::CommitDetail, stats: params[:stats]
else
diff --git a/lib/api/container_registry.rb b/lib/api/container_registry.rb
deleted file mode 100644
index 7dad20a822a..00000000000
--- a/lib/api/container_registry.rb
+++ /dev/null
@@ -1,149 +0,0 @@
-# frozen_string_literal: true
-
-module API
- class ContainerRegistry < Grape::API
- include PaginationParams
-
- REGISTRY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
- tag_name: API::NO_SLASH_URL_PART_REGEX)
-
- before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) }
- before { authorize_read_container_images! }
-
- params do
- requires :id, type: String, desc: 'The ID of a project'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- desc 'Get a project container repositories' do
- detail 'This feature was introduced in GitLab 11.8.'
- success Entities::ContainerRegistry::Repository
- end
- params do
- use :pagination
- end
- get ':id/registry/repositories' do
- repositories = user_project.container_repositories.ordered
-
- present paginate(repositories), with: Entities::ContainerRegistry::Repository
- end
-
- desc 'Delete repository' do
- detail 'This feature was introduced in GitLab 11.8.'
- end
- params do
- requires :repository_id, type: Integer, desc: 'The ID of the repository'
- end
- delete ':id/registry/repositories/:repository_id', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
- authorize_admin_container_image!
-
- DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id)
-
- status :accepted
- end
-
- desc 'Get a list of repositories tags' do
- detail 'This feature was introduced in GitLab 11.8.'
- success Entities::ContainerRegistry::Tag
- end
- params do
- requires :repository_id, type: Integer, desc: 'The ID of the repository'
- use :pagination
- end
- get ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
- authorize_read_container_image!
-
- tags = Kaminari.paginate_array(repository.tags)
- present paginate(tags), with: Entities::ContainerRegistry::Tag
- end
-
- desc 'Delete repository tags (in bulk)' do
- detail 'This feature was introduced in GitLab 11.8.'
- end
- params do
- requires :repository_id, type: Integer, desc: 'The ID of the repository'
- requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
- optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
- optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
- end
- delete ':id/registry/repositories/:repository_id/tags', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
- authorize_admin_container_image!
-
- message = 'This request has already been made. You can run this at most once an hour for a given container repository'
- render_api_error!(message, 400) unless obtain_new_cleanup_container_lease
-
- CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id,
- declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord
-
- status :accepted
- end
-
- desc 'Get a details about repository tag' do
- detail 'This feature was introduced in GitLab 11.8.'
- success Entities::ContainerRegistry::TagDetails
- end
- params do
- requires :repository_id, type: Integer, desc: 'The ID of the repository'
- requires :tag_name, type: String, desc: 'The name of the tag'
- end
- get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
- authorize_read_container_image!
- validate_tag!
-
- present tag, with: Entities::ContainerRegistry::TagDetails
- end
-
- desc 'Delete repository tag' do
- detail 'This feature was introduced in GitLab 11.8.'
- end
- params do
- requires :repository_id, type: Integer, desc: 'The ID of the repository'
- requires :tag_name, type: String, desc: 'The name of the tag'
- end
- delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REGISTRY_ENDPOINT_REQUIREMENTS do
- authorize_destroy_container_image!
- validate_tag!
-
- tag.delete
-
- status :ok
- end
- end
-
- helpers do
- def authorize_read_container_images!
- authorize! :read_container_image, user_project
- end
-
- def authorize_read_container_image!
- authorize! :read_container_image, repository
- end
-
- def authorize_destroy_container_image!
- authorize! :destroy_container_image, repository
- end
-
- def authorize_admin_container_image!
- authorize! :admin_container_image, repository
- end
-
- def obtain_new_cleanup_container_lease
- Gitlab::ExclusiveLease
- .new("container_repository:cleanup_tags:#{repository.id}",
- timeout: 1.hour)
- .try_obtain
- end
-
- def repository
- @repository ||= user_project.container_repositories.find(params[:repository_id])
- end
-
- def tag
- @tag ||= repository.tag(params[:tag_name])
- end
-
- def validate_tag!
- not_found!('Tag') unless tag.valid?
- end
- end
- end
-end
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index cc62ce22a1b..6c1acc3963f 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -4,6 +4,7 @@ module API
class Discussions < Grape::API
include PaginationParams
helpers ::API::Helpers::NotesHelpers
+ helpers ::RendersNotes
before { authenticate! }
@@ -23,21 +24,15 @@ module API
requires :noteable_id, types: [Integer, String], desc: 'The ID of the noteable'
use :pagination
end
- # rubocop: disable CodeReuse/ActiveRecord
+
get ":id/#{noteables_path}/:noteable_id/discussions" do
noteable = find_noteable(parent_type, params[:id], noteable_type, params[:noteable_id])
- notes = noteable.notes
- .inc_relations_for_view
- .includes(:noteable)
- .fresh
-
- notes = notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
+ notes = readable_discussion_notes(noteable)
discussions = Kaminari.paginate_array(Discussion.build_collection(notes, noteable))
present paginate(discussions), with: Entities::Discussion
end
- # rubocop: enable CodeReuse/ActiveRecord
desc "Get a single #{noteable_type.to_s.downcase} discussion" do
success Entities::Discussion
@@ -226,13 +221,24 @@ module API
helpers do
# rubocop: disable CodeReuse/ActiveRecord
- def readable_discussion_notes(noteable, discussion_id)
+ def readable_discussion_notes(noteable, discussion_id = nil)
notes = noteable.notes
- .where(discussion_id: discussion_id)
+ notes = notes.where(discussion_id: discussion_id) if discussion_id
+ notes = notes
.inc_relations_for_view
.includes(:noteable)
.fresh
+ # Without RendersActions#prepare_notes_for_rendering,
+ # Note#cross_reference_not_visible_for? will attempt to render
+ # Markdown references mentioned in the note to see whether they
+ # should be redacted. For notes that reference a commit, this
+ # would also incur a Gitaly call to verify the commit exists.
+ #
+ # With prepare_notes_for_rendering, we can avoid Gitaly calls
+ # because notes are redacted if they point to projects that
+ # cannot be accessed by the user.
+ notes = prepare_notes_for_rendering(notes)
notes.reject { |n| n.cross_reference_not_visible_for?(current_user) }
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 494da770279..5e66b4e76a5 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -2,6 +2,19 @@
module API
module Entities
+ class BlameRangeCommit < Grape::Entity
+ expose :id
+ expose :parent_ids
+ expose :message
+ expose :authored_date, :author_name, :author_email
+ expose :committed_date, :committer_name, :committer_email
+ end
+
+ class BlameRange < Grape::Entity
+ expose :commit, using: BlameRangeCommit
+ expose :lines
+ end
+
class WikiPageBasic < Grape::Entity
expose :format
expose :slug
@@ -64,6 +77,11 @@ module API
expose :last_activity_on, as: :last_activity_at # Back-compat
end
+ class UserStarsProject < Grape::Entity
+ expose :starred_since
+ expose :user, using: Entities::UserBasic
+ end
+
class Identity < Grape::Entity
expose :provider, :extern_uid
end
@@ -366,10 +384,7 @@ module API
end
expose :request_access_enabled
expose :full_name, :full_path
-
- if ::Group.supports_nested_objects?
- expose :parent_id
- end
+ expose :parent_id
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
@@ -630,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
@@ -1052,15 +1070,8 @@ module API
# rubocop: disable CodeReuse/ActiveRecord
def self.preload_relation(projects_relation, options = {})
relation = super(projects_relation, options)
-
- # MySQL doesn't support LIMIT inside an IN subquery
- if Gitlab::Database.mysql?
- project_ids = relation.pluck('projects.id')
- namespace_ids = relation.pluck(:namespace_id)
- else
- project_ids = relation.select('projects.id')
- namespace_ids = relation.select(:namespace_id)
- end
+ project_ids = relation.select('projects.id')
+ namespace_ids = relation.select(:namespace_id)
options[:project_members] = options[:current_user]
.project_members
@@ -1082,16 +1093,18 @@ module API
end
class Label < LabelBasic
- expose :open_issues_count do |label, options|
- label.open_issues_count(options[:current_user])
- end
+ with_options if: lambda { |_, options| options[:with_counts] } do
+ expose :open_issues_count do |label, options|
+ label.open_issues_count(options[:current_user])
+ end
- expose :closed_issues_count do |label, options|
- label.closed_issues_count(options[:current_user])
- end
+ expose :closed_issues_count do |label, options|
+ label.closed_issues_count(options[:current_user])
+ end
- expose :open_merge_requests_count do |label, options|
- label.open_merge_requests_count(options[:current_user])
+ expose :open_merge_requests_count do |label, options|
+ label.open_merge_requests_count(options[:current_user])
+ end
end
expose :subscribed do |label, options|
@@ -1159,6 +1172,7 @@ module API
attributes = ::ApplicationSettingsHelper.visible_attributes
attributes.delete(:performance_bar_allowed_group_path)
attributes.delete(:performance_bar_enabled)
+ attributes.delete(:allow_local_requests_from_hooks_and_services)
attributes
end
@@ -1177,6 +1191,7 @@ module API
# support legacy names, can be removed in v5
expose :password_authentication_enabled_for_web, as: :password_authentication_enabled
expose :password_authentication_enabled_for_web, as: :signin_enabled
+ expose :allow_local_requests_from_web_hooks_and_services, as: :allow_local_requests_from_hooks_and_services
end
# deprecated old Release representation
@@ -1336,6 +1351,7 @@ module API
expose :variable_type, :key, :value
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
+ expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) }
end
class Pipeline < PipelineBasic
diff --git a/lib/api/entities/container_registry.rb b/lib/api/entities/container_registry.rb
index 00833ca7480..6250f35c7cb 100644
--- a/lib/api/entities/container_registry.rb
+++ b/lib/api/entities/container_registry.rb
@@ -3,18 +3,20 @@
module API
module Entities
module ContainerRegistry
- class Repository < Grape::Entity
- expose :id
+ class Tag < Grape::Entity
expose :name
expose :path
expose :location
- expose :created_at
end
- class Tag < Grape::Entity
+ class Repository < Grape::Entity
+ expose :id
expose :name
expose :path
+ expose :project_id
expose :location
+ expose :created_at
+ expose :tags, using: Tag, if: -> (_, options) { options[:tags] }
end
class TagDetails < Tag
diff --git a/lib/api/files.rb b/lib/api/files.rb
index ca59d330e1c..0b438fb5bbc 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -83,6 +83,31 @@ module API
resource :projects, requirements: FILE_ENDPOINT_REQUIREMENTS do
allow_access_with_scope :read_repository, if: -> (request) { request.get? || request.head? }
+ desc 'Get blame file metadata from repository'
+ params do
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
+ end
+ head ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do
+ assign_file_vars!
+
+ set_http_headers(blob_data)
+ end
+
+ desc 'Get blame file from the repository'
+ params do
+ requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
+ requires :ref, type: String, desc: 'The name of branch, tag or commit', allow_blank: false
+ end
+ get ":id/repository/files/:file_path/blame", requirements: FILE_ENDPOINT_REQUIREMENTS do
+ assign_file_vars!
+
+ set_http_headers(blob_data)
+
+ blame_ranges = Gitlab::Blame.new(@blob, @commit).groups(highlight: false)
+ present blame_ranges, with: Entities::BlameRange
+ end
+
desc 'Get raw file metadata from repository'
params do
requires :file_path, type: String, desc: 'The url encoded path to the file. Ex. lib%2Fclass%2Erb'
diff --git a/lib/api/group_container_repositories.rb b/lib/api/group_container_repositories.rb
new file mode 100644
index 00000000000..fd24662cc9a
--- /dev/null
+++ b/lib/api/group_container_repositories.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module API
+ class GroupContainerRepositories < Grape::API
+ include PaginationParams
+
+ before { authorize_read_group_container_images! }
+
+ REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ tag_name: API::NO_SLASH_URL_PART_REGEX)
+
+ params do
+ requires :id, type: String, desc: "Group's ID or path"
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get a list of all repositories within a group' do
+ detail 'This feature was introduced in GitLab 12.2.'
+ success Entities::ContainerRegistry::Repository
+ end
+ params do
+ use :pagination
+ optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
+ end
+ get ':id/registry/repositories' do
+ repositories = ContainerRepositoriesFinder.new(
+ id: user_group.id, container_type: :group
+ ).execute
+
+ present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
+ end
+ end
+
+ helpers do
+ def authorize_read_group_container_images!
+ authorize! :read_container_image, user_group
+ end
+ end
+ end
+end
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
index 0dbc5f45a68..79a44941c81 100644
--- a/lib/api/group_labels.rb
+++ b/lib/api/group_labels.rb
@@ -16,6 +16,8 @@ module API
success Entities::GroupLabel
end
params do
+ optional :with_counts, type: Boolean, default: false,
+ desc: 'Include issue and merge request counts'
use :pagination
end
get ':id/labels' do
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index ec1020c7c78..f545f33c06b 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -114,10 +114,7 @@ module API
params do
requires :name, type: String, desc: 'The name of the group'
requires :path, type: String, desc: 'The path of the group'
-
- if ::Group.supports_nested_objects?
- optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
- end
+ optional :parent_id, type: Integer, desc: 'The parent group id for creating nested group'
use :optional_params
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 8ae42c6dadd..1aa6dc44bf7 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -544,5 +544,9 @@ module API
params[:archived]
end
+
+ def ip_address
+ env["action_dispatch.remote_ip"].to_s || request.ip
+ end
end
end
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
index c11e7d614ab..896b0aba52b 100644
--- a/lib/api/helpers/label_helpers.rb
+++ b/lib/api/helpers/label_helpers.rb
@@ -19,7 +19,11 @@ module API
end
def get_labels(parent, entity)
- present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent
+ present paginate(available_labels_for(parent)),
+ with: entity,
+ current_user: current_user,
+ parent: parent,
+ with_counts: params[:with_counts]
end
def create_label(parent, entity)
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index b03ac7deb71..6bf9057fad7 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -74,14 +74,14 @@ module API
end
def find_noteable(parent_type, parent_id, noteable_type, noteable_id)
- params = params_by_noteable_type_and_id(noteable_type, noteable_id)
+ params = finder_params_by_noteable_type_and_id(noteable_type, noteable_id, parent_id)
- noteable = NotesFinder.new(user_project, current_user, params).target
+ noteable = NotesFinder.new(current_user, params).target
noteable = nil unless can?(current_user, noteable_read_ability_name(noteable), noteable)
noteable || not_found!(noteable_type)
end
- def params_by_noteable_type_and_id(type, id)
+ def finder_params_by_noteable_type_and_id(type, id, parent_id)
target_type = type.name.underscore
{ target_type: target_type }.tap do |h|
if %w(issue merge_request).include?(target_type)
@@ -89,9 +89,15 @@ module API
else
h[:target_id] = id
end
+
+ add_parent_to_finder_params(h, type, parent_id)
end
end
+ def add_parent_to_finder_params(finder_params, noteable_type, parent_id)
+ finder_params[:project] = user_project
+ end
+
def noteable_parent(noteable)
public_send("user_#{noteable.class.parent_class.to_s.underscore}") # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 833e3b9ebaf..51b7cf05c8f 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -71,6 +71,7 @@ module API
:build_timeout,
:builds_access_level,
:ci_config_path,
+ :ci_default_git_depth,
:container_registry_enabled,
:default_branch,
:description,
diff --git a/lib/api/helpers/runner.rb b/lib/api/helpers/runner.rb
index 100463fcb95..5b87eccf860 100644
--- a/lib/api/helpers/runner.rb
+++ b/lib/api/helpers/runner.rb
@@ -25,7 +25,7 @@ module API
end
def get_runner_ip
- { ip_address: env["action_dispatch.remote_ip"].to_s || request.ip }
+ { ip_address: ip_address }
end
def current_runner
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
index c4ecf55969c..422db5c7a50 100644
--- a/lib/api/helpers/services_helpers.rb
+++ b/lib/api/helpers/services_helpers.rb
@@ -489,32 +489,6 @@ module API
desc: 'The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`'
}
],
- 'kubernetes' => [
- {
- required: true,
- name: :namespace,
- type: String,
- desc: 'The Kubernetes namespace to use'
- },
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The service token to authenticate against the Kubernetes cluster with'
- },
- {
- required: false,
- name: :ca_pem,
- type: String,
- desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
- }
- ],
'mattermost-slash-commands' => [
{
required: true,
@@ -739,7 +713,6 @@ module API
::HipchatService,
::IrkerService,
::JiraService,
- ::KubernetesService,
::MattermostSlashCommandsService,
::SlackSlashCommandsService,
::PackagistService,
diff --git a/lib/api/helpers/variables_helpers.rb b/lib/api/helpers/variables_helpers.rb
deleted file mode 100644
index 78a92d0f5a6..00000000000
--- a/lib/api/helpers/variables_helpers.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module API
- module Helpers
- module VariablesHelpers
- extend ActiveSupport::Concern
- extend Grape::API::Helpers
-
- params :optional_params_ee do
- end
- end
- end
-end
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index d687acf3423..7819c2de515 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -96,7 +96,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
@@ -122,7 +123,8 @@ module API
with: Entities::Issue,
with_labels_details: declared_params[:with_labels_details],
current_user: current_user,
- issuable_metadata: issuable_meta_data(issues, 'Issue', current_user)
+ issuable_metadata: issuable_meta_data(issues, 'Issue', current_user),
+ include_subscribed: false
}
present issues, options
diff --git a/lib/api/job_artifacts.rb b/lib/api/job_artifacts.rb
index e7fed55170e..b35aa952f81 100644
--- a/lib/api/job_artifacts.rb
+++ b/lib/api/job_artifacts.rb
@@ -27,7 +27,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- latest_build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ latest_build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
present_carrierwave_file!(latest_build.artifacts_file)
end
@@ -45,7 +45,7 @@ module API
requirements: { ref_name: /.+/ } do
authorize_download_artifacts!
- build = user_project.latest_successful_build_for!(params[:job], params[:ref_name])
+ build = user_project.latest_successful_build_for_ref!(params[:job], params[:ref_name])
path = Gitlab::Ci::Build::Artifacts::Path
.new(params[:artifact_path])
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index d729d3ee625..c183198d3c6 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -15,6 +15,8 @@ module API
success Entities::ProjectLabel
end
params do
+ optional :with_counts, type: Boolean, default: false,
+ desc: 'Include issue and merge request counts'
use :pagination
end
get ':id/labels' do
diff --git a/lib/api/project_container_repositories.rb b/lib/api/project_container_repositories.rb
new file mode 100644
index 00000000000..6d53abcc500
--- /dev/null
+++ b/lib/api/project_container_repositories.rb
@@ -0,0 +1,152 @@
+# frozen_string_literal: true
+
+module API
+ class ProjectContainerRepositories < Grape::API
+ include PaginationParams
+
+ REPOSITORY_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(
+ tag_name: API::NO_SLASH_URL_PART_REGEX)
+
+ before { error!('404 Not Found', 404) unless Feature.enabled?(:container_registry_api, user_project, default_enabled: true) }
+ before { authorize_read_container_images! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a project'
+ end
+ resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get a project container repositories' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::Repository
+ end
+ params do
+ use :pagination
+ optional :tags, type: Boolean, default: false, desc: 'Determines if tags should be included'
+ end
+ get ':id/registry/repositories' do
+ repositories = ContainerRepositoriesFinder.new(
+ id: user_project.id, container_type: :project
+ ).execute
+
+ present paginate(repositories), with: Entities::ContainerRegistry::Repository, tags: params[:tags]
+ end
+
+ desc 'Delete repository' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ end
+ delete ':id/registry/repositories/:repository_id', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
+ authorize_admin_container_image!
+
+ DeleteContainerRepositoryWorker.perform_async(current_user.id, repository.id)
+
+ status :accepted
+ end
+
+ desc 'Get a list of repositories tags' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::Tag
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ use :pagination
+ end
+ get ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
+ authorize_read_container_image!
+
+ tags = Kaminari.paginate_array(repository.tags)
+ present paginate(tags), with: Entities::ContainerRegistry::Tag
+ end
+
+ desc 'Delete repository tags (in bulk)' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :name_regex, type: String, desc: 'The tag name regexp to delete, specify .* to delete all'
+ optional :keep_n, type: Integer, desc: 'Keep n of latest tags with matching name'
+ optional :older_than, type: String, desc: 'Delete older than: 1h, 1d, 1month'
+ end
+ delete ':id/registry/repositories/:repository_id/tags', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
+ authorize_admin_container_image!
+
+ message = 'This request has already been made. You can run this at most once an hour for a given container repository'
+ render_api_error!(message, 400) unless obtain_new_cleanup_container_lease
+
+ CleanupContainerRepositoryWorker.perform_async(current_user.id, repository.id,
+ declared_params.except(:repository_id)) # rubocop: disable CodeReuse/ActiveRecord
+
+ status :accepted
+ end
+
+ desc 'Get a details about repository tag' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ success Entities::ContainerRegistry::TagDetails
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :tag_name, type: String, desc: 'The name of the tag'
+ end
+ get ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
+ authorize_read_container_image!
+ validate_tag!
+
+ present tag, with: Entities::ContainerRegistry::TagDetails
+ end
+
+ desc 'Delete repository tag' do
+ detail 'This feature was introduced in GitLab 11.8.'
+ end
+ params do
+ requires :repository_id, type: Integer, desc: 'The ID of the repository'
+ requires :tag_name, type: String, desc: 'The name of the tag'
+ end
+ delete ':id/registry/repositories/:repository_id/tags/:tag_name', requirements: REPOSITORY_ENDPOINT_REQUIREMENTS do
+ authorize_destroy_container_image!
+ validate_tag!
+
+ tag.delete
+
+ status :ok
+ end
+ end
+
+ helpers do
+ def authorize_read_container_images!
+ authorize! :read_container_image, user_project
+ end
+
+ def authorize_read_container_image!
+ authorize! :read_container_image, repository
+ end
+
+ def authorize_destroy_container_image!
+ authorize! :destroy_container_image, repository
+ end
+
+ def authorize_admin_container_image!
+ authorize! :admin_container_image, repository
+ end
+
+ def obtain_new_cleanup_container_lease
+ Gitlab::ExclusiveLease
+ .new("container_repository:cleanup_tags:#{repository.id}",
+ timeout: 1.hour)
+ .try_obtain
+ end
+
+ def repository
+ @repository ||= user_project.container_repositories.find(params[:repository_id])
+ end
+
+ def tag
+ @tag ||= repository.tag(params[:tag_name])
+ end
+
+ def validate_tag!
+ not_found!('Tag') unless tag.valid?
+ end
+ end
+ end
+end
diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb
index 71891e43dcc..bb1b037c08f 100644
--- a/lib/api/project_import.rb
+++ b/lib/api/project_import.rb
@@ -59,6 +59,7 @@ module API
}
override_params = import_params.delete(:override_params)
+ filter_attributes_using_license!(override_params) if override_params
project = ::Projects::GitlabProjectsImportService.new(
current_user, project_params, override_params
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 0923d31f5ff..996205d4b7b 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -115,6 +115,22 @@ module API
present_projects load_projects
end
+
+ desc 'Get projects starred by a user' do
+ success Entities::BasicProjectDetails
+ end
+ params do
+ requires :user_id, type: String, desc: 'The ID or username of the user'
+ use :collection_params
+ use :statistics_params
+ end
+ get ":user_id/starred_projects" do
+ user = find_user(params[:user_id])
+ not_found!('User') unless user
+
+ starred_projects = StarredProjectsFinder.new(user, params: project_finder_params, current_user: current_user).execute
+ present_projects starred_projects
+ end
end
resource :projects do
@@ -358,6 +374,19 @@ module API
end
end
+ desc 'Get the users who starred a project' do
+ success Entities::UserBasic
+ end
+ params do
+ optional :search, type: String, desc: 'Return list of users matching the search criteria'
+ use :pagination
+ end
+ get ':id/starrers' do
+ starrers = UsersStarProjectsFinder.new(user_project, params, current_user: current_user).execute
+
+ present paginate(starrers), with: Entities::UserStarsProject
+ end
+
desc 'Get languages in project repository'
get ':id/languages' do
::Projects::RepositoryLanguagesService
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 4275d911708..c36ee5af63f 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -59,7 +59,7 @@ module API
optional :grafana_url, type: String, desc: 'Grafana URL'
optional :gravatar_enabled, type: Boolean, desc: 'Flag indicating if the Gravatar service is enabled'
optional :help_page_hide_commercial_content, type: Boolean, desc: 'Hide marketing-related entries from help'
- optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page'
+ optional :help_page_support_url, type: String, desc: 'Alternate support URL for help page and help dropdown'
optional :help_page_text, type: String, desc: 'Custom text displayed on the help page'
optional :home_page_url, type: String, desc: 'We will redirect non-logged in users to this page'
optional :housekeeping_enabled, type: Boolean, desc: 'Enable automatic repository housekeeping (git repack, git gc)'
@@ -124,6 +124,13 @@ module API
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
+ optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
+ optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
+ given snowplow_enabled: ->(val) { val } do
+ requires :snowplow_collector_hostname, type: String, desc: 'The Snowplow collector hostname'
+ optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain'
+ optional :snowplow_site_id, type: String, desc: 'The Snowplow site name / application ic'
+ end
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
@@ -158,6 +165,11 @@ module API
attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
+ # support legacy names, can be removed in v5
+ if attrs.has_key?(:allow_local_requests_from_hooks_and_services)
+ attrs[:allow_local_requests_from_web_hooks_and_services] = attrs.delete(:allow_local_requests_from_hooks_and_services)
+ end
+
attrs = filter_attributes_using_license(attrs)
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 7260ecfb5ee..404675bfaec 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -13,6 +13,13 @@ module API
'issues' => ->(iid) { find_project_issue(iid) }
}.freeze
+ helpers do
+ # EE::API::Todos would override this method
+ def find_todos
+ TodosFinder.new(current_user, params).execute
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -41,10 +48,6 @@ module API
resource :todos do
helpers do
- def find_todos
- TodosFinder.new(current_user, params).execute
- end
-
def issuable_and_awardable?(type)
obj_type = Object.const_get(type)
@@ -107,3 +110,5 @@ module API
end
end
end
+
+API::Todos.prepend_if_ee('EE::API::Todos')
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 0e829c5699b..eeecc390256 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -112,27 +112,6 @@ module API
end
end
- desc 'Take ownership of trigger' do
- success Entities::Trigger
- end
- params do
- requires :trigger_id, type: Integer, desc: 'The trigger ID'
- end
- post ':id/triggers/:trigger_id/take_ownership' do
- authenticate!
- authorize! :admin_build, user_project
-
- trigger = user_project.triggers.find(params.delete(:trigger_id))
- break not_found!('Trigger') unless trigger
-
- if trigger.update(owner: current_user)
- status :ok
- present trigger, with: Entities::Trigger, current_user: current_user
- else
- render_validation_error!(trigger)
- end
- end
-
desc 'Delete a trigger' do
success Entities::Trigger
end
diff --git a/lib/api/validations/types/labels_list.rb b/lib/api/validations/types/labels_list.rb
index 47cd83c29cf..60277b99106 100644
--- a/lib/api/validations/types/labels_list.rb
+++ b/lib/api/validations/types/labels_list.rb
@@ -10,7 +10,7 @@ module API
when String
value.split(',').map(&:strip)
when Array
- value.map { |v| v.to_s.split(',').map(&:strip) }.flatten
+ value.flat_map { |v| v.to_s.split(',').map(&:strip) }
when LabelsList
value
else
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index af1d7936556..f022b9e665a 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,8 +7,6 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
- helpers Helpers::VariablesHelpers
-
helpers do
def filter_variable_parameters(params)
# This method exists so that EE can more easily filter out certain
@@ -59,8 +57,7 @@ module API
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
-
- use :optional_params_ee
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
@@ -84,8 +81,7 @@ module API
optional :protected, type: Boolean, desc: 'Whether the variable is protected'
optional :masked, type: Boolean, desc: 'Whether the variable is masked'
optional :variable_type, type: String, values: Ci::Variable.variable_types.keys, desc: 'The type of variable, must be one of env_var or file'
-
- use :optional_params_ee
+ optional :environment_scope, type: String, desc: 'The environment_scope of the variable'
end
# rubocop: disable CodeReuse/ActiveRecord
put ':id/variables/:key' do
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index cd8e29d14d3..7e457c4982d 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -23,11 +23,6 @@ module Backup
dump_pid =
case config["adapter"]
- when /^mysql/ then
- progress.print "Dumping MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysqldump', *mysql_args, config['database'], out: compress_wr)
when "postgresql" then
progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
@@ -57,11 +52,6 @@ module Backup
restore_pid =
case config["adapter"]
- when /^mysql/ then
- progress.print "Restoring MySQL database #{config['database']} ... "
- # Workaround warnings from MySQL 5.6 about passwords on cmd line
- ENV['MYSQL_PWD'] = config["password"].to_s if config["password"]
- spawn('mysql', *mysql_args, config['database'], in: decompress_rd)
when "postgresql" then
progress.print "Restoring PostgreSQL database #{config['database']} ... "
pg_env
@@ -80,23 +70,6 @@ module Backup
protected
- def mysql_args
- args = {
- 'host' => '--host',
- 'port' => '--port',
- 'socket' => '--socket',
- 'username' => '--user',
- 'encoding' => '--default-character-set',
- # SSL
- 'sslkey' => '--ssl-key',
- 'sslcert' => '--ssl-cert',
- 'sslca' => '--ssl-ca',
- 'sslcapath' => '--ssl-capath',
- 'sslcipher' => '--ssl-cipher'
- }
- args.map { |opt, arg| "#{arg}=#{config[opt]}" if config[opt] }.compact
- end
-
def pg_env
args = {
'username' => 'PGUSER',
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 0224dd8fcd1..52af28ce8ec 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -337,6 +337,24 @@ module Banzai
@current_project_namespace_path ||= project&.namespace&.full_path
end
+ def records_per_parent
+ @_records_per_project ||= {}
+
+ @_records_per_project[object_class.to_s.underscore] ||= begin
+ hash = Hash.new { |h, k| h[k] = {} }
+
+ parent_per_reference.each do |path, parent|
+ record_ids = references_per_parent[path]
+
+ parent_records(parent, record_ids).each do |record|
+ hash[parent][record_identifier(record)] = record
+ end
+ end
+
+ hash
+ end
+ end
+
private
def full_project_path(namespace, project_ref)
diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb
index 56214043d87..5f2cbc24c60 100644
--- a/lib/banzai/filter/autolink_filter.rb
+++ b/lib/banzai/filter/autolink_filter.rb
@@ -18,6 +18,7 @@ module Banzai
#
class AutolinkFilter < HTML::Pipeline::Filter
include ActionView::Helpers::TagHelper
+ include Gitlab::Utils::SanitizeNodeLink
# Pattern to match text that should be autolinked.
#
@@ -72,19 +73,11 @@ module Banzai
private
- # Return true if any of the UNSAFE_PROTOCOLS strings are included in the URI scheme
- def contains_unsafe?(scheme)
- return false unless scheme
-
- scheme = scheme.strip.downcase
- Banzai::Filter::SanitizationFilter::UNSAFE_PROTOCOLS.any? { |protocol| scheme.include?(protocol) }
- end
-
def autolink_match(match)
# start by stripping out dangerous links
begin
uri = Addressable::URI.parse(match)
- return match if contains_unsafe?(uri.scheme)
+ return match unless safe_protocol?(uri.scheme)
rescue Addressable::URI::InvalidURIError
return match
end
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
index 420e92cb1e8..2dabca3552d 100644
--- a/lib/banzai/filter/base_sanitization_filter.rb
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -11,6 +11,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with common rules.
class BaseSanitizationFilter < HTML::Pipeline::SanitizationFilter
include Gitlab::Utils::StrongMemoize
+ extend Gitlab::Utils::SanitizeNodeLink
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
@@ -40,7 +41,7 @@ module Banzai
# Allow any protocol in `a` elements
# and then remove links with unsafe protocols
whitelist[:protocols].delete('a')
- whitelist[:transformers].push(self.class.remove_unsafe_links)
+ whitelist[:transformers].push(self.class.method(:remove_unsafe_links))
# Remove `rel` attribute from `a` elements
whitelist[:transformers].push(self.class.remove_rel)
@@ -54,35 +55,6 @@ module Banzai
end
class << self
- def remove_unsafe_links
- lambda do |env|
- node = env[:node]
-
- return unless node.name == 'a'
- return unless node.has_attribute?('href')
-
- begin
- node['href'] = node['href'].strip
- uri = Addressable::URI.parse(node['href'])
-
- return unless uri.scheme
-
- # Remove all invalid scheme characters before checking against the
- # list of unsafe protocols.
- #
- # See https://tools.ietf.org/html/rfc3986#section-3.1
- scheme = uri.scheme
- .strip
- .downcase
- .gsub(/[^A-Za-z0-9\+\.\-]+/, '')
-
- node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(scheme)
- rescue Addressable::URI::InvalidURIError
- node.remove_attribute('href')
- end
- end
- end
-
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
diff --git a/lib/banzai/filter/commit_reference_filter.rb b/lib/banzai/filter/commit_reference_filter.rb
index c3e5ac41cb8..e1d7b36b9a2 100644
--- a/lib/banzai/filter/commit_reference_filter.rb
+++ b/lib/banzai/filter/commit_reference_filter.rb
@@ -19,12 +19,11 @@ module Banzai
end
def find_object(project, id)
- return unless project.is_a?(Project)
+ return unless project.is_a?(Project) && project.valid_repo?
- if project && project.valid_repo?
- # n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/43894
- Gitlab::GitalyClient.allow_n_plus_1_calls { project.commit(id) }
- end
+ _, record = records_per_parent[project].detect { |k, _v| Gitlab::Git.shas_eql?(k, id) }
+
+ record
end
def referenced_merge_request_commit_shas
@@ -66,6 +65,14 @@ module Banzai
private
+ def record_identifier(record)
+ record.id
+ end
+
+ def parent_records(parent, ids)
+ parent.commits_by(oids: ids.to_a)
+ end
+
def noteable
context[:noteable]
end
diff --git a/lib/banzai/filter/inline_embeds_filter.rb b/lib/banzai/filter/inline_embeds_filter.rb
index 97394fd8f82..9f1ef0796f0 100644
--- a/lib/banzai/filter/inline_embeds_filter.rb
+++ b/lib/banzai/filter/inline_embeds_filter.rb
@@ -10,8 +10,6 @@ module Banzai
# the link, and insert this node after any html content
# surrounding the link.
def call
- return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
-
doc.xpath(xpath_search).each do |node|
next unless element = element_to_embed(node)
diff --git a/lib/banzai/filter/inline_metrics_filter.rb b/lib/banzai/filter/inline_metrics_filter.rb
index 0120cc37d6f..c5a328c21b2 100644
--- a/lib/banzai/filter/inline_metrics_filter.rb
+++ b/lib/banzai/filter/inline_metrics_filter.rb
@@ -15,17 +15,6 @@ module Banzai
)
end
- # Endpoint FE should hit to collect the appropriate
- # chart information
- def metrics_dashboard_url(params)
- Gitlab::Metrics::Dashboard::Url.build_dashboard_url(
- params['namespace'],
- params['project'],
- params['environment'],
- embedded: true
- )
- end
-
# Search params for selecting metrics links. A few
# simple checks is enough to boost performance without
# the cost of doing a full regex match.
@@ -38,6 +27,28 @@ module Banzai
def link_pattern
Gitlab::Metrics::Dashboard::Url.regex
end
+
+ private
+
+ # Endpoint FE should hit to collect the appropriate
+ # chart information
+ def metrics_dashboard_url(params)
+ Gitlab::Metrics::Dashboard::Url.build_dashboard_url(
+ params['namespace'],
+ params['project'],
+ params['environment'],
+ embedded: true,
+ **query_params(params['url'])
+ )
+ end
+
+ # Parses query params out from full url string into hash.
+ #
+ # Ex) 'https://<root>/<project>/<environment>/metrics?title=Title&group=Group'
+ # --> { title: 'Title', group: 'Group' }
+ def query_params(url)
+ Gitlab::Metrics::Dashboard::Url.parse_query(url)
+ end
end
end
end
diff --git a/lib/banzai/filter/inline_metrics_redactor_filter.rb b/lib/banzai/filter/inline_metrics_redactor_filter.rb
index ff91be2cbb7..4d8a5028898 100644
--- a/lib/banzai/filter/inline_metrics_redactor_filter.rb
+++ b/lib/banzai/filter/inline_metrics_redactor_filter.rb
@@ -13,8 +13,6 @@ module Banzai
# uses to identify the embedded content, removing
# only unnecessary nodes.
def call
- return doc unless Feature.enabled?(:gfm_embedded_metrics, context[:project])
-
nodes.each do |node|
path = paths_by_node[node]
user_has_access = user_access_by_path[path]
diff --git a/lib/banzai/filter/issuable_reference_filter.rb b/lib/banzai/filter/issuable_reference_filter.rb
index 2963cba91e8..b91ba9f7256 100644
--- a/lib/banzai/filter/issuable_reference_filter.rb
+++ b/lib/banzai/filter/issuable_reference_filter.rb
@@ -3,22 +3,8 @@
module Banzai
module Filter
class IssuableReferenceFilter < AbstractReferenceFilter
- def records_per_parent
- @records_per_project ||= {}
-
- @records_per_project[object_class.to_s.underscore] ||= begin
- hash = Hash.new { |h, k| h[k] = {} }
-
- parent_per_reference.each do |path, parent|
- record_ids = references_per_parent[path]
-
- parent_records(parent, record_ids).each do |record|
- hash[parent][record.iid.to_i] = record
- end
- end
-
- hash
- end
+ def record_identifier(record)
+ record.iid.to_i
end
def find_object(parent, iid)
diff --git a/lib/banzai/filter/wiki_link_filter.rb b/lib/banzai/filter/wiki_link_filter.rb
index 1728a442533..18947679b69 100644
--- a/lib/banzai/filter/wiki_link_filter.rb
+++ b/lib/banzai/filter/wiki_link_filter.rb
@@ -8,15 +8,19 @@ module Banzai
# Context options:
# :project_wiki
class WikiLinkFilter < HTML::Pipeline::Filter
+ include Gitlab::Utils::SanitizeNodeLink
+
def call
return doc unless project_wiki?
- doc.search('a:not(.gfm)').each { |el| process_link_attr(el.attribute('href')) }
- doc.search('video').each { |el| process_link_attr(el.attribute('src')) }
+ doc.search('a:not(.gfm)').each { |el| process_link(el.attribute('href'), el) }
+
+ doc.search('video').each { |el| process_link(el.attribute('src'), el) }
+
doc.search('img').each do |el|
attr = el.attribute('data-src') || el.attribute('src')
- process_link_attr(attr)
+ process_link(attr, el)
end
doc
@@ -24,6 +28,11 @@ module Banzai
protected
+ def process_link(link_attr, node)
+ process_link_attr(link_attr)
+ remove_unsafe_links({ node: node }, remove_invalid_links: false)
+ end
+
def project_wiki?
!context[:project_wiki].nil?
end
diff --git a/lib/banzai/filter/wiki_link_filter/rewriter.rb b/lib/banzai/filter/wiki_link_filter/rewriter.rb
index 77b5053f38c..f4cc8beeb52 100644
--- a/lib/banzai/filter/wiki_link_filter/rewriter.rb
+++ b/lib/banzai/filter/wiki_link_filter/rewriter.rb
@@ -4,8 +4,6 @@ module Banzai
module Filter
class WikiLinkFilter < HTML::Pipeline::Filter
class Rewriter
- UNSAFE_SLUG_REGEXES = [/\Ajavascript:/i].freeze
-
def initialize(link_string, wiki:, slug:)
@uri = Addressable::URI.parse(link_string)
@wiki_base_path = wiki && wiki.wiki_base_path
@@ -37,8 +35,6 @@ module Banzai
# Of the form `./link`, `../link`, or similar
def apply_hierarchical_link_rules!
- return if slug_considered_unsafe?
-
@uri = Addressable::URI.join(@slug, @uri) if @uri.to_s[0] == '.'
end
@@ -58,10 +54,6 @@ module Banzai
def repository_upload?
@uri.relative? && @uri.path.starts_with?(Wikis::CreateAttachmentService::ATTACHMENT_PATH)
end
-
- def slug_considered_unsafe?
- UNSAFE_SLUG_REGEXES.any? { |r| r.match?(@slug) }
- end
end
end
end
diff --git a/lib/banzai/reference_redactor.rb b/lib/banzai/reference_redactor.rb
index eb5c35da375..936436982e7 100644
--- a/lib/banzai/reference_redactor.rb
+++ b/lib/banzai/reference_redactor.rb
@@ -33,7 +33,7 @@ module Banzai
#
# data - An Array of a Hashes mapping an HTML document to nodes to redact.
def redact_document_nodes(all_document_nodes)
- all_nodes = all_document_nodes.map { |x| x[:nodes] }.flatten
+ all_nodes = all_document_nodes.flat_map { |x| x[:nodes] }
visible = nodes_visible_to_user(all_nodes)
metadata = []
diff --git a/lib/bitbucket/representation/comment.rb b/lib/bitbucket/representation/comment.rb
index 1b8dc27793a..127598a76ab 100644
--- a/lib/bitbucket/representation/comment.rb
+++ b/lib/bitbucket/representation/comment.rb
@@ -4,7 +4,7 @@ module Bitbucket
module Representation
class Comment < Representation::Base
def author
- user['username']
+ user['nickname']
end
def note
diff --git a/lib/bitbucket/representation/issue.rb b/lib/bitbucket/representation/issue.rb
index a88797cdab9..3f6db9cb75b 100644
--- a/lib/bitbucket/representation/issue.rb
+++ b/lib/bitbucket/representation/issue.rb
@@ -14,7 +14,7 @@ module Bitbucket
end
def author
- raw.dig('reporter', 'username')
+ raw.dig('reporter', 'nickname')
end
def description
diff --git a/lib/bitbucket/representation/pull_request.rb b/lib/bitbucket/representation/pull_request.rb
index 6a0e8b354bf..a498c9bc213 100644
--- a/lib/bitbucket/representation/pull_request.rb
+++ b/lib/bitbucket/representation/pull_request.rb
@@ -4,7 +4,7 @@ module Bitbucket
module Representation
class PullRequest < Representation::Base
def author
- raw.fetch('author', {}).fetch('username', nil)
+ raw.fetch('author', {}).fetch('nickname', nil)
end
def description
diff --git a/lib/bitbucket_server/client.rb b/lib/bitbucket_server/client.rb
index 6a608058813..cf55c692271 100644
--- a/lib/bitbucket_server/client.rb
+++ b/lib/bitbucket_server/client.rb
@@ -23,8 +23,9 @@ module BitbucketServer
BitbucketServer::Representation::Repo.new(parsed_response)
end
- def repos(page_offset: 0, limit: nil)
+ def repos(page_offset: 0, limit: nil, filter: nil)
path = "/repos"
+ path += "?name=#{filter}" if filter
get_collection(path, :repo, page_offset: page_offset, limit: limit)
end
diff --git a/lib/container_registry/client.rb b/lib/container_registry/client.rb
index c80f49f5ae0..82810ea4076 100644
--- a/lib/container_registry/client.rb
+++ b/lib/container_registry/client.rb
@@ -7,7 +7,9 @@ module ContainerRegistry
class Client
attr_accessor :uri
- MANIFEST_VERSION = 'application/vnd.docker.distribution.manifest.v2+json'.freeze
+ DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE = 'application/vnd.docker.distribution.manifest.v2+json'
+ OCI_MANIFEST_V1_TYPE = 'application/vnd.oci.image.manifest.v1+json'
+ ACCEPTED_TYPES = [DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE, OCI_MANIFEST_V1_TYPE].freeze
# Taken from: FaradayMiddleware::FollowRedirects
REDIRECT_CODES = Set.new [301, 302, 303, 307]
@@ -60,12 +62,13 @@ module ContainerRegistry
end
def accept_manifest(conn)
- conn.headers['Accept'] = MANIFEST_VERSION
+ conn.headers['Accept'] = ACCEPTED_TYPES
conn.response :json, content_type: 'application/json'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+prettyjws'
conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v1+json'
- conn.response :json, content_type: 'application/vnd.docker.distribution.manifest.v2+json'
+ conn.response :json, content_type: DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ conn.response :json, content_type: OCI_MANIFEST_V1_TYPE
end
def response_body(response, allow_redirect: false)
@@ -79,7 +82,10 @@ module ContainerRegistry
def redirect_response(location)
return unless location
- faraday_redirect.get(location)
+ uri = URI(@base_uri).merge(location)
+ raise ArgumentError, "Invalid scheme for #{location}" unless %w[http https].include?(uri.scheme)
+
+ faraday_redirect.get(uri)
end
def faraday
diff --git a/lib/container_registry/tag.rb b/lib/container_registry/tag.rb
index ef41dc560c9..ebea84fa1ca 100644
--- a/lib/container_registry/tag.rb
+++ b/lib/container_registry/tag.rb
@@ -6,6 +6,9 @@ module ContainerRegistry
attr_reader :repository, :name
+ # https://github.com/docker/distribution/commit/3150937b9f2b1b5b096b2634d0e7c44d4a0f89fb
+ TAG_NAME_REGEX = /^[\w][\w.-]{0,127}$/.freeze
+
delegate :registry, :client, to: :repository
delegate :revision, :short_revision, to: :config_blob, allow_nil: true
@@ -13,6 +16,10 @@ module ContainerRegistry
@repository, @name = repository, name
end
+ def valid_name?
+ !name.match(TAG_NAME_REGEX).nil?
+ end
+
def valid?
manifest.present?
end
diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb
index c83cec9dc4a..45af30f46dc 100644
--- a/lib/expand_variables.rb
+++ b/lib/expand_variables.rb
@@ -3,6 +3,20 @@
module ExpandVariables
class << self
def expand(value, variables)
+ variables_hash = nil
+
+ value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
+ variables_hash ||= transform_variables(variables)
+ variables_hash[$1 || $2]
+ end
+ end
+
+ private
+
+ def transform_variables(variables)
+ # Lazily initialise variables
+ variables = variables.call if variables.is_a?(Proc)
+
# Convert hash array to variables
if variables.is_a?(Array)
variables = variables.reduce({}) do |hash, variable|
@@ -11,9 +25,7 @@ module ExpandVariables
end
end
- value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do
- variables[$1 || $2]
- end
+ variables
end
end
end
diff --git a/lib/feature.rb b/lib/feature.rb
index e28333aa58e..c70a6980f19 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -80,6 +80,13 @@ class Feature
get(key).disable_group(group)
end
+ def remove(key)
+ feature = get(key)
+ return unless persisted?(feature)
+
+ feature.remove
+ end
+
def flipper
if Gitlab::SafeRequestStore.active?
Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance
diff --git a/lib/feature/gitaly.rb b/lib/feature/gitaly.rb
index edfd2fb17f3..9ded1aed4e3 100644
--- a/lib/feature/gitaly.rb
+++ b/lib/feature/gitaly.rb
@@ -7,7 +7,7 @@ class Feature
# Server feature flags should use '_' to separate words.
SERVER_FEATURE_FLAGS =
[
- 'get_commit_signatures'.freeze
+ # 'get_commit_signatures'.freeze
].freeze
DEFAULT_ON_FLAGS = Set.new([]).freeze
diff --git a/lib/forever.rb b/lib/forever.rb
index 0a37118fe68..3f923557441 100644
--- a/lib/forever.rb
+++ b/lib/forever.rb
@@ -1,15 +1,9 @@
# frozen_string_literal: true
class Forever
- POSTGRESQL_DATE = DateTime.new(3000, 1, 1)
- MYSQL_DATE = DateTime.new(2038, 01, 19)
+ DATE = DateTime.new(3000, 1, 1)
- # MySQL timestamp has a range of '1970-01-01 00:00:01' UTC to '2038-01-19 03:14:07' UTC
def self.date
- if Gitlab::Database.postgresql?
- POSTGRESQL_DATE
- else
- MYSQL_DATE
- end
+ DATE
end
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index c62d1071dba..d9d8dcf7900 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require_dependency File.expand_path('gitlab/popen', __dir__)
+require 'pathname'
module Gitlab
def self.root
@@ -61,7 +61,7 @@ module Gitlab
def self.ee?
@is_ee ||=
- if ENV['IS_GITLAB_EE'].present?
+ if ENV['IS_GITLAB_EE'] && !ENV['IS_GITLAB_EE'].empty?
Gitlab::Utils.to_boolean(ENV['IS_GITLAB_EE'])
else
# We may use this method when the Rails environment is not loaded. This
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index 6eb08f674c2..7ef9f7ef630 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -29,6 +29,10 @@ module Gitlab
MAINTAINER_PROJECT_ACCESS = 1
DEVELOPER_MAINTAINER_PROJECT_ACCESS = 2
+ # Default subgroup creation level
+ OWNER_SUBGROUP_ACCESS = 0
+ MAINTAINER_SUBGROUP_ACCESS = 1
+
class << self
delegate :values, to: :options
@@ -106,6 +110,13 @@ module Gitlab
def project_creation_level_name(name)
project_creation_options.key(name)
end
+
+ def subgroup_creation_options
+ {
+ s_('SubgroupCreationlevel|Owners') => OWNER_SUBGROUP_ACCESS,
+ s_('SubgroupCreationlevel|Maintainers') => MAINTAINER_SUBGROUP_ACCESS
+ }
+ end
end
def human_access
diff --git a/lib/gitlab/action_rate_limiter.rb b/lib/gitlab/action_rate_limiter.rb
index c442211e073..0e8707af631 100644
--- a/lib/gitlab/action_rate_limiter.rb
+++ b/lib/gitlab/action_rate_limiter.rb
@@ -33,16 +33,48 @@ module Gitlab
# Increments the given key and returns true if the action should
# be throttled.
#
- # key - An array of ActiveRecord instances
- # threshold_value - The maximum number of times this action should occur in the given time interval
+ # key - An array of ActiveRecord instances or strings
+ # threshold_value - The maximum number of times this action should occur in the given time interval. If number is zero is considered disabled.
def throttled?(key, threshold_value)
- self.increment(key) > threshold_value
+ threshold_value > 0 &&
+ self.increment(key) > threshold_value
+ end
+
+ # Logs request into auth.log
+ #
+ # request - Web request to be logged
+ # type - A symbol key that represents the request.
+ # current_user - Current user of the request, it can be nil.
+ def log_request(request, type, current_user)
+ request_information = {
+ message: 'Action_Rate_Limiter_Request',
+ env: type,
+ remote_ip: request.ip,
+ request_method: request.request_method,
+ path: request.fullpath
+ }
+
+ if current_user
+ request_information.merge!({
+ user_id: current_user.id,
+ username: current_user.username
+ })
+ end
+
+ Gitlab::AuthLogger.error(request_information)
end
private
def action_key(key)
- serialized = key.map { |obj| "#{obj.class.model_name.to_s.underscore}:#{obj.id}" }.join(":")
+ serialized = key.map do |obj|
+ if obj.is_a?(String)
+ "#{obj}"
+ else
+ "#{obj.class.model_name.to_s.underscore}:#{obj.id}"
+ end
+ end.join(":")
+
"action_rate_limiter:#{action}:#{serialized}"
end
end
diff --git a/lib/gitlab/auth/activity.rb b/lib/gitlab/auth/activity.rb
index 558628b5422..988ff196193 100644
--- a/lib/gitlab/auth/activity.rb
+++ b/lib/gitlab/auth/activity.rb
@@ -37,14 +37,17 @@ module Gitlab
def user_authenticated!
self.class.user_authenticated_counter_increment!
+
+ case @opts[:message]
+ when :two_factor_authenticated
+ self.class.user_two_factor_authenticated_counter_increment!
+ end
end
def user_session_override!
self.class.user_session_override_counter_increment!
case @opts[:message]
- when :two_factor_authenticated
- self.class.user_two_factor_authenticated_counter_increment!
when :sessionless_sign_in
self.class.user_sessionless_authentication_counter_increment!
end
diff --git a/lib/gitlab/auth/o_auth/auth_hash.rb b/lib/gitlab/auth/o_auth/auth_hash.rb
index 72a187377d0..91b9ddc0d00 100644
--- a/lib/gitlab/auth/o_auth/auth_hash.rb
+++ b/lib/gitlab/auth/o_auth/auth_hash.rb
@@ -60,8 +60,7 @@ module Gitlab
def get_info(key)
value = info[key]
- Gitlab::Utils.force_utf8(value) if value
- value
+ value.is_a?(String) ? Gitlab::Utils.force_utf8(value) : value
end
def username_and_email
diff --git a/lib/gitlab/auth/user_auth_finders.rb b/lib/gitlab/auth/user_auth_finders.rb
index a5efe33bdc6..bba7e2cbb3c 100644
--- a/lib/gitlab/auth/user_auth_finders.rb
+++ b/lib/gitlab/auth/user_auth_finders.rb
@@ -90,8 +90,8 @@ module Gitlab
def find_personal_access_token
token =
current_request.params[PRIVATE_TOKEN_PARAM].presence ||
- current_request.env[PRIVATE_TOKEN_HEADER].presence
-
+ current_request.env[PRIVATE_TOKEN_HEADER].presence ||
+ parsed_oauth_token
return unless token
# Expiration, revocation and scopes are verified in `validate_access_token!`
@@ -99,9 +99,12 @@ module Gitlab
end
def find_oauth_access_token
- token = Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
+ token = parsed_oauth_token
return unless token
+ # PATs with OAuth headers are not handled by OauthAccessToken
+ return if matches_personal_access_token_length?(token)
+
# Expiration, revocation and scopes are verified in `validate_access_token!`
oauth_token = OauthAccessToken.by_token(token)
raise UnauthorizedError unless oauth_token
@@ -110,6 +113,14 @@ module Gitlab
oauth_token
end
+ def parsed_oauth_token
+ Doorkeeper::OAuth::Token.from_request(current_request, *Doorkeeper.configuration.access_token_methods)
+ end
+
+ def matches_personal_access_token_length?(token)
+ token.length == PersonalAccessToken::TOKEN_LENGTH
+ end
+
# Check if the request is GET/HEAD, or if CSRF token is valid.
def verified_request?
Gitlab::RequestForgeryProtection.verified?(current_request.env)
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index c8d83cc1803..1d9aa050041 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -40,7 +40,7 @@ module Gitlab
end
def reload!
- @shards = Hash[*Shard.all.map { |shard| [shard.name, shard.id] }.flatten]
+ @shards = Hash[*Shard.all.flat_map { |shard| [shard.name, shard.id] }]
end
end
diff --git a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
index 6046d33aeac..4016b807f21 100644
--- a/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
+++ b/lib/gitlab/background_migration/fill_valid_time_for_pages_domain_certificate.rb
@@ -19,18 +19,11 @@ module Gitlab
def perform(start_id, stop_id)
PagesDomain.where(id: start_id..stop_id).find_each do |domain|
- if Gitlab::Database.mysql?
- domain.update_columns(
- certificate_valid_not_before: domain.x509&.not_before,
- certificate_valid_not_after: domain.x509&.not_after
- )
- else
- # for some reason activerecord doesn't append timezone, iso8601 forces this
- domain.update_columns(
- certificate_valid_not_before: domain.x509&.not_before&.iso8601,
- certificate_valid_not_after: domain.x509&.not_after&.iso8601
- )
- end
+ # for some reason activerecord doesn't append timezone, iso8601 forces this
+ domain.update_columns(
+ certificate_valid_not_before: domain.x509&.not_before&.iso8601,
+ certificate_valid_not_after: domain.x509&.not_after&.iso8601
+ )
rescue => e
Rails.logger.error "Failed to update pages domain certificate valid time. id: #{domain.id}, message: #{e.message}" # rubocop:disable Gitlab/RailsLogger
end
diff --git a/lib/gitlab/background_migration/legacy_upload_mover.rb b/lib/gitlab/background_migration/legacy_upload_mover.rb
new file mode 100644
index 00000000000..051c1176edb
--- /dev/null
+++ b/lib/gitlab/background_migration/legacy_upload_mover.rb
@@ -0,0 +1,140 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This class takes a legacy upload and migrates it to the correct location
+ class LegacyUploadMover
+ include Gitlab::Utils::StrongMemoize
+
+ attr_reader :upload, :project, :note
+ attr_accessor :logger
+
+ def initialize(upload)
+ @upload = upload
+ @note = Note.find_by(id: upload.model_id)
+ @project = note&.project
+ @logger = Gitlab::BackgroundMigration::Logger.build
+ end
+
+ def execute
+ return unless upload
+
+ if !project
+ # if we don't have models associated with the upload we can not move it
+ warn('Deleting upload due to model not found.')
+
+ destroy_legacy_upload
+ elsif note.is_a?(LegacyDiffNote)
+ return unless move_legacy_diff_file
+
+ migrate_upload
+ elsif !legacy_file_exists?
+ warn('Deleting upload due to file not found.')
+ destroy_legacy_upload
+ else
+ migrate_upload
+ end
+ end
+
+ private
+
+ def migrate_upload
+ return unless copy_upload_to_project
+
+ add_upload_link_to_note_text
+ destroy_legacy_file
+ destroy_legacy_upload
+ end
+
+ # we should proceed and log whenever one upload copy fails, no matter the reasons
+ # rubocop: disable Lint/RescueException
+ def copy_upload_to_project
+ @uploader = FileUploader.copy_to(legacy_file_uploader, project)
+
+ logger.info(
+ message: 'MigrateLegacyUploads: File copied successfully',
+ old_path: legacy_file_uploader.file.path, new_path: @uploader.file.path
+ )
+ true
+ rescue Exception => e
+ warn(
+ 'File could not be copied to project uploads',
+ file_path: legacy_file_uploader.file.path, error: e.message
+ )
+ false
+ end
+ # rubocop: enable Lint/RescueException
+
+ def destroy_legacy_upload
+ if note
+ note.remove_attachment = true
+ note.save
+ end
+
+ if upload.destroy
+ logger.info(message: 'MigrateLegacyUploads: Upload was destroyed.', upload: upload.inspect)
+ else
+ warn('MigrateLegacyUploads: Upload destroy failed.')
+ end
+ end
+
+ def destroy_legacy_file
+ legacy_file_uploader.file.delete
+ end
+
+ def add_upload_link_to_note_text
+ new_text = "#{note.note} \n #{@uploader.markdown_link}"
+ # Bypass validations because old data may have invalid
+ # noteable values. If we fail hard here, we may kill the
+ # entire background migration, which affects a range of notes.
+ note.update_attribute(:note, new_text)
+ end
+
+ def legacy_file_uploader
+ strong_memoize(:legacy_file_uploader) do
+ uploader = upload.build_uploader
+ uploader.retrieve_from_store!(File.basename(upload.path))
+ uploader
+ end
+ end
+
+ def legacy_file_exists?
+ legacy_file_uploader.file.exists?
+ end
+
+ # we should proceed and log whenever one upload copy fails, no matter the reasons
+ # rubocop: disable Lint/RescueException
+ def move_legacy_diff_file
+ old_path = upload.absolute_path
+ old_path_sub = '-/system/note/attachment'
+
+ if !File.exist?(old_path) || !old_path.include?(old_path_sub)
+ log_legacy_diff_note_problem(old_path)
+ return false
+ end
+
+ new_path = upload.absolute_path.sub(old_path_sub, '-/system/legacy_diff_note/attachment')
+ new_dir = File.dirname(new_path)
+ FileUtils.mkdir_p(new_dir)
+
+ FileUtils.mv(old_path, new_path)
+ rescue Exception => e
+ log_legacy_diff_note_problem(old_path, new_path, e)
+ false
+ end
+
+ def warn(message, params = {})
+ logger.warn(
+ params.merge(message: "MigrateLegacyUploads: #{message}", upload: upload.inspect)
+ )
+ end
+
+ def log_legacy_diff_note_problem(old_path, new_path = nil, error = nil)
+ warn('LegacyDiffNote upload could not be moved to a new path',
+ old_path: old_path, new_path: new_path, error: error&.message
+ )
+ end
+ # rubocop: enable Lint/RescueException
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/legacy_uploads_migrator.rb b/lib/gitlab/background_migration/legacy_uploads_migrator.rb
new file mode 100644
index 00000000000..a9d38a27e0c
--- /dev/null
+++ b/lib/gitlab/background_migration/legacy_uploads_migrator.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # This migration takes all legacy uploads (that were uploaded using AttachmentUploader)
+ # and migrate them to the new (FileUploader) location (=under projects).
+ #
+ # We have dependencies (uploaders) in this migration because extracting code would add a lot of complexity
+ # and possible errors could appear as the logic in the uploaders is not trivial.
+ #
+ # This migration will be removed in 13.0 in order to get rid of a migration that depends on
+ # the application code.
+ class LegacyUploadsMigrator
+ include Database::MigrationHelpers
+
+ def perform(start_id, end_id)
+ Upload.where(id: start_id..end_id, uploader: 'AttachmentUploader').find_each do |upload|
+ LegacyUploadMover.new(upload).execute
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/logger.rb b/lib/gitlab/background_migration/logger.rb
new file mode 100644
index 00000000000..4ea89771eff
--- /dev/null
+++ b/lib/gitlab/background_migration/logger.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Logger that can be used for migrations logging
+ class Logger < ::Gitlab::JsonLogger
+ def self.file_name_noext
+ 'migrations'
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/migrate_legacy_artifacts.rb b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb
index 5cd638083b0..4377ec2987c 100644
--- a/lib/gitlab/background_migration/migrate_legacy_artifacts.rb
+++ b/lib/gitlab/background_migration/migrate_legacy_artifacts.rb
@@ -39,10 +39,10 @@ module Gitlab
SELECT
project_id,
id,
- artifacts_expire_at,
+ artifacts_expire_at #{add_missing_db_timezone},
#{LEGACY_PATH_FILE_LOCATION},
- created_at,
- created_at,
+ created_at #{add_missing_db_timezone},
+ created_at #{add_missing_db_timezone},
artifacts_file,
artifacts_size,
COALESCE(artifacts_file_store, #{FILE_LOCAL_STORE}),
@@ -81,10 +81,10 @@ module Gitlab
SELECT
project_id,
id,
- artifacts_expire_at,
+ artifacts_expire_at #{add_missing_db_timezone},
#{LEGACY_PATH_FILE_LOCATION},
- created_at,
- created_at,
+ created_at #{add_missing_db_timezone},
+ created_at #{add_missing_db_timezone},
artifacts_metadata,
NULL,
COALESCE(artifacts_metadata_store, #{FILE_LOCAL_STORE}),
@@ -121,6 +121,12 @@ module Gitlab
AND artifacts_file <> ''
SQL
end
+
+ def add_missing_db_timezone
+ return '' unless Gitlab::Database.postgresql?
+
+ 'at time zone \'UTC\''
+ end
end
end
end
diff --git a/lib/gitlab/background_migration/migrate_stage_index.rb b/lib/gitlab/background_migration/migrate_stage_index.rb
index f921233460d..55608529cee 100644
--- a/lib/gitlab/background_migration/migrate_stage_index.rb
+++ b/lib/gitlab/background_migration/migrate_stage_index.rb
@@ -13,34 +13,22 @@ module Gitlab
private
def migrate_stage_index_sql(start_id, stop_id)
- if Gitlab::Database.postgresql?
- <<~SQL
- WITH freqs AS (
- SELECT stage_id, stage_idx, COUNT(*) AS freq FROM ci_builds
- WHERE stage_id BETWEEN #{start_id} AND #{stop_id}
- AND stage_idx IS NOT NULL
- GROUP BY stage_id, stage_idx
- ), indexes AS (
- SELECT DISTINCT stage_id, first_value(stage_idx)
- OVER (PARTITION BY stage_id ORDER BY freq DESC) AS index
- FROM freqs
- )
+ <<~SQL
+ WITH freqs AS (
+ SELECT stage_id, stage_idx, COUNT(*) AS freq FROM ci_builds
+ WHERE stage_id BETWEEN #{start_id} AND #{stop_id}
+ AND stage_idx IS NOT NULL
+ GROUP BY stage_id, stage_idx
+ ), indexes AS (
+ SELECT DISTINCT stage_id, first_value(stage_idx)
+ OVER (PARTITION BY stage_id ORDER BY freq DESC) AS index
+ FROM freqs
+ )
- UPDATE ci_stages SET position = indexes.index
- FROM indexes WHERE indexes.stage_id = ci_stages.id
- AND ci_stages.position IS NULL;
- SQL
- else
- <<~SQL
- UPDATE ci_stages
- SET position =
- (SELECT stage_idx FROM ci_builds
- WHERE ci_builds.stage_id = ci_stages.id
- GROUP BY ci_builds.stage_idx ORDER BY COUNT(*) DESC LIMIT 1)
- WHERE ci_stages.id BETWEEN #{start_id} AND #{stop_id}
- AND ci_stages.position IS NULL
- SQL
- end
+ UPDATE ci_stages SET position = indexes.index
+ FROM indexes WHERE indexes.stage_id = ci_stages.id
+ AND ci_stages.position IS NULL;
+ SQL
end
end
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb b/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
index 1924f2ffee2..f5fb33f1660 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads_dependencies.rb
@@ -176,23 +176,12 @@ module Gitlab
self.table_name = 'projects'
def self.find_by_full_path(path)
- binary = Gitlab::Database.mysql? ? 'BINARY' : ''
- order_sql = "(CASE WHEN #{binary} routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
+ order_sql = "(CASE WHEN routes.path = #{connection.quote(path)} THEN 0 ELSE 1 END)"
where_full_path_in(path).reorder(order_sql).take
end
def self.where_full_path_in(path)
- cast_lower = Gitlab::Database.postgresql?
-
- path = connection.quote(path)
-
- where =
- if cast_lower
- "(LOWER(routes.path) = LOWER(#{path}))"
- else
- "(routes.path = #{path})"
- end
-
+ where = "(LOWER(routes.path) = LOWER(#{connection.quote(path)}))"
joins("INNER JOIN routes ON routes.source_id = projects.id AND routes.source_type = 'Project'").where(where)
end
end
diff --git a/lib/gitlab/background_migration/prepare_untracked_uploads.rb b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
index cce2a82c098..2ac51dd7b55 100644
--- a/lib/gitlab/background_migration/prepare_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/prepare_untracked_uploads.rb
@@ -55,7 +55,7 @@ module Gitlab
def ensure_temporary_tracking_table_exists
table_name = :untracked_files_for_uploads
- unless ActiveRecord::Base.connection.data_source_exists?(table_name)
+ unless ActiveRecord::Base.connection.table_exists?(table_name)
UntrackedFile.connection.create_table table_name do |t|
t.string :path, limit: 600, null: false
t.index :path, unique: true
@@ -133,12 +133,9 @@ module Gitlab
def insert_sql(file_paths)
if postgresql_pre_9_5?
"INSERT INTO #{table_columns_and_values_for_insert(file_paths)};"
- elsif postgresql?
+ else
"INSERT INTO #{table_columns_and_values_for_insert(file_paths)}"\
" ON CONFLICT DO NOTHING;"
- else # MySQL
- "INSERT IGNORE INTO"\
- " #{table_columns_and_values_for_insert(file_paths)};"
end
end
@@ -150,19 +147,13 @@ module Gitlab
"#{UntrackedFile.table_name} (path) VALUES #{values}"
end
- def postgresql?
- strong_memoize(:postgresql) do
- Gitlab::Database.postgresql?
- end
- end
-
def can_bulk_insert_and_ignore_duplicates?
!postgresql_pre_9_5?
end
def postgresql_pre_9_5?
strong_memoize(:postgresql_pre_9_5) do
- postgresql? && Gitlab::Database.version.to_f < 9.5
+ Gitlab::Database.version.to_f < 9.5
end
end
diff --git a/lib/gitlab/background_migration/remove_restricted_todos.rb b/lib/gitlab/background_migration/remove_restricted_todos.rb
index 47579d46c1b..9ef6d8654ae 100644
--- a/lib/gitlab/background_migration/remove_restricted_todos.rb
+++ b/lib/gitlab/background_migration/remove_restricted_todos.rb
@@ -50,14 +50,7 @@ module Gitlab
private
def remove_non_members_todos(project_id)
- if Gitlab::Database.postgresql?
- batch_remove_todos_cte(project_id)
- else
- unauthorized_project_todos(project_id)
- .each_batch(of: 5000) do |batch|
- batch.delete_all
- end
- end
+ batch_remove_todos_cte(project_id)
end
def remove_confidential_issue_todos(project_id)
@@ -90,13 +83,7 @@ module Gitlab
next if target_types.empty?
- if Gitlab::Database.postgresql?
- batch_remove_todos_cte(project_id, target_types)
- else
- unauthorized_project_todos(project_id)
- .where(target_type: target_types)
- .delete_all
- end
+ batch_remove_todos_cte(project_id, target_types)
end
end
diff --git a/lib/gitlab/badge/coverage/report.rb b/lib/gitlab/badge/coverage/report.rb
index 7f7cc62c8ef..15cccc6f287 100644
--- a/lib/gitlab/badge/coverage/report.rb
+++ b/lib/gitlab/badge/coverage/report.rb
@@ -14,7 +14,7 @@ module Gitlab
@ref = ref
@job = job
- @pipeline = @project.ci_pipelines.latest_successful_for(@ref)
+ @pipeline = @project.ci_pipelines.latest_successful_for_ref(@ref)
end
def entity
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 8047ef4fa15..24bc73e0de5 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -262,13 +262,19 @@ module Gitlab
def pull_request_comment_attributes(comment)
{
project: project,
- note: comment.note,
author_id: gitlab_user_id(project, comment.author),
+ note: comment_note(comment),
created_at: comment.created_at,
updated_at: comment.updated_at
}
end
+ def comment_note(comment)
+ author = @formatter.author_line(comment.author) unless find_user_id(comment.author)
+
+ author.to_s + comment.note.to_s
+ end
+
def log_error(details)
logger.error(log_base_data.merge(details))
end
diff --git a/lib/gitlab/blob_helper.rb b/lib/gitlab/blob_helper.rb
index d3e15a79a8b..fc579ad8d2a 100644
--- a/lib/gitlab/blob_helper.rb
+++ b/lib/gitlab/blob_helper.rb
@@ -45,7 +45,7 @@ module Gitlab
end
def image?
- ['.png', '.jpg', '.jpeg', '.gif'].include?(extname.downcase)
+ ['.png', '.jpg', '.jpeg', '.gif', '.svg'].include?(extname.downcase)
end
# Internal: Lookup mime type for extension.
diff --git a/lib/gitlab/chaos.rb b/lib/gitlab/chaos.rb
new file mode 100644
index 00000000000..4f47cdef971
--- /dev/null
+++ b/lib/gitlab/chaos.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Gitlab
+ # Chaos methods for GitLab.
+ # See https://docs.gitlab.com/ee/development/chaos_endpoints.html for more details.
+ class Chaos
+ # leak_mem will retain the specified amount of memory and sleep.
+ # On return, the memory will be released.
+ def self.leak_mem(memory_mb, duration_s)
+ start_time = Time.now
+
+ retainer = []
+ # Add `n` 1mb chunks of memory to the retainer array
+ memory_mb.times { retainer << "x" * 1.megabyte }
+
+ duration_left = [start_time + duration_s - Time.now, 0].max
+ Kernel.sleep(duration_left)
+ end
+
+ # cpu_spin will consume all CPU on a single core for the specified duration
+ def self.cpu_spin(duration_s)
+ expected_end_time = Time.now + duration_s
+
+ rand while Time.now < expected_end_time
+ end
+
+ # db_spin will query the database in a tight loop for the specified duration
+ def self.db_spin(duration_s, interval_s)
+ expected_end_time = Time.now + duration_s
+
+ while Time.now < expected_end_time
+ ActiveRecord::Base.connection.execute("SELECT 1")
+
+ end_interval_time = Time.now + [duration_s, interval_s].min
+ rand while Time.now < end_interval_time
+ end
+ end
+
+ # sleep will sleep for the specified duration
+ def self.sleep(duration_s)
+ Kernel.sleep(duration_s)
+ end
+
+ # Kill will send a SIGKILL signal to the current process
+ def self.kill
+ Process.kill("KILL", Process.pid)
+ end
+ end
+end
diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb
index fc3223e7442..b7886114e9c 100644
--- a/lib/gitlab/ci/ansi2html.rb
+++ b/lib/gitlab/ci/ansi2html.rb
@@ -194,16 +194,10 @@ module Gitlab
end
def handle_new_line
- css_classes = []
-
- if @sections.any?
- css_classes = %w[section line] + sections.map { |section| "s_#{section}" }
- end
-
write_in_tag %{<br/>}
- write_raw %{<span class="#{css_classes.join(' ')}"></span>} if css_classes.any?
+
+ close_open_tags if @sections.any? && @lineno_in_section == 0
@lineno_in_section += 1
- open_new_tag
end
def handle_section(scanner)
@@ -224,7 +218,7 @@ module Gitlab
return if @sections.include?(section)
@sections << section
- write_raw %{<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>}
+ write_raw %{<div class="js-section-start section-start fa fa-caret-down pr-2 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>}
@lineno_in_section = 0
end
@@ -310,11 +304,24 @@ module Gitlab
if @sections.any?
css_classes << "section"
- css_classes << "js-section-header section-header" if @lineno_in_section == 0
+
+ css_classes << if @lineno_in_section == 0
+ "js-section-header section-header cursor-pointer"
+ else
+ "line"
+ end
+
css_classes += sections.map { |section| "js-s-#{section}" }
end
- @out << %{<span class="#{css_classes.join(' ')}">}
+ close_open_tags
+
+ @out << if css_classes.any?
+ %{<span class="#{css_classes.join(' ')}">}
+ else
+ %{<span>}
+ end
+
@n_open_tags += 1
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/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index e6e0aaab60b..6ab4fca3854 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -8,31 +8,51 @@ module Gitlab
def unmet?
deployment_cluster.present? &&
deployment_cluster.managed? &&
- (kubernetes_namespace.new_record? || kubernetes_namespace.service_account_token.blank?)
+ missing_namespace?
end
def complete!
return unless unmet?
- create_or_update_namespace
+ create_namespace
end
private
+ def missing_namespace?
+ kubernetes_namespace.nil? || kubernetes_namespace.service_account_token.blank?
+ end
+
def deployment_cluster
build.deployment&.cluster
end
+ def environment
+ build.deployment.environment
+ end
+
def kubernetes_namespace
strong_memoize(:kubernetes_namespace) do
- deployment_cluster.find_or_initialize_kubernetes_namespace_for_project(build.project)
+ Clusters::KubernetesNamespaceFinder.new(
+ deployment_cluster,
+ project: environment.project,
+ environment_slug: environment.slug,
+ allow_blank_token: true
+ ).execute
end
end
- def create_or_update_namespace
+ def create_namespace
Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
cluster: deployment_cluster,
- kubernetes_namespace: kubernetes_namespace
+ kubernetes_namespace: kubernetes_namespace || build_namespace_record
+ ).execute
+ end
+
+ def build_namespace_record
+ Clusters::BuildKubernetesNamespaceService.new(
+ deployment_cluster,
+ environment: environment
).execute
end
end
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/charts.rb b/lib/gitlab/ci/charts.rb
index 7cabaadb122..3fbfdffe277 100644
--- a/lib/gitlab/ci/charts.rb
+++ b/lib/gitlab/ci/charts.rb
@@ -21,16 +21,10 @@ module Gitlab
module MonthlyInterval
# rubocop: disable CodeReuse/ActiveRecord
def grouped_count(query)
- if Gitlab::Database.postgresql?
- query
- .group("to_char(#{::Ci::Pipeline.table_name}.created_at, '01 Month YYYY')")
- .count(:created_at)
- .transform_keys(&:squish)
- else
- query
- .group("DATE_FORMAT(#{::Ci::Pipeline.table_name}.created_at, '01 %M %Y')")
- .count(:created_at)
- end
+ query
+ .group("to_char(#{::Ci::Pipeline.table_name}.created_at, '01 Month YYYY')")
+ .count(:created_at)
+ .transform_keys(&:squish)
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 5ab795359b8..6e11c582750 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -11,17 +11,28 @@ 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 before_script after_script variables
+ dependencies needs before_script after_script variables
environment coverage retry parallel extends].freeze
+ 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
@@ -29,16 +40,29 @@ 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?
+ next unless needs.present?
+
+ missing_needs = dependencies - needs
+ if missing_needs.any?
+ errors.add(:dependencies, "the #{missing_needs.join(", ")} should be part of needs")
+ end
+ end
end
entry :before_script, Entry::Script,
@@ -77,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.'
@@ -95,10 +122,10 @@ module Gitlab
helpers :before_script, :script, :stage, :type, :after_script,
:cache, :image, :services, :only, :except, :variables,
:artifacts, :environment, :coverage, :retry,
- :parallel
+ :parallel, :needs
attributes :script, :tags, :allow_failure, :when, :dependencies,
- :retry, :parallel, :extends, :start_in
+ :needs, :retry, :parallel, :extends, :start_in, :rules
def self.matching?(name, config)
!name.to_s.start_with?('.') &&
@@ -137,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
@@ -178,7 +209,8 @@ module Gitlab
parallel: parallel_defined? ? parallel_value.to_i : nil,
artifacts: artifacts_value,
after_script: after_script_value,
- ignore: ignored? }
+ ignore: ignored?,
+ needs: needs_defined? ? needs_value : nil }
end
end
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/config/normalizer.rb b/lib/gitlab/ci/config/normalizer.rb
index 191f5d09645..09f9bf5f69f 100644
--- a/lib/gitlab/ci/config/normalizer.rb
+++ b/lib/gitlab/ci/config/normalizer.rb
@@ -4,61 +4,63 @@ module Gitlab
module Ci
class Config
class Normalizer
+ include Gitlab::Utils::StrongMemoize
+
def initialize(jobs_config)
@jobs_config = jobs_config
end
def normalize_jobs
- extract_parallelized_jobs!
- return @jobs_config if @parallelized_jobs.empty?
+ return @jobs_config if parallelized_jobs.empty?
+
+ expand_parallelize_jobs do |job_name, config|
+ if config[:dependencies]
+ config[:dependencies] = expand_names(config[:dependencies])
+ end
- parallelized_config = parallelize_jobs
- parallelize_dependencies(parallelized_config)
+ if config[:needs]
+ config[:needs] = expand_names(config[:needs])
+ end
+
+ config
+ end
end
private
- def extract_parallelized_jobs!
- @parallelized_jobs = {}
+ def expand_names(job_names)
+ return unless job_names
- @jobs_config.each do |job_name, config|
- if config[:parallel]
- @parallelized_jobs[job_name] = self.class.parallelize_job_names(job_name, config[:parallel])
- end
+ job_names.flat_map do |job_name|
+ parallelized_jobs[job_name.to_sym] || job_name
end
-
- @parallelized_jobs
end
- def parallelize_jobs
- @jobs_config.each_with_object({}) do |(job_name, config), hash|
- if @parallelized_jobs.key?(job_name)
- @parallelized_jobs[job_name].each { |name, index| hash[name.to_sym] = config.merge(name: name, instance: index) }
- else
- hash[job_name] = config
- end
+ def parallelized_jobs
+ strong_memoize(:parallelized_jobs) do
+ @jobs_config.each_with_object({}) do |(job_name, config), hash|
+ next unless config[:parallel]
- hash
+ hash[job_name] = self.class.parallelize_job_names(job_name, config[:parallel])
+ end
end
end
- def parallelize_dependencies(parallelized_config)
- parallelized_job_names = @parallelized_jobs.keys.map(&:to_s)
- parallelized_config.each_with_object({}) do |(job_name, config), hash|
- if config[:dependencies] && (intersection = config[:dependencies] & parallelized_job_names).any?
- parallelized_deps = intersection.map { |dep| @parallelized_jobs[dep.to_sym].map(&:first) }.flatten
- deps = config[:dependencies] - intersection + parallelized_deps
- hash[job_name] = config.merge(dependencies: deps)
+ def expand_parallelize_jobs
+ @jobs_config.each_with_object({}) do |(job_name, config), hash|
+ if parallelized_jobs.key?(job_name)
+ parallelized_jobs[job_name].each_with_index do |name, index|
+ hash[name.to_sym] =
+ yield(name, config.merge(name: name, instance: index + 1))
+ end
else
- hash[job_name] = config
+ hash[job_name] = yield(job_name, config)
end
-
- hash
end
end
def self.parallelize_job_names(name, total)
- Array.new(total) { |index| ["#{name} #{index + 1}/#{total}", index + 1] }
+ Array.new(total) { |index| "#{name} #{index + 1}/#{total}" }
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 0405292a25b..65029f5ce7f 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -23,12 +23,17 @@ module Gitlab
@command.seeds_block&.call(pipeline)
##
- # Populate pipeline with all stages, and stages with builds.
+ # Gather all runtime build/stage errors
#
- pipeline.stage_seeds.each do |stage|
- pipeline.stages << stage.to_resource
+ if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
+ return error(seeds_errors.join("\n"))
end
+ ##
+ # Populate pipeline with all stages, and stages with builds.
+ #
+ pipeline.stages = pipeline.stage_seeds.map(&:to_resource)
+
if pipeline.stages.none?
return error('No stages / jobs for this pipeline.')
end
diff --git a/lib/gitlab/ci/pipeline/seed/base.rb b/lib/gitlab/ci/pipeline/seed/base.rb
index 1fd3a61017f..e9e22569ae0 100644
--- a/lib/gitlab/ci/pipeline/seed/base.rb
+++ b/lib/gitlab/ci/pipeline/seed/base.rb
@@ -13,6 +13,10 @@ module Gitlab
raise NotImplementedError
end
+ def errors
+ raise NotImplementedError
+ end
+
def to_resource
raise NotImplementedError
end
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index d8296940a04..1066331062b 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -7,39 +7,65 @@ module Gitlab
class Build < Seed::Base
include Gitlab::Utils::StrongMemoize
- delegate :dig, to: :@attributes
+ delegate :dig, to: :@seed_attributes
- def initialize(pipeline, attributes)
+ # When the `ci_dag_limit_needs` is enabled it uses the lower limit
+ LOW_NEEDS_LIMIT = 5
+ HARD_NEEDS_LIMIT = 50
+
+ 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
+ dig(:name)
end
def included?
strong_memoize(:inclusion) do
- @only.all? { |spec| spec.satisfied_by?(@pipeline, self) } &&
- @except.none? { |spec| spec.satisfied_by?(@pipeline, self) }
+ if @using_rules
+ included_by_rules?
+ elsif @using_only || @using_except
+ all_of_only? && none_of_except?
+ else
+ true
+ end
+ end
+ end
+
+ def errors
+ return unless included?
+
+ strong_memoize(:errors) do
+ needs_errors
end
end
def attributes
- @attributes.merge(
- pipeline: @pipeline,
- project: @pipeline.project,
- user: @pipeline.user,
- ref: @pipeline.ref,
- tag: @pipeline.tag,
- trigger_request: @pipeline.legacy_trigger,
- protected: @pipeline.protected_ref?
- )
+ @seed_attributes
+ .deep_merge(pipeline_attributes)
+ .deep_merge(rules_attributes)
end
def bridge?
- @attributes.to_h.dig(:options, :trigger).present?
+ attributes_hash = @seed_attributes.to_h
+ attributes_hash.dig(:options, :trigger).present? ||
+ (attributes_hash.dig(:options, :bridge_needs).instance_of?(Hash) &&
+ attributes_hash.dig(:options, :bridge_needs, :pipeline).present?)
end
def to_resource
@@ -51,6 +77,77 @@ module Gitlab
end
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?
+ @only.all? { |spec| spec.satisfied_by?(@pipeline, self) }
+ end
+
+ def none_of_except?
+ @except.none? { |spec| spec.satisfied_by?(@pipeline, self) }
+ end
+
+ def needs_errors
+ return if @needs_attributes.nil?
+
+ if @needs_attributes.size > max_needs_allowed
+ return [
+ "#{name}: one job can only need #{max_needs_allowed} others, but you have listed #{@needs_attributes.size}. " \
+ "See needs keyword documentation for more details"
+ ]
+ end
+
+ @needs_attributes.flat_map do |need|
+ result = @previous_stages.any? do |stage|
+ stage.seeds_names.include?(need[:name])
+ end
+
+ "#{name}: needs '#{need[:name]}'" unless result
+ end.compact
+ end
+
+ def max_needs_allowed
+ if Feature.enabled?(:ci_dag_limit_needs, @project, default_enabled: true)
+ LOW_NEEDS_LIMIT
+ else
+ 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/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb
index 9c15064756a..b600df2f656 100644
--- a/lib/gitlab/ci/pipeline/seed/stage.rb
+++ b/lib/gitlab/ci/pipeline/seed/stage.rb
@@ -10,12 +10,13 @@ module Gitlab
delegate :size, to: :seeds
delegate :dig, to: :seeds
- def initialize(pipeline, attributes)
+ def initialize(pipeline, attributes, previous_stages)
@pipeline = pipeline
@attributes = attributes
+ @previous_stages = previous_stages
@builds = attributes.fetch(:builds).map do |attributes|
- Seed::Build.new(@pipeline, attributes)
+ Seed::Build.new(@pipeline, attributes, previous_stages)
end
end
@@ -32,6 +33,18 @@ module Gitlab
end
end
+ def errors
+ strong_memoize(:errors) do
+ seeds.flat_map(&:errors).compact
+ end
+ end
+
+ def seeds_names
+ strong_memoize(:seeds_names) do
+ seeds.map(&:name).to_set
+ end
+ end
+
def included?
seeds.any?
end
@@ -39,13 +52,7 @@ module Gitlab
def to_resource
strong_memoize(:stage) do
::Ci::Stage.new(attributes).tap do |stage|
- seeds.each do |seed|
- if seed.bridge?
- stage.bridges << seed.to_resource
- else
- stage.builds << seed.to_resource
- end
- end
+ stage.statuses = seeds.map(&:to_resource)
end
end
end
diff --git a/lib/gitlab/ci/status/build/manual.rb b/lib/gitlab/ci/status/build/manual.rb
index d01b09f1398..df572188194 100644
--- a/lib/gitlab/ci/status/build/manual.rb
+++ b/lib/gitlab/ci/status/build/manual.rb
@@ -10,7 +10,7 @@ module Gitlab
image: 'illustrations/manual_action.svg',
size: 'svg-394',
title: _('This job requires a manual action'),
- content: _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
+ content: _('This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.')
}
end
diff --git a/lib/gitlab/ci/status/factory.rb b/lib/gitlab/ci/status/factory.rb
index 3446644eff8..2a0bf060c9b 100644
--- a/lib/gitlab/ci/status/factory.rb
+++ b/lib/gitlab/ci/status/factory.rb
@@ -34,11 +34,9 @@ module Gitlab
def extended_statuses
return @extended_statuses if defined?(@extended_statuses)
- groups = self.class.extended_statuses.map do |group|
+ @extended_statuses = self.class.extended_statuses.flat_map do |group|
Array(group).find { |status| status.matches?(@subject, @user) }
- end
-
- @extended_statuses = groups.flatten.compact
+ end.compact
end
def self.extended_statuses
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index cf3d261c1cb..5c1c0c142e5 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -50,13 +50,12 @@ variables:
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
POSTGRES_VERSION: 9.6.2
- KUBERNETES_VERSION: 1.11.10
- HELM_VERSION: 2.14.0
-
DOCKER_DRIVER: overlay2
ROLLOUT_RESOURCE_TYPE: deployment
+ DOCKER_TLS_CERTDIR: "" # https://gitlab.com/gitlab-org/gitlab-runner/issues/4501
+
stages:
- build
- test
diff --git a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
index a09217e8cf0..b0a79950667 100644
--- a/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Browser-Performance-Testing.gitlab-ci.yml
@@ -2,6 +2,8 @@ performance:
stage: performance
image: docker:stable
allow_failure: true
+ variables:
+ DOCKER_TLS_CERTDIR: ""
services:
- docker:stable-dind
script:
diff --git a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
index 18f7290e1d9..8061da968ed 100644
--- a/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
@@ -1,6 +1,8 @@
build:
stage: build
image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image/master:stable"
+ variables:
+ DOCKER_TLS_CERTDIR: ""
services:
- docker:stable-dind
script:
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index 005ea4b7a46..3adc6a72874 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -6,6 +6,7 @@ code_quality:
- docker:stable-dind
variables:
DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
script:
- |
if ! docker info &>/dev/null; then
diff --git a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
index 108f0119ae1..a8ec2d4781d 100644
--- a/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Deploy.gitlab-ci.yml
@@ -1,14 +1,17 @@
+.auto-deploy:
+ image: "registry.gitlab.com/gitlab-org/cluster-integration/auto-deploy-image:v0.1.0"
+
review:
+ extends: .auto-deploy
stage: review
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
+ - auto-deploy persist_environment_url
environment:
name: review/$CI_COMMIT_REF_NAME
url: http://$CI_PROJECT_ID-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -27,13 +30,13 @@ review:
- $REVIEW_DISABLED
stop_review:
+ extends: .auto-deploy
stage: cleanup
variables:
GIT_STRATEGY: none
script:
- - install_dependencies
- - initialize_tiller
- - delete
+ - auto-deploy initialize_tiller
+ - auto-deploy delete
environment:
name: review/$CI_COMMIT_REF_NAME
action: stop
@@ -57,15 +60,15 @@ stop_review:
# STAGING_ENABLED.
staging:
+ extends: .auto-deploy
stage: staging
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
environment:
name: staging
url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN
@@ -81,15 +84,15 @@ staging:
# CANARY_ENABLED.
canary:
+ extends: .auto-deploy
stage: canary
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy canary
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy canary
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -102,18 +105,18 @@ canary:
- $CANARY_ENABLED
.production: &production_template
+ extends: .auto-deploy
stage: production
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy
- - delete canary
- - delete rollout
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy
+ - auto-deploy delete canary
+ - auto-deploy delete rollout
+ - auto-deploy persist_environment_url
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -152,17 +155,17 @@ production_manual:
# This job implements incremental rollout on for every push to `master`.
.rollout: &rollout_template
+ extends: .auto-deploy
script:
- - check_kube_domain
- - install_dependencies
- - download_chart
- - ensure_namespace
- - initialize_tiller
- - create_secret
- - deploy rollout $ROLLOUT_PERCENTAGE
- - scale stable $((100-ROLLOUT_PERCENTAGE))
- - delete canary
- - persist_environment_url
+ - auto-deploy check_kube_domain
+ - auto-deploy download_chart
+ - auto-deploy ensure_namespace
+ - auto-deploy initialize_tiller
+ - auto-deploy create_secret
+ - auto-deploy deploy rollout $ROLLOUT_PERCENTAGE
+ - auto-deploy scale stable $((100-ROLLOUT_PERCENTAGE))
+ - auto-deploy delete canary
+ - auto-deploy persist_environment_url
environment:
name: production
url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
@@ -240,330 +243,3 @@ rollout 100%:
<<: *manual_rollout_template
<<: *production_template
allow_failure: false
-
-.deploy_helpers: &deploy_helpers |
- [[ "$TRACE" ]] && set -x
- auto_database_url=postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${CI_ENVIRONMENT_SLUG}-postgres:5432/${POSTGRES_DB}
- export DATABASE_URL=${DATABASE_URL-$auto_database_url}
- export TILLER_NAMESPACE=$KUBE_NAMESPACE
-
- function get_replicas() {
- track="${1:-stable}"
- percentage="${2:-100}"
-
- env_track=$( echo $track | tr -s '[:lower:]' '[:upper:]' )
- env_slug=$( echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]' )
-
- if [[ "$track" == "stable" ]] || [[ "$track" == "rollout" ]]; then
- # for stable track get number of replicas from `PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- new_replicas=$REPLICAS
- fi
- else
- # for all tracks get number of replicas from `CANARY_PRODUCTION_REPLICAS`
- eval new_replicas=\$${env_track}_${env_slug}_REPLICAS
- if [[ -z "$new_replicas" ]]; then
- eval new_replicas=\${env_track}_REPLICAS
- fi
- fi
-
- replicas="${new_replicas:-1}"
- replicas="$(($replicas * $percentage / 100))"
-
- # always return at least one replicas
- if [[ $replicas -gt 0 ]]; then
- echo "$replicas"
- else
- echo 1
- fi
- }
-
- # Extracts variables prefixed with K8S_SECRET_
- # and creates a Kubernetes secret.
- #
- # e.g. If we have the following environment variables:
- # K8S_SECRET_A=value1
- # K8S_SECRET_B=multi\ word\ value
- #
- # Then we will create a secret with the following key-value pairs:
- # data:
- # A: dmFsdWUxCg==
- # B: bXVsdGkgd29yZCB2YWx1ZQo=
- function create_application_secret() {
- track="${1-stable}"
- export APPLICATION_SECRET_NAME=$(application_secret_name "$track")
-
- env | sed -n "s/^K8S_SECRET_\(.*\)$/\1/p" > k8s_prefixed_variables
-
- kubectl create secret \
- -n "$KUBE_NAMESPACE" generic "$APPLICATION_SECRET_NAME" \
- --from-env-file k8s_prefixed_variables -o yaml --dry-run |
- kubectl replace -n "$KUBE_NAMESPACE" --force -f -
-
- export APPLICATION_SECRET_CHECKSUM=$(cat k8s_prefixed_variables | sha256sum | cut -d ' ' -f 1)
-
- rm k8s_prefixed_variables
- }
-
- function deploy_name() {
- name="$CI_ENVIRONMENT_SLUG"
- track="${1-stable}"
-
- if [[ "$track" != "stable" ]]; then
- name="$name-$track"
- fi
-
- echo $name
- }
-
- function application_secret_name() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- echo "${name}-secret"
- }
-
- function deploy() {
- track="${1-stable}"
- percentage="${2:-100}"
- name=$(deploy_name "$track")
-
- if [[ -z "$CI_COMMIT_TAG" ]]; then
- image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
- image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
- else
- image_repository=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
- image_tag=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
- fi
-
- service_enabled="true"
- postgres_enabled="$POSTGRES_ENABLED"
-
- # if track is different than stable,
- # re-use all attached resources
- if [[ "$track" != "stable" ]]; then
- service_enabled="false"
- postgres_enabled="false"
- fi
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
- secret_name='gitlab-registry'
- else
- secret_name=''
- fi
-
- create_application_secret "$track"
-
- env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
- eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
- if [ -n "$env_ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$env_ADDITIONAL_HOSTS}"
- elif [ -n "$ADDITIONAL_HOSTS" ]; then
- additional_hosts="{$ADDITIONAL_HOSTS}"
- fi
-
- if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
- echo "Deploying first release with database initialization..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="$image_repository" \
- --set image.tag="$image_tag" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set postgresql.imageTag="$POSTGRES_VERSION" \
- --set application.initializeCommand="$DB_INITIALIZE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
-
- echo "Deploying second release..."
- helm upgrade --reuse-values \
- --wait \
- --set application.initializeCommand="" \
- --set application.migrateCommand="$DB_MIGRATE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- else
- echo "Deploying new release..."
- helm upgrade --install \
- --wait \
- --set service.enabled="$service_enabled" \
- --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
- --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
- --set releaseOverride="$CI_ENVIRONMENT_SLUG" \
- --set image.repository="$image_repository" \
- --set image.tag="$image_tag" \
- --set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
- --set application.track="$track" \
- --set application.database_url="$DATABASE_URL" \
- --set application.secretName="$APPLICATION_SECRET_NAME" \
- --set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le-$CI_PROJECT_ID.$KUBE_INGRESS_BASE_DOMAIN" \
- --set service.url="$CI_ENVIRONMENT_URL" \
- --set service.additionalHosts="$additional_hosts" \
- --set replicaCount="$replicas" \
- --set postgresql.enabled="$postgres_enabled" \
- --set postgresql.nameOverride="postgres" \
- --set postgresql.postgresUser="$POSTGRES_USER" \
- --set postgresql.postgresPassword="$POSTGRES_PASSWORD" \
- --set postgresql.postgresDatabase="$POSTGRES_DB" \
- --set postgresql.imageTag="$POSTGRES_VERSION" \
- --set application.migrateCommand="$DB_MIGRATE" \
- $HELM_UPGRADE_EXTRA_ARGS \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
-
- if [[ -z "$ROLLOUT_STATUS_DISABLED" ]]; then
- kubectl rollout status -n "$KUBE_NAMESPACE" -w "$ROLLOUT_RESOURCE_TYPE/$name"
- fi
- }
-
- function scale() {
- track="${1-stable}"
- percentage="${2-100}"
- name=$(deploy_name "$track")
-
- replicas=$(get_replicas "$track" "$percentage")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm upgrade --reuse-values \
- --wait \
- --set replicaCount="$replicas" \
- --namespace="$KUBE_NAMESPACE" \
- "$name" \
- chart/
- fi
- }
-
- function install_dependencies() {
- apk add -U openssl curl tar gzip bash ca-certificates git
- curl -sSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- curl -sSL -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
- apk add glibc-2.28-r0.apk
- rm glibc-2.28-r0.apk
-
- curl -sS "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
- mv linux-amd64/helm /usr/bin/
- mv linux-amd64/tiller /usr/bin/
- helm version --client
- tiller -version
-
- curl -sSL -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
- chmod +x /usr/bin/kubectl
- kubectl version --client
- }
-
- function download_chart() {
- if [[ ! -d chart ]]; then
- auto_chart=${AUTO_DEVOPS_CHART:-gitlab/auto-deploy-app}
- auto_chart_name=$(basename $auto_chart)
- auto_chart_name=${auto_chart_name%.tgz}
- auto_chart_name=${auto_chart_name%.tar.gz}
- else
- auto_chart="chart"
- auto_chart_name="chart"
- fi
-
- helm init --client-only
- helm repo add ${AUTO_DEVOPS_CHART_REPOSITORY_NAME:-gitlab} ${AUTO_DEVOPS_CHART_REPOSITORY:-https://charts.gitlab.io} ${AUTO_DEVOPS_CHART_REPOSITORY_USERNAME:+"--username" "$AUTO_DEVOPS_CHART_REPOSITORY_USERNAME"} ${AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD:+"--password" "$AUTO_DEVOPS_CHART_REPOSITORY_PASSWORD"}
- if [[ ! -d "$auto_chart" ]]; then
- helm fetch ${auto_chart} --untar
- fi
- if [ "$auto_chart_name" != "chart" ]; then
- mv ${auto_chart_name} chart
- fi
-
- helm dependency update chart/
- helm dependency build chart/
- }
-
- function ensure_namespace() {
- kubectl get namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
- }
-
- function check_kube_domain() {
- if [[ -z "$KUBE_INGRESS_BASE_DOMAIN" ]]; then
- echo "In order to deploy or use Review Apps,"
- echo "KUBE_INGRESS_BASE_DOMAIN variables must be set"
- echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
- echo "or by defining a variable at group or project level."
- echo "You can also manually add it in .gitlab-ci.yml"
- false
- else
- true
- fi
- }
-
- function initialize_tiller() {
- echo "Checking Tiller..."
-
- export HELM_HOST="localhost:44134"
- tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 &
- echo "Tiller is listening on ${HELM_HOST}"
-
- if ! helm version --debug; then
- echo "Failed to init Tiller."
- return 1
- fi
- echo ""
- }
-
- function create_secret() {
- echo "Create secret..."
- if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
- return
- fi
-
- kubectl create secret -n "$KUBE_NAMESPACE" \
- docker-registry gitlab-registry \
- --docker-server="$CI_REGISTRY" \
- --docker-username="${CI_DEPLOY_USER:-$CI_REGISTRY_USER}" \
- --docker-password="${CI_DEPLOY_PASSWORD:-$CI_REGISTRY_PASSWORD}" \
- --docker-email="$GITLAB_USER_EMAIL" \
- -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f -
- }
-
- function persist_environment_url() {
- echo $CI_ENVIRONMENT_URL > environment_url.txt
- }
-
- function delete() {
- track="${1-stable}"
- name=$(deploy_name "$track")
-
- if [[ -n "$(helm ls -q "^$name$")" ]]; then
- helm delete --purge "$name"
- fi
-
- secret_name=$(application_secret_name "$track")
- kubectl delete secret --ignore-not-found -n "$KUBE_NAMESPACE" "$secret_name"
- }
-
-before_script:
- - *deploy_helpers
diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
index 13ab98d3a16..84bb0ff3b33 100644
--- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
@@ -1,5 +1,3 @@
-# This file is a template, and might need editing before it works on your project.
-
# Build JAVA applications using Apache Maven (http://maven.apache.org)
# For docker image tags see https://hub.docker.com/_/maven/
#
diff --git a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
index 83e179f37c3..0a3cf3dcf77 100644
--- a/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Packer.gitlab-ci.yml
@@ -1,5 +1,5 @@
image:
- name: hashicorp/packer:1.0.4
+ name: hashicorp/packer:latest
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index 5ad624bb15f..2afc99d0bf8 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -5,6 +5,7 @@ container_scanning:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
# Defining two new variables based on GitLab's CI/CD predefined variables
# https://docs.gitlab.com/ee/ci/variables/#predefined-environment-variables
CI_APPLICATION_REPOSITORY: $CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG
@@ -22,8 +23,9 @@ container_scanning:
DOCKER_SERVICE: docker
DOCKER_HOST: tcp://${DOCKER_SERVICE}:2375/
# https://hub.docker.com/r/arminc/clair-local-scan/tags
- CLAIR_LOCAL_SCAN_VERSION: v2.0.8_fe9b059d930314b54c78f75afe265955faf4fdc1
- CLAIR_EXECUTABLE_VERSION: v11
+ CLAIR_LOCAL_SCAN_VERSION: v2.0.8_0ed98e9ead65a51ba53f7cc53fa5e80c92169207
+ CLAIR_EXECUTABLE_VERSION: v12
+ CLAIR_EXECUTABLE_SHA: 44f2a3fdd7b0d102c98510e7586f6956edc89ab72c6943980f92f4979f7f4081
## Disable the proxy for clair-local-scan, otherwise Container Scanning will
## fail when a proxy is used.
NO_PROXY: ${DOCKER_SERVICE},localhost
@@ -43,6 +45,7 @@ container_scanning:
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/${CLAIR_EXECUTABLE_VERSION}/clair-scanner_linux_amd64
+ - echo "${CLAIR_EXECUTABLE_SHA} clair-scanner_linux_amd64" | sha256sum -c
- mv clair-scanner_linux_amd64 clair-scanner
- chmod +x clair-scanner
- touch clair-whitelist.yml
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index 89eccce69f6..15b84f1540d 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -9,6 +9,7 @@ dependency_scanning:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
allow_failure: true
services:
- docker:stable-dind
@@ -41,6 +42,7 @@ dependency_scanning:
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
DS_RUN_ANALYZER_TIMEOUT \
DS_PYTHON_VERSION \
+ DS_PIP_DEPENDENCY_PATH \
PIP_INDEX_URL \
PIP_EXTRA_INDEX_URL \
) \
diff --git a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
index 0a97a16b83c..90278122361 100644
--- a/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml
@@ -9,6 +9,7 @@ sast:
image: docker:stable
variables:
DOCKER_DRIVER: overlay2
+ DOCKER_TLS_CERTDIR: ""
allow_failure: true
services:
- docker:stable-dind
@@ -45,11 +46,14 @@ sast:
SAST_DOCKER_CLIENT_NEGOTIATION_TIMEOUT \
SAST_PULL_ANALYZER_IMAGE_TIMEOUT \
SAST_RUN_ANALYZER_TIMEOUT \
+ SAST_JAVA_VERSION \
ANT_HOME \
ANT_PATH \
GRADLE_PATH \
JAVA_OPTS \
JAVA_PATH \
+ JAVA_8_VERSION \
+ JAVA_11_VERSION \
MAVEN_CLI_OPTS \
MAVEN_PATH \
MAVEN_REPO_PATH \
diff --git a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
index a3db2705bf6..280e75d46f5 100644
--- a/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Serverless.gitlab-ci.yml
@@ -8,26 +8,23 @@ stages:
- deploy
.serverless:build:image:
- stage: build
image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ stage: build
script: /usr/bin/gitlabktl app build
.serverless:deploy:image:
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
stage: deploy
- image: gcr.io/triggermesh/tm@sha256:3cfdd470a66b741004fb02354319d79f1598c70117ce79978d2e07e192bfb336 # v0.0.11
environment: development
- script:
- - echo "$CI_REGISTRY_IMAGE"
- - tm -n "$KUBE_NAMESPACE" --config "$KUBECONFIG" deploy service "$CI_PROJECT_NAME" --from-image "$CI_REGISTRY_IMAGE" --wait
+ script: /usr/bin/gitlabktl app deploy
.serverless:build:functions:
- stage: build
- environment: development
image: registry.gitlab.com/gitlab-org/gitlabktl:latest
+ stage: build
script: /usr/bin/gitlabktl serverless build
.serverless:deploy:functions:
+ image: registry.gitlab.com/gitlab-org/gitlabktl:latest
stage: deploy
environment: development
- image: registry.gitlab.com/gitlab-org/gitlabktl:latest
script: /usr/bin/gitlabktl serverless deploy
diff --git a/lib/gitlab/ci/trace.rb b/lib/gitlab/ci/trace.rb
index ce5857965bf..9550bc6d39c 100644
--- a/lib/gitlab/ci/trace.rb
+++ b/lib/gitlab/ci/trace.rb
@@ -63,7 +63,15 @@ module Gitlab
end
def exist?
- trace_artifact&.exists? || job.trace_chunks.any? || current_path.present? || old_trace.present?
+ archived_trace_exist? || live_trace_exist?
+ end
+
+ def archived_trace_exist?
+ trace_artifact&.exists?
+ end
+
+ def live_trace_exist?
+ job.trace_chunks.any? || current_path.present? || old_trace.present?
end
def read
@@ -118,7 +126,7 @@ module Gitlab
raise AlreadyArchivedError, 'Could not write to the archived trace'
elsif current_path
File.open(current_path, mode)
- elsif Feature.enabled?('ci_enable_live_trace')
+ elsif Feature.enabled?('ci_enable_live_trace', job.project)
Gitlab::Ci::Trace::ChunkedIO.new(job)
else
File.open(ensure_path, mode)
@@ -167,7 +175,7 @@ module Gitlab
def clone_file!(src_stream, temp_dir)
FileUtils.mkdir_p(temp_dir)
- Dir.mktmpdir('tmp-trace', temp_dir) do |dir_path|
+ Dir.mktmpdir("tmp-trace-#{job.id}", temp_dir) do |dir_path|
temp_path = File.join(dir_path, "job.log")
FileUtils.touch(temp_path)
size = IO.copy_stream(src_stream, temp_path)
diff --git a/lib/gitlab/ci/trace/chunked_io.rb b/lib/gitlab/ci/trace/chunked_io.rb
index 8c6fd56493f..e99889f4a25 100644
--- a/lib/gitlab/ci/trace/chunked_io.rb
+++ b/lib/gitlab/ci/trace/chunked_io.rb
@@ -166,6 +166,13 @@ module Gitlab
end
def destroy!
+ # TODO: Remove this logging once we confirmed new live trace architecture is functional.
+ # See https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/4667.
+ unless build.has_archived_trace?
+ Sidekiq.logger.warn(message: 'The job does not have archived trace but going to be destroyed.',
+ job_id: build.id)
+ end
+
trace_chunks.fast_destroy_all
@tell = @size = 0
ensure
diff --git a/lib/gitlab/ci/yaml_processor.rb b/lib/gitlab/ci/yaml_processor.rb
index a5693dc4f81..2e1eab270ff 100644
--- a/lib/gitlab/ci/yaml_processor.rb
+++ b/lib/gitlab/ci/yaml_processor.rb
@@ -40,6 +40,7 @@ module Gitlab
environment: job[:environment_name],
coverage_regex: job[:coverage],
yaml_variables: yaml_variables(name),
+ needs_attributes: job[:needs]&.map { |need| { name: need } },
options: {
image: job[:image],
services: job[:services],
@@ -54,7 +55,8 @@ module Gitlab
parallel: job[:parallel],
instance: job[:instance],
start_in: job[:start_in],
- trigger: job[:trigger]
+ trigger: job[:trigger],
+ bridge_needs: job[:needs]
}.compact }.compact
end
@@ -108,6 +110,7 @@ module Gitlab
validate_job_stage!(name, job)
validate_job_dependencies!(name, job)
+ validate_job_needs!(name, job)
validate_job_environment!(name, job)
end
end
@@ -144,12 +147,30 @@ module Gitlab
job[:dependencies].each do |dependency|
raise ValidationError, "#{name} job: undefined dependency: #{dependency}" unless @jobs[dependency.to_sym]
- unless @stages.index(@jobs[dependency.to_sym][:stage]) < stage_index
+ dependency_stage_index = @stages.index(@jobs[dependency.to_sym][:stage])
+
+ unless dependency_stage_index.present? && dependency_stage_index < stage_index
raise ValidationError, "#{name} job: dependency #{dependency} is not defined in prior stages"
end
end
end
+ def validate_job_needs!(name, job)
+ return unless job[:needs]
+
+ stage_index = @stages.index(job[:stage])
+
+ job[:needs].each do |need|
+ raise ValidationError, "#{name} job: undefined need: #{need}" unless @jobs[need.to_sym]
+
+ needs_stage_index = @stages.index(@jobs[need.to_sym][:stage])
+
+ unless needs_stage_index.present? && needs_stage_index < stage_index
+ raise ValidationError, "#{name} job: need #{need} is not defined in prior stages"
+ end
+ end
+ end
+
def validate_job_environment!(name, job)
return unless job[:environment]
return unless job[:environment].is_a?(Hash)
diff --git a/lib/gitlab/cluster/puma_worker_killer_observer.rb b/lib/gitlab/cluster/puma_worker_killer_observer.rb
index 3b4ebc3fbae..f53051c32ff 100644
--- a/lib/gitlab/cluster/puma_worker_killer_observer.rb
+++ b/lib/gitlab/cluster/puma_worker_killer_observer.rb
@@ -15,9 +15,7 @@ module Gitlab
private
def log_termination(worker)
- labels = { worker: "worker_#{worker.index}" }
-
- @counter.increment(labels)
+ @counter.increment
end
end
end
diff --git a/lib/gitlab/config/entry/attributable.rb b/lib/gitlab/config/entry/attributable.rb
index 560fe63df0e..87bd257f69a 100644
--- a/lib/gitlab/config/entry/attributable.rb
+++ b/lib/gitlab/config/entry/attributable.rb
@@ -18,6 +18,10 @@ module Gitlab
config[attribute]
end
+
+ define_method("has_#{attribute}?") do
+ config.is_a?(Hash) && config.key?(attribute)
+ end
end
end
end
diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb
index 6796fcce75f..374f929878e 100644
--- a/lib/gitlab/config/entry/validators.rb
+++ b/lib/gitlab/config/entry/validators.rb
@@ -20,7 +20,20 @@ module Gitlab
present_keys = value.try(:keys).to_a & options[:in]
if present_keys.any?
- record.errors.add(attribute, "contains disallowed keys: " +
+ message = options[:message] || "contains disallowed keys"
+ message += ": #{present_keys.join(', ')}"
+
+ record.errors.add(attribute, message)
+ end
+ end
+ end
+
+ class RequiredKeysValidator < ActiveModel::EachValidator
+ def validate_each(record, attribute, value)
+ present_keys = options[:in] - value.try(:keys).to_a
+
+ if present_keys.any?
+ record.errors.add(attribute, "missing required keys: " +
present_keys.join(', '))
end
end
@@ -54,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)
@@ -220,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/content_security_policy/config_loader.rb b/lib/gitlab/content_security_policy/config_loader.rb
new file mode 100644
index 00000000000..ff844645b11
--- /dev/null
+++ b/lib/gitlab/content_security_policy/config_loader.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module ContentSecurityPolicy
+ class ConfigLoader
+ DIRECTIVES = %w(base_uri child_src connect_src default_src font_src
+ form_action frame_ancestors frame_src img_src manifest_src
+ media_src object_src report_uri script_src style_src worker_src).freeze
+
+ def self.default_settings_hash
+ {
+ 'enabled' => false,
+ 'report_only' => false,
+ 'directives' => DIRECTIVES.each_with_object({}) { |directive, hash| hash[directive] = nil }
+ }
+ end
+
+ def initialize(csp_directives)
+ @csp_directives = HashWithIndifferentAccess.new(csp_directives)
+ end
+
+ def load(policy)
+ DIRECTIVES.each do |directive|
+ arguments = arguments_for(directive)
+
+ next unless arguments.present?
+
+ policy.public_send(directive, *arguments) # rubocop:disable GitlabSecurity/PublicSend
+ end
+ end
+
+ private
+
+ def arguments_for(directive)
+ arguments = @csp_directives[directive.to_s]
+
+ return unless arguments.present? && arguments.is_a?(String)
+
+ arguments.strip.split(' ').map(&:strip)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index f7d046600e8..5b0b91de5da 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -84,11 +84,7 @@ module Gitlab
.and(t[:created_at].lteq(Date.current.end_of_day))
.and(t[:author_id].eq(contributor.id))
- date_interval = if Gitlab::Database.postgresql?
- "INTERVAL '#{Time.zone.now.utc_offset} seconds'"
- else
- "INTERVAL #{Time.zone.now.utc_offset} SECOND"
- end
+ date_interval = "INTERVAL '#{Time.zone.now.utc_offset} seconds'"
Event.reorder(nil)
.select(t[:project_id], t[:target_type], t[:action], "date(created_at + #{date_interval}) AS date", 'count(id) as total_amount')
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 32d5e4b9ea3..6ce47650562 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -7,6 +7,11 @@ module Gitlab
Gitlab::SafeRequestStore.fetch(:current_application_settings) { ensure_application_settings! }
end
+ def expire_current_application_settings
+ ::ApplicationSetting.expire
+ Gitlab::SafeRequestStore.delete(:current_application_settings)
+ end
+
def clear_in_memory_application_settings!
@in_memory_application_settings = nil
end
diff --git a/lib/gitlab/cycle_analytics/base_event_fetcher.rb b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
index 0cacef5b278..07ae430c45e 100644
--- a/lib/gitlab/cycle_analytics/base_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/base_event_fetcher.rb
@@ -4,13 +4,13 @@ module Gitlab
module CycleAnalytics
class BaseEventFetcher
include BaseQuery
+ include GroupProjectsProvider
- attr_reader :projections, :query, :stage, :order, :project, :options
+ attr_reader :projections, :query, :stage, :order, :options
MAX_EVENTS = 50
- def initialize(project: nil, stage:, options:)
- @project = project
+ def initialize(stage:, options:)
@stage = stage
@options = options
end
@@ -68,11 +68,11 @@ module Gitlab
end
def allowed_ids_source
- { project_id: project.id }
+ group ? { group_id: group.id, include_subgroups: true } : { project_id: project.id }
end
- def projects
- [project]
+ def serialization_context
+ {}
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb
index 39fc1759cfc..459bb5177b5 100644
--- a/lib/gitlab/cycle_analytics/base_query.rb
+++ b/lib/gitlab/cycle_analytics/base_query.rb
@@ -16,17 +16,32 @@ module Gitlab
def stage_query(project_ids)
query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id]))
.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
- .where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(options[:from]))
+ .project(projects_table[:path].as("project_path"))
+ .project(routes_table[:path].as("namespace_path"))
+
+ query = limit_query(query, project_ids)
# Load merge_requests
- query = query.join(mr_table, Arel::Nodes::OuterJoin)
+
+ query = load_merge_requests(query)
+
+ query
+ end
+
+ def limit_query(query, project_ids)
+ query.where(issue_table[:project_id].in(project_ids))
+ .where(routes_table[:source_type].eq('Namespace'))
+ .where(issue_table[:created_at].gteq(options[:from]))
+ end
+
+ def load_merge_requests(query)
+ query.join(mr_table, Arel::Nodes::OuterJoin)
.on(mr_table[:id].eq(mr_closing_issues_table[:merge_request_id]))
.join(mr_metrics_table)
.on(mr_table[:id].eq(mr_metrics_table[:merge_request_id]))
-
- query
end
end
end
diff --git a/lib/gitlab/cycle_analytics/base_stage.rb b/lib/gitlab/cycle_analytics/base_stage.rb
index 98b86e54340..1cd54238bb4 100644
--- a/lib/gitlab/cycle_analytics/base_stage.rb
+++ b/lib/gitlab/cycle_analytics/base_stage.rb
@@ -4,11 +4,11 @@ module Gitlab
module CycleAnalytics
class BaseStage
include BaseQuery
+ include GroupProjectsProvider
- attr_reader :project, :options
+ attr_reader :options
- def initialize(project: nil, options:)
- @project = project
+ def initialize(options:)
@options = options
end
@@ -24,7 +24,7 @@ module Gitlab
raise NotImplementedError.new("Expected #{self.name} to implement title")
end
- def median
+ def project_median
return if project.nil?
BatchLoader.for(project.id).batch(key: name) do |project_ids, loader|
@@ -42,6 +42,10 @@ module Gitlab
end
end
+ def group_median
+ median_query(projects.map(&:id))
+ end
+
def median_query(project_ids)
# Build a `SELECT` query. We find the first of the `end_time_attrs` that isn't `NULL` (call this end_time).
# Next, we find the first of the start_time_attrs that isn't `NULL` (call this start_time).
@@ -67,18 +71,13 @@ module Gitlab
private
def event_fetcher
- @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(project: project,
- stage: name,
+ @event_fetcher ||= Gitlab::CycleAnalytics::EventFetcher[name].new(stage: name,
options: event_options)
end
def event_options
options.merge(start_time_attrs: start_time_attrs, end_time_attrs: end_time_attrs)
end
-
- def projects
- [project]
- end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
index 9e7ca529579..fcc282bf7a6 100644
--- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb
@@ -20,7 +20,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: project).represent(event)
+ AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/group_projects_provider.rb b/lib/gitlab/cycle_analytics/group_projects_provider.rb
new file mode 100644
index 00000000000..1287a48daaa
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/group_projects_provider.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module GroupProjectsProvider
+ def projects
+ group ? projects_for_group : [project]
+ end
+
+ def group
+ @group ||= options.fetch(:group, nil)
+ end
+
+ def project
+ @project ||= options.fetch(:project, nil)
+ end
+
+ private
+
+ def projects_for_group
+ projects = Project.inside_path(group.full_path)
+ projects = projects.where(id: options[:projects]) if options[:projects]
+ projects
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/group_stage_summary.rb b/lib/gitlab/cycle_analytics/group_stage_summary.rb
new file mode 100644
index 00000000000..a1fc941495d
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/group_stage_summary.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ class GroupStageSummary
+ attr_reader :group, :from, :current_user, :options
+
+ def initialize(group, options:)
+ @group = group
+ @from = options[:from]
+ @current_user = options[:current_user]
+ @options = options
+ end
+
+ def data
+ [serialize(Summary::Group::Issue.new(group: group, from: from, current_user: current_user, options: options)),
+ serialize(Summary::Group::Deploy.new(group: group, from: from, options: options))]
+ end
+
+ private
+
+ def serialize(summary_object)
+ AnalyticsSummarySerializer.new.represent(summary_object)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
index bb3520ae920..6914cf24c19 100644
--- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb
index ac836b8bf0f..295eca5edca 100644
--- a/lib/gitlab/cycle_analytics/issue_helper.rb
+++ b/lib/gitlab/cycle_analytics/issue_helper.rb
@@ -5,13 +5,23 @@ module Gitlab
module IssueHelper
def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
- .where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(options[:from]))
- .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
+ .project(projects_table[:path].as("project_path"))
+ .project(routes_table[:path].as("namespace_path"))
+
+ query = limit_query(query, project_ids)
query
end
+
+ def limit_query(query, project_ids)
+ query.where(issue_table[:project_id].in(project_ids))
+ .where(routes_table[:source_type].eq('Namespace'))
+ .where(issue_table[:created_at].gteq(options[:from]))
+ .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
+ end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/metrics_tables.rb b/lib/gitlab/cycle_analytics/metrics_tables.rb
index 3e0302d308d..015f7bfde24 100644
--- a/lib/gitlab/cycle_analytics/metrics_tables.rb
+++ b/lib/gitlab/cycle_analytics/metrics_tables.rb
@@ -35,6 +35,14 @@ module Gitlab
User.arel_table
end
+ def projects_table
+ Project.arel_table
+ end
+
+ def routes_table
+ Route.arel_table
+ end
+
def build_table
::CommitStatus.arel_table
end
diff --git a/lib/gitlab/cycle_analytics/permissions.rb b/lib/gitlab/cycle_analytics/permissions.rb
index 03ba98b4dfb..55214e6b896 100644
--- a/lib/gitlab/cycle_analytics/permissions.rb
+++ b/lib/gitlab/cycle_analytics/permissions.rb
@@ -23,7 +23,7 @@ module Gitlab
end
def get
- ::CycleAnalytics::Base::STAGES.each do |stage|
+ ::CycleAnalytics::LevelBase::STAGES.each do |stage|
@stage_permission_hash[stage] = authorized_stage?(stage)
end
diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
index 49a6b099f34..bad02e00a13 100644
--- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb
@@ -18,7 +18,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb
index ae578d45ad5..a63ae58ad21 100644
--- a/lib/gitlab/cycle_analytics/plan_helper.rb
+++ b/lib/gitlab/cycle_analytics/plan_helper.rb
@@ -5,14 +5,23 @@ module Gitlab
module PlanHelper
def stage_query(project_ids)
query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id]))
+ .join(projects_table).on(issue_table[:project_id].eq(projects_table[:id]))
+ .join(routes_table).on(projects_table[:namespace_id].eq(routes_table[:source_id]))
.project(issue_table[:project_id].as("project_id"))
+ .project(projects_table[:path].as("project_path"))
+ .project(routes_table[:path].as("namespace_path"))
.where(issue_table[:project_id].in(project_ids))
- .where(issue_table[:created_at].gteq(options[:from]))
- .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
- .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
+ .where(routes_table[:source_type].eq('Namespace'))
+ query = limit_query(query)
query
end
+
+ def limit_query(query)
+ query.where(issue_table[:created_at].gteq(options[:from]))
+ .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil)))
+ .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil))
+ end
end
end
end
diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
index 949119d69a0..8843ab2bcb9 100644
--- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb
@@ -10,7 +10,8 @@ module Gitlab
issue_table[:iid],
issue_table[:id],
issue_table[:created_at],
- issue_table[:author_id]]
+ issue_table[:author_id],
+ routes_table[:path]]
super(*args)
end
@@ -18,7 +19,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsIssueSerializer.new(project: project).represent(event)
+ AnalyticsIssueSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
index d31736e755d..4b5d79097b7 100644
--- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb
+++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb
@@ -19,7 +19,7 @@ module Gitlab
private
def serialize(event)
- AnalyticsMergeRequestSerializer.new(project: project).represent(event)
+ AnalyticsMergeRequestSerializer.new(serialization_context).represent(event)
end
def allowed_ids_finder_class
diff --git a/lib/gitlab/cycle_analytics/summary/group/base.rb b/lib/gitlab/cycle_analytics/summary/group/base.rb
new file mode 100644
index 00000000000..48d8164bde1
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Base
+ attr_reader :group, :from, :options
+
+ def initialize(group:, from:, options:)
+ @group = group
+ @from = from
+ @options = options
+ end
+
+ def title
+ raise NotImplementedError.new("Expected #{self.name} to implement title")
+ end
+
+ def value
+ raise NotImplementedError.new("Expected #{self.name} to implement value")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/summary/group/deploy.rb b/lib/gitlab/cycle_analytics/summary/group/deploy.rb
new file mode 100644
index 00000000000..78d677cf558
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/deploy.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Deploy < Group::Base
+ include GroupProjectsProvider
+
+ def title
+ n_('Deploy', 'Deploys', value)
+ end
+
+ def value
+ @value ||= find_deployments
+ end
+
+ private
+
+ def find_deployments
+ deployments = Deployment.joins(:project).merge(Project.inside_path(group.full_path))
+ deployments = deployments.where(projects: { id: options[:projects] }) if options[:projects]
+ deployments = deployments.where("deployments.created_at > ?", from)
+ deployments.success.count
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/cycle_analytics/summary/group/issue.rb b/lib/gitlab/cycle_analytics/summary/group/issue.rb
new file mode 100644
index 00000000000..9daae8531d8
--- /dev/null
+++ b/lib/gitlab/cycle_analytics/summary/group/issue.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module CycleAnalytics
+ module Summary
+ module Group
+ class Issue < Group::Base
+ attr_reader :group, :from, :current_user, :options
+
+ def initialize(group:, from:, current_user:, options:)
+ @group = group
+ @from = from
+ @current_user = current_user
+ @options = options
+ end
+
+ def title
+ n_('New Issue', 'New Issues', value)
+ end
+
+ def value
+ @value ||= find_issues
+ end
+
+ private
+
+ def find_issues
+ issues = IssuesFinder.new(current_user, group_id: group.id, include_subgroups: true, created_after: from).execute
+ issues = issues.where(projects: { id: options[:projects] }) if options[:projects]
+ issues.count
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index 0fc145534bf..332ca8bf9b8 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -46,6 +46,16 @@ module Gitlab
ee? ? 'gitlab-ee' : 'gitlab-ce'
end
+ def markdown_list(items)
+ list = items.map { |item| "* `#{item}`" }.join("\n")
+
+ if items.size > 10
+ "\n<details>\n\n#{list}\n\n</details>\n"
+ else
+ list
+ end
+ end
+
# @return [Hash<String,Array<String>>]
def changes_by_category
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
@@ -103,7 +113,7 @@ module Gitlab
yarn\.lock
)\z}x => :frontend,
- %r{\A(ee/)?db/} => :database,
+ %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
%r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
%r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
%r{\Arubocop/cop/migration(/|\.rb)} => :database,
@@ -132,6 +142,22 @@ module Gitlab
def new_teammates(usernames)
usernames.map { |u| Gitlab::Danger::Teammate.new('username' => u) }
end
+
+ def missing_database_labels(current_mr_labels)
+ labels = if has_database_scoped_labels?(current_mr_labels)
+ ['database']
+ else
+ ['database', 'database::review pending']
+ end
+
+ labels - current_mr_labels
+ end
+
+ private
+
+ def has_database_scoped_labels?(current_mr_labels)
+ current_mr_labels.any? { |label| label.start_with?('database::') }
+ end
end
end
end
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index b44f134f2c1..74cbcc11255 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -39,7 +39,7 @@ module Gitlab
def has_capability?(project, category, kind, labels)
case category
when :test
- area = role[/Test Automation Engineer, (\w+)/, 1]
+ area = role[/Test Automation Engineer(?:.*?, (\w+))/, 1]
area && labels.any?(area) if kind == :reviewer
else
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index 40bda3410e1..75d9a2d55b9 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -60,7 +60,8 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def build(
project:, user:, ref:, oldrev: nil, newrev: nil,
- commits: [], commits_count: nil, message: nil, push_options: {})
+ commits: [], commits_count: nil, message: nil, push_options: {},
+ with_changed_files: true)
commits = Array(commits)
@@ -75,7 +76,7 @@ module Gitlab
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38259
commit_attrs = Gitlab::GitalyClient.allow_n_plus_1_calls do
commits_limited.map do |commit|
- commit.hook_attrs(with_changed_files: true)
+ commit.hook_attrs(with_changed_files: with_changed_files)
end
end
@@ -128,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.rb b/lib/gitlab/database.rb
index 3e4c720b49a..cbdff0ab060 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -4,13 +4,13 @@ module Gitlab
module Database
include Gitlab::Metrics::Methods
- # The max value of INTEGER type is the same between MySQL and PostgreSQL:
# https://www.postgresql.org/docs/9.2/static/datatype-numeric.html
- # http://dev.mysql.com/doc/refman/5.7/en/integer-types.html
MAX_INT_VALUE = 2147483647
+
# The max value between MySQL's TIMESTAMP and PostgreSQL's timestampz:
# https://www.postgresql.org/docs/9.1/static/datatype-datetime.html
# https://dev.mysql.com/doc/refman/5.7/en/datetime.html
+ # FIXME: this should just be the max value of timestampz
MAX_TIMESTAMP_VALUE = Time.at((1 << 31) - 1).freeze
# Minimum schema version from which migrations are supported
@@ -39,13 +39,14 @@ module Gitlab
end
def self.human_adapter_name
- postgresql? ? 'PostgreSQL' : 'MySQL'
- end
-
- def self.mysql?
- adapter_name.casecmp('mysql2').zero?
+ if postgresql?
+ 'PostgreSQL'
+ else
+ 'Unknown'
+ end
end
+ # @deprecated
def self.postgresql?
adapter_name.casecmp('postgresql').zero?
end
@@ -60,15 +61,14 @@ module Gitlab
# Check whether the underlying database is in read-only mode
def self.db_read_only?
- if postgresql?
- pg_is_in_recovery =
- ActiveRecord::Base.connection.execute('SELECT pg_is_in_recovery()')
- .first.fetch('pg_is_in_recovery')
+ pg_is_in_recovery =
+ ActiveRecord::Base
+ .connection
+ .execute('SELECT pg_is_in_recovery()')
+ .first
+ .fetch('pg_is_in_recovery')
- Gitlab::Utils.to_boolean(pg_is_in_recovery)
- else
- false
- end
+ Gitlab::Utils.to_boolean(pg_is_in_recovery)
end
def self.db_read_write?
@@ -80,19 +80,19 @@ module Gitlab
end
def self.postgresql_9_or_less?
- postgresql? && version.to_f < 10
+ version.to_f < 10
end
def self.join_lateral_supported?
- postgresql? && version.to_f >= 9.3
+ version.to_f >= 9.3
end
def self.replication_slots_supported?
- postgresql? && version.to_f >= 9.4
+ version.to_f >= 9.4
end
def self.postgresql_minimum_supported_version?
- postgresql? && version.to_f >= 9.6
+ version.to_f >= 9.6
end
# map some of the function names that changed between PostgreSQL 9 and 10
@@ -118,51 +118,23 @@ module Gitlab
end
def self.nulls_last_order(field, direction = 'ASC')
- order = "#{field} #{direction}"
-
- if postgresql?
- order = "#{order} NULLS LAST"
- else
- # `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
- # columns. In the (default) ascending order, `0` comes first.
- order = "#{field} IS NULL, #{order}" if direction == 'ASC'
- end
-
- Arel.sql(order)
+ Arel.sql("#{field} #{direction} NULLS LAST")
end
def self.nulls_first_order(field, direction = 'ASC')
- order = "#{field} #{direction}"
-
- if postgresql?
- order = "#{order} NULLS FIRST"
- else
- # `field IS NULL` will be `0` for non-NULL columns and `1` for NULL
- # columns. In the (default) ascending order, `0` comes first.
- order = "#{field} IS NULL, #{order}" if direction == 'DESC'
- end
-
- Arel.sql(order)
+ Arel.sql("#{field} #{direction} NULLS FIRST")
end
def self.random
- postgresql? ? "RANDOM()" : "RAND()"
+ "RANDOM()"
end
def self.true_value
- if postgresql?
- "'t'"
- else
- 1
- end
+ "'t'"
end
def self.false_value
- if postgresql?
- "'f'"
- else
- 0
- end
+ "'f'"
end
def self.with_connection_pool(pool_size)
@@ -182,7 +154,7 @@ module Gitlab
# rows - An Array of Hash instances, each mapping the columns to their
# values.
# return_ids - When set to true the return value will be an Array of IDs of
- # the inserted rows, this only works on PostgreSQL.
+ # the inserted rows
# disable_quote - A key or an Array of keys to exclude from quoting (You
# become responsible for protection from SQL injection for
# these keys!)
@@ -191,7 +163,6 @@ module Gitlab
keys = rows.first.keys
columns = keys.map { |key| connection.quote_column_name(key) }
- return_ids = false if mysql?
disable_quote = Array(disable_quote).to_set
tuples = rows.map do |row|
@@ -258,11 +229,7 @@ module Gitlab
def self.database_version
row = connection.execute("SELECT VERSION()").first
- if postgresql?
- row['version']
- else
- row.first
- end
+ row['version']
end
private_class_method :database_version
diff --git a/lib/gitlab/database/count.rb b/lib/gitlab/database/count.rb
index f3d37ccd72a..eac61254bdf 100644
--- a/lib/gitlab/database/count.rb
+++ b/lib/gitlab/database/count.rb
@@ -37,16 +37,14 @@ module Gitlab
# @return [Hash] of Model -> count mapping
def self.approximate_counts(models, strategies: [TablesampleCountStrategy, ReltuplesCountStrategy, ExactCountStrategy])
strategies.each_with_object({}) do |strategy, counts_by_model|
- if strategy.enabled?
- models_with_missing_counts = models - counts_by_model.keys
+ models_with_missing_counts = models - counts_by_model.keys
- break counts_by_model if models_with_missing_counts.empty?
+ break counts_by_model if models_with_missing_counts.empty?
- counts = strategy.new(models_with_missing_counts).count
+ counts = strategy.new(models_with_missing_counts).count
- counts.each do |model, count|
- counts_by_model[model] = count
- end
+ counts.each do |model, count|
+ counts_by_model[model] = count
end
end
end
diff --git a/lib/gitlab/database/count/exact_count_strategy.rb b/lib/gitlab/database/count/exact_count_strategy.rb
index fa6951eda22..0b8fe640bf8 100644
--- a/lib/gitlab/database/count/exact_count_strategy.rb
+++ b/lib/gitlab/database/count/exact_count_strategy.rb
@@ -23,10 +23,6 @@ module Gitlab
rescue *CONNECTION_ERRORS
{}
end
-
- def self.enabled?
- true
- end
end
end
end
diff --git a/lib/gitlab/database/count/reltuples_count_strategy.rb b/lib/gitlab/database/count/reltuples_count_strategy.rb
index 695f6fa766e..6cd90c01ab2 100644
--- a/lib/gitlab/database/count/reltuples_count_strategy.rb
+++ b/lib/gitlab/database/count/reltuples_count_strategy.rb
@@ -31,10 +31,6 @@ module Gitlab
{}
end
- def self.enabled?
- Gitlab::Database.postgresql?
- end
-
private
# Models using single-type inheritance (STI) don't work with
diff --git a/lib/gitlab/database/count/tablesample_count_strategy.rb b/lib/gitlab/database/count/tablesample_count_strategy.rb
index 7777f31f702..e9387a91a14 100644
--- a/lib/gitlab/database/count/tablesample_count_strategy.rb
+++ b/lib/gitlab/database/count/tablesample_count_strategy.rb
@@ -28,10 +28,6 @@ module Gitlab
{}
end
- def self.enabled?
- Gitlab::Database.postgresql? && Feature.enabled?(:tablesample_counts)
- end
-
private
def perform_count(model, estimate)
diff --git a/lib/gitlab/database/date_time.rb b/lib/gitlab/database/date_time.rb
index 79d2caff151..1392b397012 100644
--- a/lib/gitlab/database/date_time.rb
+++ b/lib/gitlab/database/date_time.rb
@@ -7,8 +7,7 @@ module Gitlab
# the first of the `start_time_attrs` that isn't NULL. `SELECT` the resulting interval
# along with an alias specified by the `as` parameter.
#
- # Note: For MySQL, the interval is returned in seconds.
- # For PostgreSQL, the interval is returned as an INTERVAL type.
+ # Note: the interval is returned as an INTERVAL type.
def subtract_datetimes(query_so_far, start_time_attrs, end_time_attrs, as)
diff_fn = subtract_datetimes_diff(query_so_far, start_time_attrs, end_time_attrs)
@@ -16,17 +15,10 @@ module Gitlab
end
def subtract_datetimes_diff(query_so_far, start_time_attrs, end_time_attrs)
- if Gitlab::Database.postgresql?
- Arel::Nodes::Subtraction.new(
- Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)),
- Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)))
- elsif Gitlab::Database.mysql?
- Arel::Nodes::NamedFunction.new(
- "TIMESTAMPDIFF",
- [Arel.sql('second'),
- Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs)),
- Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs))])
- end
+ Arel::Nodes::Subtraction.new(
+ Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(end_time_attrs)),
+ Arel::Nodes::NamedFunction.new("COALESCE", Array.wrap(start_time_attrs))
+ )
end
end
end
diff --git a/lib/gitlab/database/grant.rb b/lib/gitlab/database/grant.rb
index 26adf4e221b..1f47f320a29 100644
--- a/lib/gitlab/database/grant.rb
+++ b/lib/gitlab/database/grant.rb
@@ -6,47 +6,25 @@ module Gitlab
class Grant < ActiveRecord::Base
include FromUnion
- self.table_name =
- if Database.postgresql?
- 'information_schema.role_table_grants'
- else
- 'information_schema.schema_privileges'
- end
+ self.table_name = 'information_schema.role_table_grants'
# Returns true if the current user can create and execute triggers on the
# given table.
def self.create_and_execute_trigger?(table)
- if Database.postgresql?
- # We _must not_ use quote_table_name as this will produce double
- # quotes on PostgreSQL and for "has_table_privilege" we need single
- # quotes.
- quoted_table = connection.quote(table)
-
- begin
- from(nil)
- .pluck(Arel.sql("has_table_privilege(#{quoted_table}, 'TRIGGER')"))
- .first
- rescue ActiveRecord::StatementInvalid
- # This error is raised when using a non-existing table name. In this
- # case we just want to return false as a user technically can't
- # create triggers for such a table.
- false
- end
- else
- queries = [
- Grant.select(1)
- .from('information_schema.user_privileges')
- .where("PRIVILEGE_TYPE = 'SUPER'")
- .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')"),
-
- Grant.select(1)
- .from('information_schema.schema_privileges')
- .where("PRIVILEGE_TYPE = 'TRIGGER'")
- .where('TABLE_SCHEMA = ?', Gitlab::Database.database_name)
- .where("GRANTEE = CONCAT('\\'', REPLACE(CURRENT_USER(), '@', '\\'@\\''), '\\'')")
- ]
+ # We _must not_ use quote_table_name as this will produce double
+ # quotes on PostgreSQL and for "has_table_privilege" we need single
+ # quotes.
+ quoted_table = connection.quote(table)
- Grant.from_union(queries, alias_as: 'privs').any?
+ begin
+ from(nil)
+ .pluck(Arel.sql("has_table_privilege(#{quoted_table}, 'TRIGGER')"))
+ .first
+ rescue ActiveRecord::StatementInvalid
+ # This error is raised when using a non-existing table name. In this
+ # case we just want to return false as a user technically can't
+ # create triggers for such a table.
+ false
end
end
end
diff --git a/lib/gitlab/database/median.rb b/lib/gitlab/database/median.rb
index b8d895dee7d..603b125d8b4 100644
--- a/lib/gitlab/database/median.rb
+++ b/lib/gitlab/database/median.rb
@@ -17,13 +17,9 @@ module Gitlab
def extract_median(results)
result = results.compact.first
- if Gitlab::Database.postgresql?
- result = result.first.presence
+ result = result.first.presence
- result['median']&.to_f if result
- elsif Gitlab::Database.mysql?
- result.to_a.flatten.first
- end
+ result['median']&.to_f if result
end
def extract_medians(results)
@@ -34,31 +30,6 @@ module Gitlab
end
end
- def mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
- query = arel_table.from
- .from(arel_table.project(Arel.sql('*')).order(arel_table[column_sym]).as(arel_table.table_name))
- .project(average([arel_table[column_sym]], 'median'))
- .where(
- Arel::Nodes::Between.new(
- Arel.sql("(select @row_id := @row_id + 1)"),
- Arel::Nodes::And.new(
- [Arel.sql('@ct/2.0'),
- Arel.sql('@ct/2.0 + 1')]
- )
- )
- ).
- # Disallow negative values
- where(arel_table[column_sym].gteq(0))
-
- [
- Arel.sql("CREATE TEMPORARY TABLE IF NOT EXISTS #{query_so_far.to_sql}"),
- Arel.sql("set @ct := (select count(1) from #{arel_table.table_name});"),
- Arel.sql("set @row_id := 0;"),
- query.to_sql,
- Arel.sql("DROP TEMPORARY TABLE IF EXISTS #{arel_table.table_name};")
- ]
- end
-
def pg_median_datetime_sql(arel_table, query_so_far, column_sym, partition_column = nil)
# Create a CTE with the column we're operating on, row number (after sorting by the column
# we're operating on), and count of the table we're operating on (duplicated across) all rows
@@ -113,18 +84,8 @@ module Gitlab
private
- def median_queries(arel_table, query_so_far, column_sym, partition_column = nil)
- if Gitlab::Database.postgresql?
- pg_median_datetime_sql(arel_table, query_so_far, column_sym, partition_column)
- elsif Gitlab::Database.mysql?
- raise NotSupportedError, "partition_column is not supported for MySQL" if partition_column
-
- mysql_median_datetime_sql(arel_table, query_so_far, column_sym)
- end
- end
-
def execute_queries(arel_table, query_so_far, column_sym, partition_column = nil)
- queries = median_queries(arel_table, query_so_far, column_sym, partition_column)
+ queries = pg_median_datetime_sql(arel_table, query_so_far, column_sym, partition_column)
Array.wrap(queries).map { |query| ActiveRecord::Base.connection.execute(query) }
end
@@ -176,8 +137,6 @@ module Gitlab
end
def extract_diff_epoch(diff)
- return diff unless Gitlab::Database.postgresql?
-
Arel.sql(%Q{EXTRACT(EPOCH FROM (#{diff.to_sql}))})
end
diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb
index 0c5f33e1b2a..9bba4f6ce1e 100644
--- a/lib/gitlab/database/migration_helpers.rb
+++ b/lib/gitlab/database/migration_helpers.rb
@@ -6,31 +6,45 @@ module Gitlab
BACKGROUND_MIGRATION_BATCH_SIZE = 1000 # Number of rows to process per job
BACKGROUND_MIGRATION_JOB_BUFFER_SIZE = 1000 # Number of jobs to bulk queue at a time
+ PERMITTED_TIMESTAMP_COLUMNS = %i[created_at updated_at deleted_at].to_set.freeze
+ DEFAULT_TIMESTAMP_COLUMNS = %i[created_at updated_at].freeze
+
# Adds `created_at` and `updated_at` columns with timezone information.
#
# This method is an improved version of Rails' built-in method `add_timestamps`.
#
+ # By default, adds `created_at` and `updated_at` columns, but these can be specified as:
+ #
+ # add_timestamps_with_timezone(:my_table, columns: [:created_at, :deleted_at])
+ #
+ # This allows you to create just the timestamps you need, saving space.
+ #
# Available options are:
- # default - The default value for the column.
- # null - When set to `true` the column will allow NULL values.
+ # :default - The default value for the column.
+ # :null - When set to `true` the column will allow NULL values.
# The default is to not allow NULL values.
+ # :columns - the column names to create. Must be one
+ # of `Gitlab::Database::MigrationHelpers::PERMITTED_TIMESTAMP_COLUMNS`.
+ # Default value: `DEFAULT_TIMESTAMP_COLUMNS`
+ #
+ # All options are optional.
def add_timestamps_with_timezone(table_name, options = {})
options[:null] = false if options[:null].nil?
+ columns = options.fetch(:columns, DEFAULT_TIMESTAMP_COLUMNS)
+ default_value = options[:default]
- [:created_at, :updated_at].each do |column_name|
- if options[:default] && transaction_open?
- raise '`add_timestamps_with_timezone` with default value cannot be run inside a transaction. ' \
- 'You can disable transactions by calling `disable_ddl_transaction!` ' \
- 'in the body of your migration class'
- end
+ validate_not_in_transaction!(:add_timestamps_with_timezone, 'with default value') if default_value
+
+ columns.each do |column_name|
+ validate_timestamp_column_name!(column_name)
# If default value is presented, use `add_column_with_default` method instead.
- if options[:default]
+ if default_value
add_column_with_default(
table_name,
column_name,
:datetime_with_timezone,
- default: options[:default],
+ default: default_value,
allow_null: options[:null]
)
else
@@ -39,10 +53,22 @@ module Gitlab
end
end
- # Creates a new index, concurrently when supported
+ # To be used in the `#down` method of migrations that
+ # use `#add_timestamps_with_timezone`.
#
- # On PostgreSQL this method creates an index concurrently, on MySQL this
- # creates a regular index.
+ # Available options are:
+ # :columns - the column names to remove. Must be one
+ # Default value: `DEFAULT_TIMESTAMP_COLUMNS`
+ #
+ # All options are optional.
+ def remove_timestamps(table_name, options = {})
+ columns = options.fetch(:columns, DEFAULT_TIMESTAMP_COLUMNS)
+ columns.each do |column_name|
+ remove_column(table_name, column_name)
+ end
+ end
+
+ # Creates a new index, concurrently
#
# Example:
#
@@ -56,9 +82,7 @@ module Gitlab
'in the body of your migration class'
end
- if Database.postgresql?
- options = options.merge({ algorithm: :concurrently })
- end
+ options = options.merge({ algorithm: :concurrently })
if index_exists?(table_name, column_name, options)
Rails.logger.warn "Index not created because it already exists (this may be due to an aborted migration or similar): table_name: #{table_name}, column_name: #{column_name}" # rubocop:disable Gitlab/RailsLogger
@@ -70,9 +94,7 @@ module Gitlab
end
end
- # Removes an existed index, concurrently when supported
- #
- # On PostgreSQL this method removes an index concurrently.
+ # Removes an existed index, concurrently
#
# Example:
#
@@ -100,9 +122,7 @@ module Gitlab
end
end
- # Removes an existing index, concurrently when supported
- #
- # On PostgreSQL this method removes an index concurrently.
+ # Removes an existing index, concurrently
#
# Example:
#
@@ -132,8 +152,6 @@ module Gitlab
# Only available on Postgresql >= 9.2
def supports_drop_index_concurrently?
- return false unless Database.postgresql?
-
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
version >= 90200
@@ -141,8 +159,7 @@ module Gitlab
# Adds a foreign key with only minimal locking on the tables involved.
#
- # This method only requires minimal locking when using PostgreSQL. When
- # using MySQL this method will use Rails' default `add_foreign_key`.
+ # This method only requires minimal locking
#
# source - The source table containing the foreign key.
# target - The target table the key points to.
@@ -158,27 +175,7 @@ module Gitlab
raise 'add_concurrent_foreign_key can not be run inside a transaction'
end
- # While MySQL does allow disabling of foreign keys it has no equivalent
- # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
- # back to the normal foreign key procedure.
- if Database.mysql?
- if foreign_key_exists?(source, target, column: column)
- Rails.logger.warn "Foreign key not created because it exists already " \
- "(this may be due to an aborted migration or similar): " \
- "source: #{source}, target: #{target}, column: #{column}"
- return
- end
-
- key_options = { column: column, on_delete: on_delete }
-
- # The MySQL adapter tries to create a foreign key without a name when
- # `:name` is nil, instead of generating a name for us.
- key_options[:name] = name if name
-
- return add_foreign_key(source, target, key_options)
- else
- on_delete = 'SET NULL' if on_delete == :nullify
- end
+ on_delete = 'SET NULL' if on_delete == :nullify
key_name = name || concurrent_foreign_key_name(source, column)
@@ -236,7 +233,7 @@ module Gitlab
# Long-running migrations may take more than the timeout allowed by
# the database. Disable the session's statement timeout to ensure
- # migrations don't get killed prematurely. (PostgreSQL only)
+ # migrations don't get killed prematurely.
#
# There are two possible ways to disable the statement timeout:
#
@@ -248,15 +245,6 @@ module Gitlab
# otherwise the statement will still be disabled until connection is dropped
# or `RESET ALL` is executed
def disable_statement_timeout
- # bypass disabled_statement logic when not using postgres, but still execute block when one is given
- unless Database.postgresql?
- if block_given?
- yield
- end
-
- return
- end
-
if block_given?
begin
execute('SET statement_timeout TO 0')
@@ -506,13 +494,12 @@ module Gitlab
quoted_old = quote_column_name(old_column)
quoted_new = quote_column_name(new_column)
- if Database.postgresql?
- install_rename_triggers_for_postgresql(trigger_name, quoted_table,
- quoted_old, quoted_new)
- else
- install_rename_triggers_for_mysql(trigger_name, quoted_table,
- quoted_old, quoted_new)
- end
+ install_rename_triggers_for_postgresql(
+ trigger_name,
+ quoted_table,
+ quoted_old,
+ quoted_new
+ )
end
# Changes the type of a column concurrently.
@@ -555,11 +542,7 @@ module Gitlab
check_trigger_permissions!(table)
- if Database.postgresql?
- remove_rename_triggers_for_postgresql(table, trigger_name)
- else
- remove_rename_triggers_for_mysql(trigger_name)
- end
+ remove_rename_triggers_for_postgresql(table, trigger_name)
remove_column(table, old)
end
@@ -772,38 +755,12 @@ module Gitlab
EOF
end
- # Installs the triggers necessary to perform a concurrent column rename on
- # MySQL.
- def install_rename_triggers_for_mysql(trigger, table, old, new)
- execute <<-EOF.strip_heredoc
- CREATE TRIGGER #{trigger}_insert
- BEFORE INSERT
- ON #{table}
- FOR EACH ROW
- SET NEW.#{new} = NEW.#{old}
- EOF
-
- execute <<-EOF.strip_heredoc
- CREATE TRIGGER #{trigger}_update
- BEFORE UPDATE
- ON #{table}
- FOR EACH ROW
- SET NEW.#{new} = NEW.#{old}
- EOF
- end
-
# Removes the triggers used for renaming a PostgreSQL column concurrently.
def remove_rename_triggers_for_postgresql(table, trigger)
execute("DROP TRIGGER IF EXISTS #{trigger} ON #{table}")
execute("DROP FUNCTION IF EXISTS #{trigger}()")
end
- # Removes the triggers used for renaming a MySQL column concurrently.
- def remove_rename_triggers_for_mysql(trigger)
- execute("DROP TRIGGER IF EXISTS #{trigger}_insert")
- execute("DROP TRIGGER IF EXISTS #{trigger}_update")
- end
-
# Returns the (base) name to use for triggers when renaming columns.
def rename_trigger_name(table, old, new)
'trigger_' + Digest::SHA256.hexdigest("#{table}_#{old}_#{new}").first(12)
@@ -853,8 +810,6 @@ module Gitlab
order: index.orders
}
- # These options are not supported by MySQL, so we only add them if
- # they were previously set.
options[:using] = index.using if index.using
options[:where] = index.where if index.where
@@ -894,26 +849,16 @@ module Gitlab
end
# This will replace the first occurrence of a string in a column with
- # the replacement
- # On postgresql we can use `regexp_replace` for that.
- # On mysql we find the location of the pattern, and overwrite it
- # with the replacement
+ # the replacement using `regexp_replace`
def replace_sql(column, pattern, replacement)
quoted_pattern = Arel::Nodes::Quoted.new(pattern.to_s)
quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
- if Database.mysql?
- locate = Arel::Nodes::NamedFunction
- .new('locate', [quoted_pattern, column])
- insert_in_place = Arel::Nodes::NamedFunction
- .new('insert', [column, locate, pattern.size, quoted_replacement])
+ replace = Arel::Nodes::NamedFunction.new(
+ "regexp_replace", [column, quoted_pattern, quoted_replacement]
+ )
- Arel::Nodes::SqlLiteral.new(insert_in_place.to_sql)
- else
- replace = Arel::Nodes::NamedFunction
- .new("regexp_replace", [column, quoted_pattern, quoted_replacement])
- Arel::Nodes::SqlLiteral.new(replace.to_sql)
- end
+ Arel::Nodes::SqlLiteral.new(replace.to_sql)
end
def remove_foreign_key_if_exists(*args)
@@ -955,11 +900,7 @@ database (#{dbname}) using a super user and running:
ALTER #{user} WITH SUPERUSER
-For MySQL you instead need to run:
-
- GRANT ALL PRIVILEGES ON #{dbname}.* TO #{user}@'%'
-
-Both queries will grant the user super user permissions, ensuring you don't run
+This query will grant the user super user permissions, ensuring you don't run
into similar problems in the future (e.g. when new tables are created).
EOF
end
@@ -1062,10 +1003,6 @@ into similar problems in the future (e.g. when new tables are created).
# This will include indexes using an expression on the column, for example:
# `CREATE INDEX CONCURRENTLY index_name ON table (LOWER(column));`
#
- # For mysql, it falls back to the default ActiveRecord implementation that
- # will not find custom indexes. But it will select by name without passing
- # a column.
- #
# We can remove this when upgrading to Rails 5 with an updated `index_exists?`:
# - https://github.com/rails/rails/commit/edc2b7718725016e988089b5fb6d6fb9d6e16882
#
@@ -1076,10 +1013,8 @@ into similar problems in the future (e.g. when new tables are created).
# does not find indexes without passing a column name.
if indexes(table).map(&:name).include?(index.to_s)
true
- elsif Gitlab::Database.postgresql?
- postgres_exists_by_name?(table, index)
else
- false
+ postgres_exists_by_name?(table, index)
end
end
@@ -1095,8 +1030,26 @@ into similar problems in the future (e.g. when new tables are created).
connection.select_value(index_sql).to_i > 0
end
- def mysql_compatible_index_length
- Gitlab::Database.mysql? ? 20 : nil
+ private
+
+ def validate_timestamp_column_name!(column_name)
+ return if PERMITTED_TIMESTAMP_COLUMNS.member?(column_name)
+
+ raise <<~MESSAGE
+ Illegal timestamp column name! Got #{column_name}.
+ Must be one of: #{PERMITTED_TIMESTAMP_COLUMNS.to_a}
+ MESSAGE
+ end
+
+ def validate_not_in_transaction!(method_name, modifier = nil)
+ return unless transaction_open?
+
+ raise <<~ERROR
+ #{["`#{method_name}`", modifier].compact.join(' ')} cannot be run inside a transaction.
+
+ You can disable transactions by calling `disable_ddl_transaction!` in the body of
+ your migration class
+ ERROR
end
end
end
diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
index 60afa4bcd52..565f34b78b7 100644
--- a/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
+++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base.rb
@@ -51,14 +51,10 @@ module Gitlab
quoted_old_full_path = quote_string(old_full_path)
quoted_old_wildcard_path = quote_string("#{old_full_path}/%")
- filter = if Database.mysql?
- "lower(routes.path) = lower('#{quoted_old_full_path}') "\
- "OR routes.path LIKE '#{quoted_old_wildcard_path}'"
- else
- "routes.id IN "\
- "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
- "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
- end
+ filter =
+ "routes.id IN "\
+ "( SELECT routes.id FROM routes WHERE lower(routes.path) = lower('#{quoted_old_full_path}') "\
+ "UNION SELECT routes.id FROM routes WHERE routes.path ILIKE '#{quoted_old_wildcard_path}' )"
replace_statement = replace_sql(Route.arel_table[:path],
old_full_path,
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index 109ae7893da..ddbabc9098e 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -2,14 +2,9 @@
module Gitlab
module Database
- BINARY_TYPE =
- if Gitlab::Database.postgresql?
- # PostgreSQL defines its own class with slightly different
- # behaviour from the default Binary type.
- ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
- else
- ActiveModel::Type::Binary
- end
+ # PostgreSQL defines its own class with slightly different
+ # behaviour from the default Binary type.
+ BINARY_TYPE = ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Bytea
# Class for casting binary data to hexadecimal SHA1 hashes (and vice-versa).
#
diff --git a/lib/gitlab/database/subquery.rb b/lib/gitlab/database/subquery.rb
index 10971d2b274..2a6f39c6a27 100644
--- a/lib/gitlab/database/subquery.rb
+++ b/lib/gitlab/database/subquery.rb
@@ -6,11 +6,7 @@ module Gitlab
class << self
def self_join(relation)
t = relation.arel_table
- # Work around a bug in Rails 5, where LIMIT causes trouble
- # See https://gitlab.com/gitlab-org/gitlab-ce/issues/51729
- r = relation.limit(nil).arel
- r.take(relation.limit_value) if relation.limit_value
- t2 = r.as('t2')
+ t2 = relation.arel.as('t2')
relation.unscoped.joins(t.join(t2).on(t[:id].eq(t2[:id])).join_sources.first)
end
diff --git a/lib/gitlab/ee_compat_check.rb b/lib/gitlab/ee_compat_check.rb
index 01fd261404b..86e532766b1 100644
--- a/lib/gitlab/ee_compat_check.rb
+++ b/lib/gitlab/ee_compat_check.rb
@@ -7,7 +7,7 @@ module Gitlab
CANONICAL_CE_PROJECT_URL = 'https://gitlab.com/gitlab-org/gitlab-ce'.freeze
CANONICAL_EE_REPO_URL = 'https://gitlab.com/gitlab-org/gitlab-ee.git'.freeze
CHECK_DIR = Rails.root.join('ee_compat_check')
- IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md/i.freeze
+ IGNORED_FILES_REGEX = /VERSION|CHANGELOG\.md|doc\/.+/i.freeze
PLEASE_READ_THIS_BANNER = %Q{
============================================================
===================== PLEASE READ THIS =====================
diff --git a/lib/gitlab/etag_caching/router.rb b/lib/gitlab/etag_caching/router.rb
index 17fbecbd097..d09dcdbb337 100644
--- a/lib/gitlab/etag_caching/router.rb
+++ b/lib/gitlab/etag_caching/router.rb
@@ -61,6 +61,10 @@ module Gitlab
Gitlab::EtagCaching::Router::Route.new(
%r(#{RESERVED_WORDS_PREFIX}/import/gitea/realtime_changes\.json\z),
'realtime_changes_import_gitea'
+ ),
+ Gitlab::EtagCaching::Router::Route.new(
+ %r(#{RESERVED_WORDS_PREFIX}/merge_requests/\d+/cached_widget\.json\z),
+ 'merge_request_widget'
)
].freeze
diff --git a/lib/gitlab/exclusive_lease_helpers.rb b/lib/gitlab/exclusive_lease_helpers.rb
index 7961d4bbd6e..61eb030563d 100644
--- a/lib/gitlab/exclusive_lease_helpers.rb
+++ b/lib/gitlab/exclusive_lease_helpers.rb
@@ -15,17 +15,18 @@ module Gitlab
raise ArgumentError, 'Key needs to be specified' unless key
lease = Gitlab::ExclusiveLease.new(key, timeout: ttl)
+ retried = false
until uuid = lease.try_obtain
# Keep trying until we obtain the lease. To prevent hammering Redis too
# much we'll wait for a bit.
sleep(sleep_sec)
- break if (retries -= 1) < 0
+ (retries -= 1) < 0 ? break : retried ||= true
end
raise FailedToObtainLockError, 'Failed to obtain a lock' unless uuid
- yield
+ yield(retried)
ensure
Gitlab::ExclusiveLease.cancel(key, uuid)
end
diff --git a/lib/gitlab/gfm/uploads_rewriter.rb b/lib/gitlab/gfm/uploads_rewriter.rb
index 2d1c9ac40ae..6b52d6e88e5 100644
--- a/lib/gitlab/gfm/uploads_rewriter.rb
+++ b/lib/gitlab/gfm/uploads_rewriter.rb
@@ -27,7 +27,15 @@ module Gitlab
klass = target_parent.is_a?(Namespace) ? NamespaceFileUploader : FileUploader
moved = klass.copy_to(file, target_parent)
- moved.markdown_link
+
+ moved_markdown = moved.markdown_link
+
+ # Prevents rewrite of plain links as embedded
+ if was_embedded?(markdown)
+ moved_markdown
+ else
+ moved_markdown.sub(/\A!/, "")
+ end
end
end
@@ -43,6 +51,10 @@ module Gitlab
referenced_files.compact.select(&:exists?)
end
+ def was_embedded?(markdown)
+ markdown.starts_with?("!")
+ end
+
private
def find_file(project, secret, file)
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
index 44a62586a23..df9f33baec2 100644
--- a/lib/gitlab/git.rb
+++ b/lib/gitlab/git.rb
@@ -9,6 +9,7 @@ module Gitlab
# https://github.com/git/git/blob/3ad8b5bf26362ac67c9020bf8c30eee54a84f56d/cache.h#L1011-L1012
EMPTY_TREE_ID = '4b825dc642cb6eb9a060e54bf8d69288fbee4904'.freeze
BLANK_SHA = ('0' * 40).freeze
+ COMMIT_ID = /\A[0-9a-f]{40}\z/.freeze
TAG_REF_PREFIX = "refs/tags/".freeze
BRANCH_REF_PREFIX = "refs/heads/".freeze
@@ -65,6 +66,10 @@ module Gitlab
ref == BLANK_SHA
end
+ def commit_id?(ref)
+ COMMIT_ID.match?(ref)
+ end
+
def version
Gitlab::Git::Version.git_version
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index a7d9ba51277..27032602828 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -55,6 +55,10 @@ module Gitlab
@name = @relative_path.split("/").last
end
+ def to_s
+ "<#{self.class.name}: #{self.gl_project_path}>"
+ end
+
def ==(other)
other.is_a?(self.class) && [storage, relative_path] == [other.storage, other.relative_path]
end
@@ -873,13 +877,13 @@ module Gitlab
def multi_action(
user, branch_name:, message:, actions:,
author_email: nil, author_name: nil,
- start_branch_name: nil, start_repository: self,
+ start_branch_name: nil, start_sha: nil, start_repository: self,
force: false)
wrapped_gitaly_errors do
gitaly_operation_client.user_commit_files(user, branch_name,
message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
end
end
# rubocop:enable Metrics/ParameterLists
diff --git a/lib/gitlab/git/rugged_impl/blob.rb b/lib/gitlab/git/rugged_impl/blob.rb
index 86c9f33d82a..5c73c0c66a9 100644
--- a/lib/gitlab/git/rugged_impl/blob.rb
+++ b/lib/gitlab/git/rugged_impl/blob.rb
@@ -16,7 +16,7 @@ module Gitlab
override :tree_entry
def tree_entry(repository, sha, path, limit)
if use_rugged?(repository, :rugged_tree_entry)
- rugged_tree_entry(repository, sha, path, limit)
+ execute_rugged_call(:rugged_tree_entry, repository, sha, path, limit)
else
super
end
diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb
index 971a33b2e99..0eff35ab1c4 100644
--- a/lib/gitlab/git/rugged_impl/commit.rb
+++ b/lib/gitlab/git/rugged_impl/commit.rb
@@ -36,7 +36,7 @@ module Gitlab
override :find_commit
def find_commit(repo, commit_id)
if use_rugged?(repo, :rugged_find_commit)
- rugged_find(repo, commit_id)
+ execute_rugged_call(:rugged_find, repo, commit_id)
else
super
end
@@ -45,7 +45,7 @@ module Gitlab
override :batch_by_oid
def batch_by_oid(repo, oids)
if use_rugged?(repo, :rugged_list_commits_by_oid)
- rugged_batch_by_oid(repo, oids)
+ execute_rugged_call(:rugged_batch_by_oid, repo, oids)
else
super
end
@@ -68,7 +68,7 @@ module Gitlab
override :commit_tree_entry
def commit_tree_entry(path)
if use_rugged?(@repository, :rugged_commit_tree_entry)
- rugged_tree_entry(path)
+ execute_rugged_call(:rugged_tree_entry, path)
else
super
end
diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb
index 9268abdfed9..8fde93e71e2 100644
--- a/lib/gitlab/git/rugged_impl/repository.rb
+++ b/lib/gitlab/git/rugged_impl/repository.rb
@@ -48,7 +48,7 @@ module Gitlab
override :ancestor?
def ancestor?(from, to)
if use_rugged?(self, :rugged_commit_is_ancestor)
- rugged_is_ancestor?(from, to)
+ execute_rugged_call(:rugged_is_ancestor?, from, to)
else
super
end
diff --git a/lib/gitlab/git/rugged_impl/tree.rb b/lib/gitlab/git/rugged_impl/tree.rb
index f3721a3f1b7..389c9d32ccb 100644
--- a/lib/gitlab/git/rugged_impl/tree.rb
+++ b/lib/gitlab/git/rugged_impl/tree.rb
@@ -16,7 +16,7 @@ module Gitlab
override :tree_entries
def tree_entries(repository, sha, path, recursive)
if use_rugged?(repository, :rugged_tree_entries)
- tree_entries_with_flat_path_from_rugged(repository, sha, path, recursive)
+ execute_rugged_call(:tree_entries_with_flat_path_from_rugged, repository, sha, path, recursive)
else
super
end
diff --git a/lib/gitlab/git/rugged_impl/use_rugged.rb b/lib/gitlab/git/rugged_impl/use_rugged.rb
index 99091b03cd1..80b75689334 100644
--- a/lib/gitlab/git/rugged_impl/use_rugged.rb
+++ b/lib/gitlab/git/rugged_impl/use_rugged.rb
@@ -10,6 +10,29 @@ module Gitlab
Gitlab::GitalyClient.can_use_disk?(repo.storage)
end
+
+ def execute_rugged_call(method_name, *args)
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ start = Gitlab::Metrics::System.monotonic_time
+
+ result = send(method_name, *args) # rubocop:disable GitlabSecurity/PublicSend
+
+ duration = Gitlab::Metrics::System.monotonic_time - start
+
+ if Gitlab::RuggedInstrumentation.active?
+ Gitlab::RuggedInstrumentation.increment_query_count
+ Gitlab::RuggedInstrumentation.query_time += duration
+
+ Gitlab::RuggedInstrumentation.add_call_details(
+ feature: method_name,
+ args: args,
+ duration: duration,
+ backtrace: Gitlab::Profiler.clean_backtrace(caller))
+ end
+
+ result
+ end
+ end
end
end
end
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index dac4ddd320f..545451f0dc9 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,13 +1,9 @@
# frozen_string_literal: true
module Gitlab
- class GitLogger < Gitlab::Logger
+ class GitLogger < JsonLogger
def self.file_name_noext
- 'githost'
- end
-
- def format_message(severity, timestamp, progname, msg)
- "#{timestamp.to_s(:long)} -> #{severity} -> #{msg}\n"
+ 'git_json'
end
end
end
diff --git a/lib/gitlab/git_post_receive.rb b/lib/gitlab/git_post_receive.rb
index d98b85fecc4..2a8bcd015a8 100644
--- a/lib/gitlab/git_post_receive.rb
+++ b/lib/gitlab/git_post_receive.rb
@@ -27,6 +27,29 @@ module Gitlab
end
end
+ def includes_branches?
+ enum_for(:changes_refs).any? do |_oldrev, _newrev, ref|
+ Gitlab::Git.branch_ref?(ref)
+ end
+ end
+
+ def includes_tags?
+ enum_for(:changes_refs).any? do |_oldrev, _newrev, ref|
+ Gitlab::Git.tag_ref?(ref)
+ end
+ end
+
+ def includes_default_branch?
+ # If the branch doesn't have a default branch yet, we presume the
+ # first branch pushed will be the default.
+ return true unless project.default_branch.present?
+
+ enum_for(:changes_refs).any? do |_oldrev, _newrev, ref|
+ Gitlab::Git.branch_ref?(ref) &&
+ Gitlab::Git.branch_name(ref) == project.default_branch
+ end
+ end
+
private
def deserialize_changes(changes)
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 0b6321c66ab..e6cbfb00f60 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -240,7 +240,7 @@ module Gitlab
# Ensures that Gitaly is not being abuse through n+1 misuse etc
def self.enforce_gitaly_request_limits(call_site)
- # Only count limits in request-response environments (not sidekiq for example)
+ # Only count limits in request-response environments
return unless Gitlab::SafeRequestStore.active?
# This is this actual number of times this call was made. Used for information purposes only
@@ -387,21 +387,20 @@ module Gitlab
end
def self.can_use_disk?(storage)
- false
- # cached_value = MUTEX.synchronize do
- # @can_use_disk ||= {}
- # @can_use_disk[storage]
- # end
+ cached_value = MUTEX.synchronize do
+ @can_use_disk ||= {}
+ @can_use_disk[storage]
+ end
- # return cached_value unless cached_value.nil?
+ return cached_value unless cached_value.nil?
- # gitaly_filesystem_id = filesystem_id(storage)
- # direct_filesystem_id = filesystem_id_from_disk(storage)
+ gitaly_filesystem_id = filesystem_id(storage)
+ direct_filesystem_id = filesystem_id_from_disk(storage)
- # MUTEX.synchronize do
- # @can_use_disk[storage] = gitaly_filesystem_id.present? &&
- # gitaly_filesystem_id == direct_filesystem_id
- # end
+ MUTEX.synchronize do
+ @can_use_disk[storage] = gitaly_filesystem_id.present? &&
+ gitaly_filesystem_id == direct_filesystem_id
+ end
end
def self.filesystem_id(storage)
@@ -414,7 +413,7 @@ module Gitlab
metadata_file = File.read(storage_metadata_file_path(storage))
metadata_hash = JSON.parse(metadata_file)
metadata_hash['gitaly_filesystem_id']
- rescue Errno::ENOENT, JSON::ParserError
+ rescue Errno::ENOENT, Errno::EACCES, JSON::ParserError
nil
end
diff --git a/lib/gitlab/gitaly_client/notification_service.rb b/lib/gitlab/gitaly_client/notification_service.rb
deleted file mode 100644
index 873c3e4086d..00000000000
--- a/lib/gitlab/gitaly_client/notification_service.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module GitalyClient
- class NotificationService
- # 'repository' is a Gitlab::Git::Repository
- def initialize(repository)
- @gitaly_repo = repository.gitaly_repository
- @storage = repository.storage
- end
-
- def post_receive
- GitalyClient.call(
- @storage,
- :notification_service,
- :post_receive,
- Gitaly::PostReceiveRequest.new(repository: @gitaly_repo)
- )
- end
- end
- end
-end
diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb
index 783c2ff0915..33ca428a942 100644
--- a/lib/gitlab/gitaly_client/operation_service.rb
+++ b/lib/gitlab/gitaly_client/operation_service.rb
@@ -325,11 +325,11 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force = false)
+ start_branch_name, start_repository, force = false, start_sha = nil)
req_enum = Enumerator.new do |y|
header = user_commit_files_request_header(user, branch_name,
commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
y.yield Gitaly::UserCommitFilesRequest.new(header: header)
@@ -445,7 +445,7 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def user_commit_files_request_header(
user, branch_name, commit_message, actions, author_email, author_name,
- start_branch_name, start_repository, force)
+ start_branch_name, start_repository, force, start_sha)
Gitaly::UserCommitFilesRequestHeader.new(
repository: @gitaly_repo,
@@ -456,7 +456,8 @@ module Gitlab
commit_author_email: encode_binary(author_email),
start_branch_name: encode_binary(start_branch_name),
start_repository: start_repository.gitaly_repository,
- force: force
+ force: force,
+ start_sha: encode_binary(start_sha)
)
end
# rubocop:enable Metrics/ParameterLists
diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb
index a61beafae0d..826b35d685c 100644
--- a/lib/gitlab/github_import/client.rb
+++ b/lib/gitlab/github_import/client.rb
@@ -40,7 +40,7 @@ module Gitlab
# otherwise hitting the rate limit will result in a thread
# being blocked in a `sleep()` call for up to an hour.
def initialize(token, per_page: 100, parallel: true)
- @octokit = Octokit::Client.new(
+ @octokit = ::Octokit::Client.new(
access_token: token,
per_page: per_page,
api_endpoint: api_endpoint
@@ -139,7 +139,7 @@ module Gitlab
begin
yield
- rescue Octokit::TooManyRequests
+ rescue ::Octokit::TooManyRequests
raise_or_wait_for_rate_limit
# This retry will only happen when running in sequential mode as we'll
diff --git a/lib/gitlab/github_import/importer/releases_importer.rb b/lib/gitlab/github_import/importer/releases_importer.rb
index 0e7c9ee0d00..9d925581441 100644
--- a/lib/gitlab/github_import/importer/releases_importer.rb
+++ b/lib/gitlab/github_import/importer/releases_importer.rb
@@ -36,6 +36,7 @@ module Gitlab
description: description_for(release),
created_at: release.created_at,
updated_at: release.updated_at,
+ released_at: release.published_at,
project_id: project.id
}
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 41ec8741eb1..92917028851 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -38,11 +38,6 @@ module Gitlab
gon.current_user_fullname = current_user.name
gon.current_user_avatar_url = current_user.avatar_url
end
-
- # Flag controls a GFM feature used across many routes.
- # Pushing the flag from one place simplifies control
- # and facilitates easy removal.
- push_frontend_feature_flag(:gfm_embedded_metrics)
end
# Exposes the state of a feature flag to the frontend code.
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/grape_logging/loggers/perf_logger.rb b/lib/gitlab/grape_logging/loggers/perf_logger.rb
index 18ea3a8d2f3..7e86b35a215 100644
--- a/lib/gitlab/grape_logging/loggers/perf_logger.rb
+++ b/lib/gitlab/grape_logging/loggers/perf_logger.rb
@@ -6,11 +6,30 @@ module Gitlab
module Loggers
class PerfLogger < ::GrapeLogging::Loggers::Base
def parameters(_, _)
+ gitaly_data.merge(rugged_data)
+ end
+
+ def gitaly_data
+ gitaly_calls = Gitlab::GitalyClient.get_request_count
+
+ return {} if gitaly_calls.zero?
+
{
gitaly_calls: Gitlab::GitalyClient.get_request_count,
gitaly_duration: Gitlab::GitalyClient.query_time_ms
}
end
+
+ def rugged_data
+ rugged_calls = Gitlab::RuggedInstrumentation.query_count
+
+ return {} if rugged_calls.zero?
+
+ {
+ rugged_calls: rugged_calls,
+ rugged_duration_ms: Gitlab::RuggedInstrumentation.query_time_ms
+ }
+ end
end
end
end
diff --git a/lib/gitlab/health_checks/db_check.rb b/lib/gitlab/health_checks/db_check.rb
index 2bcd25cd3cc..ec4b97eaca4 100644
--- a/lib/gitlab/health_checks/db_check.rb
+++ b/lib/gitlab/health_checks/db_check.rb
@@ -18,11 +18,7 @@ module Gitlab
def check
catch_timeout 10.seconds do
- if Gitlab::Database.postgresql?
- ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')&.to_s
- else
- ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.first&.to_s
- end
+ ActiveRecord::Base.connection.execute('SELECT 1 as ping')&.first&.[]('ping')&.to_s
end
end
end
diff --git a/lib/gitlab/http_connection_adapter.rb b/lib/gitlab/http_connection_adapter.rb
index 41eab3658bc..84eb60f3a5d 100644
--- a/lib/gitlab/http_connection_adapter.rb
+++ b/lib/gitlab/http_connection_adapter.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# This class is part of the Gitlab::HTTP wrapper. Depending on the value
-# of the global setting allow_local_requests_from_hooks_and_services this adapter
+# of the global setting allow_local_requests_from_web_hooks_and_services this adapter
# will allow/block connection to internal IPs and/or urls.
#
# This functionality can be overridden by providing the setting the option
@@ -38,7 +38,7 @@ module Gitlab
end
def allow_settings_local_requests?
- Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end
end
diff --git a/lib/gitlab/import/database_helpers.rb b/lib/gitlab/import/database_helpers.rb
index 5b3f30d894a..aaade39dd62 100644
--- a/lib/gitlab/import/database_helpers.rb
+++ b/lib/gitlab/import/database_helpers.rb
@@ -6,9 +6,7 @@ module Gitlab
# Inserts a raw row and returns the ID of the inserted row.
#
# attributes - The attributes/columns to set.
- # relation - An ActiveRecord::Relation to use for finding the ID of the row
- # when using MySQL.
- # rubocop: disable CodeReuse/ActiveRecord
+ # relation - An ActiveRecord::Relation to use for finding the table name
def insert_and_return_id(attributes, relation)
# We use bulk_insert here so we can bypass any queries executed by
# callbacks or validation rules, as doing this wouldn't scale when
@@ -16,12 +14,8 @@ module Gitlab
result = Gitlab::Database
.bulk_insert(relation.table_name, [attributes], return_ids: true)
- # MySQL doesn't support returning the IDs of a bulk insert in a way that
- # is not a pain, so in this case we'll issue an extra query instead.
- result.first ||
- relation.where(iid: attributes[:iid]).limit(1).pluck(:id).first
+ result.first
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb
index f63a5ece71e..bb46bd657e8 100644
--- a/lib/gitlab/import_export.rb
+++ b/lib/gitlab/import_export.rb
@@ -4,7 +4,9 @@ module Gitlab
module ImportExport
extend self
- # For every version update, the version history in import_export.md has to be kept up to date.
+ # For every version update the version history in these docs must be kept up to date:
+ # - development/import_export.md
+ # - user/project/settings/import_export.md
VERSION = '0.2.4'.freeze
FILENAME_LIMIT = 50
@@ -28,6 +30,14 @@ module Gitlab
"project.bundle"
end
+ def lfs_objects_filename
+ "lfs-objects.json"
+ end
+
+ def lfs_objects_storage
+ "lfs-objects"
+ end
+
def config_file
Rails.root.join('lib/gitlab/import_export/import_export.yml')
end
diff --git a/lib/gitlab/import_export/attributes_finder.rb b/lib/gitlab/import_export/attributes_finder.rb
index 409243e68a5..42cd94add79 100644
--- a/lib/gitlab/import_export/attributes_finder.rb
+++ b/lib/gitlab/import_export/attributes_finder.rb
@@ -45,7 +45,7 @@ module Gitlab
end
def key_from_hash(value)
- value.is_a?(Hash) ? value.keys.first : value
+ value.is_a?(Hash) ? value.first.first : value
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 01437c67fa9..bd0f3e70749 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -37,11 +37,11 @@ project_tree:
- :user
- merge_requests:
- :metrics
- - :suggestions
- notes:
- :author
- events:
- :push_event_payload
+ - :suggestions
- merge_request_diff:
- :merge_request_diff_commits
- :merge_request_diff_files
@@ -80,6 +80,10 @@ project_tree:
- :ci_cd_settings
- :error_tracking_setting
- :metrics_setting
+ - boards:
+ - lists:
+ - label:
+ - :priorities
# Only include the following attributes for the models specified.
included_attributes:
@@ -133,6 +137,7 @@ excluded_attributes:
- :packages_enabled
- :mirror_last_update_at
- :mirror_last_successful_update_at
+ - :emails_disabled
namespaces:
- :runners_token
- :runners_token_encrypted
@@ -216,6 +221,8 @@ methods:
- :action
project_badges:
- :type
+ lists:
+ - :list_type
# EE specific relationships and settings to include. All of this will be merged
# into the previous structures if EE is used.
diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb
index b145f37c052..a92e3862361 100644
--- a/lib/gitlab/import_export/json_hash_builder.rb
+++ b/lib/gitlab/import_export/json_hash_builder.rb
@@ -27,7 +27,7 @@ module Gitlab
# {:merge_requests=>[:merge_request_diff, :notes]}
def process_model_objects(model_object_hash)
json_config_hash = {}
- current_key = model_object_hash.keys.first
+ current_key = model_object_hash.first.first
model_object_hash.values.flatten.each do |model_object|
@attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash }
diff --git a/lib/gitlab/import_export/lfs_restorer.rb b/lib/gitlab/import_export/lfs_restorer.rb
index 345c7880e30..1de8a5bf9ec 100644
--- a/lib/gitlab/import_export/lfs_restorer.rb
+++ b/lib/gitlab/import_export/lfs_restorer.rb
@@ -3,6 +3,10 @@
module Gitlab
module ImportExport
class LfsRestorer
+ include Gitlab::Utils::StrongMemoize
+
+ attr_accessor :project, :shared
+
def initialize(project:, shared:)
@project = project
@shared = shared
@@ -17,7 +21,7 @@ module Gitlab
true
rescue => e
- @shared.error(e)
+ shared.error(e)
false
end
@@ -29,16 +33,57 @@ module Gitlab
lfs_object = LfsObject.find_or_initialize_by(oid: oid, size: size)
lfs_object.file = File.open(path) unless lfs_object.file&.exists?
+ lfs_object.save! if lfs_object.changed?
- @project.all_lfs_objects << lfs_object
+ repository_types(oid).each do |repository_type|
+ LfsObjectsProject.create!(
+ project: project,
+ lfs_object: lfs_object,
+ repository_type: repository_type
+ )
+ end
+ end
+
+ def repository_types(oid)
+ # We allow support for imports created before the `lfs-objects.json`
+ # file was generated. In this case, the restorer will link an LFS object
+ # with a single `lfs_objects_projects` relation.
+ #
+ # This allows us backwards-compatibility without version bumping.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30830#note_192608870
+ return ['project'] unless has_lfs_json?
+
+ lfs_json[oid]
end
def lfs_file_paths
@lfs_file_paths ||= Dir.glob("#{lfs_storage_path}/*")
end
+ def has_lfs_json?
+ strong_memoize(:has_lfs_json) do
+ File.exist?(lfs_json_path)
+ end
+ end
+
+ def lfs_json
+ return {} unless has_lfs_json?
+
+ @lfs_json ||=
+ begin
+ json = IO.read(lfs_json_path)
+ ActiveSupport::JSON.decode(json)
+ rescue
+ raise Gitlab::ImportExport::Error.new('Incorrect JSON format')
+ end
+ end
+
def lfs_storage_path
- File.join(@shared.export_path, 'lfs-objects')
+ File.join(shared.export_path, ImportExport.lfs_objects_storage)
+ end
+
+ def lfs_json_path
+ File.join(shared.export_path, ImportExport.lfs_objects_filename)
end
end
end
diff --git a/lib/gitlab/import_export/lfs_saver.rb b/lib/gitlab/import_export/lfs_saver.rb
index 954f6f00078..18c590e1ca9 100644
--- a/lib/gitlab/import_export/lfs_saver.rb
+++ b/lib/gitlab/import_export/lfs_saver.rb
@@ -5,25 +5,40 @@ module Gitlab
class LfsSaver
include Gitlab::ImportExport::CommandLineUtil
+ attr_accessor :lfs_json, :project, :shared
+
+ BATCH_SIZE = 100
+
def initialize(project:, shared:)
@project = project
@shared = shared
+ @lfs_json = {}
end
def save
- @project.all_lfs_objects.each do |lfs_object|
- save_lfs_object(lfs_object)
+ project.all_lfs_objects.find_in_batches(batch_size: BATCH_SIZE) do |batch|
+ batch.each do |lfs_object|
+ save_lfs_object(lfs_object)
+ end
+
+ append_lfs_json_for_batch(batch) if write_lfs_json_enabled?
end
+ write_lfs_json if write_lfs_json_enabled?
+
true
rescue => e
- @shared.error(e)
+ shared.error(e)
false
end
private
+ def write_lfs_json_enabled?
+ ::Feature.enabled?(:export_lfs_objects_projects, default_enabled: true)
+ end
+
def save_lfs_object(lfs_object)
if lfs_object.local_store?
copy_file_for_lfs_object(lfs_object)
@@ -45,12 +60,36 @@ module Gitlab
copy_files(lfs_object.file.path, destination_path_for_object(lfs_object))
end
+ def append_lfs_json_for_batch(lfs_objects_batch)
+ lfs_objects_projects = LfsObjectsProject
+ .select('lfs_objects.oid, array_agg(distinct lfs_objects_projects.repository_type) as repository_types')
+ .joins(:lfs_object)
+ .where(project: project, lfs_object: lfs_objects_batch)
+ .group('lfs_objects.oid')
+
+ lfs_objects_projects.each do |group|
+ oid = group.oid
+
+ lfs_json[oid] ||= []
+ lfs_json[oid] += group.repository_types
+ end
+ end
+
+ def write_lfs_json
+ mkdir_p(shared.export_path)
+ File.write(lfs_json_path, lfs_json.to_json)
+ end
+
def destination_path_for_object(lfs_object)
File.join(lfs_export_path, lfs_object.oid)
end
def lfs_export_path
- File.join(@shared.export_path, 'lfs-objects')
+ File.join(shared.export_path, ImportExport.lfs_objects_storage)
+ end
+
+ def lfs_json_path
+ File.join(shared.export_path, ImportExport.lfs_objects_filename)
end
end
end
diff --git a/lib/gitlab/import_export/members_mapper.rb b/lib/gitlab/import_export/members_mapper.rb
index a154de5419e..4e976cfca3a 100644
--- a/lib/gitlab/import_export/members_mapper.rb
+++ b/lib/gitlab/import_export/members_mapper.rb
@@ -35,7 +35,7 @@ module Gitlab
end
def include?(old_author_id)
- map.keys.include?(old_author_id) && map[old_author_id] != default_user_id
+ map.has_key?(old_author_id) && map[old_author_id] != default_user_id
end
private
@@ -50,6 +50,8 @@ module Gitlab
@project.project_members.destroy_all # rubocop: disable DestroyAll
ProjectMember.create!(user: @user, access_level: ProjectMember::MAINTAINER, source_id: @project.id, importing: true)
+ rescue => e
+ raise e, "Error adding importer user to project members. #{e.message}"
end
def add_team_member(member, existing_user = nil)
diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb
index dec99c23a2d..91fe4e5d074 100644
--- a/lib/gitlab/import_export/project_tree_restorer.rb
+++ b/lib/gitlab/import_export/project_tree_restorer.rb
@@ -130,6 +130,7 @@ module Gitlab
def visibility_level
level = override_params['visibility_level'] || json_params['visibility_level'] || @project.visibility_level
level = @project.group.visibility_level if @project.group && level.to_i > @project.group.visibility_level
+ level = Gitlab::VisibilityLevel::PRIVATE if level == Gitlab::VisibilityLevel::INTERNAL && Gitlab::CurrentSettings.restricted_visibility_levels.include?(level)
{ 'visibility_level' => level }
end
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 1b545b1d049..0be49e27acb 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -185,7 +185,7 @@ module Gitlab
return unless EXISTING_OBJECT_CHECK.include?(@relation_name)
return unless @relation_hash['group_id']
- @relation_hash['group_id'] = @project.group&.id
+ @relation_hash['group_id'] = @project.namespace_id
end
def reset_tokens!
diff --git a/lib/gitlab/instrumentation_helper.rb b/lib/gitlab/instrumentation_helper.rb
new file mode 100644
index 00000000000..e6a5facb2a5
--- /dev/null
+++ b/lib/gitlab/instrumentation_helper.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module InstrumentationHelper
+ extend self
+
+ KEYS = %i(gitaly_calls gitaly_duration rugged_calls rugged_duration_ms).freeze
+
+ def add_instrumentation_data(payload)
+ gitaly_calls = Gitlab::GitalyClient.get_request_count
+
+ if gitaly_calls > 0
+ payload[:gitaly_calls] = gitaly_calls
+ payload[:gitaly_duration] = Gitlab::GitalyClient.query_time_ms
+ end
+
+ rugged_calls = Gitlab::RuggedInstrumentation.query_count
+
+ if rugged_calls > 0
+ payload[:rugged_calls] = rugged_calls
+ payload[:rugged_duration_ms] = Gitlab::RuggedInstrumentation.query_time_ms
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/default_namespace.rb b/lib/gitlab/kubernetes/default_namespace.rb
new file mode 100644
index 00000000000..c95362b024b
--- /dev/null
+++ b/lib/gitlab/kubernetes/default_namespace.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ class DefaultNamespace
+ attr_reader :cluster, :project
+
+ delegate :platform_kubernetes, to: :cluster
+
+ ##
+ # Ideally we would just use an environment record here instead of
+ # passing a project and name/slug separately, but we need to be able
+ # to look up namespaces before the environment has been persisted.
+ def initialize(cluster, project:)
+ @cluster = cluster
+ @project = project
+ end
+
+ def from_environment_name(name)
+ from_environment_slug(generate_slug(name))
+ end
+
+ def from_environment_slug(slug)
+ default_platform_namespace(slug) || default_project_namespace(slug)
+ end
+
+ private
+
+ def default_platform_namespace(slug)
+ return unless platform_kubernetes&.namespace.present?
+
+ if cluster.managed? && cluster.namespace_per_environment?
+ "#{platform_kubernetes.namespace}-#{slug}"
+ else
+ platform_kubernetes.namespace
+ end
+ end
+
+ def default_project_namespace(slug)
+ namespace_slug = "#{project.path}-#{project.id}".downcase
+
+ if cluster.namespace_per_environment?
+ namespace_slug += "-#{slug}"
+ end
+
+ Gitlab::NamespaceSanitizer.sanitize(namespace_slug)
+ end
+
+ ##
+ # Environment slug can be predicted given an environment
+ # name, so even if the environment isn't persisted yet we
+ # still know what to look for.
+ def generate_slug(name)
+ Gitlab::Slug::Environment.new(name).generate
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/helm.rb b/lib/gitlab/kubernetes/helm.rb
index 42c4745ff98..6e4286589c1 100644
--- a/lib/gitlab/kubernetes/helm.rb
+++ b/lib/gitlab/kubernetes/helm.rb
@@ -3,8 +3,8 @@
module Gitlab
module Kubernetes
module Helm
- HELM_VERSION = '2.12.3'.freeze
- KUBECTL_VERSION = '1.11.7'.freeze
+ HELM_VERSION = '2.14.3'.freeze
+ KUBECTL_VERSION = '1.11.10'.freeze
NAMESPACE = 'gitlab-managed-apps'.freeze
SERVICE_ACCOUNT = 'tiller'.freeze
CLUSTER_ROLE_BINDING = 'tiller-admin'.freeze
diff --git a/lib/gitlab/kubernetes/helm/delete_command.rb b/lib/gitlab/kubernetes/helm/delete_command.rb
index 876994d2678..dcf22e7abb6 100644
--- a/lib/gitlab/kubernetes/helm/delete_command.rb
+++ b/lib/gitlab/kubernetes/helm/delete_command.rb
@@ -7,19 +7,24 @@ module Gitlab
include BaseCommand
include ClientCommand
+ attr_reader :predelete, :postdelete
attr_accessor :name, :files
- def initialize(name:, rbac:, files:)
+ def initialize(name:, rbac:, files:, predelete: nil, postdelete: nil)
@name = name
@files = files
@rbac = rbac
+ @predelete = predelete
+ @postdelete = postdelete
end
def generate_script
super + [
init_command,
wait_for_tiller_command,
- delete_command
+ predelete,
+ delete_command,
+ postdelete
].compact.join("\n")
end
@@ -38,17 +43,6 @@ module Gitlab
command.shelljoin
end
-
- def optional_tls_flags
- return [] unless files.key?(:'ca.pem')
-
- [
- '--tls',
- '--tls-ca-cert', "#{files_dir}/ca.pem",
- '--tls-cert', "#{files_dir}/cert.pem",
- '--tls-key', "#{files_dir}/key.pem"
- ]
- end
end
end
end
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
index 9744a5f3d8a..f572bc43533 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -27,9 +27,9 @@ module Gitlab
wait_for_tiller_command,
repository_command,
repository_update_command,
- preinstall_command,
+ preinstall,
install_command,
- postinstall_command
+ postinstall
].compact.join("\n")
end
@@ -58,14 +58,6 @@ module Gitlab
command.shelljoin
end
- def preinstall_command
- preinstall.join("\n") if preinstall
- end
-
- def postinstall_command
- postinstall.join("\n") if postinstall
- end
-
def install_flag
['--install']
end
diff --git a/lib/gitlab/kubernetes/helm/reset_command.rb b/lib/gitlab/kubernetes/helm/reset_command.rb
new file mode 100644
index 00000000000..a35ffa34c58
--- /dev/null
+++ b/lib/gitlab/kubernetes/helm/reset_command.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module Helm
+ class ResetCommand
+ include BaseCommand
+ include ClientCommand
+
+ attr_reader :name, :files
+
+ def initialize(name:, rbac:, files:)
+ @name = name
+ @files = files
+ @rbac = rbac
+ end
+
+ def generate_script
+ super + [
+ reset_helm_command,
+ delete_tiller_replicaset
+ ].join("\n")
+ end
+
+ def rbac?
+ @rbac
+ end
+
+ def pod_name
+ "uninstall-#{name}"
+ end
+
+ private
+
+ # This method can be delete once we upgrade Helm to > 12.13.0
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/27096#note_159695900
+ #
+ # Tracking this method to be removed here:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/52791#note_199374155
+ def delete_tiller_replicaset
+ delete_args = %w[replicaset -n gitlab-managed-apps -l name=tiller]
+
+ Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
+ end
+
+ def reset_helm_command
+ command = %w[helm reset] + optional_tls_flags
+
+ command.shelljoin
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/kubernetes/kube_client.rb b/lib/gitlab/kubernetes/kube_client.rb
index 1350924cd76..64317225ec6 100644
--- a/lib/gitlab/kubernetes/kube_client.rb
+++ b/lib/gitlab/kubernetes/kube_client.rb
@@ -128,7 +128,7 @@ module Gitlab
private
def validate_url!
- return if Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
+ return if Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
Gitlab::UrlBlocker.validate!(api_prefix, allow_local_network: false)
end
diff --git a/lib/gitlab/kubernetes/kubectl_cmd.rb b/lib/gitlab/kubernetes/kubectl_cmd.rb
new file mode 100644
index 00000000000..981eb5681dc
--- /dev/null
+++ b/lib/gitlab/kubernetes/kubectl_cmd.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module KubectlCmd
+ class << self
+ def delete(*args)
+ %w(kubectl delete).concat(args).shelljoin
+ end
+
+ def apply_file(filename, *args)
+ raise ArgumentError, "filename is not present" unless filename.present?
+
+ %w(kubectl apply -f).concat([filename], args).shelljoin
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/legacy_github_import/client.rb b/lib/gitlab/legacy_github_import/client.rb
index bbdd094e33b..b23efd64dee 100644
--- a/lib/gitlab/legacy_github_import/client.rb
+++ b/lib/gitlab/legacy_github_import/client.rb
@@ -101,7 +101,7 @@ module Gitlab
# GitHub Rate Limit API returns 404 when the rate limit is
# disabled. In this case we just want to return gracefully
# instead of spitting out an error.
- rescue Octokit::NotFound
+ rescue ::Octokit::NotFound
nil
end
diff --git a/lib/gitlab/metrics/dashboard/base_service.rb b/lib/gitlab/metrics/dashboard/base_service.rb
deleted file mode 100644
index 0628e82e592..00000000000
--- a/lib/gitlab/metrics/dashboard/base_service.rb
+++ /dev/null
@@ -1,72 +0,0 @@
-# frozen_string_literal: true
-
-# Searches a projects repository for a metrics dashboard and formats the output.
-# Expects any custom dashboards will be located in `.gitlab/dashboards`
-module Gitlab
- module Metrics
- module Dashboard
- class BaseService < ::BaseService
- PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
- NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
-
- def get_dashboard
- return error('Insufficient permissions.', :unauthorized) unless allowed?
-
- success(dashboard: process_dashboard)
- rescue NOT_FOUND_ERROR
- error("#{dashboard_path} could not be found.", :not_found)
- rescue PROCESSING_ERROR => e
- error(e.message, :unprocessable_entity)
- end
-
- # Summary of all known dashboards for the service.
- # @return [Array<Hash>] ex) [{ path: String, default: Boolean }]
- def self.all_dashboard_paths(_project)
- raise NotImplementedError
- end
-
- # Returns an un-processed dashboard from the cache.
- def raw_dashboard
- Gitlab::Metrics::Dashboard::Cache.fetch(cache_key) { get_raw_dashboard }
- end
-
- private
-
- # Determines whether users should be able to view
- # dashboards at all.
- def allowed?
- Ability.allowed?(current_user, :read_environment, project)
- end
-
- # Returns a new dashboard Hash, supplemented with DB info
- def process_dashboard
- Gitlab::Metrics::Dashboard::Processor
- .new(project, params[:environment], raw_dashboard)
- .process(insert_project_metrics: insert_project_metrics?)
- end
-
- # @return [String] Relative filepath of the dashboard yml
- def dashboard_path
- params[:dashboard_path]
- end
-
- # @return [Hash] an unmodified dashboard
- def get_raw_dashboard
- raise NotImplementedError
- end
-
- # @return [String]
- def cache_key
- raise NotImplementedError
- end
-
- # Determines whether custom metrics should be included
- # in the processed output.
- # @return [Boolean]
- def insert_project_metrics?
- false
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/defaults.rb b/lib/gitlab/metrics/dashboard/defaults.rb
new file mode 100644
index 00000000000..3c39a7c6911
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/defaults.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# Central point for managing default attributes from within
+# the metrics dashboard module.
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Defaults
+ DEFAULT_PANEL_TYPE = 'area-chart'
+ DEFAULT_PANEL_WEIGHT = 0
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb b/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb
deleted file mode 100644
index 81ed8922e17..00000000000
--- a/lib/gitlab/metrics/dashboard/dynamic_dashboard_service.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-# Responsible for returning a filtered system dashboard
-# containing only the default embedded metrics. In future,
-# this class may be updated to support filtering to
-# alternate metrics/panels.
-#
-# Why isn't this filtering in a processing stage? By filtering
-# here, we ensure the dynamically-determined dashboard is cached.
-#
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class DynamicDashboardService < Gitlab::Metrics::Dashboard::BaseService
- # For the default filtering for embedded metrics,
- # uses the 'id' key in dashboard-yml definition for
- # identification.
- DEFAULT_EMBEDDED_METRICS_IDENTIFIERS = %w(
- system_metrics_kubernetes_container_memory_total
- system_metrics_kubernetes_container_cores_total
- ).freeze
-
- # Returns a new dashboard with only the matching
- # metrics from the system dashboard, stripped of groups.
- # @return [Hash]
- def raw_dashboard
- panels = panel_groups.each_with_object([]) do |group, panels|
- matched_panels = group['panels'].select { |panel| matching_panel?(panel) }
-
- panels.concat(matched_panels)
- end
-
- { 'panel_groups' => [{ 'panels' => panels }] }
- end
-
- def cache_key
- "dynamic_metrics_dashboard_#{metric_identifiers.join('_')}"
- end
-
- private
-
- # Returns an array of the panels groups on the
- # system dashboard
- def panel_groups
- Gitlab::Metrics::Dashboard::SystemDashboardService
- .new(project, nil)
- .raw_dashboard['panel_groups']
- end
-
- # Identifies a panel as "matching" if any metric ids in
- # the panel is in the list of identifiers to collect.
- def matching_panel?(panel)
- panel['metrics'].any? do |metric|
- metric_identifiers.include?(metric['id'])
- end
- end
-
- def metric_identifiers
- DEFAULT_EMBEDDED_METRICS_IDENTIFIERS
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/errors.rb b/lib/gitlab/metrics/dashboard/errors.rb
new file mode 100644
index 00000000000..1739a4e6738
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/errors.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+# Central point for managing errors from within the metrics
+# dashboard module. Handles errors from dashboard retrieval
+# and processing steps, as well as defines shared error classes.
+module Gitlab
+ module Metrics
+ module Dashboard
+ module Errors
+ PanelNotFoundError = Class.new(StandardError)
+
+ PROCESSING_ERROR = Gitlab::Metrics::Dashboard::Stages::BaseStage::DashboardProcessingError
+ NOT_FOUND_ERROR = Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError
+
+ def handle_errors(error)
+ case error
+ when PROCESSING_ERROR
+ error(error.message, :unprocessable_entity)
+ when NOT_FOUND_ERROR
+ error("#{dashboard_path} could not be found.", :not_found)
+ when PanelNotFoundError
+ error(error.message, :not_found)
+ else
+ raise error
+ end
+ end
+
+ def panels_not_found!(opts)
+ raise PanelNotFoundError.new("No panels matching properties #{opts}")
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/finder.rb b/lib/gitlab/metrics/dashboard/finder.rb
index d7491d1553d..66c4d662a6c 100644
--- a/lib/gitlab/metrics/dashboard/finder.rb
+++ b/lib/gitlab/metrics/dashboard/finder.rb
@@ -12,21 +12,37 @@ module Gitlab
# @param project [Project]
# @param user [User]
# @param environment [Environment]
- # @param opts - dashboard_path [String] Path at which the
- # dashboard can be found. Nil values will
- # default to the system dashboard.
- # @param opts - embedded [Boolean] Determines whether the
+ # @param options - embedded [Boolean] Determines whether the
# dashboard is to be rendered as part of an
# issue or location other than the primary
# metrics dashboard UI. Returns only the
# Memory/CPU charts of the system dash.
+ # @param options - dashboard_path [String] Path at which the
+ # dashboard can be found. Nil values will
+ # default to the system dashboard.
+ # @param options - group [String] Title of the group
+ # to which a panel might belong. Used by
+ # embedded dashboards.
+ # @param options - title [String] Title of the panel.
+ # Used by embedded dashboards.
+ # @param options - y_label [String] Y-Axis label of
+ # a panel. Used by embedded dashboards.
# @return [Hash]
- def find(project, user, environment, dashboard_path: nil, embedded: false)
- service_for_path(dashboard_path, embedded: embedded)
- .new(project, user, environment: environment, dashboard_path: dashboard_path)
+ def find(project, user, environment, options = {})
+ service_for(options)
+ .new(project, user, options.merge(environment: environment))
.get_dashboard
end
+ # Returns a dashboard without any supplemental info.
+ # Returns only full, yml-defined dashboards.
+ # @return [Hash]
+ def find_raw(project, dashboard_path: nil)
+ service_for(dashboard_path: dashboard_path)
+ .new(project, nil, dashboard_path: dashboard_path)
+ .raw_dashboard
+ end
+
# Summary of all known dashboards.
# @return [Array<Hash>] ex) [{ path: String,
# display_name: String,
@@ -46,27 +62,16 @@ module Gitlab
private
- def service_for_path(dashboard_path, embedded:)
- return dynamic_service if embedded
- return system_service if system_dashboard?(dashboard_path)
-
- project_service
- end
-
def system_service
- Gitlab::Metrics::Dashboard::SystemDashboardService
+ ::Metrics::Dashboard::SystemDashboardService
end
def project_service
- Gitlab::Metrics::Dashboard::ProjectDashboardService
- end
-
- def dynamic_service
- Gitlab::Metrics::Dashboard::DynamicDashboardService
+ ::Metrics::Dashboard::ProjectDashboardService
end
- def system_dashboard?(filepath)
- !filepath || system_service.system_dashboard?(filepath)
+ def service_for(options)
+ Gitlab::Metrics::Dashboard::ServiceSelector.call(options)
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/project_dashboard_service.rb b/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
deleted file mode 100644
index 5a1c4ecf886..00000000000
--- a/lib/gitlab/metrics/dashboard/project_dashboard_service.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# frozen_string_literal: true
-
-# Searches a projects repository for a metrics dashboard and formats the output.
-# Expects any custom dashboards will be located in `.gitlab/dashboards`
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class ProjectDashboardService < Gitlab::Metrics::Dashboard::BaseService
- DASHBOARD_ROOT = ".gitlab/dashboards"
-
- class << self
- def all_dashboard_paths(project)
- file_finder(project)
- .list_files_for(DASHBOARD_ROOT)
- .map do |filepath|
- {
- path: filepath,
- display_name: name_for_path(filepath),
- default: false
- }
- end
- end
-
- def file_finder(project)
- Gitlab::Template::Finders::RepoTemplateFinder.new(project, DASHBOARD_ROOT, '.yml')
- end
-
- # Grabs the filepath after the base directory.
- def name_for_path(filepath)
- filepath.delete_prefix("#{DASHBOARD_ROOT}/")
- end
- end
-
- private
-
- # Searches the project repo for a custom-defined dashboard.
- def get_raw_dashboard
- yml = self.class.file_finder(project).read(dashboard_path)
-
- YAML.safe_load(yml)
- end
-
- def cache_key
- "project_#{project.id}_metrics_dashboard_#{dashboard_path}"
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb
new file mode 100644
index 00000000000..934ba9145a2
--- /dev/null
+++ b/lib/gitlab/metrics/dashboard/service_selector.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+# Responsible for determining which dashboard service should
+# be used to fetch or generate a dashboard hash.
+# The services can be considered in two categories - embeds
+# and dashboards. Embeds are all portions of dashboards.
+module Gitlab
+ module Metrics
+ module Dashboard
+ class ServiceSelector
+ SERVICES = ::Metrics::Dashboard
+
+ class << self
+ include Gitlab::Utils::StrongMemoize
+
+ # Returns a class which inherits from the BaseService
+ # class that can be used to obtain a dashboard.
+ # @return [Gitlab::Metrics::Dashboard::Services::BaseService]
+ def call(params)
+ return SERVICES::CustomMetricEmbedService if custom_metric_embed?(params)
+ return SERVICES::DynamicEmbedService if dynamic_embed?(params)
+ return SERVICES::DefaultEmbedService if params[:embedded]
+ return SERVICES::SystemDashboardService if system_dashboard?(params[:dashboard_path])
+ return SERVICES::ProjectDashboardService if params[:dashboard_path]
+
+ default_service
+ end
+
+ private
+
+ def default_service
+ SERVICES::SystemDashboardService
+ end
+
+ def system_dashboard?(filepath)
+ SERVICES::SystemDashboardService.system_dashboard?(filepath)
+ end
+
+ def custom_metric_embed?(params)
+ SERVICES::CustomMetricEmbedService.valid_params?(params)
+ end
+
+ def dynamic_embed?(params)
+ SERVICES::DynamicEmbedService.valid_params?(params)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/dashboard/stages/base_stage.rb b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
index 0db7b176e8d..514ed50e58d 100644
--- a/lib/gitlab/metrics/dashboard/stages/base_stage.rb
+++ b/lib/gitlab/metrics/dashboard/stages/base_stage.rb
@@ -5,11 +5,11 @@ module Gitlab
module Dashboard
module Stages
class BaseStage
+ include Gitlab::Metrics::Dashboard::Defaults
+
DashboardProcessingError = Class.new(StandardError)
LayoutError = Class.new(DashboardProcessingError)
- DEFAULT_PANEL_TYPE = 'area-chart'
-
attr_reader :project, :environment, :dashboard
def initialize(project, environment, dashboard)
diff --git a/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb
index 221610a14d1..643be309992 100644
--- a/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb
+++ b/lib/gitlab/metrics/dashboard/stages/project_metrics_inserter.rb
@@ -97,7 +97,7 @@ module Gitlab
end
def new_metric(metric)
- metric.queries.first.merge(metric_id: metric.id)
+ metric.to_metric_hash
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/system_dashboard_service.rb b/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
deleted file mode 100644
index 82421572f4a..00000000000
--- a/lib/gitlab/metrics/dashboard/system_dashboard_service.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# frozen_string_literal: true
-
-# Fetches the system metrics dashboard and formats the output.
-# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
-module Gitlab
- module Metrics
- module Dashboard
- class SystemDashboardService < Gitlab::Metrics::Dashboard::BaseService
- SYSTEM_DASHBOARD_PATH = 'config/prometheus/common_metrics.yml'
- SYSTEM_DASHBOARD_NAME = 'Default'
-
- class << self
- def all_dashboard_paths(_project)
- [{
- path: SYSTEM_DASHBOARD_PATH,
- display_name: SYSTEM_DASHBOARD_NAME,
- default: true
- }]
- end
-
- def system_dashboard?(filepath)
- filepath == SYSTEM_DASHBOARD_PATH
- end
- end
-
- private
-
- def dashboard_path
- SYSTEM_DASHBOARD_PATH
- end
-
- # Returns the base metrics shipped with every GitLab service.
- def get_raw_dashboard
- yml = File.read(Rails.root.join(dashboard_path))
-
- YAML.safe_load(yml)
- end
-
- def cache_key
- "metrics_dashboard_#{dashboard_path}"
- end
-
- def insert_project_metrics?
- true
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/dashboard/url.rb b/lib/gitlab/metrics/dashboard/url.rb
index b197e7ca86b..94f8b2e02b1 100644
--- a/lib/gitlab/metrics/dashboard/url.rb
+++ b/lib/gitlab/metrics/dashboard/url.rb
@@ -21,14 +21,26 @@ module Gitlab
\/(?<environment>\d+)
\/metrics
(?<query>
- \?[a-z0-9_=-]+
- (&[a-z0-9_=-]+)*
+ \?[a-zA-Z0-9%.()+_=-]+
+ (&[a-zA-Z0-9%.()+_=-]+)*
)?
(?<anchor>\#[a-z0-9_-]+)?
)
}x
end
+ # Parses query params out from full url string into hash.
+ #
+ # Ex) 'https://<root>/<project>/<environment>/metrics?title=Title&group=Group'
+ # --> { title: 'Title', group: 'Group' }
+ def parse_query(url)
+ query_string = URI.parse(url).query.to_s
+
+ CGI.parse(query_string)
+ .transform_values { |value| value.first }
+ .symbolize_keys
+ end
+
# Builds a metrics dashboard url based on the passed in arguments
def build_dashboard_url(*args)
Gitlab::Routing.url_helpers.metrics_dashboard_namespace_project_environment_url(*args)
diff --git a/lib/gitlab/metrics/samplers/influx_sampler.rb b/lib/gitlab/metrics/samplers/influx_sampler.rb
index 5138b37f83e..1eae0a7bf45 100644
--- a/lib/gitlab/metrics/samplers/influx_sampler.rb
+++ b/lib/gitlab/metrics/samplers/influx_sampler.rb
@@ -15,19 +15,14 @@ module Gitlab
@last_step = nil
@metrics = []
-
- @last_minor_gc = Delta.new(GC.stat[:minor_gc_count])
- @last_major_gc = Delta.new(GC.stat[:major_gc_count])
end
def sample
sample_memory_usage
sample_file_descriptors
- sample_gc
flush
ensure
- GC::Profiler.clear
@metrics.clear
end
@@ -43,23 +38,6 @@ module Gitlab
add_metric('file_descriptors', value: System.file_descriptor_count)
end
- def sample_gc
- time = GC::Profiler.total_time * 1000.0
- stats = GC.stat.merge(total_time: time)
-
- # We want the difference of GC runs compared to the last sample, not the
- # total amount since the process started.
- stats[:minor_gc_count] =
- @last_minor_gc.compared_with(stats[:minor_gc_count])
-
- stats[:major_gc_count] =
- @last_major_gc.compared_with(stats[:major_gc_count])
-
- stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
-
- add_metric('gc_statistics', stats)
- end
-
def add_metric(series, values, tags = {})
prefix = sidekiq? ? 'sidekiq_' : 'rails_'
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
index 4e835f37c04..8a24d4f3663 100644
--- a/lib/gitlab/metrics/samplers/puma_sampler.rb
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -15,7 +15,6 @@ module Gitlab
puma_workers: ::Gitlab::Metrics.gauge(:puma_workers, 'Total number of workers'),
puma_running_workers: ::Gitlab::Metrics.gauge(:puma_running_workers, 'Number of active workers'),
puma_stale_workers: ::Gitlab::Metrics.gauge(:puma_stale_workers, 'Number of stale workers'),
- puma_phase: ::Gitlab::Metrics.gauge(:puma_phase, 'Phase number (increased during phased restarts)'),
puma_running: ::Gitlab::Metrics.gauge(:puma_running, 'Number of running threads'),
puma_queued_connections: ::Gitlab::Metrics.gauge(:puma_queued_connections, 'Number of connections in that worker\'s "todo" set waiting for a worker thread'),
puma_active_connections: ::Gitlab::Metrics.gauge(:puma_active_connections, 'Number of threads processing a request'),
@@ -54,7 +53,6 @@ module Gitlab
last_status = worker['last_status']
labels = { worker: "worker_#{worker['index']}" }
- metrics[:puma_phase].set(labels, worker['phase'])
set_worker_metrics(last_status, labels) if last_status.present?
end
end
@@ -76,7 +74,6 @@ module Gitlab
metrics[:puma_workers].set(labels, stats['workers'])
metrics[:puma_running_workers].set(labels, stats['booted_workers'])
metrics[:puma_stale_workers].set(labels, stats['old_workers'])
- metrics[:puma_phase].set(labels, stats['phase'])
end
def set_worker_metrics(stats, labels = {})
diff --git a/lib/gitlab/metrics/samplers/ruby_sampler.rb b/lib/gitlab/metrics/samplers/ruby_sampler.rb
index eef802caabb..3bfa3da35e0 100644
--- a/lib/gitlab/metrics/samplers/ruby_sampler.rb
+++ b/lib/gitlab/metrics/samplers/ruby_sampler.rb
@@ -6,8 +6,12 @@ module Gitlab
module Metrics
module Samplers
class RubySampler < BaseSampler
+ GC_REPORT_BUCKETS = [0.001, 0.002, 0.005, 0.01, 0.05, 0.1, 0.5].freeze
+
def initialize(interval)
- metrics[:process_start_time_seconds].set(labels.merge(worker_label), Time.now.to_i)
+ GC::Profiler.clear
+
+ metrics[:process_start_time_seconds].set(labels, Time.now.to_i)
super
end
@@ -30,18 +34,18 @@ module Gitlab
def init_metrics
metrics = {
- file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels, :livesum),
- memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels, :livesum),
+ file_descriptors: ::Gitlab::Metrics.gauge(with_prefix(:file, :descriptors), 'File descriptors used', labels),
+ memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:memory, :bytes), 'Memory used', labels),
process_cpu_seconds_total: ::Gitlab::Metrics.gauge(with_prefix(:process, :cpu_seconds_total), 'Process CPU seconds total'),
process_max_fds: ::Gitlab::Metrics.gauge(with_prefix(:process, :max_fds), 'Process max fds'),
- process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels, :livesum),
+ process_resident_memory_bytes: ::Gitlab::Metrics.gauge(with_prefix(:process, :resident_memory_bytes), 'Memory used', labels),
process_start_time_seconds: ::Gitlab::Metrics.gauge(with_prefix(:process, :start_time_seconds), 'Process start time seconds'),
sampler_duration: ::Gitlab::Metrics.counter(with_prefix(:sampler, :duration_seconds_total), 'Sampler time', labels),
- total_time: ::Gitlab::Metrics.counter(with_prefix(:gc, :duration_seconds_total), 'Total GC time', labels)
+ gc_duration_seconds: ::Gitlab::Metrics.histogram(with_prefix(:gc, :duration_seconds), 'GC time', labels, GC_REPORT_BUCKETS)
}
GC.stat.keys.each do |key|
- metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels, :livesum)
+ metrics[key] = ::Gitlab::Metrics.gauge(with_prefix(:gc_stat, key), to_doc_string(key), labels)
end
metrics
@@ -50,47 +54,41 @@ module Gitlab
def sample
start_time = System.monotonic_time
- metrics[:file_descriptors].set(labels.merge(worker_label), System.file_descriptor_count)
- metrics[:process_cpu_seconds_total].set(labels.merge(worker_label), ::Gitlab::Metrics::System.cpu_time)
- metrics[:process_max_fds].set(labels.merge(worker_label), ::Gitlab::Metrics::System.max_open_file_descriptors)
+ metrics[:file_descriptors].set(labels, System.file_descriptor_count)
+ metrics[:process_cpu_seconds_total].set(labels, ::Gitlab::Metrics::System.cpu_time)
+ metrics[:process_max_fds].set(labels, ::Gitlab::Metrics::System.max_open_file_descriptors)
set_memory_usage_metrics
sample_gc
metrics[:sampler_duration].increment(labels, System.monotonic_time - start_time)
- ensure
- GC::Profiler.clear
end
private
def sample_gc
- # Collect generic GC stats.
+ # Observe all GC samples
+ sample_gc_reports.each do |report|
+ metrics[:gc_duration_seconds].observe(labels, report[:GC_TIME])
+ end
+
+ # Collect generic GC stats
GC.stat.each do |key, value|
metrics[key].set(labels, value)
end
+ end
- # Collect the GC time since last sample in float seconds.
- metrics[:total_time].increment(labels, GC::Profiler.total_time)
+ def sample_gc_reports
+ GC::Profiler.enable
+ GC::Profiler.raw_data
+ ensure
+ GC::Profiler.clear
end
def set_memory_usage_metrics
memory_usage = System.memory_usage
- memory_labels = labels.merge(worker_label)
- metrics[:memory_bytes].set(memory_labels, memory_usage)
- metrics[:process_resident_memory_bytes].set(memory_labels, memory_usage)
- end
-
- def worker_label
- return { worker: 'sidekiq' } if Sidekiq.server?
- return {} unless defined?(Unicorn::Worker)
-
- worker_no = ::Prometheus::Client::Support::Unicorn.worker_id
- if worker_no
- { worker: worker_no }
- else
- { worker: 'master' }
- end
+ metrics[:memory_bytes].set(labels, memory_usage)
+ metrics[:process_resident_memory_bytes].set(labels, memory_usage)
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/rails_cache.rb b/lib/gitlab/metrics/subscribers/rails_cache.rb
index 01db507761b..2ee7144fe2f 100644
--- a/lib/gitlab/metrics/subscribers/rails_cache.rb
+++ b/lib/gitlab/metrics/subscribers/rails_cache.rb
@@ -50,7 +50,8 @@ module Gitlab
def observe(key, duration)
return unless current_transaction
- metric_cache_operation_duration_seconds.observe(current_transaction.labels.merge({ operation: key }), duration / 1000.0)
+ metric_cache_operations_total.increment(current_transaction.labels.merge({ operation: key }))
+ metric_cache_operation_duration_seconds.observe({ operation: key }, duration / 1000.0)
current_transaction.increment(:cache_duration, duration, false)
current_transaction.increment(:cache_count, 1, false)
current_transaction.increment("cache_#{key}_duration".to_sym, duration, false)
@@ -63,12 +64,20 @@ module Gitlab
Transaction.current
end
+ def metric_cache_operations_total
+ @metric_cache_operations_total ||= ::Gitlab::Metrics.counter(
+ :gitlab_cache_operations_total,
+ 'Cache operations',
+ Transaction::BASE_LABELS
+ )
+ end
+
def metric_cache_operation_duration_seconds
@metric_cache_operation_duration_seconds ||= ::Gitlab::Metrics.histogram(
:gitlab_cache_operation_duration_seconds,
'Cache access time',
- Transaction::BASE_LABELS.merge({ action: nil }),
- [0.001, 0.01, 0.1, 1, 10]
+ {},
+ [0.00001, 0.0001, 0.001, 0.01, 0.1, 1.0]
)
end
diff --git a/lib/gitlab/object_hierarchy.rb b/lib/gitlab/object_hierarchy.rb
index 38b32770e90..c06f106ffe1 100644
--- a/lib/gitlab/object_hierarchy.rb
+++ b/lib/gitlab/object_hierarchy.rb
@@ -32,11 +32,6 @@ module Gitlab
# Returns the maximum depth starting from the base
# A base object with no children has a maximum depth of `1`
def max_descendants_depth
- unless hierarchy_supported?
- # This makes the return value consistent with the case where hierarchy is supported
- return descendants_base.exists? ? 1 : nil
- end
-
base_and_descendants(with_depth: true).maximum(DEPTH_COLUMN)
end
@@ -66,8 +61,6 @@ module Gitlab
# each parent.
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors(upto: nil, hierarchy_order: nil)
- return ancestors_base unless hierarchy_supported?
-
recursive_query = base_and_ancestors_cte(upto, hierarchy_order).apply_to(model.all)
recursive_query = recursive_query.order(depth: hierarchy_order) if hierarchy_order
@@ -81,10 +74,6 @@ module Gitlab
# When `with_depth` is `true`, a `depth` column is included where it starts with `1` for the base objects
# and incremented as we go down the descendant tree
def base_and_descendants(with_depth: false)
- unless hierarchy_supported?
- return with_depth ? descendants_base.select("1 as #{DEPTH_COLUMN}", objects_table[Arel.star]) : descendants_base
- end
-
read_only(base_and_descendants_cte(with_depth: with_depth).apply_to(model.all))
end
@@ -112,8 +101,6 @@ module Gitlab
# If nested objects are not supported, ancestors_base is returned.
# rubocop: disable CodeReuse/ActiveRecord
def all_objects
- return ancestors_base unless hierarchy_supported?
-
ancestors = base_and_ancestors_cte
descendants = base_and_descendants_cte
@@ -135,10 +122,6 @@ module Gitlab
private
- def hierarchy_supported?
- Gitlab::Database.postgresql?
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def base_and_ancestors_cte(stop_id = nil, hierarchy_order = nil)
cte = SQL::RecursiveCTE.new(:base_and_ancestors)
diff --git a/lib/gitlab/octokit/middleware.rb b/lib/gitlab/octokit/middleware.rb
new file mode 100644
index 00000000000..2dd7d08a58b
--- /dev/null
+++ b/lib/gitlab/octokit/middleware.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Octokit
+ class Middleware
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ Gitlab::UrlBlocker.validate!(env[:url], { allow_localhost: allow_local_requests?, allow_local_network: allow_local_requests? })
+
+ @app.call(env)
+ end
+
+ private
+
+ def allow_local_requests?
+ Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/path_regex.rb b/lib/gitlab/path_regex.rb
index a13b3f9e069..f96466b2b00 100644
--- a/lib/gitlab/path_regex.rb
+++ b/lib/gitlab/path_regex.rb
@@ -175,6 +175,10 @@ module Gitlab
@project_git_route_regex ||= /#{project_route_regex}\.git/.freeze
end
+ def project_wiki_git_route_regex
+ @project_wiki_git_route_regex ||= /#{PATH_REGEX_STR}\.wiki/.freeze
+ end
+
def full_namespace_path_regex
@full_namespace_path_regex ||= %r{\A#{full_namespace_route_regex}/\z}
end
diff --git a/lib/gitlab/performance_bar/peek_query_tracker.rb b/lib/gitlab/performance_bar/peek_query_tracker.rb
deleted file mode 100644
index 3a27e26eaba..00000000000
--- a/lib/gitlab/performance_bar/peek_query_tracker.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-# Inspired by https://github.com/peek/peek-pg/blob/master/lib/peek/views/pg.rb
-# PEEK_DB_CLIENT is a constant set in config/initializers/peek.rb
-module Gitlab
- module PerformanceBar
- module PeekQueryTracker
- def sorted_queries
- PEEK_DB_CLIENT.query_details
- .sort { |a, b| b[:duration] <=> a[:duration] }
- end
-
- def results
- super.merge(queries: sorted_queries)
- end
-
- private
-
- def setup_subscribers
- super
-
- # Reset each counter when a new request starts
- before_request do
- PEEK_DB_CLIENT.query_details = []
- end
-
- subscribe('sql.active_record') do |_, start, finish, _, data|
- if Gitlab::SafeRequestStore.store[:peek_enabled]
- unless data[:cached]
- backtrace = Gitlab::Profiler.clean_backtrace(caller)
- track_query(data[:sql].strip, data[:binds], backtrace, start, finish)
- end
- end
- end
- end
-
- def track_query(raw_query, bindings, backtrace, start, finish)
- duration = (finish - start) * 1000.0
- query_info = { duration: duration.round(3), sql: raw_query, backtrace: backtrace }
-
- PEEK_DB_CLIENT.query_details << query_info
- end
- end
- end
-end
diff --git a/lib/gitlab/profiler.rb b/lib/gitlab/profiler.rb
index 890228e5e78..ec7671f9a8b 100644
--- a/lib/gitlab/profiler.rb
+++ b/lib/gitlab/profiler.rb
@@ -21,6 +21,9 @@ module Gitlab
lib/gitlab/profiler.rb
lib/gitlab/correlation_id.rb
lib/gitlab/webpack/dev_server_middleware.rb
+ lib/gitlab/sidekiq_status/
+ lib/gitlab/sidekiq_logging/
+ lib/gitlab/sidekiq_middleware/
].freeze
# Takes a URL to profile (can be a fully-qualified URL, or an absolute path)
@@ -166,7 +169,7 @@ module Gitlab
[model, times.count, times.sum]
end
- summarised_load_times.sort_by(&:last).reverse.each do |(model, query_count, time)|
+ summarised_load_times.sort_by(&:last).reverse_each do |(model, query_count, time)|
logger.info("#{model} total (#{query_count}): #{time.round(2)}ms")
end
end
diff --git a/lib/gitlab/project_authorizations.rb b/lib/gitlab/project_authorizations.rb
new file mode 100644
index 00000000000..a9270cd536e
--- /dev/null
+++ b/lib/gitlab/project_authorizations.rb
@@ -0,0 +1,121 @@
+# frozen_string_literal: true
+
+# This class relies on Common Table Expressions to efficiently get all data,
+# including data for nested groups.
+module Gitlab
+ class ProjectAuthorizations
+ attr_reader :user
+
+ # user - The User object for which to calculate the authorizations.
+ def initialize(user)
+ @user = user
+ end
+
+ def calculate
+ cte = recursive_cte
+ cte_alias = cte.table.alias(Group.table_name)
+ projects = Project.arel_table
+ links = ProjectGroupLink.arel_table
+
+ relations = [
+ # The project a user has direct access to.
+ user.projects.select_for_project_authorization,
+
+ # The personal projects of the user.
+ user.personal_projects.select_as_maintainer_for_project_authorization,
+
+ # Projects that belong directly to any of the groups the user has
+ # access to.
+ Namespace
+ .unscoped
+ .select([alias_as_column(projects[:id], 'project_id'),
+ cte_alias[:access_level]])
+ .from(cte_alias)
+ .joins(:projects),
+
+ # Projects shared with any of the namespaces the user has access to.
+ Namespace
+ .unscoped
+ .select([
+ links[:project_id],
+ least(cte_alias[:access_level], links[:group_access], 'access_level')
+ ])
+ .from(cte_alias)
+ .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
+ .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
+ .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
+ .where('p_ns.share_with_group_lock IS FALSE')
+ ]
+
+ ProjectAuthorization
+ .unscoped
+ .with
+ .recursive(cte.to_arel)
+ .select_from_union(relations)
+ end
+
+ private
+
+ # Builds a recursive CTE that gets all the groups the current user has
+ # access to, including any nested groups.
+ def recursive_cte
+ cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
+ members = Member.arel_table
+ namespaces = Namespace.arel_table
+
+ # Namespaces the user is a member of.
+ cte << user.groups
+ .select([namespaces[:id], members[:access_level]])
+ .except(:order)
+
+ # Sub groups of any groups the user is a member of.
+ cte << Group.select([
+ namespaces[:id],
+ greatest(members[:access_level], cte.table[:access_level], 'access_level')
+ ])
+ .joins(join_cte(cte))
+ .joins(join_members)
+ .except(:order)
+
+ cte
+ end
+
+ # Builds a LEFT JOIN to join optional memberships onto the CTE.
+ def join_members
+ members = Member.arel_table
+ namespaces = Namespace.arel_table
+
+ cond = members[:source_id]
+ .eq(namespaces[:id])
+ .and(members[:source_type].eq('Namespace'))
+ .and(members[:requested_at].eq(nil))
+ .and(members[:user_id].eq(user.id))
+
+ Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
+ end
+
+ # Builds an INNER JOIN to join namespaces onto the CTE.
+ def join_cte(cte)
+ namespaces = Namespace.arel_table
+ cond = cte.table[:id].eq(namespaces[:parent_id])
+
+ Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
+ end
+
+ def greatest(left, right, column_alias)
+ sql_function('GREATEST', [left, right], column_alias)
+ end
+
+ def least(left, right, column_alias)
+ sql_function('LEAST', [left, right], column_alias)
+ end
+
+ def sql_function(name, args, column_alias)
+ alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
+ end
+
+ def alias_as_column(value, alias_to)
+ Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
+ end
+ end
+end
diff --git a/lib/gitlab/project_authorizations/with_nested_groups.rb b/lib/gitlab/project_authorizations/with_nested_groups.rb
deleted file mode 100644
index 2372a316ab0..00000000000
--- a/lib/gitlab/project_authorizations/with_nested_groups.rb
+++ /dev/null
@@ -1,125 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module ProjectAuthorizations
- # Calculating new project authorizations when supporting nested groups.
- #
- # This class relies on Common Table Expressions to efficiently get all data,
- # including data for nested groups. As a result this class can only be used
- # on PostgreSQL.
- class WithNestedGroups
- attr_reader :user
-
- # user - The User object for which to calculate the authorizations.
- def initialize(user)
- @user = user
- end
-
- def calculate
- cte = recursive_cte
- cte_alias = cte.table.alias(Group.table_name)
- projects = Project.arel_table
- links = ProjectGroupLink.arel_table
-
- relations = [
- # The project a user has direct access to.
- user.projects.select_for_project_authorization,
-
- # The personal projects of the user.
- user.personal_projects.select_as_maintainer_for_project_authorization,
-
- # Projects that belong directly to any of the groups the user has
- # access to.
- Namespace
- .unscoped
- .select([alias_as_column(projects[:id], 'project_id'),
- cte_alias[:access_level]])
- .from(cte_alias)
- .joins(:projects),
-
- # Projects shared with any of the namespaces the user has access to.
- Namespace
- .unscoped
- .select([links[:project_id],
- least(cte_alias[:access_level],
- links[:group_access],
- 'access_level')])
- .from(cte_alias)
- .joins('INNER JOIN project_group_links ON project_group_links.group_id = namespaces.id')
- .joins('INNER JOIN projects ON projects.id = project_group_links.project_id')
- .joins('INNER JOIN namespaces p_ns ON p_ns.id = projects.namespace_id')
- .where('p_ns.share_with_group_lock IS FALSE')
- ]
-
- ProjectAuthorization
- .unscoped
- .with
- .recursive(cte.to_arel)
- .select_from_union(relations)
- end
-
- private
-
- # Builds a recursive CTE that gets all the groups the current user has
- # access to, including any nested groups.
- def recursive_cte
- cte = Gitlab::SQL::RecursiveCTE.new(:namespaces_cte)
- members = Member.arel_table
- namespaces = Namespace.arel_table
-
- # Namespaces the user is a member of.
- cte << user.groups
- .select([namespaces[:id], members[:access_level]])
- .except(:order)
-
- # Sub groups of any groups the user is a member of.
- cte << Group.select([namespaces[:id],
- greatest(members[:access_level],
- cte.table[:access_level], 'access_level')])
- .joins(join_cte(cte))
- .joins(join_members)
- .except(:order)
-
- cte
- end
-
- # Builds a LEFT JOIN to join optional memberships onto the CTE.
- def join_members
- members = Member.arel_table
- namespaces = Namespace.arel_table
-
- cond = members[:source_id]
- .eq(namespaces[:id])
- .and(members[:source_type].eq('Namespace'))
- .and(members[:requested_at].eq(nil))
- .and(members[:user_id].eq(user.id))
-
- Arel::Nodes::OuterJoin.new(members, Arel::Nodes::On.new(cond))
- end
-
- # Builds an INNER JOIN to join namespaces onto the CTE.
- def join_cte(cte)
- namespaces = Namespace.arel_table
- cond = cte.table[:id].eq(namespaces[:parent_id])
-
- Arel::Nodes::InnerJoin.new(cte.table, Arel::Nodes::On.new(cond))
- end
-
- def greatest(left, right, column_alias)
- sql_function('GREATEST', [left, right], column_alias)
- end
-
- def least(left, right, column_alias)
- sql_function('LEAST', [left, right], column_alias)
- end
-
- def sql_function(name, args, column_alias)
- alias_as_column(Arel::Nodes::NamedFunction.new(name, args), column_alias)
- end
-
- def alias_as_column(value, alias_to)
- Arel::Nodes::As.new(value, Arel::Nodes::SqlLiteral.new(alias_to))
- end
- end
- end
-end
diff --git a/lib/gitlab/project_authorizations/without_nested_groups.rb b/lib/gitlab/project_authorizations/without_nested_groups.rb
deleted file mode 100644
index 50b41b17649..00000000000
--- a/lib/gitlab/project_authorizations/without_nested_groups.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module ProjectAuthorizations
- # Calculating new project authorizations when not supporting nested groups.
- class WithoutNestedGroups
- attr_reader :user
-
- # user - The User object for which to calculate the authorizations.
- def initialize(user)
- @user = user
- end
-
- def calculate
- relations = [
- # Projects the user is a direct member of
- user.projects.select_for_project_authorization,
-
- # Personal projects
- user.personal_projects.select_as_maintainer_for_project_authorization,
-
- # Projects of groups the user is a member of
- user.groups_projects.select_for_project_authorization,
-
- # Projects shared with groups the user is a member of
- user.groups.joins(:shared_projects).select_for_project_authorization
- ]
-
- ProjectAuthorization
- .unscoped
- .select_from_union(relations)
- end
- end
- end
-end
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 0f3b97e2317..2669adb8455 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -29,6 +29,21 @@ module Gitlab
end
end
+ def formatted_count(scope)
+ case scope
+ when 'blobs'
+ blobs_count.to_s
+ when 'notes'
+ formatted_limited_count(limited_notes_count)
+ when 'wiki_blobs'
+ wiki_blobs_count.to_s
+ when 'commits'
+ commits_count.to_s
+ else
+ super
+ end
+ end
+
def users
super.where(id: @project.team.members) # rubocop:disable CodeReuse/ActiveRecord
end
@@ -108,7 +123,7 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def notes_finder(type)
- NotesFinder.new(project, @current_user, search: query, target_type: type).execute.user.order('updated_at DESC')
+ NotesFinder.new(@current_user, search: query, target_type: type, project: project).execute.user.order('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -134,9 +149,11 @@ module Gitlab
project.repository.commit(key) if Commit.valid_hash?(key)
end
+ # rubocop: disable CodeReuse/ActiveRecord
def project_ids_relation
- project
+ Project.where(id: project).select(:id).reorder(nil)
end
+ # rubocop: enabled CodeReuse/ActiveRecord
def filter_milestones_by_project(milestones)
return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project)
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 99885be8755..fa1d1203842 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -13,11 +13,23 @@ module Gitlab
end
def archive_path
- Rails.root.join("vendor/project_templates/#{name}.tar.gz")
+ self.class.archive_directory.join(archive_filename)
+ end
+
+ def archive_filename
+ "#{name}.tar.gz"
end
def clone_url
- "https://gitlab.com/gitlab-org/project-templates/#{name}.git"
+ "#{preview}.git"
+ end
+
+ def project_path
+ URI.parse(preview).path.sub(%r{\A/}, '')
+ end
+
+ def uri_encoded_project_path
+ ERB::Util.url_encode(project_path)
end
def ==(other)
@@ -54,7 +66,7 @@ module Gitlab
end
def archive_directory
- Rails.root.join("vendor_directory/project_templates")
+ Rails.root.join("vendor/project_templates")
end
end
end
diff --git a/lib/gitlab/prometheus/query_variables.rb b/lib/gitlab/prometheus/query_variables.rb
index 9cc21129547..ba2d33ee1c1 100644
--- a/lib/gitlab/prometheus/query_variables.rb
+++ b/lib/gitlab/prometheus/query_variables.rb
@@ -4,12 +4,9 @@ module Gitlab
module Prometheus
module QueryVariables
def self.call(environment)
- deployment_platform = environment.deployment_platform
- namespace = deployment_platform&.kubernetes_namespace_for(environment.project) || ''
-
{
ci_environment_slug: environment.slug,
- kube_namespace: namespace,
+ kube_namespace: environment.deployment_namespace || '',
environment_filter: %{container_name!="POD",environment="#{environment.slug}"}
}
end
diff --git a/lib/gitlab/prometheus_client.rb b/lib/gitlab/prometheus_client.rb
index f13156f898e..9fefffefcde 100644
--- a/lib/gitlab/prometheus_client.rb
+++ b/lib/gitlab/prometheus_client.rb
@@ -3,6 +3,7 @@
module Gitlab
# Helper methods to interact with Prometheus network services & resources
class PrometheusClient
+ include Gitlab::Utils::StrongMemoize
Error = Class.new(StandardError)
QueryError = Class.new(Gitlab::PrometheusClient::Error)
@@ -14,10 +15,17 @@ module Gitlab
# Minimal value of the `step` parameter for `query_range` in seconds.
QUERY_RANGE_MIN_STEP = 60
- attr_reader :rest_client, :headers
+ # Key translation between RestClient and Gitlab::HTTP (HTTParty)
+ RESTCLIENT_GITLAB_HTTP_KEYMAP = {
+ ssl_cert_store: :cert_store
+ }.freeze
- def initialize(rest_client)
- @rest_client = rest_client
+ attr_reader :api_url, :options
+ private :api_url, :options
+
+ def initialize(api_url, options = {})
+ @api_url = api_url.chomp('/')
+ @options = options
end
def ping
@@ -27,14 +35,10 @@ module Gitlab
def proxy(type, args)
path = api_path(type)
get(path, args)
- rescue RestClient::ExceptionWithResponse => ex
- if ex.response
- ex.response
- else
- raise PrometheusClient::Error, "Network connection error"
- end
- rescue RestClient::Exception
- raise PrometheusClient::Error, "Network connection error"
+ rescue Gitlab::HTTP::ResponseError => ex
+ raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
+
+ handle_response(ex.response)
end
def query(query, time: Time.now)
@@ -78,50 +82,58 @@ module Gitlab
private
def api_path(type)
- ['api', 'v1', type].join('/')
+ [api_url, 'api', 'v1', type].join('/')
end
def json_api_get(type, args = {})
path = api_path(type)
response = get(path, args)
handle_response(response)
- rescue RestClient::ExceptionWithResponse => ex
- if ex.response
- handle_exception_response(ex.response)
- else
- raise PrometheusClient::Error, "Network connection error"
+ rescue Gitlab::HTTP::ResponseError => ex
+ raise PrometheusClient::Error, "Network connection error" unless ex.response && ex.response.try(:code)
+
+ handle_response(ex.response)
+ end
+
+ def gitlab_http_key(key)
+ RESTCLIENT_GITLAB_HTTP_KEYMAP[key] || key
+ end
+
+ def mapped_options
+ options.keys.map { |k| [gitlab_http_key(k), options[k]] }.to_h
+ end
+
+ def http_options
+ strong_memoize(:http_options) do
+ { follow_redirects: false }.merge(mapped_options)
end
- rescue RestClient::Exception
- raise PrometheusClient::Error, "Network connection error"
end
def get(path, args)
- rest_client[path].get(params: args)
+ Gitlab::HTTP.get(path, { query: args }.merge(http_options) )
rescue SocketError
- raise PrometheusClient::Error, "Can't connect to #{rest_client.url}"
+ raise PrometheusClient::Error, "Can't connect to #{api_url}"
rescue OpenSSL::SSL::SSLError
- raise PrometheusClient::Error, "#{rest_client.url} contains invalid SSL data"
+ raise PrometheusClient::Error, "#{api_url} contains invalid SSL data"
rescue Errno::ECONNREFUSED
raise PrometheusClient::Error, 'Connection refused'
end
def handle_response(response)
- json_data = parse_json(response.body)
- if response.code == 200 && json_data['status'] == 'success'
- json_data['data'] || {}
- else
- raise PrometheusClient::Error, "#{response.code} - #{response.body}"
- end
- end
+ response_code = response.try(:code)
+ response_body = response.try(:body)
+
+ raise PrometheusClient::Error, "#{response_code} - #{response_body}" unless response_code
+
+ json_data = parse_json(response_body) if [200, 400].include?(response_code)
- def handle_exception_response(response)
- if response.code == 200 && response['status'] == 'success'
- response['data'] || {}
- elsif response.code == 400
- json_data = parse_json(response.body)
+ case response_code
+ when 200
+ json_data['data'] if response['status'] == 'success'
+ when 400
raise PrometheusClient::QueryError, json_data['error'] || 'Bad data received'
else
- raise PrometheusClient::Error, "#{response.code} - #{response.body}"
+ raise PrometheusClient::Error, "#{response_code} - #{response_body}"
end
end
diff --git a/lib/gitlab/push_options.rb b/lib/gitlab/push_options.rb
index 3137676ba4b..682edfc4259 100644
--- a/lib/gitlab/push_options.rb
+++ b/lib/gitlab/push_options.rb
@@ -4,7 +4,14 @@ module Gitlab
class PushOptions
VALID_OPTIONS = HashWithIndifferentAccess.new({
merge_request: {
- keys: [:create, :merge_when_pipeline_succeeds, :target]
+ keys: [
+ :create,
+ :description,
+ :merge_when_pipeline_succeeds,
+ :remove_source_branch,
+ :target,
+ :title
+ ]
},
ci: {
keys: [:skip]
diff --git a/lib/gitlab/quick_actions/command_definition.rb b/lib/gitlab/quick_actions/command_definition.rb
index 93030fd454e..ebdae139315 100644
--- a/lib/gitlab/quick_actions/command_definition.rb
+++ b/lib/gitlab/quick_actions/command_definition.rb
@@ -3,8 +3,8 @@
module Gitlab
module QuickActions
class CommandDefinition
- attr_accessor :name, :aliases, :description, :explanation, :params,
- :condition_block, :parse_params_block, :action_block, :warning, :types
+ attr_accessor :name, :aliases, :description, :explanation, :execution_message,
+ :params, :condition_block, :parse_params_block, :action_block, :warning, :types
def initialize(name, attributes = {})
@name = name
@@ -13,6 +13,7 @@ module Gitlab
@description = attributes[:description] || ''
@warning = attributes[:warning] || ''
@explanation = attributes[:explanation] || ''
+ @execution_message = attributes[:execution_message] || ''
@params = attributes[:params] || []
@condition_block = attributes[:condition_block]
@parse_params_block = attributes[:parse_params_block]
@@ -48,13 +49,23 @@ module Gitlab
end
def execute(context, arg)
- return if noop? || !available?(context)
+ return unless executable?(context)
count_commands_executed_in(context)
execute_block(action_block, context, arg)
end
+ def execute_message(context, arg)
+ return unless executable?(context)
+
+ if execution_message.respond_to?(:call)
+ execute_block(execution_message, context, arg)
+ else
+ execution_message
+ end
+ end
+
def to_h(context)
desc = description
if desc.respond_to?(:call)
@@ -77,6 +88,10 @@ module Gitlab
private
+ def executable?(context)
+ !noop? && available?(context)
+ end
+
def count_commands_executed_in(context)
return unless context.respond_to?(:commands_executed_count=)
diff --git a/lib/gitlab/quick_actions/commit_actions.rb b/lib/gitlab/quick_actions/commit_actions.rb
index 1018910e8e9..49f5ddf24eb 100644
--- a/lib/gitlab/quick_actions/commit_actions.rb
+++ b/lib/gitlab/quick_actions/commit_actions.rb
@@ -16,6 +16,13 @@ module Gitlab
_("Tags this commit to %{tag_name}.") % { tag_name: tag_name }
end
end
+ execution_message do |tag_name, message|
+ if message.present?
+ _("Tagged this commit to %{tag_name} with \"%{message}\".") % { tag_name: tag_name, message: message }
+ else
+ _("Tagged this commit to %{tag_name}.") % { tag_name: tag_name }
+ end
+ end
params 'v1.2.3 <message>'
parse_params do |tag_name_and_message|
tag_name_and_message.split(' ', 2)
diff --git a/lib/gitlab/quick_actions/dsl.rb b/lib/gitlab/quick_actions/dsl.rb
index ecb2169151e..5abbd377642 100644
--- a/lib/gitlab/quick_actions/dsl.rb
+++ b/lib/gitlab/quick_actions/dsl.rb
@@ -66,6 +66,35 @@ module Gitlab
@explanation = block_given? ? block : text
end
+ # Allows to provide a message about quick action execution result, success or failure.
+ # This message is shown after quick action execution and after saving the note.
+ #
+ # Example:
+ #
+ # execution_message do |arguments|
+ # "Added label(s) #{arguments.join(' ')}"
+ # end
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # end
+ #
+ # Note: The execution_message won't be executed unless the condition block returns true.
+ # execution_message block is executed always after the command block has run,
+ # for this reason if the condition block doesn't return true after the command block has
+ # run you need to set the @execution_message variable inside the command block instead as
+ # shown in the following example.
+ #
+ # Example using instance variable:
+ #
+ # command :command_key do |arguments|
+ # # Awesome code block
+ # @execution_message[:command_key] = 'command_key executed successfully'
+ # end
+ #
+ def execution_message(text = '', &block)
+ @execution_message = block_given? ? block : text
+ end
+
# Allows to define type(s) that must be met in order for the command
# to be returned by `.command_names` & `.command_definitions`.
#
@@ -121,10 +150,16 @@ module Gitlab
# comment.
# It accepts aliases and takes a block.
#
+ # You can also set the @execution_message instance variable, on conflicts with
+ # execution_message method the instance variable has precedence.
+ #
# Example:
#
# command :my_command, :alias_for_my_command do |arguments|
# # Awesome code block
+ # @updates[:my_command] = 'foo'
+ #
+ # @execution_message[:my_command] = 'my_command executed successfully'
# end
def command(*command_names, &block)
define_command(CommandDefinition, *command_names, &block)
@@ -158,6 +193,7 @@ module Gitlab
description: @description,
warning: @warning,
explanation: @explanation,
+ execution_message: @execution_message,
params: @params,
condition_block: @condition_block,
parse_params_block: @parse_params_block,
@@ -173,6 +209,7 @@ module Gitlab
@description = nil
@explanation = nil
+ @execution_message = nil
@params = nil
@condition_block = nil
@warning = nil
diff --git a/lib/gitlab/quick_actions/issuable_actions.rb b/lib/gitlab/quick_actions/issuable_actions.rb
index f7f89d4e897..e5d99ebee35 100644
--- a/lib/gitlab/quick_actions/issuable_actions.rb
+++ b/lib/gitlab/quick_actions/issuable_actions.rb
@@ -12,10 +12,16 @@ module Gitlab
included do
# Issue, MergeRequest, Epic: quick actions definitions
desc do
- "Close this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ _('Close this %{quick_action_target}') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
- "Closes this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Closes this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Closed this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -28,10 +34,16 @@ module Gitlab
end
desc do
- "Reopen this #{quick_action_target.to_ability_name.humanize(capitalize: false)}"
+ _('Reopen this %{quick_action_target}') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
explanation do
- "Reopens this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Reopens this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Reopened this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -45,7 +57,10 @@ module Gitlab
desc _('Change title')
explanation do |title_param|
- _("Changes the title to \"%{title_param}\".") % { title_param: title_param }
+ _('Changes the title to "%{title_param}".') % { title_param: title_param }
+ end
+ execution_message do |title_param|
+ _('Changed the title to "%{title_param}".') % { title_param: title_param }
end
params '<New title>'
types Issuable
@@ -61,7 +76,10 @@ module Gitlab
explanation do |labels_param|
labels = find_label_references(labels_param)
- "Adds #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ if labels.any?
+ _("Adds %{labels} %{label_text}.") %
+ { labels: labels.join(' '), label_text: 'label'.pluralize(labels.count) }
+ end
end
params '~label1 ~"label 2"'
types Issuable
@@ -71,21 +89,15 @@ module Gitlab
find_labels.any?
end
command :label do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:add_label_ids] ||= []
- @updates[:add_label_ids] += label_ids
-
- @updates[:add_label_ids].uniq!
- end
+ run_label_command(labels: find_labels(labels_param), command: :label, updates_key: :add_label_ids)
end
desc _('Remove all or specific label(s)')
explanation do |labels_param = nil|
- if labels_param.present?
- labels = find_label_references(labels_param)
- "Removes #{labels.join(' ')} #{'label'.pluralize(labels.count)}." if labels.any?
+ label_references = labels_param.present? ? find_label_references(labels_param) : []
+ if label_references.any?
+ _("Removes %{label_references} %{label_text}.") %
+ { label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
else
_('Removes all labels.')
end
@@ -99,7 +111,9 @@ module Gitlab
end
command :unlabel do |labels_param = nil|
if labels_param.present?
- label_ids = find_label_ids(labels_param)
+ labels = find_labels(labels_param)
+ label_ids = labels.map(&:id)
+ label_references = labels_to_reference(labels, :name)
if label_ids.any?
@updates[:remove_label_ids] ||= []
@@ -109,7 +123,10 @@ module Gitlab
end
else
@updates[:label_ids] = []
+ label_references = []
end
+
+ @execution_message[:unlabel] = remove_label_message(label_references)
end
desc _('Replace all label(s)')
@@ -125,18 +142,12 @@ module Gitlab
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", parent)
end
command :relabel do |labels_param|
- label_ids = find_label_ids(labels_param)
-
- if label_ids.any?
- @updates[:label_ids] ||= []
- @updates[:label_ids] += label_ids
-
- @updates[:label_ids].uniq!
- end
+ run_label_command(labels: find_labels(labels_param), command: :relabel, updates_key: :label_ids)
end
- desc _('Add a todo')
- explanation _('Adds a todo.')
+ desc _('Add a To Do')
+ explanation _('Adds a To Do.')
+ execution_message _('Added a To Do.')
types Issuable
condition do
quick_action_target.persisted? &&
@@ -146,8 +157,9 @@ module Gitlab
@updates[:todo_event] = 'add'
end
- desc _('Mark to do as done')
- explanation _('Marks to do as done.')
+ desc _('Mark To Do as done')
+ explanation _('Marks To Do as done.')
+ execution_message _('Marked To Do as done.')
types Issuable
condition do
quick_action_target.persisted? &&
@@ -159,7 +171,12 @@ module Gitlab
desc _('Subscribe')
explanation do
- "Subscribes to this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Subscribes to this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Subscribed to this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -172,7 +189,12 @@ module Gitlab
desc _('Unsubscribe')
explanation do
- "Unsubscribes from this #{quick_action_target.to_ability_name.humanize(capitalize: false)}."
+ _('Unsubscribes from this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
+ end
+ execution_message do
+ _('Unsubscribed from this %{quick_action_target}.') %
+ { quick_action_target: quick_action_target.to_ability_name.humanize(capitalize: false) }
end
types Issuable
condition do
@@ -187,6 +209,9 @@ module Gitlab
explanation do |name|
_("Toggles :%{name}: emoji award.") % { name: name } if name
end
+ execution_message do |name|
+ _("Toggled :%{name}: emoji award.") % { name: name } if name
+ end
params ':emoji:'
types Issuable
condition do
@@ -215,6 +240,41 @@ module Gitlab
substitution :tableflip do |comment|
"#{comment} #{TABLEFLIP}"
end
+
+ private
+
+ def run_label_command(labels:, command:, updates_key:)
+ return if labels.empty?
+
+ @updates[updates_key] ||= []
+ @updates[updates_key] += labels.map(&:id)
+ @updates[updates_key].uniq!
+
+ label_references = labels_to_reference(labels, :name)
+ @execution_message[command] = case command
+ when :relabel
+ _('Replaced all labels with %{label_references} %{label_text}.') %
+ {
+ label_references: label_references.join(' '),
+ label_text: 'label'.pluralize(label_references.count)
+ }
+ when :label
+ _('Added %{label_references} %{label_text}.') %
+ {
+ label_references: label_references.join(' '),
+ label_text: 'label'.pluralize(labels.count)
+ }
+ end
+ end
+
+ def remove_label_message(label_references)
+ if label_references.any?
+ _("Removed %{label_references} %{label_text}.") %
+ { label_references: label_references.join(' '), label_text: 'label'.pluralize(label_references.count) }
+ else
+ _('Removed all labels.')
+ end
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/issue_actions.rb b/lib/gitlab/quick_actions/issue_actions.rb
index 85e62f950c8..da28fbf5be0 100644
--- a/lib/gitlab/quick_actions/issue_actions.rb
+++ b/lib/gitlab/quick_actions/issue_actions.rb
@@ -12,6 +12,9 @@ module Gitlab
explanation do |due_date|
_("Sets the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
end
+ execution_message do |due_date|
+ _("Set the due date to %{due_date}.") % { due_date: due_date.strftime('%b %-d, %Y') } if due_date
+ end
params '<in 2 days | this Friday | December 31st>'
types Issue
condition do
@@ -27,6 +30,7 @@ module Gitlab
desc _('Remove due date')
explanation _('Removes the due date.')
+ execution_message _('Removed the due date.')
types Issue
condition do
quick_action_target.persisted? &&
@@ -49,22 +53,26 @@ module Gitlab
current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target) &&
quick_action_target.project.boards.count == 1
end
- # rubocop: disable CodeReuse/ActiveRecord
command :board_move do |target_list_name|
- label_ids = find_label_ids(target_list_name)
+ labels = find_labels(target_list_name)
+ label_ids = labels.map(&:id)
- if label_ids.size == 1
+ if label_ids.size > 1
+ message = _('Failed to move this issue because only a single label can be provided.')
+ elsif !Label.on_project_board?(quick_action_target.project_id, label_ids.first)
+ message = _('Failed to move this issue because label was not found.')
+ else
label_id = label_ids.first
- # Ensure this label corresponds to a list on the board
- next unless Label.on_project_boards(quick_action_target.project_id).where(id: label_id).exists?
-
@updates[:remove_label_ids] =
- quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id)
+ quick_action_target.labels.on_project_boards(quick_action_target.project_id).where.not(id: label_id).pluck(:id) # rubocop: disable CodeReuse/ActiveRecord
@updates[:add_label_ids] = [label_id]
+
+ message = _("Moved issue to %{label} column in the board.") % { label: labels_to_reference(labels).first }
end
+
+ @execution_message[:board_move] = message
end
- # rubocop: enable CodeReuse/ActiveRecord
desc _('Mark this issue as a duplicate of another issue')
explanation do |duplicate_reference|
@@ -81,7 +89,13 @@ module Gitlab
if canonical_issue.present?
@updates[:canonical_issue_id] = canonical_issue.id
+
+ message = _("Marked this issue as a duplicate of %{duplicate_param}.") % { duplicate_param: duplicate_param }
+ else
+ message = _('Failed to mark this issue as a duplicate because referenced issue was not found.')
end
+
+ @execution_message[:duplicate] = message
end
desc _('Move this issue to another project.')
@@ -99,12 +113,21 @@ module Gitlab
if target_project.present?
@updates[:target_project] = target_project
+
+ message = _("Moved this issue to %{path_to_project}.") % { path_to_project: target_project_path }
+ else
+ message = _("Failed to move this issue because target project doesn't exist.")
end
+
+ @execution_message[:move] = message
end
- desc _('Make issue confidential.')
+ desc _('Make issue confidential')
explanation do
- _('Makes this issue confidential')
+ _('Makes this issue confidential.')
+ end
+ execution_message do
+ _('Made this issue confidential.')
end
types Issue
condition do
@@ -114,12 +137,19 @@ module Gitlab
@updates[:confidential] = true
end
- desc _('Create a merge request.')
+ desc _('Create a merge request')
explanation do |branch_name = nil|
if branch_name
- _("Creates branch '%{branch_name}' and a merge request to resolve this issue") % { branch_name: branch_name }
+ _("Creates branch '%{branch_name}' and a merge request to resolve this issue.") % { branch_name: branch_name }
+ else
+ _('Creates a branch and a merge request to resolve this issue.')
+ end
+ end
+ execution_message do |branch_name = nil|
+ if branch_name
+ _("Created branch '%{branch_name}' and a merge request to resolve this issue.") % { branch_name: branch_name }
else
- "Creates a branch and a merge request to resolve this issue"
+ _('Created a branch and a merge request to resolve this issue.')
end
end
params "<branch name>"
diff --git a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
index e1579cfddc0..533c74ba9b4 100644
--- a/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/issue_and_merge_request_actions.rb
@@ -9,12 +9,9 @@ module Gitlab
included do
# Issue, MergeRequest: quick actions definitions
desc _('Assign')
- # rubocop: disable CodeReuse/ActiveRecord
explanation do |users|
- users = quick_action_target.allows_multiple_assignees? ? users : users.take(1)
- "Assigns #{users.map(&:to_reference).to_sentence}."
+ _('Assigns %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
- # rubocop: enable CodeReuse/ActiveRecord
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : '@user'
end
@@ -26,7 +23,10 @@ module Gitlab
extract_users(assignee_param)
end
command :assign do |users|
- next if users.empty?
+ if users.empty?
+ @execution_message[:assign] = _("Failed to assign a user because no user was found.")
+ next
+ end
if quick_action_target.allows_multiple_assignees?
@updates[:assignee_ids] ||= quick_action_target.assignees.map(&:id)
@@ -34,6 +34,8 @@ module Gitlab
else
@updates[:assignee_ids] = [users.first.id]
end
+
+ @execution_message[:assign] = _('Assigned %{assignee_users_sentence}.') % { assignee_users_sentence: assignee_users_sentence(users) }
end
desc do
@@ -44,9 +46,14 @@ module Gitlab
end
end
explanation do |users = nil|
- assignees = quick_action_target.assignees
- assignees &= users if users.present? && quick_action_target.allows_multiple_assignees?
- "Removes #{'assignee'.pluralize(assignees.size)} #{assignees.map(&:to_reference).to_sentence}."
+ assignees = assignees_for_removal(users)
+ _("Removes %{assignee_text} %{assignee_references}.") %
+ { assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
+ end
+ execution_message do |users = nil|
+ assignees = assignees_for_removal(users)
+ _("Removed %{assignee_text} %{assignee_references}.") %
+ { assignee_text: 'assignee'.pluralize(assignees.size), assignee_references: assignees.map(&:to_reference).to_sentence }
end
params do
quick_action_target.allows_multiple_assignees? ? '@user1 @user2' : ''
@@ -74,6 +81,9 @@ module Gitlab
explanation do |milestone|
_("Sets the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
end
+ execution_message do |milestone|
+ _("Set the milestone to %{milestone_reference}.") % { milestone_reference: milestone.to_reference } if milestone
+ end
params '%"milestone"'
types Issue, MergeRequest
condition do
@@ -92,6 +102,9 @@ module Gitlab
explanation do
_("Removes %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
end
+ execution_message do
+ _("Removed %{milestone_reference} milestone.") % { milestone_reference: quick_action_target.milestone.to_reference(format: :name) }
+ end
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -116,17 +129,22 @@ module Gitlab
extract_references(issuable_param, :merge_request).first
end
command :copy_metadata do |source_issuable|
- if source_issuable.present? && source_issuable.project.id == quick_action_target.project.id
+ if can_copy_metadata?(source_issuable)
@updates[:add_label_ids] = source_issuable.labels.map(&:id)
@updates[:milestone_id] = source_issuable.milestone.id if source_issuable.milestone
+
+ @execution_message[:copy_metadata] = _("Copied labels and milestone from %{source_issuable_reference}.") % { source_issuable_reference: source_issuable.to_reference }
end
end
desc _('Set time estimate')
explanation do |time_estimate|
- time_estimate = Gitlab::TimeTrackingFormatter.output(time_estimate)
-
- _("Sets time estimate to %{time_estimate}.") % { time_estimate: time_estimate } if time_estimate
+ formatted_time_estimate = format_time_estimate(time_estimate)
+ _("Sets time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
+ end
+ execution_message do |time_estimate|
+ formatted_time_estimate = format_time_estimate(time_estimate)
+ _("Set time estimate to %{time_estimate}.") % { time_estimate: formatted_time_estimate } if formatted_time_estimate
end
params '<1w 3d 2h 14m>'
types Issue, MergeRequest
@@ -144,18 +162,12 @@ module Gitlab
desc _('Add or subtract spent time')
explanation do |time_spent, time_spent_date|
- if time_spent
- if time_spent > 0
- verb = _('Adds')
- value = time_spent
- else
- verb = _('Subtracts')
- value = -time_spent
- end
-
- _("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: Gitlab::TimeTrackingFormatter.output(value) }
- end
+ spend_time_message(time_spent, time_spent_date, false)
end
+ execution_message do |time_spent, time_spent_date|
+ spend_time_message(time_spent, time_spent_date, true)
+ end
+
params '<time(1h30m | -1h30m)> <date(YYYY-MM-DD)>'
types Issue, MergeRequest
condition do
@@ -176,6 +188,7 @@ module Gitlab
desc _('Remove time estimate')
explanation _('Removes time estimate.')
+ execution_message _('Removed time estimate.')
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -187,6 +200,7 @@ module Gitlab
desc _('Remove spent time')
explanation _('Removes spent time.')
+ execution_message _('Removed spent time.')
condition do
quick_action_target.persisted? &&
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", project)
@@ -197,7 +211,8 @@ module Gitlab
end
desc _("Lock the discussion")
- explanation _("Locks the discussion")
+ explanation _("Locks the discussion.")
+ execution_message _("Locked the discussion.")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -209,7 +224,8 @@ module Gitlab
end
desc _("Unlock the discussion")
- explanation _("Unlocks the discussion")
+ explanation _("Unlocks the discussion.")
+ execution_message _("Unlocked the discussion.")
types Issue, MergeRequest
condition do
quick_action_target.persisted? &&
@@ -219,6 +235,47 @@ module Gitlab
command :unlock do
@updates[:discussion_locked] = false
end
+
+ private
+
+ def assignee_users_sentence(users)
+ if quick_action_target.allows_multiple_assignees?
+ users
+ else
+ [users.first]
+ end.map(&:to_reference).to_sentence
+ end
+
+ def assignees_for_removal(users)
+ assignees = quick_action_target.assignees
+ if users.present? && quick_action_target.allows_multiple_assignees?
+ assignees & users
+ else
+ assignees
+ end
+ end
+
+ def can_copy_metadata?(source_issuable)
+ source_issuable.present? && source_issuable.project_id == quick_action_target.project_id
+ end
+
+ def format_time_estimate(time_estimate)
+ Gitlab::TimeTrackingFormatter.output(time_estimate)
+ end
+
+ def spend_time_message(time_spent, time_spent_date, paste_tense)
+ return unless time_spent
+
+ if time_spent > 0
+ verb = paste_tense ? _('Added') : _('Adds')
+ value = time_spent
+ else
+ verb = paste_tense ? _('Subtracted') : _('Subtracts')
+ value = -time_spent
+ end
+
+ _("%{verb} %{time_spent_value} spent time.") % { verb: verb, time_spent_value: format_time_estimate(value) }
+ end
end
end
end
diff --git a/lib/gitlab/quick_actions/merge_request_actions.rb b/lib/gitlab/quick_actions/merge_request_actions.rb
index bade59182a1..e9127095a0d 100644
--- a/lib/gitlab/quick_actions/merge_request_actions.rb
+++ b/lib/gitlab/quick_actions/merge_request_actions.rb
@@ -8,8 +8,9 @@ module Gitlab
included do
# MergeRequest only quick actions definitions
- desc 'Merge (when the pipeline succeeds)'
- explanation 'Merges this merge request when the pipeline succeeds.'
+ desc _('Merge (when the pipeline succeeds)')
+ explanation _('Merges this merge request when the pipeline succeeds.')
+ execution_message _('Scheduled to merge this merge request when the pipeline succeeds.')
types MergeRequest
condition do
last_diff_sha = params && params[:merge_request_diff_head_sha]
@@ -22,10 +23,22 @@ module Gitlab
desc 'Toggle the Work In Progress status'
explanation do
- verb = quick_action_target.work_in_progress? ? 'Unmarks' : 'Marks'
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
- "#{verb} this #{noun} as Work In Progress."
+ if quick_action_target.work_in_progress?
+ _("Unmarks this %{noun} as Work In Progress.")
+ else
+ _("Marks this %{noun} as Work In Progress.")
+ end % { noun: noun }
end
+ execution_message do
+ noun = quick_action_target.to_ability_name.humanize(capitalize: false)
+ if quick_action_target.work_in_progress?
+ _("Unmarked this %{noun} as Work In Progress.")
+ else
+ _("Marked this %{noun} as Work In Progress.")
+ end % { noun: noun }
+ end
+
types MergeRequest
condition do
quick_action_target.respond_to?(:work_in_progress?) &&
@@ -36,9 +49,12 @@ module Gitlab
@updates[:wip_event] = quick_action_target.work_in_progress? ? 'unwip' : 'wip'
end
- desc 'Set target branch'
+ desc _('Set target branch')
explanation do |branch_name|
- "Sets target branch to #{branch_name}."
+ _('Sets target branch to %{branch_name}.') % { branch_name: branch_name }
+ end
+ execution_message do |branch_name|
+ _('Set target branch to %{branch_name}.') % { branch_name: branch_name }
end
params '<Local branch name>'
types MergeRequest
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index e43147a3f37..e6372a42dda 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -46,6 +46,18 @@ module Gitlab
"can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', and spaces, but it cannot start or end with '/'"
end
+ def environment_scope_regex_chars
+ "#{environment_name_regex_chars}\\*"
+ end
+
+ def environment_scope_regex
+ @environment_scope_regex ||= /\A[#{environment_scope_regex_chars}]+\z/.freeze
+ end
+
+ def environment_scope_regex_message
+ "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.', '*' and spaces"
+ end
+
def kubernetes_namespace_regex
/\A[a-z0-9]([-a-z0-9]*[a-z0-9])?\z/
end
@@ -94,6 +106,12 @@ module Gitlab
}mx
end
+ # Based on Jira's project key format
+ # https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html
+ def jira_issue_key_regex
+ @jira_issue_key_regex ||= /[A-Z][A-Z_0-9]+-\d+/
+ end
+
def jira_transition_id_regex
@jira_transition_id_regex ||= /\d+/
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/request_profiler.rb b/lib/gitlab/request_profiler.rb
index 64593153686..033e451dbee 100644
--- a/lib/gitlab/request_profiler.rb
+++ b/lib/gitlab/request_profiler.rb
@@ -6,6 +6,21 @@ module Gitlab
module RequestProfiler
PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles".freeze
+ def all
+ Dir["#{PROFILES_DIR}/*.{html,txt}"].map do |path|
+ Profile.new(File.basename(path))
+ end.select(&:valid?)
+ end
+ module_function :all
+
+ def find(name)
+ file_path = File.join(PROFILES_DIR, name)
+ return unless File.exist?(file_path)
+
+ Profile.new(name)
+ end
+ module_function :find
+
def profile_token
Rails.cache.fetch('profile-token') do
Devise.friendly_token
diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb
index 7615f6f443b..99958d7a211 100644
--- a/lib/gitlab/request_profiler/middleware.rb
+++ b/lib/gitlab/request_profiler/middleware.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'ruby-prof'
+require 'memory_profiler'
module Gitlab
module RequestProfiler
@@ -28,22 +29,73 @@ module Gitlab
end
def call_with_profiling(env)
+ case env['HTTP_X_PROFILE_MODE']
+ when 'execution', nil
+ call_with_call_stack_profiling(env)
+ when 'memory'
+ call_with_memory_profiling(env)
+ else
+ raise ActionController::BadRequest, invalid_profile_mode(env)
+ end
+ end
+
+ def invalid_profile_mode(env)
+ <<~HEREDOC
+ Invalid X-Profile-Mode: #{env['HTTP_X_PROFILE_MODE']}.
+ Supported profile mode request header:
+ - X-Profile-Mode: execution
+ - X-Profile-Mode: memory
+ HEREDOC
+ end
+
+ def call_with_call_stack_profiling(env)
ret = nil
- result = RubyProf::Profile.profile do
+ report = RubyProf::Profile.profile do
ret = catch(:warden) do
@app.call(env)
end
end
- printer = RubyProf::CallStackPrinter.new(result)
- file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
+ generate_report(env, 'execution', 'html') do |file|
+ printer = RubyProf::CallStackPrinter.new(report)
+ printer.print(file)
+ end
+
+ handle_request_ret(ret)
+ end
+
+ def call_with_memory_profiling(env)
+ ret = nil
+ report = MemoryProfiler.report do
+ ret = catch(:warden) do
+ @app.call(env)
+ end
+ end
+
+ generate_report(env, 'memory', 'txt') do |file|
+ report.pretty_print(to_file: file)
+ end
+
+ handle_request_ret(ret)
+ end
+
+ def generate_report(env, report_type, extension)
+ file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}"\
+ "_#{report_type}.#{extension}"
file_path = "#{PROFILES_DIR}/#{file_name}"
FileUtils.mkdir_p(PROFILES_DIR)
- File.open(file_path, 'wb') do |file|
- printer.print(file)
+
+ begin
+ File.open(file_path, 'wb') do |file|
+ yield(file)
+ end
+ rescue
+ FileUtils.rm(file_path)
end
+ end
+ def handle_request_ret(ret)
if ret.is_a?(Array)
ret
else
diff --git a/lib/gitlab/request_profiler/profile.rb b/lib/gitlab/request_profiler/profile.rb
index 46996ef8c51..76c675658b1 100644
--- a/lib/gitlab/request_profiler/profile.rb
+++ b/lib/gitlab/request_profiler/profile.rb
@@ -3,42 +3,40 @@
module Gitlab
module RequestProfiler
class Profile
- attr_reader :name, :time, :request_path
+ attr_reader :name, :time, :file_path, :request_path, :profile_mode, :type
alias_method :to_param, :name
- def self.all
- Dir["#{PROFILES_DIR}/*.html"].map do |path|
- new(File.basename(path))
- end
- end
-
- def self.find(name)
- name_dup = name.dup
- name_dup << '.html' unless name.end_with?('.html')
-
- file_path = "#{PROFILES_DIR}/#{name_dup}"
- return unless File.exist?(file_path)
-
- new(name_dup)
- end
-
def initialize(name)
@name = name
+ @file_path = File.join(PROFILES_DIR, name)
set_attributes
end
- def content
- File.read("#{PROFILES_DIR}/#{name}")
+ def valid?
+ @request_path.present?
+ end
+
+ def content_type
+ case type
+ when 'html'
+ 'text/html'
+ when 'txt'
+ 'text/plain'
+ end
end
private
def set_attributes
- _, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
- @request_path = path.tr('|', '/')
- @time = Time.at(timestamp.to_i).utc
+ matches = name.match(/^(?<path>.*)_(?<timestamp>\d+)(_(?<profile_mode>\w+))?\.(?<type>html|txt)$/)
+ return unless matches
+
+ @request_path = matches[:path].tr('|', '/')
+ @time = Time.at(matches[:timestamp].to_i).utc
+ @profile_mode = matches[:profile_mode] || 'unknown'
+ @type = matches[:type]
end
end
end
diff --git a/lib/gitlab/rugged_instrumentation.rb b/lib/gitlab/rugged_instrumentation.rb
new file mode 100644
index 00000000000..8bb8c547ae1
--- /dev/null
+++ b/lib/gitlab/rugged_instrumentation.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module RuggedInstrumentation
+ def self.query_time
+ SafeRequestStore[:rugged_query_time] ||= 0
+ end
+
+ def self.query_time=(duration)
+ SafeRequestStore[:rugged_query_time] = duration
+ end
+
+ def self.query_time_ms
+ (self.query_time * 1000).round(2)
+ end
+
+ def self.query_count
+ SafeRequestStore[:rugged_call_count] ||= 0
+ end
+
+ def self.increment_query_count
+ SafeRequestStore[:rugged_call_count] ||= 0
+ SafeRequestStore[:rugged_call_count] += 1
+ end
+
+ def self.active?
+ SafeRequestStore.active?
+ end
+
+ def self.peek_enabled?
+ SafeRequestStore[:peek_enabled]
+ end
+
+ def self.add_call_details(details)
+ return unless peek_enabled?
+
+ Gitlab::SafeRequestStore[:rugged_call_details] ||= []
+ Gitlab::SafeRequestStore[:rugged_call_details] << details
+ end
+
+ def self.list_call_details
+ return [] unless peek_enabled?
+
+ Gitlab::SafeRequestStore[:rugged_call_details] || []
+ end
+ end
+end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 7c1e6b1baff..ce4c1611687 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -43,6 +43,29 @@ module Gitlab
without_count ? collection.without_count : collection
end
+ def formatted_count(scope)
+ case scope
+ when 'projects'
+ formatted_limited_count(limited_projects_count)
+ when 'issues'
+ formatted_limited_count(limited_issues_count)
+ when 'merge_requests'
+ formatted_limited_count(limited_merge_requests_count)
+ when 'milestones'
+ formatted_limited_count(limited_milestones_count)
+ when 'users'
+ formatted_limited_count(limited_users_count)
+ end
+ end
+
+ def formatted_limited_count(count)
+ if count >= COUNT_LIMIT
+ "#{COUNT_LIMIT - 1}+"
+ else
+ count.to_s
+ end
+ end
+
def limited_projects_count
@limited_projects_count ||= limited_count(projects)
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/sherlock/query.rb b/lib/gitlab/sherlock/query.rb
index 159ce27e702..cbd89b7629f 100644
--- a/lib/gitlab/sherlock/query.rb
+++ b/lib/gitlab/sherlock/query.rb
@@ -96,12 +96,7 @@ module Gitlab
private
def raw_explain(query)
- explain =
- if Gitlab::Database.postgresql?
- "EXPLAIN ANALYZE #{query};"
- else
- "EXPLAIN #{query};"
- end
+ explain = "EXPLAIN ANALYZE #{query};"
ActiveRecord::Base.connection.execute(explain)
end
diff --git a/lib/gitlab/sidekiq_logging/structured_logger.rb b/lib/gitlab/sidekiq_logging/structured_logger.rb
index fdc0d518c59..60782306ade 100644
--- a/lib/gitlab/sidekiq_logging/structured_logger.rb
+++ b/lib/gitlab/sidekiq_logging/structured_logger.rb
@@ -15,9 +15,9 @@ module Gitlab
yield
- Sidekiq.logger.info log_job_done(started_at, base_payload)
+ Sidekiq.logger.info log_job_done(job, started_at, base_payload)
rescue => job_exception
- Sidekiq.logger.warn log_job_done(started_at, base_payload, job_exception)
+ Sidekiq.logger.warn log_job_done(job, started_at, base_payload, job_exception)
raise
end
@@ -28,15 +28,26 @@ module Gitlab
"#{payload['class']} JID-#{payload['jid']}"
end
+ def add_instrumentation_keys!(job, output_payload)
+ output_payload.merge!(job.slice(*::Gitlab::InstrumentationHelper::KEYS))
+ end
+
def log_job_start(started_at, payload)
payload['message'] = "#{base_message(payload)}: start"
payload['job_status'] = 'start'
+ # Old gitlab-shell messages don't provide enqueued_at/created_at attributes
+ enqueued_at = payload['enqueued_at'] || payload['created_at']
+ if enqueued_at
+ payload['scheduling_latency_s'] = elapsed_by_absolute_time(Time.iso8601(enqueued_at))
+ end
+
payload
end
- def log_job_done(started_at, payload, job_exception = nil)
+ def log_job_done(job, started_at, payload, job_exception = nil)
payload = payload.dup
+ add_instrumentation_keys!(job, payload)
payload['duration'] = elapsed(started_at)
payload['completed_at'] = Time.now.utc
@@ -78,6 +89,10 @@ module Gitlab
end
end
+ def elapsed_by_absolute_time(start)
+ (Time.now.utc - start).to_f.round(3)
+ end
+
def elapsed(start)
(current_time - start).round(3)
end
diff --git a/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb
new file mode 100644
index 00000000000..979a3fce7e6
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/instrumentation_logger.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module SidekiqMiddleware
+ class InstrumentationLogger
+ def call(worker, job, queue)
+ yield
+
+ # The Sidekiq logger is called outside the middleware block, so
+ # we need to modify the job hash to pass along this information
+ # since RequestStore is only active in the Sidekiq middleware.
+ #
+ # Modifying the job hash in a middleware is permitted by Sidekiq
+ # because Sidekiq keeps a pristine copy of the original hash
+ # before sending it to the middleware:
+ # https://github.com/mperham/sidekiq/blob/53bd529a0c3f901879925b8390353129c465b1f2/lib/sidekiq/processor.rb#L115-L118
+ ::Gitlab::InstrumentationHelper.add_instrumentation_data(job)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index 671d795ec33..49c4fdc3033 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -14,9 +14,12 @@ module Gitlab
# shut Sidekiq down
MUTEX = Mutex.new
+ attr_reader :worker
+
def call(worker, job, queue)
yield
+ @worker = worker
current_rss = get_rss
return unless MAX_RSS > 0 && current_rss > MAX_RSS
@@ -25,9 +28,11 @@ module Gitlab
# Return if another thread is already waiting to shut Sidekiq down
next unless MUTEX.try_lock
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
- " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}"
- Sidekiq.logger.warn "Sidekiq worker PID-#{pid} will stop fetching new jobs in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later"
+ warn("Sidekiq worker PID-#{pid} current RSS #{current_rss}"\
+ " exceeds maximum RSS #{MAX_RSS} after finishing job #{worker.class} JID-#{job['jid']}")
+
+ warn("Sidekiq worker PID-#{pid} will stop fetching new jobs"\
+ " in #{GRACE_TIME} seconds, and will be shut down #{SHUTDOWN_WAIT} seconds later")
# Wait `GRACE_TIME` to give the memory intensive job time to finish.
# Then, tell Sidekiq to stop fetching new jobs.
@@ -59,24 +64,28 @@ module Gitlab
def wait_and_signal_pgroup(time, signal, explanation)
return wait_and_signal(time, signal, explanation) unless Process.getpgrp == pid
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ warn("waiting #{time} seconds before sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
sleep(time)
- Sidekiq.logger.warn "sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
+ warn("sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})", signal: signal)
Process.kill(signal, 0)
end
def wait_and_signal(time, signal, explanation)
- Sidekiq.logger.warn "waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ warn("waiting #{time} seconds before sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
sleep(time)
- Sidekiq.logger.warn "sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})"
+ warn("sending Sidekiq worker PID-#{pid} #{signal} (#{explanation})", signal: signal)
Process.kill(signal, pid)
end
def pid
Process.pid
end
+
+ def warn(message, signal: nil)
+ Sidekiq.logger.warn(class: worker.class.name, pid: pid, signal: signal, message: message)
+ end
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/metrics.rb b/lib/gitlab/sidekiq_middleware/metrics.rb
new file mode 100644
index 00000000000..3dc9521ee8b
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/metrics.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+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
+
+ def call(_worker, job, queue)
+ labels = create_labels(queue)
+ @metrics[:sidekiq_running_jobs].increment(labels, 1)
+
+ if job['retry_count'].present?
+ @metrics[:sidekiq_jobs_retried_total].increment(labels, 1)
+ end
+
+ realtime = Benchmark.realtime do
+ yield
+ end
+
+ @metrics[:sidekiq_jobs_completion_seconds].observe(labels, realtime)
+ rescue Exception # rubocop: disable Lint/RescueException
+ @metrics[:sidekiq_jobs_failed_total].increment(labels, 1)
+ raise
+ ensure
+ @metrics[:sidekiq_running_jobs].increment(labels, -1)
+ end
+
+ private
+
+ def init_metrics
+ {
+ 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)
+ }
+ end
+
+ def create_labels(queue)
+ {
+ queue: queue
+ }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/slug/environment.rb b/lib/gitlab/slug/environment.rb
new file mode 100644
index 00000000000..1b87d3bb626
--- /dev/null
+++ b/lib/gitlab/slug/environment.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+# An environment name is not necessarily suitable for use in URLs, DNS
+# or other third-party contexts, so provide a slugified version. A slug has
+# the following properties:
+# * contains only lowercase letters (a-z), numbers (0-9), and '-'
+# * begins with a letter
+# * has a maximum length of 24 bytes (OpenShift limitation)
+# * cannot end with `-`
+module Gitlab
+ module Slug
+ class Environment
+ attr_reader :name
+
+ def initialize(name)
+ @name = name
+ end
+
+ def generate
+ # Lowercase letters and numbers only
+ slugified = name.to_s.downcase.gsub(/[^a-z0-9]/, '-')
+
+ # Must start with a letter
+ slugified = 'env-' + slugified unless slugified.match?(/^[a-z]/)
+
+ # Repeated dashes are invalid (OpenShift limitation)
+ slugified.squeeze!('-')
+
+ slugified =
+ if slugified.size > 24 || slugified != name
+ # Maximum length: 24 characters (OpenShift limitation)
+ shorten_and_add_suffix(slugified)
+ else
+ # Cannot end with a dash (Kubernetes label limitation)
+ slugified.chomp('-')
+ end
+
+ slugified
+ end
+
+ private
+
+ def shorten_and_add_suffix(slug)
+ slug = slug[0..16]
+ slug << '-' unless slug.ends_with?('-')
+ slug << suffix
+ end
+
+ # Slugifying a name may remove the uniqueness guarantee afforded by it being
+ # based on name (which must be unique). To compensate, we add a predictable
+ # 6-byte suffix in those circumstances. This is not *guaranteed* uniqueness,
+ # but the chance of collisions is vanishingly small
+ def suffix
+ Digest::SHA2.hexdigest(name.to_s).to_i(16).to_s(36).last(6)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index e360b552f89..ac3b219e0c7 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -22,6 +22,17 @@ module Gitlab
end
end
+ def formatted_count(scope)
+ case scope
+ when 'snippet_titles'
+ snippet_titles_count.to_s
+ when 'snippet_blobs'
+ snippet_blobs_count.to_s
+ else
+ super
+ end
+ end
+
def snippet_titles_count
@snippet_titles_count ||= snippet_titles.count
end
diff --git a/lib/gitlab/snowplow_tracker.rb b/lib/gitlab/snowplow_tracker.rb
new file mode 100644
index 00000000000..9f12513e09e
--- /dev/null
+++ b/lib/gitlab/snowplow_tracker.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'snowplow-tracker'
+
+module Gitlab
+ module SnowplowTracker
+ NAMESPACE = 'cf'
+
+ class << self
+ def track_event(category, action, label: nil, property: nil, value: nil, context: nil)
+ tracker&.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
+ end
+
+ private
+
+ def tracker
+ return unless enabled?
+
+ @tracker ||= ::SnowplowTracker::Tracker.new(emitter, subject, NAMESPACE, Gitlab::CurrentSettings.snowplow_site_id)
+ end
+
+ def subject
+ ::SnowplowTracker::Subject.new
+ end
+
+ def emitter
+ ::SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname)
+ end
+
+ def enabled?
+ Gitlab::CurrentSettings.snowplow_enabled?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/submodule_links.rb b/lib/gitlab/submodule_links.rb
index a6c0369d864..18fd604a3b0 100644
--- a/lib/gitlab/submodule_links.rb
+++ b/lib/gitlab/submodule_links.rb
@@ -9,7 +9,7 @@ module Gitlab
end
def for(submodule, sha)
- submodule_url = submodule_url_for(sha)[submodule.path]
+ submodule_url = submodule_url_for(sha, submodule.path)
SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository)
end
@@ -17,10 +17,15 @@ module Gitlab
attr_reader :repository
- def submodule_url_for(sha)
- strong_memoize(:"submodule_links_for_#{sha}") do
+ def submodule_urls_for(sha)
+ strong_memoize(:"submodule_urls_for_#{sha}") do
repository.submodule_urls_for(sha)
end
end
+
+ def submodule_url_for(sha, path)
+ urls = submodule_urls_for(sha)
+ urls && urls[path]
+ end
end
end
diff --git a/lib/gitlab/url_blocker.rb b/lib/gitlab/url_blocker.rb
index f6b2e2acf16..9c35d200dcb 100644
--- a/lib/gitlab/url_blocker.rb
+++ b/lib/gitlab/url_blocker.rb
@@ -45,18 +45,21 @@ module Gitlab
ascii_only: ascii_only
)
+ normalized_hostname = uri.normalized_host
hostname = uri.hostname
port = get_port(uri)
address_info = get_address_info(hostname, port)
return [uri, nil] unless address_info
- protected_uri_with_hostname = enforce_uri_hostname(address_info, uri, hostname, dns_rebind_protection)
+ ip_address = ip_address(address_info)
+ protected_uri_with_hostname = enforce_uri_hostname(ip_address, uri, hostname, dns_rebind_protection)
# Allow url from the GitLab instance itself but only for the configured hostname and ports
return protected_uri_with_hostname if internal?(uri)
validate_local_request(
+ normalized_hostname: normalized_hostname,
address_info: address_info,
allow_localhost: allow_localhost,
allow_local_network: allow_local_network
@@ -83,10 +86,7 @@ module Gitlab
#
# The original hostname is used to validate the SSL, given in that scenario
# we'll be making the request to the IP address, instead of using the hostname.
- def enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
- address = addrs_info.first
- ip_address = address&.ip_address
-
+ def enforce_uri_hostname(ip_address, uri, hostname, dns_rebind_protection)
return [uri, nil] unless dns_rebind_protection && ip_address && ip_address != hostname
uri = uri.dup
@@ -94,6 +94,10 @@ module Gitlab
[uri, hostname]
end
+ def ip_address(address_info)
+ address_info.first&.ip_address
+ end
+
def validate_uri(uri:, schemes:, ports:, enforce_sanitization:, enforce_user:, ascii_only:)
validate_html_tags(uri) if enforce_sanitization
@@ -111,11 +115,30 @@ module Gitlab
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
+ # In the test suite we use a lot of mocked urls that are either invalid or
+ # don't exist. In order to avoid modifying a ton of tests and factories
+ # we allow invalid urls unless the environment variable RSPEC_ALLOW_INVALID_URLS
+ # is not true
+ return if Rails.env.test? && ENV['RSPEC_ALLOW_INVALID_URLS'] == 'true'
+
+ # If the addr can't be resolved or the url is invalid (i.e http://1.1.1.1.1)
+ # we block the url
+ raise BlockedUrlError, "Host cannot be resolved or invalid"
end
- def validate_local_request(address_info:, allow_localhost:, allow_local_network:)
+ def validate_local_request(
+ normalized_hostname:,
+ address_info:,
+ allow_localhost:,
+ allow_local_network:)
return if allow_local_network && allow_localhost
+ ip_whitelist, domain_whitelist =
+ Gitlab::CurrentSettings.outbound_local_requests_whitelist_arrays
+
+ return if local_domain_whitelisted?(domain_whitelist, normalized_hostname) ||
+ local_ip_whitelisted?(ip_whitelist, ip_address(address_info))
+
unless allow_localhost
validate_localhost(address_info)
validate_loopback(address_info)
@@ -231,6 +254,16 @@ module Gitlab
(uri.port.blank? || uri.port == config.gitlab_shell.ssh_port)
end
+ def local_ip_whitelisted?(ip_whitelist, ip_string)
+ ip_obj = Gitlab::Utils.string_to_ip_object(ip_string)
+
+ ip_whitelist.any? { |ip| ip.include?(ip_obj) }
+ end
+
+ def local_domain_whitelisted?(domain_whitelist, domain_string)
+ domain_whitelist.include?(domain_string)
+ end
+
def config
Gitlab.config
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 055e01a9399..353298e67b3 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -6,7 +6,9 @@ module Gitlab
class << self
def data(force_refresh: false)
- Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) { uncached_data }
+ Rails.cache.fetch('usage_data', force: force_refresh, expires_in: 2.weeks) do
+ uncached_data
+ end
end
def uncached_data
@@ -98,9 +100,7 @@ module Gitlab
.merge(services_usage)
.merge(approximate_counts)
}.tap do |data|
- if Feature.enabled?(:group_overview_security_dashboard)
- data[:counts][:user_preferences] = user_preferences_usage
- end
+ data[:counts][:user_preferences] = user_preferences_usage
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -128,10 +128,22 @@ module Gitlab
}
end
+ # @return [Hash<Symbol, Integer>]
def usage_counters
- {
- web_ide_commits: Gitlab::UsageDataCounters::WebIdeCommitsCounter.total_count
- }
+ usage_data_counters.map(&:totals).reduce({}) { |a, b| a.merge(b) }
+ end
+
+ # @return [Array<#totals>] An array of objects that respond to `#totals`
+ def usage_data_counters
+ [
+ Gitlab::UsageDataCounters::WikiPageCounter,
+ Gitlab::UsageDataCounters::WebIdeCounter,
+ Gitlab::UsageDataCounters::NoteCounter,
+ Gitlab::UsageDataCounters::SnippetCounter,
+ Gitlab::UsageDataCounters::SearchCounter,
+ Gitlab::UsageDataCounters::CycleAnalyticsCounter,
+ Gitlab::UsageDataCounters::SourceCodeCounter
+ ]
end
def components_usage_data
@@ -176,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/base_counter.rb b/lib/gitlab/usage_data_counters/base_counter.rb
new file mode 100644
index 00000000000..2b52571c3cc
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/base_counter.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class BaseCounter
+ extend RedisCounter
+
+ UnknownEvent = Class.new(StandardError)
+
+ class << self
+ def redis_key(event)
+ Gitlab::Sentry.track_exception(UnknownEvent, extra: { event: event }) unless known_events.include?(event.to_s)
+
+ "USAGE_#{prefix}_#{event}".upcase
+ end
+
+ def count(event)
+ increment(redis_key event)
+ end
+
+ def read(event)
+ total_count(redis_key event)
+ end
+
+ def totals
+ known_events.map { |e| ["#{prefix}_#{e}".to_sym, read(e)] }.to_h
+ end
+
+ private
+
+ def known_events
+ self::KNOWN_EVENTS
+ end
+
+ def prefix
+ self::PREFIX
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb b/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb
new file mode 100644
index 00000000000..1ff4296ef65
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class CycleAnalyticsCounter < BaseCounter
+ KNOWN_EVENTS = %w[views].freeze
+ PREFIX = 'cycle_analytics'
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/note_counter.rb b/lib/gitlab/usage_data_counters/note_counter.rb
new file mode 100644
index 00000000000..672450ec82b
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/note_counter.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class NoteCounter < BaseCounter
+ KNOWN_EVENTS = %w[create].freeze
+ PREFIX = 'note'
+ COUNTABLE_TYPES = %w[Snippet Commit MergeRequest].freeze
+
+ class << self
+ def redis_key(event, noteable_type)
+ "#{super(event)}_#{noteable_type}".upcase
+ end
+
+ def count(event, noteable_type)
+ return unless countable?(noteable_type)
+
+ increment(redis_key(event, noteable_type))
+ end
+
+ def read(event, noteable_type)
+ return 0 unless countable?(noteable_type)
+
+ total_count(redis_key(event, noteable_type))
+ end
+
+ def totals
+ COUNTABLE_TYPES.map do |countable_type|
+ [:"#{countable_type.underscore}_comment", read(:create, countable_type)]
+ end.to_h
+ end
+
+ private
+
+ def countable?(noteable_type)
+ COUNTABLE_TYPES.include?(noteable_type.to_s)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/redis_counter.rb b/lib/gitlab/usage_data_counters/redis_counter.rb
index 123b8e1bef1..75d5a75e3a4 100644
--- a/lib/gitlab/usage_data_counters/redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/redis_counter.rb
@@ -3,17 +3,15 @@
module Gitlab
module UsageDataCounters
module RedisCounter
- def increment
+ def increment(redis_counter_key)
+ return unless Gitlab::CurrentSettings.usage_ping_enabled
+
Gitlab::Redis::SharedState.with { |redis| redis.incr(redis_counter_key) }
end
- def total_count
+ def total_count(redis_counter_key)
Gitlab::Redis::SharedState.with { |redis| redis.get(redis_counter_key).to_i }
end
-
- def redis_counter_key
- raise NotImplementedError
- end
end
end
end
diff --git a/lib/gitlab/usage_data_counters/search_counter.rb b/lib/gitlab/usage_data_counters/search_counter.rb
new file mode 100644
index 00000000000..5f0735347e1
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/search_counter.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ class SearchCounter
+ extend RedisCounter
+
+ NAVBAR_SEARCHES_COUNT_KEY = 'NAVBAR_SEARCHES_COUNT'
+
+ class << self
+ def increment_navbar_searches_count
+ increment(NAVBAR_SEARCHES_COUNT_KEY)
+ end
+
+ def total_navbar_searches_count
+ total_count(NAVBAR_SEARCHES_COUNT_KEY)
+ end
+
+ def totals
+ {
+ navbar_searches: total_navbar_searches_count
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/snippet_counter.rb b/lib/gitlab/usage_data_counters/snippet_counter.rb
new file mode 100644
index 00000000000..e4d234ce4d9
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/snippet_counter.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class SnippetCounter < BaseCounter
+ KNOWN_EVENTS = %w[create update].freeze
+ PREFIX = 'snippet'
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/source_code_counter.rb b/lib/gitlab/usage_data_counters/source_code_counter.rb
new file mode 100644
index 00000000000..8a1771a7bd1
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/source_code_counter.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class SourceCodeCounter < BaseCounter
+ KNOWN_EVENTS = %w[pushes].freeze
+ PREFIX = 'source_code'
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb b/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb
deleted file mode 100644
index 62236fa07a3..00000000000
--- a/lib/gitlab/usage_data_counters/web_ide_commits_counter.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module UsageDataCounters
- class WebIdeCommitsCounter
- extend RedisCounter
-
- def self.redis_counter_key
- 'WEB_IDE_COMMITS_COUNT'
- end
- end
- end
-end
diff --git a/lib/gitlab/usage_data_counters/web_ide_counter.rb b/lib/gitlab/usage_data_counters/web_ide_counter.rb
new file mode 100644
index 00000000000..0718c1dd761
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/web_ide_counter.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module UsageDataCounters
+ class WebIdeCounter
+ extend RedisCounter
+
+ COMMITS_COUNT_KEY = 'WEB_IDE_COMMITS_COUNT'
+ MERGE_REQUEST_COUNT_KEY = 'WEB_IDE_MERGE_REQUESTS_COUNT'
+ VIEWS_COUNT_KEY = 'WEB_IDE_VIEWS_COUNT'
+
+ class << self
+ def increment_commits_count
+ increment(COMMITS_COUNT_KEY)
+ end
+
+ def total_commits_count
+ total_count(COMMITS_COUNT_KEY)
+ end
+
+ def increment_merge_requests_count
+ increment(MERGE_REQUEST_COUNT_KEY)
+ end
+
+ def total_merge_requests_count
+ total_count(MERGE_REQUEST_COUNT_KEY)
+ end
+
+ def increment_views_count
+ increment(VIEWS_COUNT_KEY)
+ end
+
+ def total_views_count
+ total_count(VIEWS_COUNT_KEY)
+ end
+
+ def totals
+ {
+ web_ide_commits: total_commits_count,
+ web_ide_views: total_views_count,
+ web_ide_merge_requests: total_merge_requests_count
+ }
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/wiki_page_counter.rb b/lib/gitlab/usage_data_counters/wiki_page_counter.rb
new file mode 100644
index 00000000000..9cfe0be5bab
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/wiki_page_counter.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class WikiPageCounter < BaseCounter
+ KNOWN_EVENTS = %w[create update delete].freeze
+ PREFIX = 'wiki_pages'
+ end
+end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 16ec8a8bb28..c66ce0434a4 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -22,7 +22,7 @@ module Gitlab
end
def force_utf8(str)
- str.force_encoding(Encoding::UTF_8)
+ str.dup.force_encoding(Encoding::UTF_8)
end
def ensure_utf8_size(str, bytes:)
@@ -131,5 +131,12 @@ module Gitlab
data
end
end
+
+ def string_to_ip_object(str)
+ return unless str
+
+ IPAddr.new(str)
+ rescue IPAddr::InvalidAddressError
+ end
end
end
diff --git a/lib/gitlab/utils/sanitize_node_link.rb b/lib/gitlab/utils/sanitize_node_link.rb
new file mode 100644
index 00000000000..620d71a7814
--- /dev/null
+++ b/lib/gitlab/utils/sanitize_node_link.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require_dependency 'gitlab/utils'
+
+module Gitlab
+ module Utils
+ module SanitizeNodeLink
+ UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
+ ATTRS_TO_SANITIZE = %w(href src data-src).freeze
+
+ def remove_unsafe_links(env, remove_invalid_links: true)
+ node = env[:node]
+
+ sanitize_node(node: node, remove_invalid_links: remove_invalid_links)
+
+ # HTML entities such as <video></video> have scannable attrs in
+ # children elements, which also need to be sanitized.
+ #
+ node.children.each do |child_node|
+ sanitize_node(node: child_node, remove_invalid_links: remove_invalid_links)
+ end
+ end
+
+ # Remove all invalid scheme characters before checking against the
+ # list of unsafe protocols.
+ #
+ # See https://tools.ietf.org/html/rfc3986#section-3.1
+ #
+ def safe_protocol?(scheme)
+ return false unless scheme
+
+ scheme = scheme
+ .strip
+ .downcase
+ .gsub(/[^A-Za-z\+\.\-]+/, '')
+
+ UNSAFE_PROTOCOLS.none?(scheme)
+ end
+
+ private
+
+ def sanitize_node(node:, remove_invalid_links: true)
+ ATTRS_TO_SANITIZE.each do |attr|
+ next unless node.has_attribute?(attr)
+
+ begin
+ node[attr] = node[attr].strip
+ uri = Addressable::URI.parse(node[attr])
+
+ next unless uri.scheme
+ next if safe_protocol?(uri.scheme)
+
+ node.remove_attribute(attr)
+ rescue Addressable::URI::InvalidURIError
+ node.remove_attribute(attr) if remove_invalid_links
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb
index 46a7b5b982a..3b77fe838ae 100644
--- a/lib/gitlab/workhorse.rb
+++ b/lib/gitlab/workhorse.rb
@@ -221,7 +221,7 @@ module Gitlab
end
def set_key_and_notify(key, value, expire: nil, overwrite: true)
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
result = redis.set(key, value, ex: expire, nx: !overwrite)
if result
redis.publish(NOTIFICATION_CHANNEL, "#{key}=#{value}")
diff --git a/lib/gitlab/zoom_link_extractor.rb b/lib/gitlab/zoom_link_extractor.rb
index d9994898a08..7ac14eb2d4f 100644
--- a/lib/gitlab/zoom_link_extractor.rb
+++ b/lib/gitlab/zoom_link_extractor.rb
@@ -17,5 +17,9 @@ module Gitlab
def links
@text.scan(ZOOM_REGEXP)
end
+
+ def match?
+ ZOOM_REGEXP.match?(@text)
+ end
end
end
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/mysql_zero_date.rb b/lib/mysql_zero_date.rb
deleted file mode 100644
index f36610abf8f..00000000000
--- a/lib/mysql_zero_date.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-# Disable NO_ZERO_DATE mode for mysql in rails 5.
-# We use zero date as a default value
-# (config/initializers/active_record_mysql_timestamp.rb), in
-# Rails 5 using zero date fails by default (https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/75450216)
-# and NO_ZERO_DATE has to be explicitly disabled. Disabling strict mode
-# is not sufficient.
-
-require 'active_record/connection_adapters/abstract_mysql_adapter'
-
-module MysqlZeroDate
- def configure_connection
- super
-
- @connection.query "SET @@SESSION.sql_mode = REPLACE(@@SESSION.sql_mode, 'NO_ZERO_DATE', '');" # rubocop:disable Gitlab/ModuleWithInstanceVariables
- end
-end
-
-ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter.prepend(MysqlZeroDate)
diff --git a/lib/peek/rblineprof/custom_controller_helpers.rb b/lib/peek/rblineprof/custom_controller_helpers.rb
deleted file mode 100644
index 581cc6a37b4..00000000000
--- a/lib/peek/rblineprof/custom_controller_helpers.rb
+++ /dev/null
@@ -1,124 +0,0 @@
-# frozen_string_literal: true
-
-module Peek
- module Rblineprof
- module CustomControllerHelpers
- extend ActiveSupport::Concern
-
- # This will become useless once https://github.com/peek/peek-rblineprof/pull/5
- # is merged
- def pygmentize(file_name, code, lexer = nil)
- if lexer.present?
- Gitlab::Highlight.highlight(file_name, code)
- else
- "<pre>#{Rack::Utils.escape_html(code)}</pre>"
- end
- end
-
- # rubocop:disable all
- def inject_rblineprof
- ret = nil
- profile = lineprof(rblineprof_profiler_regex) do
- ret = yield
- end
-
- if response.content_type =~ %r|text/html|
- sort = params[:lineprofiler_sort]
- mode = params[:lineprofiler_mode] || 'cpu'
- min = (params[:lineprofiler_min] || 5).to_i * 1000
- summary = params[:lineprofiler_summary]
-
- # Sort each file by the longest calculated time
- per_file = profile.map do |file, lines|
- total, child, excl, total_cpu, child_cpu, excl_cpu = lines[0]
-
- wall = summary == 'exclusive' ? excl : total
- cpu = summary == 'exclusive' ? excl_cpu : total_cpu
- idle = summary == 'exclusive' ? (excl - excl_cpu) : (total - total_cpu)
-
- [
- file, lines,
- wall, cpu, idle,
- sort == 'idle' ? idle : sort == 'cpu' ? cpu : wall
- ]
- end.sort_by{ |a,b,c,d,e,f| -f }
-
- output = ["<div class='modal-dialog modal-xl'><div class='modal-content'>"]
- output << "<div class='modal-header'>"
- output << "<h4>Line profiling: #{human_description(params[:lineprofiler])}</h4>"
- output << "<button class='close' type='button' data-dismiss='modal' aria-label='close'><span aria-hidden='true'>&times;</span></button>"
- output << "</div>"
- output << "<div class='modal-body'>"
-
- per_file.each do |file_name, lines, file_wall, file_cpu, file_idle, file_sort|
- output << "<div class='peek-rblineprof-file'><div class='heading'>"
-
- show_src = file_sort > min
- tmpl = show_src ? "<a href='#' class='js-lineprof-file'>%s</a>" : "%s"
-
- if mode == 'cpu'
- output << sprintf("<span class='duration'>% 8.1fms + % 8.1fms</span> #{tmpl}", file_cpu / 1000.0, file_idle / 1000.0, file_name.sub(Rails.root.to_s + '/', ''))
- else
- output << sprintf("<span class='duration'>% 8.1fms</span> #{tmpl}", file_wall/1000.0, file_name.sub(Rails.root.to_s + '/', ''))
- end
-
- output << "</div>" # .heading
-
- next unless show_src
-
- output << "<div class='data'>"
- code = []
- times = []
- File.readlines(file_name).each_with_index do |line, i|
- code << line
- wall, cpu, calls = lines[i + 1]
-
- if calls && calls > 0
- if mode == 'cpu'
- idle = wall - cpu
- times << sprintf("% 8.1fms + % 8.1fms (% 5d)", cpu / 1000.0, idle / 1000.0, calls)
- else
- times << sprintf("% 8.1fms (% 5d)", wall / 1000.0, calls)
- end
- else
- times << ' '
- end
- end
- output << "<pre class='duration'>#{times.join("\n")}</pre>"
- # The following line was changed from
- # https://github.com/peek/peek-rblineprof/blob/8d3b7a283a27de2f40abda45974516693d882258/lib/peek/rblineprof/controller_helpers.rb#L125
- # This will become useless once https://github.com/peek/peek-rblineprof/pull/16
- # is merged and is implemented.
- output << "<pre class='code highlight white'>#{pygmentize(file_name, code.join, 'ruby')}</pre>"
- output << "</div></div>" # .data then .peek-rblineprof-file
- end
-
- output << "</div></div></div>"
-
- response.body += "<div class='modal' id='modal-peek-line-profile' tabindex=-1>#{output.join}</div>".html_safe
- end
-
- ret
- end
-
- private
-
- def human_description(lineprofiler_param)
- case lineprofiler_param
- when 'app'
- 'app/ & lib/'
- when 'views'
- 'app/view/'
- when 'gems'
- 'vendor/gems'
- when 'all'
- 'everything in Rails.root'
- when 'stdlib'
- 'everything in the Ruby standard library'
- else
- 'app/, config/, lib/, vendor/ & plugin/'
- end
- end
- end
- end
-end
diff --git a/lib/peek/views/active_record.rb b/lib/peek/views/active_record.rb
new file mode 100644
index 00000000000..2d78818630d
--- /dev/null
+++ b/lib/peek/views/active_record.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Peek
+ module Views
+ class ActiveRecord < DetailedView
+ private
+
+ def setup_subscribers
+ super
+
+ subscribe('sql.active_record') do |_, start, finish, _, data|
+ if Gitlab::SafeRequestStore.store[:peek_enabled]
+ unless data[:cached]
+ detail_store << {
+ duration: finish - start,
+ sql: data[:sql].strip,
+ backtrace: Gitlab::Profiler.clean_backtrace(caller)
+ }
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/peek/views/detailed_view.rb b/lib/peek/views/detailed_view.rb
new file mode 100644
index 00000000000..f4ca1cb5075
--- /dev/null
+++ b/lib/peek/views/detailed_view.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module Peek
+ module Views
+ class DetailedView < View
+ def results
+ {
+ duration: formatted_duration,
+ calls: calls,
+ details: details
+ }
+ end
+
+ def detail_store
+ ::Gitlab::SafeRequestStore["#{key}_call_details"] ||= []
+ end
+
+ private
+
+ def duration
+ detail_store.map { |entry| entry[:duration] }.sum # rubocop:disable CodeReuse/ActiveRecord
+ end
+
+ def calls
+ detail_store.count
+ end
+
+ def call_details
+ detail_store
+ end
+
+ def format_call_details(call)
+ call.merge(duration: (call[:duration] * 1000).round(3))
+ end
+
+ def details
+ call_details
+ .sort { |a, b| b[:duration] <=> a[:duration] }
+ .map(&method(:format_call_details))
+ end
+
+ def formatted_duration
+ ms = duration * 1000
+
+ if ms >= 1000
+ "%.2fms" % ms
+ else
+ "%.0fms" % ms
+ end
+ end
+ end
+ end
+end
diff --git a/lib/peek/views/gitaly.rb b/lib/peek/views/gitaly.rb
index 30f95a10024..6ad6ddfd89d 100644
--- a/lib/peek/views/gitaly.rb
+++ b/lib/peek/views/gitaly.rb
@@ -2,7 +2,9 @@
module Peek
module Views
- class Gitaly < View
+ class Gitaly < DetailedView
+ private
+
def duration
::Gitlab::GitalyClient.query_time
end
@@ -11,36 +13,14 @@ module Peek
::Gitlab::GitalyClient.get_request_count
end
- def results
- {
- duration: formatted_duration,
- calls: calls,
- details: details
- }
- end
-
- private
-
- def details
+ def call_details
::Gitlab::GitalyClient.list_call_details
- .sort { |a, b| b[:duration] <=> a[:duration] }
- .map(&method(:format_call_details))
end
def format_call_details(call)
pretty_request = call[:request]&.reject { |k, v| v.blank? }.to_h.pretty_inspect
- call.merge(duration: (call[:duration] * 1000).round(3),
- request: pretty_request || {})
- end
-
- def formatted_duration
- ms = duration * 1000
- if ms >= 1000
- "%.2fms" % ms
- else
- "%.0fms" % ms
- end
+ super.merge(request: pretty_request || {})
end
def setup_subscribers
diff --git a/lib/peek/views/redis.rb b/lib/peek/views/redis.rb
deleted file mode 100644
index 73de8672fa4..00000000000
--- a/lib/peek/views/redis.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-# frozen_string_literal: true
-
-require 'redis'
-require 'peek-redis'
-
-module Gitlab
- module Peek
- module RedisInstrumented
- def call(*args, &block)
- start = Time.now
- super(*args, &block)
- ensure
- duration = (Time.now - start)
- add_call_details(duration, args)
- end
-
- private
-
- def add_call_details(duration, args)
- # redis-rb passes an array (e.g. [:get, key])
- return unless args.length == 1
-
- detail_store << {
- cmd: args.first,
- duration: duration,
- backtrace: Gitlab::Profiler.clean_backtrace(caller)
- }
- end
-
- def detail_store
- ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
- end
- end
- end
-end
-
-module Peek
- module Views
- module RedisDetailed
- REDACTED_MARKER = "<redacted>"
-
- def results
- super.merge(details: details)
- end
-
- def details
- detail_store
- .sort { |a, b| b[:duration] <=> a[:duration] }
- .map(&method(:format_call_details))
- end
-
- def detail_store
- ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
- end
-
- def format_call_details(call)
- call.merge(cmd: format_command(call[:cmd]),
- duration: (call[:duration] * 1000).round(3))
- end
-
- def format_command(cmd)
- if cmd.length >= 2 && cmd.first =~ /^auth$/i
- cmd[-1] = REDACTED_MARKER
- # Scrub out the value of the SET calls to avoid binary
- # data or large data from spilling into the view
- elsif cmd.length >= 3 && cmd.first =~ /set/i
- cmd[2..-1] = REDACTED_MARKER
- end
-
- cmd.join(' ')
- end
- end
- end
-end
-
-class Redis::Client
- prepend Gitlab::Peek::RedisInstrumented
-end
-
-module Peek
- module Views
- class Redis < View
- prepend Peek::Views::RedisDetailed
- end
- end
-end
diff --git a/lib/peek/views/redis_detailed.rb b/lib/peek/views/redis_detailed.rb
new file mode 100644
index 00000000000..f36f581d5e9
--- /dev/null
+++ b/lib/peek/views/redis_detailed.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'redis'
+
+module Gitlab
+ module Peek
+ module RedisInstrumented
+ def call(*args, &block)
+ start = Time.now
+ super(*args, &block)
+ ensure
+ duration = (Time.now - start)
+ add_call_details(duration, args)
+ end
+
+ private
+
+ def add_call_details(duration, args)
+ return unless peek_enabled?
+ # redis-rb passes an array (e.g. [:get, key])
+ return unless args.length == 1
+
+ detail_store << {
+ cmd: args.first,
+ duration: duration,
+ backtrace: ::Gitlab::Profiler.clean_backtrace(caller)
+ }
+ end
+
+ def peek_enabled?
+ Gitlab::SafeRequestStore.store[:peek_enabled]
+ end
+
+ def detail_store
+ ::Gitlab::SafeRequestStore['redis_call_details'] ||= []
+ end
+ end
+ end
+end
+
+module Peek
+ module Views
+ class RedisDetailed < DetailedView
+ REDACTED_MARKER = "<redacted>"
+
+ def key
+ 'redis'
+ end
+
+ private
+
+ def format_call_details(call)
+ super.merge(cmd: format_command(call[:cmd]))
+ end
+
+ def format_command(cmd)
+ if cmd.length >= 2 && cmd.first =~ /^auth$/i
+ cmd[-1] = REDACTED_MARKER
+ # Scrub out the value of the SET calls to avoid binary
+ # data or large data from spilling into the view
+ elsif cmd.length >= 3 && cmd.first =~ /set/i
+ cmd[2..-1] = REDACTED_MARKER
+ end
+
+ cmd.join(' ')
+ end
+ end
+ end
+end
+
+class Redis::Client
+ prepend Gitlab::Peek::RedisInstrumented
+end
diff --git a/lib/peek/views/rugged.rb b/lib/peek/views/rugged.rb
new file mode 100644
index 00000000000..18b3f422852
--- /dev/null
+++ b/lib/peek/views/rugged.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module Peek
+ module Views
+ class Rugged < DetailedView
+ def results
+ return {} unless calls > 0
+
+ super
+ end
+
+ private
+
+ def duration
+ ::Gitlab::RuggedInstrumentation.query_time
+ end
+
+ def calls
+ ::Gitlab::RuggedInstrumentation.query_count
+ end
+
+ def call_details
+ ::Gitlab::RuggedInstrumentation.list_call_details
+ end
+
+ def format_call_details(call)
+ super.merge(args: format_args(call[:args]))
+ end
+
+ def format_args(args)
+ args.map do |arg|
+ # ActiveSupport::JSON recursively calls as_json on all
+ # instance variables, and if that instance variable points to
+ # something that refers back to the same instance, we can wind
+ # up in an infinite loop. Currently this only seems to happen with
+ # Gitlab::Git::Repository and ::Repository.
+ if arg.instance_variables.present?
+ arg.to_s
+ else
+ arg
+ end
+ end
+ end
+ end
+ 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/prometheus/pid_provider.rb b/lib/prometheus/pid_provider.rb
new file mode 100644
index 00000000000..e0f7e7e0a9e
--- /dev/null
+++ b/lib/prometheus/pid_provider.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+module Prometheus
+ module PidProvider
+ extend self
+
+ def worker_id
+ if Sidekiq.server?
+ 'sidekiq'
+ elsif defined?(Unicorn::Worker)
+ unicorn_worker_id
+ elsif defined?(::Puma)
+ puma_worker_id
+ else
+ unknown_process_id
+ end
+ end
+
+ private
+
+ def unicorn_worker_id
+ if matches = process_name.match(/unicorn.*worker\[([0-9]+)\]/)
+ "unicorn_#{matches[1]}"
+ elsif process_name =~ /unicorn/
+ "unicorn_master"
+ else
+ unknown_process_id
+ end
+ end
+
+ def puma_worker_id
+ if matches = process_name.match(/puma.*cluster worker ([0-9]+):/)
+ "puma_#{matches[1]}"
+ elsif process_name =~ /puma/
+ "puma_master"
+ else
+ unknown_process_id
+ end
+ end
+
+ def unknown_process_id
+ "process_#{Process.pid}"
+ end
+
+ def process_name
+ $0
+ end
+ end
+end
diff --git a/lib/quality/test_level.rb b/lib/quality/test_level.rb
index 24d8eac200c..60d79b52680 100644
--- a/lib/quality/test_level.rb
+++ b/lib/quality/test_level.rb
@@ -14,6 +14,7 @@ module Quality
finders
frontend
graphql
+ haml_lint
helpers
initializers
javascripts
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 4022e8ff946..07cca1c8d1e 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -67,7 +67,7 @@ module Sentry
def handle_request_exceptions
yield
- rescue HTTParty::Error => e
+ rescue Gitlab::HTTP::Error => e
Gitlab::Sentry.track_acceptable_exception(e)
raise_error 'Error when connecting to Sentry'
rescue Net::OpenTimeout
diff --git a/lib/serializers/json.rb b/lib/serializers/json.rb
index 93cb192087a..1ed5d5dc3f5 100644
--- a/lib/serializers/json.rb
+++ b/lib/serializers/json.rb
@@ -1,32 +1,16 @@
# frozen_string_literal: true
module Serializers
- # This serializer exports data as JSON,
- # it is designed to be used with interwork compatibility between MySQL and PostgreSQL
- # implementations, as used version of MySQL does not support native json type
- #
- # Secondly, the loader makes the resulting hash to have deep indifferent access
+ # Make the resulting hash have deep indifferent access
class JSON
class << self
def dump(obj)
- # MySQL stores data as text
- # look at ./config/initializers/ar_mysql_jsonb_support.rb
- if Gitlab::Database.mysql?
- obj = ActiveSupport::JSON.encode(obj)
- end
-
obj
end
def load(data)
return if data.nil?
- # On MySQL we store data as text
- # look at ./config/initializers/ar_mysql_jsonb_support.rb
- if Gitlab::Database.mysql?
- data = ActiveSupport::JSON.decode(data)
- end
-
Gitlab::Utils.deep_indifferent_access(data)
end
end
diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh
index ab46c47d8f5..1f8c915140c 100755
--- a/lib/support/deploy/deploy.sh
+++ b/lib/support/deploy/deploy.sh
@@ -28,7 +28,7 @@ sudo -u git -H git pull origin master
echo 'Deploy: Bundle and migrate'
# change it to your needs
-sudo -u git -H bundle --without aws development test mysql --deployment
+sudo -u git -H bundle --without aws development test --deployment
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
sudo -u git -H bundle exec rake gitlab:assets:clean RAILS_ENV=production
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 32df74f104a..bccb94ff0bf 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -67,6 +67,13 @@ if ! cd "$app_root" ; then
echo "Failed to cd into $app_root, exiting!"; exit 1
fi
+# Select the web server to use
+if [ -z "$EXPERIMENTAL_PUMA" ]; then
+ use_web_server="unicorn"
+else
+ use_web_server="puma"
+fi
+
### Init Script functions
@@ -256,7 +263,7 @@ start_gitlab() {
check_stale_pids
if [ "$web_status" != "0" ]; then
- echo "Starting GitLab web server"
+ echo "Starting GitLab web server ($use_web_server)"
fi
if [ "$sidekiq_status" != "0" ]; then
echo "Starting GitLab Sidekiq"
@@ -281,7 +288,7 @@ start_gitlab() {
# Remove old socket if it exists
rm -f "$rails_socket" 2>/dev/null
# Start the web server
- RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web start
+ RAILS_ENV=$RAILS_ENV USE_WEB_SERVER=$use_web_server bin/web start
fi
# If sidekiq is already running, don't start it again.
@@ -343,7 +350,7 @@ stop_gitlab() {
if [ "$web_status" = "0" ]; then
echo "Shutting down GitLab web server"
- RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web stop
+ RAILS_ENV=$RAILS_ENV USE_WEB_SERVER=$use_web_server bin/web stop
fi
if [ "$sidekiq_status" = "0" ]; then
echo "Shutting down GitLab Sidekiq"
@@ -447,7 +454,7 @@ reload_gitlab(){
exit 1
fi
printf "Reloading GitLab web server configuration... "
- RAILS_ENV=$RAILS_ENV EXPERIMENTAL_PUMA=$EXPERIMENTAL_PUMA bin/web reload
+ RAILS_ENV=$RAILS_ENV USE_WEB_SERVER=$use_web_server bin/web reload
echo "Done."
echo "Restarting GitLab Sidekiq since it isn't capable of reloading its config..."
diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb
index 467711fb74e..08c8df9b044 100644
--- a/lib/system_check/app/git_version_check.rb
+++ b/lib/system_check/app/git_version_check.rb
@@ -7,7 +7,7 @@ module SystemCheck
set_check_pass -> { "yes (#{self.current_version})" }
def self.required_version
- @required_version ||= Gitlab::VersionInfo.parse('2.21.0')
+ @required_version ||= Gitlab::VersionInfo.parse('2.22.0')
end
def self.current_version
diff --git a/lib/tasks/frontend.rake b/lib/tasks/frontend.rake
new file mode 100644
index 00000000000..1cac7520227
--- /dev/null
+++ b/lib/tasks/frontend.rake
@@ -0,0 +1,21 @@
+unless Rails.env.production?
+ namespace :frontend do
+ desc 'GitLab | Frontend | Generate fixtures for JavaScript tests'
+ RSpec::Core::RakeTask.new(:fixtures, [:pattern]) do |t, args|
+ args.with_defaults(pattern: '{spec,ee/spec}/frontend/fixtures/*.rb')
+ ENV['NO_KNAPSACK'] = 'true'
+ t.pattern = args[:pattern]
+ t.rspec_opts = '--format documentation'
+ end
+
+ desc 'GitLab | Frontend | Run JavaScript tests'
+ task tests: ['yarn:check'] do
+ sh "yarn test" do |ok, res|
+ abort('rake frontend:tests failed') unless ok
+ end
+ end
+ end
+
+ desc 'GitLab | Frontend | Shortcut for frontend:fixtures and frontend:tests'
+ task frontend: ['frontend:fixtures', 'frontend:tests']
+end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 88172e26c67..4d854cd178d 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -127,6 +127,58 @@ namespace :gitlab do
end
end
+ namespace :sessions do
+ desc "GitLab | Cleanup | Sessions | Clean ActiveSession lookup keys"
+ task active_sessions_lookup_keys: :gitlab_environment do
+ session_key_pattern = "#{Gitlab::Redis::SharedState::USER_SESSIONS_LOOKUP_NAMESPACE}:*"
+ last_save_check = Time.at(0)
+ wait_time = 10.seconds
+ cursor = 0
+ total_users_scanned = 0
+
+ Gitlab::Redis::SharedState.with do |redis|
+ begin
+ cursor, keys = redis.scan(cursor, match: session_key_pattern)
+ total_users_scanned += keys.count
+
+ if last_save_check < Time.now - 1.second
+ while redis.info('persistence')['rdb_bgsave_in_progress'] == '1'
+ puts "BGSAVE in progress, waiting #{wait_time} seconds"
+ sleep(wait_time)
+ end
+ last_save_check = Time.now
+ end
+
+ keys.each do |key|
+ user_id = key.split(':').last
+
+ lookup_key_count = redis.scard(key)
+
+ session_ids = ActiveSession.session_ids_for_user(user_id)
+ entries = ActiveSession.raw_active_session_entries(session_ids, user_id)
+ session_ids_and_entries = session_ids.zip(entries)
+
+ inactive_session_ids = session_ids_and_entries.map do |session_id, session|
+ session_id if session.nil?
+ end.compact
+
+ redis.pipelined do |conn|
+ inactive_session_ids.each do |session_id|
+ conn.srem(key, session_id)
+ end
+ end
+
+ if inactive_session_ids
+ puts "deleted #{inactive_session_ids.count} out of #{lookup_key_count} lookup keys for User ##{user_id}"
+ end
+ end
+ end while cursor.to_i != 0
+
+ puts "--- All done! Total number of scanned users: #{total_users_scanned}"
+ end
+ end
+ end
+
def remove?
ENV['REMOVE'] == 'true'
end
diff --git a/lib/tasks/gitlab/db.rake b/lib/tasks/gitlab/db.rake
index 4e7a8adbef6..1961f64659c 100644
--- a/lib/tasks/gitlab/db.rake
+++ b/lib/tasks/gitlab/db.rake
@@ -26,26 +26,19 @@ namespace :gitlab do
task drop_tables: :environment do
connection = ActiveRecord::Base.connection
- # If MySQL, turn off foreign key checks
- connection.execute('SET FOREIGN_KEY_CHECKS=0') if Gitlab::Database.mysql?
-
- # connection.tables is deprecated in MySQLAdapter, but in PostgreSQLAdapter
- # data_sources returns both views and tables, so use #tables instead
- tables = Gitlab::Database.mysql? ? connection.data_sources : connection.tables
+ # In PostgreSQLAdapter, data_sources returns both views and tables, so use
+ # #tables instead
+ tables = connection.tables
# Removes the entry from the array
tables.delete 'schema_migrations'
# Truncate schema_migrations to ensure migrations re-run
- connection.execute('TRUNCATE schema_migrations') if connection.data_source_exists? 'schema_migrations'
+ connection.execute('TRUNCATE schema_migrations') if connection.table_exists? 'schema_migrations'
# Drop tables with cascade to avoid dependent table errors
# PG: http://www.postgresql.org/docs/current/static/ddl-depend.html
- # MySQL: http://dev.mysql.com/doc/refman/5.7/en/drop-table.html
# Add `IF EXISTS` because cascade could have already deleted a table.
tables.each { |t| connection.execute("DROP TABLE IF EXISTS #{connection.quote_table_name(t)} CASCADE") }
-
- # If MySQL, re-enable foreign key checks
- connection.execute('SET FOREIGN_KEY_CHECKS=1') if Gitlab::Database.mysql?
end
desc 'Configures the database by running migrate, or by loading the schema and seeding if needed'
@@ -77,5 +70,13 @@ namespace :gitlab do
Gitlab::DowntimeCheck.new.check_and_print(migrations)
end
+
+ desc 'Sets up EE specific database functionality'
+
+ if Gitlab.ee?
+ task setup_ee: %w[geo:db:drop geo:db:create geo:db:schema:load geo:db:migrate]
+ else
+ task :setup_ee
+ end
end
end
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index e876b23d43f..5d86d6e466c 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -31,7 +31,6 @@ namespace :gitlab do
terminate_all_connections unless Rails.env.production?
Rake::Task["db:reset"].invoke
- Rake::Task["add_limits_mysql"].invoke
Rake::Task["setup_postgresql"].invoke
Rake::Task["db:seed_fu"].invoke
rescue Gitlab::TaskAbortedByUserError
@@ -46,8 +45,6 @@ namespace :gitlab do
# method terminates all the connections so that a subsequent DROP
# will work.
def self.terminate_all_connections
- return false unless Gitlab::Database.postgresql?
-
cmd = <<~SQL
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
diff --git a/lib/tasks/gitlab/update_templates.rake b/lib/tasks/gitlab/update_templates.rake
index e058e9fe069..fdcd34320b1 100644
--- a/lib/tasks/gitlab/update_templates.rake
+++ b/lib/tasks/gitlab/update_templates.rake
@@ -5,25 +5,42 @@ namespace :gitlab do
end
desc "GitLab | Update project templates"
- task :update_project_templates do
- include Gitlab::ImportExport::CommandLineUtil
+ task :update_project_templates, [] => :environment do |_task, args|
+ # we need an instance method from Gitlab::ImportExport::CommandLineUtil and don't
+ # want to include it in the task, as this would affect subsequent tasks as well
+ downloader = Class.new do
+ extend Gitlab::ImportExport::CommandLineUtil
+
+ def self.call(uploader, upload_path)
+ download_or_copy_upload(uploader, upload_path)
+ end
+ end
+
+ template_names = args.extras.to_set
if Rails.env.production?
- puts "This rake task is not meant fo production instances".red
- exit(1)
+ raise "This rake task is not meant for production instances"
end
admin = User.find_by(admin: true)
unless admin
- puts "No admin user could be found".red
- exit(1)
+ raise "No admin user could be found"
end
- Gitlab::ProjectTemplate.all.each do |template|
+ tmp_namespace_path = "tmp-project-import-#{Time.now.to_i}"
+ puts "Creating temporary namespace #{tmp_namespace_path}"
+ tmp_namespace = Namespace.create!(owner: admin, name: tmp_namespace_path, path: tmp_namespace_path)
+
+ templates = if template_names.empty?
+ Gitlab::ProjectTemplate.all
+ else
+ Gitlab::ProjectTemplate.all.select { |template| template_names.include?(template.name) }
+ end
+
+ templates.each do |template|
params = {
- import_url: template.clone_url,
- namespace_id: admin.namespace.id,
+ namespace_id: tmp_namespace.id,
path: template.name,
skip_wiki: true
}
@@ -32,33 +49,67 @@ namespace :gitlab do
project = Projects::CreateService.new(admin, params).execute
unless project.persisted?
- puts project.errors.messages
- exit(1)
+ raise "Failed to create project: #{project.errors.messages}"
end
- loop do
- if project.finished?
- puts "Import finished for #{template.name}"
- break
+ uri_encoded_project_path = template.uri_encoded_project_path
+
+ # extract a concrete commit for signing off what we actually downloaded
+ # this way we do the right thing even if the repository gets updated in the meantime
+ get_commits_response = Gitlab::HTTP.get("https://gitlab.com/api/v4/projects/#{uri_encoded_project_path}/repository/commits",
+ query: { page: 1, per_page: 1 }
+ )
+ raise "Failed to retrieve latest commit for template '#{template.name}'" unless get_commits_response.success?
+
+ commit_sha = get_commits_response.parsed_response.dig(0, 'id')
+
+ project_archive_uri = "https://gitlab.com/api/v4/projects/#{uri_encoded_project_path}/repository/archive.tar.gz?sha=#{commit_sha}"
+ commit_message = <<~MSG
+ Initialized from '#{template.title}' project template
+
+ Template repository: #{template.preview}
+ Commit SHA: #{commit_sha}
+ MSG
+
+ Dir.mktmpdir do |tmpdir|
+ Dir.chdir(tmpdir) do
+ Gitlab::TaskHelpers.run_command!(['wget', project_archive_uri, '-O', 'archive.tar.gz'])
+ Gitlab::TaskHelpers.run_command!(['tar', 'xf', 'archive.tar.gz'])
+ extracted_project_basename = Dir['*/'].first
+ Dir.chdir(extracted_project_basename) do
+ Gitlab::TaskHelpers.run_command!(%w(git init))
+ Gitlab::TaskHelpers.run_command!(%w(git add .))
+ Gitlab::TaskHelpers.run_command!(['git', 'commit', '--author', 'GitLab <root@localhost>', '--message', commit_message])
+
+ # Hacky workaround to push to the project in a way that works with both GDK and the test environment
+ Gitlab::GitalyClient::StorageSettings.allow_disk_access do
+ Gitlab::TaskHelpers.run_command!(['git', 'remote', 'add', 'origin', "file://#{project.repository.raw.path}"])
+ end
+ Gitlab::TaskHelpers.run_command!(['git', 'push', '-u', 'origin', 'master'])
+ end
end
+ end
- if project.failed?
- puts "Failed to import from #{project_params[:import_url]}".red
- exit(1)
- end
+ project.reset
- puts "Waiting for the import to finish"
+ Projects::ImportExport::ExportService.new(project, admin).execute
+ downloader.call(project.export_file, template.archive_path)
- sleep(5)
- project.reset
+ unless Projects::DestroyService.new(project, admin).execute
+ puts "Failed to destroy project #{template.name} (but namespace will be cleaned up later)"
end
- Projects::ImportExport::ExportService.new(project, admin).execute
- download_or_copy_upload(project.export_file, template.archive_path)
- Projects::DestroyService.new(admin, project).execute
puts "Exported #{template.name}".green
end
- puts "Done".green
+
+ success = true
+ ensure
+ if tmp_namespace
+ puts "Destroying temporary namespace #{tmp_namespace_path}"
+ tmp_namespace.destroy
+ end
+
+ puts "Done".green if success
end
def update(template)
diff --git a/lib/tasks/gitlab/uploads/legacy.rake b/lib/tasks/gitlab/uploads/legacy.rake
new file mode 100644
index 00000000000..18fb8afe455
--- /dev/null
+++ b/lib/tasks/gitlab/uploads/legacy.rake
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+namespace :gitlab do
+ namespace :uploads do
+ namespace :legacy do
+ desc "GitLab | Uploads | Migrate all legacy attachments"
+ task migrate: :environment do
+ class Upload < ApplicationRecord
+ self.table_name = 'uploads'
+
+ include ::EachBatch
+ end
+
+ migration = 'LegacyUploadsMigrator'.freeze
+ batch_size = 5000
+ delay_interval = 5.minutes.to_i
+
+ Upload.where(uploader: 'AttachmentUploader').each_batch(of: batch_size) do |relation, index|
+ start_id, end_id = relation.pluck('MIN(id), MAX(id)').first
+ delay = index * delay_interval
+
+ BackgroundMigrationWorker.perform_in(delay, migration, [start_id, end_id])
+ end
+ end
+ end
+ end
+end
diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake
index 2dc14183fa3..36590010406 100644
--- a/lib/tasks/karma.rake
+++ b/lib/tasks/karma.rake
@@ -1,15 +1,8 @@
unless Rails.env.production?
namespace :karma do
+ # alias exists for legacy reasons
desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
- task fixtures: ['karma:rspec_fixtures']
-
- desc 'GitLab | Karma | Generate fixtures using RSpec'
- RSpec::Core::RakeTask.new(:rspec_fixtures, [:pattern]) do |t, args|
- args.with_defaults(pattern: '{spec,ee/spec}/javascripts/fixtures/*.rb')
- ENV['NO_KNAPSACK'] = 'true'
- t.pattern = args[:pattern]
- t.rspec_opts = '--format documentation'
- end
+ task fixtures: ['frontend:fixtures']
desc 'GitLab | Karma | Run JavaScript tests'
task tests: ['yarn:check'] do
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
deleted file mode 100644
index c77fa49d586..00000000000
--- a/lib/tasks/migrate/add_limits_mysql.rake
+++ /dev/null
@@ -1,17 +0,0 @@
-require Rails.root.join('db/migrate/limits_to_mysql')
-require Rails.root.join('db/migrate/markdown_cache_limits_to_mysql')
-require Rails.root.join('db/migrate/merge_request_diff_file_limits_to_mysql')
-require Rails.root.join('db/migrate/limits_ci_build_trace_chunks_raw_data_for_mysql')
-require Rails.root.join('db/migrate/gpg_keys_limits_to_mysql')
-require Rails.root.join('db/migrate/prometheus_metrics_limits_to_mysql')
-
-desc "GitLab | Add limits to strings in mysql database"
-task add_limits_mysql: :environment do
- puts "Adding limits to schema.rb for mysql"
- LimitsToMysql.new.up
- MarkdownCacheLimitsToMysql.new.up
- MergeRequestDiffFileLimitsToMysql.new.up
- LimitsCiBuildTraceChunksRawDataForMysql.new.up
- IncreaseMysqlTextLimitForGpgKeys.new.up
- PrometheusMetricsLimitsToMysql.new.up
-end
diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake
index 56b81106c5f..4ec4fdd281f 100644
--- a/lib/tasks/services.rake
+++ b/lib/tasks/services.rake
@@ -86,7 +86,7 @@ namespace :services do
doc_start = Time.now
doc_path = File.join(Rails.root, 'doc', 'api', 'services.md')
- result = ERB.new(services_template, 0, '>')
+ result = ERB.new(services_template, trim_mode: '>')
.result(OpenStruct.new(services: services).instance_eval { binding })
File.open(doc_path, 'w') do |f|
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index c881ad4cf12..bf18332a8eb 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -2,8 +2,6 @@
return if Rails.env.production?
-Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
-
namespace :spec do
desc 'GitLab | RSpec | Run unit tests'
RSpec::Core::RakeTask.new(:unit, :rspec_opts) do |t, args|
@@ -26,63 +24,8 @@ namespace :spec do
t.rspec_opts = args[:rspec_opts]
end
- desc '[Deprecated] Use the "bin/rspec --tag api" instead'
- task :api do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag @api)
- ]
- run_commands(cmds)
- end
-
- desc '[Deprecated] Use the "spec:system" task instead'
- task :feature do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag @feature)
- ]
- run_commands(cmds)
- end
-
- desc '[Deprecated] Use "bin/rspec spec/models" instead'
- task :models do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag @models)
- ]
- run_commands(cmds)
- end
-
- desc '[Deprecated] Use "bin/rspec spec/services" instead'
- task :services do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag @services)
- ]
- run_commands(cmds)
- end
-
- desc '[Deprecated] Use "bin/rspec spec/lib" instead'
- task :lib do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec --tag @lib)
- ]
- run_commands(cmds)
- end
-end
-
-desc "GitLab | Run specs"
-task :spec do
- cmds = [
- %w(rake gitlab:setup),
- %w(rspec spec)
- ]
- run_commands(cmds)
-end
-
-def run_commands(cmds)
- cmds.each do |cmd|
- system({ 'RAILS_ENV' => 'test', 'force' => 'yes' }, *cmd) || raise("#{cmd} failed!")
+ desc 'Run the code examples in spec/requests/api'
+ RSpec::Core::RakeTask.new(:api) do |t|
+ t.pattern = 'spec/requests/api/**/*_spec.rb'
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5e8d1ac206a..1c1a3a51932 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -95,11 +95,21 @@ msgid_plural "%d metrics"
msgstr[0] ""
msgstr[1] ""
+msgid "%d minute"
+msgid_plural "%d minutes"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d more comment"
msgid_plural "%d more comments"
msgstr[0] ""
msgstr[1] ""
+msgid "%d second"
+msgid_plural "%d seconds"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "%d staged change"
msgid_plural "%d staged changes"
msgstr[0] ""
@@ -118,9 +128,6 @@ msgstr[1] ""
msgid "%{actionText} & %{openOrClose} %{noteable}"
msgstr ""
-msgid "%{canMergeCount}/%{assigneesCount} can merge"
-msgstr ""
-
msgid "%{commit_author_link} authored %{commit_timeago}"
msgstr ""
@@ -135,6 +142,9 @@ msgid_plural "%{count} participants"
msgstr[0] ""
msgstr[1] ""
+msgid "%{duration}ms"
+msgstr ""
+
msgid "%{edit_in_new_fork_notice} Try to cherry-pick this commit again."
msgstr ""
@@ -189,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 ""
@@ -266,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 ""
@@ -275,6 +291,9 @@ msgstr ""
msgid "%{username}'s avatar"
msgstr ""
+msgid "%{value} ms"
+msgstr ""
+
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
@@ -284,9 +303,15 @@ msgstr ""
msgid "'%{source}' is not a import source"
msgstr ""
+msgid "(Show all)"
+msgstr ""
+
msgid "(external source)"
msgstr ""
+msgid "+ %{amount} more"
+msgstr ""
+
msgid "+ %{count} more"
msgstr ""
@@ -450,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 ""
@@ -656,9 +684,6 @@ msgstr ""
msgid "Add a task list"
msgstr ""
-msgid "Add a todo"
-msgstr ""
-
msgid "Add an SSH key"
msgstr ""
@@ -698,6 +723,9 @@ msgstr ""
msgid "Add new directory"
msgstr ""
+msgid "Add new member to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
msgid "Add or subtract spent time"
msgstr ""
@@ -725,6 +753,15 @@ msgstr ""
msgid "AddMember|Too many users specified (limit is %{user_limit})"
msgstr ""
+msgid "Added"
+msgstr ""
+
+msgid "Added %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Added a To Do."
+msgstr ""
+
msgid "Added at"
msgstr ""
@@ -734,7 +771,10 @@ msgstr ""
msgid "Adds"
msgstr ""
-msgid "Adds a todo."
+msgid "Adds %{labels} %{label_text}."
+msgstr ""
+
+msgid "Adds a To Do."
msgstr ""
msgid "Admin Area"
@@ -878,6 +918,9 @@ msgstr ""
msgid "After a successful password update, you will be redirected to the login page where you can log in with your new password."
msgstr ""
+msgid "Alerts"
+msgstr ""
+
msgid "All"
msgstr ""
@@ -926,6 +969,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow requests to the local network from system hooks"
+msgstr ""
+
+msgid "Allow requests to the local network from web hooks and services"
+msgstr ""
+
msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
msgstr ""
@@ -944,7 +993,7 @@ msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
-msgid "Alternate support URL for help page"
+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."
@@ -1070,6 +1119,9 @@ msgstr ""
msgid "An error occurred while saving assignees"
msgstr ""
+msgid "An error occurred while triggering the job."
+msgstr ""
+
msgid "An error occurred while validating username"
msgstr ""
@@ -1229,6 +1281,9 @@ msgstr ""
msgid "Are you sure you want to delete this %{typeOfComment}?"
msgstr ""
+msgid "Are you sure you want to delete this board?"
+msgstr ""
+
msgid "Are you sure you want to delete this device? This action cannot be undone."
msgstr ""
@@ -1337,6 +1392,9 @@ msgstr ""
msgid "Assign yourself to this issue"
msgstr ""
+msgid "Assigned %{assignee_users_sentence}."
+msgstr ""
+
msgid "Assigned Issues"
msgstr ""
@@ -1354,6 +1412,9 @@ msgstr[1] ""
msgid "Assignee(s)"
msgstr ""
+msgid "Assigns %{assignee_users_sentence}."
+msgstr ""
+
msgid "Attach a file"
msgstr ""
@@ -1626,6 +1687,12 @@ msgstr ""
msgid "Blog"
msgstr ""
+msgid "Board name"
+msgstr ""
+
+msgid "Board scope"
+msgstr ""
+
msgid "BoardBlankState|Add default lists"
msgstr ""
@@ -1887,6 +1954,9 @@ msgstr ""
msgid "CICD|instance enabled"
msgstr ""
+msgid "CLOSED"
+msgstr ""
+
msgid "CONTRIBUTING"
msgstr ""
@@ -1947,6 +2017,15 @@ msgstr ""
msgid "Certificate (PEM)"
msgstr ""
+msgid "Change Label"
+msgstr ""
+
+msgid "Change assignee"
+msgstr ""
+
+msgid "Change milestone"
+msgstr ""
+
msgid "Change path"
msgstr ""
@@ -1983,6 +2062,9 @@ msgstr ""
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr ""
+msgid "Changed the title to \"%{title_param}\"."
+msgstr ""
+
msgid "Changes"
msgstr ""
@@ -2001,6 +2083,57 @@ msgstr ""
msgid "Chat"
msgstr ""
+msgid "ChatMessage|%{project_link}: Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Branch"
+msgstr ""
+
+msgid "ChatMessage|Commit"
+msgstr ""
+
+msgid "ChatMessage|Failed job"
+msgstr ""
+
+msgid "ChatMessage|Failed stage"
+msgstr ""
+
+msgid "ChatMessage|Invalid CI config YAML file"
+msgstr ""
+
+msgid "ChatMessage|Pipeline #%{pipeline_id} %{humanized_status} in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|Pipeline %{pipeline_link} of %{ref_type} %{branch_link} by %{user_combined_name} %{humanized_status}"
+msgstr ""
+
+msgid "ChatMessage|Tag"
+msgstr ""
+
+msgid "ChatMessage|and [%{count} more](%{pipeline_failed_jobs_url})"
+msgstr ""
+
+msgid "ChatMessage|failed"
+msgstr ""
+
+msgid "ChatMessage|has failed"
+msgstr ""
+
+msgid "ChatMessage|has passed"
+msgstr ""
+
+msgid "ChatMessage|has passed with warnings"
+msgstr ""
+
+msgid "ChatMessage|in %{duration}"
+msgstr ""
+
+msgid "ChatMessage|in %{project_link}"
+msgstr ""
+
+msgid "ChatMessage|passed"
+msgstr ""
+
msgid "Check again"
msgstr ""
@@ -2151,6 +2284,12 @@ msgstr ""
msgid "CiVariables|Remove variable row"
msgstr ""
+msgid "CiVariables|Scope"
+msgstr ""
+
+msgid "CiVariables|Specify variable values to be used in this run. The values specified in %{linkStart}CI/CD settings%{linkEnd} will be used as default"
+msgstr ""
+
msgid "CiVariables|State"
msgstr ""
@@ -2160,21 +2299,33 @@ msgstr ""
msgid "CiVariables|Value"
msgstr ""
+msgid "CiVariables|Variables"
+msgstr ""
+
msgid "CiVariable|* (All environments)"
msgstr ""
msgid "CiVariable|All environments"
msgstr ""
+msgid "CiVariable|Create wildcard"
+msgstr ""
+
msgid "CiVariable|Error occurred while saving variables"
msgstr ""
msgid "CiVariable|Masked"
msgstr ""
+msgid "CiVariable|New environment"
+msgstr ""
+
msgid "CiVariable|Protected"
msgstr ""
+msgid "CiVariable|Search environments"
+msgstr ""
+
msgid "CiVariable|Toggle masked"
msgstr ""
@@ -2262,9 +2413,18 @@ msgstr ""
msgid "Close sidebar"
msgstr ""
+msgid "Close this %{quick_action_target}"
+msgstr ""
+
msgid "Closed"
msgstr ""
+msgid "Closed this %{quick_action_target}."
+msgstr ""
+
+msgid "Closes this %{quick_action_target}."
+msgstr ""
+
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
msgstr ""
@@ -2364,6 +2524,9 @@ msgstr ""
msgid "ClusterIntegration|Certificate Authority bundle (PEM format)"
msgstr ""
+msgid "ClusterIntegration|Choose a prefix to be used for your namespaces. Defaults to your project path."
+msgstr ""
+
msgid "ClusterIntegration|Choose which applications to install on your Kubernetes cluster. Helm Tiller is required to install any of the following applications."
msgstr ""
@@ -2532,7 +2695,7 @@ msgstr ""
msgid "ClusterIntegration|Kubernetes cluster name"
msgstr ""
-msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details"
+msgid "ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine."
msgstr ""
msgid "ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way."
@@ -2601,6 +2764,9 @@ msgstr ""
msgid "ClusterIntegration|Project namespace (optional, unique)"
msgstr ""
+msgid "ClusterIntegration|Project namespace prefix (optional, unique)"
+msgstr ""
+
msgid "ClusterIntegration|Prometheus"
msgstr ""
@@ -2697,18 +2863,24 @@ msgstr ""
msgid "ClusterIntegration|The URL used to access the Kubernetes API."
msgstr ""
-msgid "ClusterIntegration|The associated IP will be deleted and cannot be restored."
+msgid "ClusterIntegration|The associated IP and all deployed services will be deleted and cannot be restored. Uninstalling Knative will also remove Istio from your cluster. This will not effect any other applications."
msgstr ""
-msgid "ClusterIntegration|The associated certifcate will be deleted and cannot be restored."
+msgid "ClusterIntegration|The associated Tiller pod will be deleted and cannot be restored."
msgstr ""
msgid "ClusterIntegration|The associated load balancer and IP will be deleted and cannot be restored."
msgstr ""
+msgid "ClusterIntegration|The associated private key will be deleted and cannot be restored."
+msgstr ""
+
msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
+msgid "ClusterIntegration|The namespace associated with your project. This will be used for deploy boards, pod logs, and Web terminals."
+msgstr ""
+
msgid "ClusterIntegration|There was a problem authenticating with your cluster. Please ensure your CA Certificate and Token are valid."
msgstr ""
@@ -2790,6 +2962,9 @@ msgstr ""
msgid "Collapse sidebar"
msgstr ""
+msgid "Collector hostname"
+msgstr ""
+
msgid "ComboSearch is not defined"
msgstr ""
@@ -2831,6 +3006,9 @@ msgstr[1] ""
msgid "Commit %{commit_id}"
msgstr ""
+msgid "Commit (when editing commit message)"
+msgstr ""
+
msgid "Commit Message"
msgstr ""
@@ -2954,6 +3132,9 @@ msgstr ""
msgid "Configure storage path settings."
msgstr ""
+msgid "Configure the %{link} integration."
+msgstr ""
+
msgid "Configure the way a user creates a new account."
msgstr ""
@@ -3006,14 +3187,19 @@ msgid "ContainerRegistry|Quick Start"
msgstr ""
msgid "ContainerRegistry|Remove image"
-msgstr ""
+msgid_plural "ContainerRegistry|Remove images"
+msgstr[0] ""
+msgstr[1] ""
-msgid "ContainerRegistry|Remove image and tags"
+msgid "ContainerRegistry|Remove image(s) and tags"
msgstr ""
msgid "ContainerRegistry|Remove repository"
msgstr ""
+msgid "ContainerRegistry|Remove selected images"
+msgstr ""
+
msgid "ContainerRegistry|Size"
msgstr ""
@@ -3035,6 +3221,9 @@ msgstr ""
msgid "ContainerRegistry|With the Docker Container Registry integrated into GitLab, every project can have its own space to store its Docker images. %{docLinkStart}More Information%{docLinkEnd}"
msgstr ""
+msgid "ContainerRegistry|You are about to delete <b>%{count}</b> images. This will delete the images and all tags pointing to them."
+msgstr ""
+
msgid "ContainerRegistry|You are about to delete the image <b>%{title}</b>. This will delete the image and all tags pointing to this image."
msgstr ""
@@ -3095,9 +3284,15 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
+msgid "Cookie domain"
+msgstr ""
+
msgid "Copied"
msgstr ""
+msgid "Copied labels and milestone from %{source_issuable_reference}."
+msgstr ""
+
msgid "Copy %{http_label} clone URL"
msgstr ""
@@ -3155,6 +3350,12 @@ msgstr ""
msgid "Copy token to clipboard"
msgstr ""
+msgid "Could not add prometheus URL to whitelist"
+msgstr ""
+
+msgid "Could not add prometheus URL to whitelist, errors: %{errors}"
+msgstr ""
+
msgid "Could not authorize chat nickname. Try again!"
msgstr ""
@@ -3167,6 +3368,12 @@ msgstr ""
msgid "Could not create Wiki Repository at this time. Please try again later."
msgstr ""
+msgid "Could not create instance administration project. Errors: %{errors}"
+msgstr ""
+
+msgid "Could not create project"
+msgstr ""
+
msgid "Could not delete chat nickname %{chat_name}."
msgstr ""
@@ -3182,6 +3389,12 @@ msgstr ""
msgid "Could not revoke personal access token %{personal_access_token_name}."
msgstr ""
+msgid "Could not save instance administration project ID, errors: %{errors}"
+msgstr ""
+
+msgid "Could not save project ID"
+msgstr ""
+
msgid "Coverage"
msgstr ""
@@ -3200,7 +3413,7 @@ msgstr ""
msgid "Create a GitLab account first, and then connect it to your %{label} account."
msgstr ""
-msgid "Create a merge request."
+msgid "Create a merge request"
msgstr ""
msgid "Create a new branch"
@@ -3218,6 +3431,9 @@ msgstr ""
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr ""
+msgid "Create board"
+msgstr ""
+
msgid "Create branch"
msgstr ""
@@ -3257,6 +3473,9 @@ msgstr ""
msgid "Create milestone"
msgstr ""
+msgid "Create new board"
+msgstr ""
+
msgid "Create new branch"
msgstr ""
@@ -3302,6 +3521,12 @@ msgstr ""
msgid "Created At"
msgstr ""
+msgid "Created a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Created branch '%{branch_name}' and a merge request to resolve this issue."
+msgstr ""
+
msgid "Created by me"
msgstr ""
@@ -3311,7 +3536,10 @@ msgstr ""
msgid "Created on:"
msgstr ""
-msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue"
+msgid "Creates a branch and a merge request to resolve this issue."
+msgstr ""
+
+msgid "Creates branch '%{branch_name}' and a merge request to resolve this issue."
msgstr ""
msgid "Creating graphs uses the data from the Prometheus server. If this takes a long time, ensure that data is available."
@@ -3485,6 +3713,9 @@ msgstr ""
msgid "Delete Snippet"
msgstr ""
+msgid "Delete board"
+msgstr ""
+
msgid "Delete comment"
msgstr ""
@@ -3706,6 +3937,9 @@ msgstr ""
msgid "Diffs|No file name available"
msgstr ""
+msgid "Diffs|Show all lines"
+msgstr ""
+
msgid "Diffs|Something went wrong while fetching diff lines."
msgstr ""
@@ -3718,6 +3952,9 @@ msgstr ""
msgid "Disable"
msgstr ""
+msgid "Disable email notifications"
+msgstr ""
+
msgid "Disable for this project"
msgstr ""
@@ -3814,6 +4051,9 @@ msgstr ""
msgid "Download"
msgstr ""
+msgid "Download CSV"
+msgstr ""
+
msgid "Download artifacts"
msgstr ""
@@ -3877,6 +4117,9 @@ msgstr ""
msgid "Edit application"
msgstr ""
+msgid "Edit board"
+msgstr ""
+
msgid "Edit comment"
msgstr ""
@@ -3895,12 +4138,27 @@ msgstr ""
msgid "Edit identity for %{user_name}"
msgstr ""
+msgid "Edit issue"
+msgstr ""
+
msgid "Edit issues"
msgstr ""
+msgid "Edit last comment (when focused on an empty textarea)"
+msgstr ""
+
+msgid "Edit merge request"
+msgstr ""
+
msgid "Edit public deploy key"
msgstr ""
+msgid "Edit stage"
+msgstr ""
+
+msgid "Edit wiki page"
+msgstr ""
+
msgid "Email"
msgstr ""
@@ -4027,6 +4285,9 @@ msgstr ""
msgid "Enable shared Runners"
msgstr ""
+msgid "Enable snowplow tracking"
+msgstr ""
+
msgid "Enable two-factor authentication"
msgstr ""
@@ -4057,6 +4318,9 @@ msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
+msgid "Enter board name"
+msgstr ""
+
msgid "Enter in your Bitbucket Server URL and personal access token below"
msgstr ""
@@ -4438,6 +4702,9 @@ msgstr ""
msgid "Except policy:"
msgstr ""
+msgid "Existing"
+msgstr ""
+
msgid "Existing members and groups"
msgstr ""
@@ -4447,12 +4714,18 @@ msgstr ""
msgid "Expand all"
msgstr ""
+msgid "Expand down"
+msgstr ""
+
msgid "Expand dropdown"
msgstr ""
msgid "Expand sidebar"
msgstr ""
+msgid "Expand up"
+msgstr ""
+
msgid "Expiration date"
msgstr ""
@@ -4552,6 +4825,12 @@ msgstr ""
msgid "Failed create wiki"
msgstr ""
+msgid "Failed to apply commands."
+msgstr ""
+
+msgid "Failed to assign a user because no user was found."
+msgstr ""
+
msgid "Failed to change the owner"
msgstr ""
@@ -4573,6 +4852,9 @@ msgstr ""
msgid "Failed to create resources"
msgstr ""
+msgid "Failed to delete board. Please try again."
+msgstr ""
+
msgid "Failed to deploy to"
msgstr ""
@@ -4591,6 +4873,18 @@ msgstr ""
msgid "Failed to load related branches"
msgstr ""
+msgid "Failed to mark this issue as a duplicate because referenced issue was not found."
+msgstr ""
+
+msgid "Failed to move this issue because label was not found."
+msgstr ""
+
+msgid "Failed to move this issue because only a single label can be provided."
+msgstr ""
+
+msgid "Failed to move this issue because target project doesn't exist."
+msgstr ""
+
msgid "Failed to promote label due to internal error. Please contact administrators."
msgstr ""
@@ -4716,6 +5010,9 @@ msgstr ""
msgid "Filter results by project"
msgstr ""
+msgid "Filter your projects by name"
+msgstr ""
+
msgid "Filter..."
msgstr ""
@@ -4734,6 +5031,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Finding Project File"
+msgstr ""
+
msgid "Fingerprint"
msgstr ""
@@ -4758,6 +5058,12 @@ msgstr ""
msgid "FlowdockService|Flowdock is a collaboration web app for technical teams."
msgstr ""
+msgid "Focus Filter"
+msgstr ""
+
+msgid "Focus Search"
+msgstr ""
+
msgid "FogBugz Email"
msgstr ""
@@ -4794,6 +5100,9 @@ msgstr ""
msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
msgstr ""
+msgid "Forgot your password?"
+msgstr ""
+
msgid "Fork"
msgstr ""
@@ -4887,6 +5196,9 @@ msgstr ""
msgid "Generate a default set of labels"
msgstr ""
+msgid "Generate link to chart"
+msgstr ""
+
msgid "Generate new export"
msgstr ""
@@ -4944,6 +5256,12 @@ 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 ""
+
msgid "GitLab project export"
msgstr ""
@@ -4971,9 +5289,15 @@ msgstr ""
msgid "Gitea Import"
msgstr ""
+msgid "Gitlab Pages"
+msgstr ""
+
msgid "Given access %{time_ago}"
msgstr ""
+msgid "Global Shortcuts"
+msgstr ""
+
msgid "Global notification settings"
msgstr ""
@@ -4998,12 +5322,81 @@ msgstr ""
msgid "Go to %{link_to_google_takeout}."
msgstr ""
+msgid "Go to commits"
+msgstr ""
+
+msgid "Go to environments"
+msgstr ""
+
+msgid "Go to file"
+msgstr ""
+
+msgid "Go to file permalink"
+msgstr ""
+
+msgid "Go to files"
+msgstr ""
+
+msgid "Go to finding file"
+msgstr ""
+
+msgid "Go to groups"
+msgstr ""
+
+msgid "Go to issue boards"
+msgstr ""
+
+msgid "Go to issues"
+msgstr ""
+
+msgid "Go to jobs"
+msgstr ""
+
+msgid "Go to kubernetes"
+msgstr ""
+
+msgid "Go to merge requests"
+msgstr ""
+
+msgid "Go to metrics"
+msgstr ""
+
+msgid "Go to milestones"
+msgstr ""
+
+msgid "Go to network graph"
+msgstr ""
+
msgid "Go to parent"
msgstr ""
msgid "Go to project"
msgstr ""
+msgid "Go to projects"
+msgstr ""
+
+msgid "Go to repository charts"
+msgstr ""
+
+msgid "Go to snippets"
+msgstr ""
+
+msgid "Go to the activity feed"
+msgstr ""
+
+msgid "Go to the project's activity feed"
+msgstr ""
+
+msgid "Go to the project's overview page"
+msgstr ""
+
+msgid "Go to todos"
+msgstr ""
+
+msgid "Go to wiki"
+msgstr ""
+
msgid "Go to your fork"
msgstr ""
@@ -5031,6 +5424,9 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar"
+msgstr ""
+
msgid "Gravatar enabled"
msgstr ""
@@ -5085,9 +5481,6 @@ msgstr ""
msgid "Group was successfully updated."
msgstr ""
-msgid "Group:"
-msgstr ""
-
msgid "Group: %{group_name}"
msgstr ""
@@ -5106,6 +5499,9 @@ msgstr ""
msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
msgstr ""
+msgid "GroupSettings|Disable email notifications"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
@@ -5133,6 +5529,9 @@ msgstr ""
msgid "GroupSettings|This setting will be applied to all subgroups unless overridden by a group owner. Groups that already have access to the project will continue to have access unless removed manually."
msgstr ""
+msgid "GroupSettings|This setting will override user notification preferences for all members of the group, subgroups, and projects."
+msgstr ""
+
msgid "GroupSettings|cannot be disabled when the parent group \"Share with group lock\" is enabled, except by the owner of the parent group"
msgstr ""
@@ -5274,6 +5673,9 @@ msgstr ""
msgid "Hide shared projects"
msgstr ""
+msgid "Hide stage"
+msgstr ""
+
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
@@ -5282,6 +5684,9 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest number of requests per minute for each raw path, default to 300. To disable throttling set to 0."
+msgstr ""
+
msgid "Highest role:"
msgstr ""
@@ -5324,6 +5729,9 @@ msgstr ""
msgid "ID"
msgstr ""
+msgid "ID:"
+msgstr ""
+
msgid "IDE|Allow live previews of JavaScript projects in the Web IDE using CodeSandbox client side evaluation."
msgstr ""
@@ -5360,6 +5768,9 @@ msgstr ""
msgid "IDE|Review"
msgstr ""
+msgid "IDE|Successful commit"
+msgstr ""
+
msgid "IP Address"
msgstr ""
@@ -5606,6 +6017,12 @@ msgstr ""
msgid "Instance Statistics visibility"
msgstr ""
+msgid "Instance administration project already exists"
+msgstr ""
+
+msgid "Instance administrators group already exists"
+msgstr ""
+
msgid "Instance does not support multiple Kubernetes clusters"
msgstr ""
@@ -5687,12 +6104,6 @@ msgstr ""
msgid "Invocations"
msgstr ""
-msgid "Invoke Count"
-msgstr ""
-
-msgid "Invoke Time"
-msgstr ""
-
msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
msgstr ""
@@ -5717,6 +6128,21 @@ msgstr ""
msgid "IssueBoards|Boards"
msgstr ""
+msgid "IssueBoards|Create new board"
+msgstr ""
+
+msgid "IssueBoards|Delete board"
+msgstr ""
+
+msgid "IssueBoards|No matching boards found"
+msgstr ""
+
+msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
+msgstr ""
+
+msgid "IssueBoards|Switch board"
+msgstr ""
+
msgid "IssueTracker|Bugzilla issue tracker"
msgstr ""
@@ -5915,6 +6341,9 @@ msgstr ""
msgid "Key: %{key}"
msgstr ""
+msgid "Keyboard Shortcuts"
+msgstr ""
+
msgid "Kubernetes"
msgstr ""
@@ -5942,7 +6371,7 @@ msgstr ""
msgid "Kubernetes error: %{error_code}"
msgstr ""
-msgid "Kubernetes service integration has been disabled. Fields on this page are not used by GitLab, you can configure your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page"
+msgid "LDAP"
msgstr ""
msgid "LFS"
@@ -6046,9 +6475,15 @@ msgstr ""
msgid "Last seen"
msgstr ""
+msgid "Last successful update"
+msgstr ""
+
msgid "Last update"
msgstr ""
+msgid "Last update attempt"
+msgstr ""
+
msgid "Last updated"
msgstr ""
@@ -6129,6 +6564,9 @@ msgid_plural "Limited to showing %d events at most"
msgstr[0] ""
msgstr[1] ""
+msgid "Link copied to clipboard"
+msgstr ""
+
msgid "Linked emails (%{email_count})"
msgstr ""
@@ -6198,10 +6636,13 @@ msgstr ""
msgid "Locked by %{fileLockUserName}"
msgstr ""
+msgid "Locked the discussion."
+msgstr ""
+
msgid "Locked to current projects"
msgstr ""
-msgid "Locks the discussion"
+msgid "Locks the discussion."
msgstr ""
msgid "Logo was successfully removed."
@@ -6210,16 +6651,22 @@ msgstr ""
msgid "Logs"
msgstr ""
+msgid "MERGED"
+msgstr ""
+
msgid "MRDiff|Show changes only"
msgstr ""
msgid "MRDiff|Show full file"
msgstr ""
+msgid "Made this issue confidential."
+msgstr ""
+
msgid "Make and review changes in the browser with the Web IDE"
msgstr ""
-msgid "Make issue confidential."
+msgid "Make issue confidential"
msgstr ""
msgid "Make sure you save it - you won't be able to access it again."
@@ -6228,7 +6675,7 @@ msgstr ""
msgid "Make sure you're logged into the account that owns the projects you'd like to import."
msgstr ""
-msgid "Makes this issue confidential"
+msgid "Makes this issue confidential."
msgstr ""
msgid "Manage"
@@ -6294,6 +6741,9 @@ msgstr ""
msgid "March"
msgstr ""
+msgid "Mark To Do as done"
+msgstr ""
+
msgid "Mark as done"
msgstr ""
@@ -6306,9 +6756,6 @@ msgstr ""
msgid "Mark this issue as a duplicate of another issue"
msgstr ""
-msgid "Mark to do as done"
-msgstr ""
-
msgid "Markdown"
msgstr ""
@@ -6321,10 +6768,22 @@ msgstr ""
msgid "Markdown is supported"
msgstr ""
-msgid "Marks this issue as a duplicate of %{duplicate_reference}."
+msgid "Marked To Do as done."
msgstr ""
-msgid "Marks to do as done."
+msgid "Marked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marked this issue as a duplicate of %{duplicate_param}."
+msgstr ""
+
+msgid "Marks To Do as done."
+msgstr ""
+
+msgid "Marks this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Marks this issue as a duplicate of %{duplicate_reference}."
msgstr ""
msgid "Max access level"
@@ -6363,9 +6822,18 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
+msgid "Members with access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
+msgid "Members with pending access to %{strong_start}%{group_name}%{strong_end}"
+msgstr ""
+
msgid "Merge"
msgstr ""
+msgid "Merge (when the pipeline succeeds)"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -6489,6 +6957,9 @@ msgstr ""
msgid "Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes."
msgstr ""
+msgid "Merges this merge request when the pipeline succeeds."
+msgstr ""
+
msgid "Messages"
msgstr ""
@@ -6648,6 +7119,9 @@ msgstr ""
msgid "More"
msgstr ""
+msgid "More actions"
+msgstr ""
+
msgid "More information"
msgstr ""
@@ -6672,15 +7146,39 @@ msgstr ""
msgid "Move issue from one column of the board to another"
msgstr ""
+msgid "Move selection down"
+msgstr ""
+
+msgid "Move selection up"
+msgstr ""
+
msgid "Move this issue to another project."
msgstr ""
+msgid "Move to next file"
+msgstr ""
+
+msgid "Move to next unresolved discussion"
+msgstr ""
+
+msgid "Move to previous file"
+msgstr ""
+
+msgid "Move to previous unresolved discussion"
+msgstr ""
+
msgid "MoveIssue|Cannot move issue due to insufficient permissions!"
msgstr ""
msgid "MoveIssue|Cannot move issue to project it originates from!"
msgstr ""
+msgid "Moved issue to %{label} column in the board."
+msgstr ""
+
+msgid "Moved this issue to %{path_to_project}."
+msgstr ""
+
msgid "Moves issue to %{label} column in the board."
msgstr ""
@@ -6729,6 +7227,9 @@ msgstr ""
msgid "Network"
msgstr ""
+msgid "Network Graph"
+msgstr ""
+
msgid "Never"
msgstr ""
@@ -6899,6 +7400,9 @@ msgstr ""
msgid "No data found"
msgstr ""
+msgid "No data to display"
+msgstr ""
+
msgid "No details available"
msgstr ""
@@ -6941,9 +7445,6 @@ msgstr ""
msgid "No milestones to show"
msgstr ""
-msgid "No one can merge"
-msgstr ""
-
msgid "No other labels with such name or description"
msgstr ""
@@ -6968,12 +7469,18 @@ msgstr ""
msgid "No schedules"
msgstr ""
+msgid "No starrers matched your search"
+msgstr ""
+
msgid "No template"
msgstr ""
msgid "No, directly import the existing email addresses and usernames."
msgstr ""
+msgid "Nobody has starred this repository yet"
+msgstr ""
+
msgid "None"
msgstr ""
@@ -7109,6 +7616,9 @@ msgstr ""
msgid "Notifications"
msgstr ""
+msgid "Notifications have been disabled by the project or group owner"
+msgstr ""
+
msgid "Notifications off"
msgstr ""
@@ -7133,6 +7643,9 @@ msgstr ""
msgid "OfSearchInADropdown|Filter"
msgstr ""
+msgid "OmniAuth"
+msgstr ""
+
msgid "Once removed, the fork relationship cannot be restored and you will no longer be able to send merge requests to the source."
msgstr ""
@@ -7177,6 +7690,9 @@ msgstr ""
msgid "Open Documentation"
msgstr ""
+msgid "Open Selection"
+msgstr ""
+
msgid "Open comment type dropdown"
msgstr ""
@@ -7258,6 +7774,9 @@ msgstr ""
msgid "Owner"
msgstr ""
+msgid "Packages"
+msgstr ""
+
msgid "Page not found"
msgstr ""
@@ -7366,10 +7885,13 @@ msgstr ""
msgid "PerformanceBar|Gitaly calls"
msgstr ""
-msgid "PerformanceBar|SQL queries"
+msgid "PerformanceBar|Redis calls"
msgstr ""
-msgid "PerformanceBar|profile"
+msgid "PerformanceBar|Rugged calls"
+msgstr ""
+
+msgid "PerformanceBar|SQL queries"
msgstr ""
msgid "PerformanceBar|trace"
@@ -7501,6 +8023,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 ""
@@ -7558,6 +8083,9 @@ msgstr ""
msgid "Pipeline|Existing branch name or tag"
msgstr ""
+msgid "Pipeline|Key"
+msgstr ""
+
msgid "Pipeline|Pipeline"
msgstr ""
@@ -7588,6 +8116,9 @@ msgstr ""
msgid "Pipeline|Triggerer"
msgstr ""
+msgid "Pipeline|Value"
+msgstr ""
+
msgid "Pipeline|Variables"
msgstr ""
@@ -7651,6 +8182,9 @@ msgstr ""
msgid "Please add a list to your board first"
msgstr ""
+msgid "Please check your email (%{email}) to verify that you own this address. Didn't receive it? %{resend_link}. Wrong email address? %{update_link}."
+msgstr ""
+
msgid "Please choose a group URL with no special characters."
msgstr ""
@@ -8170,6 +8704,12 @@ msgstr ""
msgid "Project Badges"
msgstr ""
+msgid "Project File"
+msgstr ""
+
+msgid "Project Files browsing"
+msgstr ""
+
msgid "Project ID"
msgstr ""
@@ -8230,9 +8770,6 @@ msgstr ""
msgid "Project visibility level will be changed to match namespace rules when transferring to a group."
msgstr ""
-msgid "Project:"
-msgstr ""
-
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -8272,6 +8809,12 @@ msgstr ""
msgid "ProjectOverview|Star"
msgstr ""
+msgid "ProjectOverview|Starrer"
+msgstr ""
+
+msgid "ProjectOverview|Starrers"
+msgstr ""
+
msgid "ProjectOverview|Unstar"
msgstr ""
@@ -8494,6 +9037,9 @@ msgstr ""
msgid "ProjectsNew|Want to house several dependent projects under the same namespace? %{link_start}Create a group.%{link_end}"
msgstr ""
+msgid "Prometheus listen_address is not a valid URI"
+msgstr ""
+
msgid "PrometheusService|%{exporters} with %{metrics} were found"
msgstr ""
@@ -8677,6 +9223,9 @@ msgstr ""
msgid "Rake Tasks Help"
msgstr ""
+msgid "Raw blob request rate limit per minute"
+msgstr ""
+
msgid "Read more"
msgstr ""
@@ -8698,6 +9247,9 @@ msgstr ""
msgid "Receive notifications about your own activity"
msgstr ""
+msgid "Recent"
+msgstr ""
+
msgid "Recent Project Activity"
msgstr ""
@@ -8751,9 +9303,6 @@ msgstr ""
msgid "Register with two-factor app"
msgstr ""
-msgid "Registry"
-msgstr ""
-
msgid "Related Deployed Jobs"
msgstr ""
@@ -8826,15 +9375,45 @@ msgstr ""
msgid "Remove spent time"
msgstr ""
+msgid "Remove stage"
+msgstr ""
+
msgid "Remove time estimate"
msgstr ""
+msgid "Removed %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removed %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Removed %{milestone_reference} milestone."
+msgstr ""
+
+msgid "Removed all labels."
+msgstr ""
+
msgid "Removed group can not be restored!"
msgstr ""
msgid "Removed projects cannot be restored!"
msgstr ""
+msgid "Removed spent time."
+msgstr ""
+
+msgid "Removed the due date."
+msgstr ""
+
+msgid "Removed time estimate."
+msgstr ""
+
+msgid "Removes %{assignee_text} %{assignee_references}."
+msgstr ""
+
+msgid "Removes %{label_references} %{label_text}."
+msgstr ""
+
msgid "Removes %{milestone_reference} milestone."
msgstr ""
@@ -8868,12 +9447,30 @@ msgstr ""
msgid "Reopen milestone"
msgstr ""
+msgid "Reopen this %{quick_action_target}"
+msgstr ""
+
+msgid "Reopened this %{quick_action_target}."
+msgstr ""
+
+msgid "Reopens this %{quick_action_target}."
+msgstr ""
+
msgid "Replace"
msgstr ""
msgid "Replace all label(s)"
msgstr ""
+msgid "Replaced all labels with %{label_references} %{label_text}."
+msgstr ""
+
+msgid "Reply (quoting selected text)"
+msgstr ""
+
+msgid "Reply by email"
+msgstr ""
+
msgid "Reply to comment"
msgstr ""
@@ -8955,6 +9552,9 @@ msgstr ""
msgid "Requests Profiles"
msgstr ""
+msgid "Requests to these domain(s)/address(es) on the local network will be allowed when local requests from hooks and services are not allowed. IP ranges such as 1:0:0:0:0:0:0:0/124 or 127.0.0.0/28 are supported. Domain wildcards are not supported currently. Use comma, semicolon, or newline to separate multiple entries. The whitelist can hold a maximum of 1000 entries. Domains should use IDNA encoding. Ex: example.com, 192.168.1.1, 127.0.0.0/28, xn--itlab-j1a.com."
+msgstr ""
+
msgid "Require all users in this group to setup Two-factor authentication"
msgstr ""
@@ -8970,6 +9570,9 @@ msgstr ""
msgid "Resend invite"
msgstr ""
+msgid "Resend it"
+msgstr ""
+
msgid "Reset health check access token"
msgstr ""
@@ -9206,6 +9809,9 @@ msgstr ""
msgid "Scheduled"
msgstr ""
+msgid "Scheduled to merge this merge request when the pipeline succeeds."
+msgstr ""
+
msgid "Schedules"
msgstr ""
@@ -9227,15 +9833,27 @@ msgstr ""
msgid "Scopes"
msgstr ""
+msgid "Scroll down"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
+msgid "Scroll left"
+msgstr ""
+
+msgid "Scroll right"
+msgstr ""
+
msgid "Scroll to bottom"
msgstr ""
msgid "Scroll to top"
msgstr ""
+msgid "Scroll up"
+msgstr ""
+
msgid "Search"
msgstr ""
@@ -9314,6 +9932,12 @@ msgstr ""
msgid "SearchAutocomplete|in this project"
msgstr ""
+msgid "SearchCodeResults|in"
+msgstr ""
+
+msgid "SearchCodeResults|of %{link_to_project}"
+msgstr ""
+
msgid "SearchResults|Showing %{from} - %{to} of %{count} %{scope} for \"%{term}\""
msgstr ""
@@ -9359,9 +9983,6 @@ msgstr ""
msgid "Select branch/tag"
msgstr ""
-msgid "Select members to invite"
-msgstr ""
-
msgid "Select merge moment"
msgstr ""
@@ -9533,18 +10154,33 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set target branch"
+msgstr ""
+
+msgid "Set target branch to %{branch_name}."
+msgstr ""
+
msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
msgstr ""
+msgid "Set the due date to %{due_date}."
+msgstr ""
+
msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
msgstr ""
msgid "Set the maximum file size for each job's artifacts"
msgstr ""
+msgid "Set the milestone to %{milestone_reference}."
+msgstr ""
+
msgid "Set time estimate"
msgstr ""
+msgid "Set time estimate to %{time_estimate}."
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -9590,6 +10226,9 @@ msgstr ""
msgid "SetStatusModal|What's your status?"
msgstr ""
+msgid "Sets target branch to %{branch_name}."
+msgstr ""
+
msgid "Sets the due date to %{due_date}."
msgstr ""
@@ -9656,6 +10295,9 @@ msgstr ""
msgid "Show whitespace changes"
msgstr ""
+msgid "Show/hide this dialog"
+msgstr ""
+
msgid "Showing %d event"
msgid_plural "Showing %d events"
msgstr[0] ""
@@ -9682,6 +10324,9 @@ msgstr ""
msgid "Sign out"
msgstr ""
+msgid "Sign up"
+msgstr ""
+
msgid "Sign-in restrictions"
msgstr ""
@@ -9709,6 +10354,9 @@ msgstr ""
msgid "Similar issues"
msgstr ""
+msgid "Site ID"
+msgstr ""
+
msgid "Size and domain settings for static websites"
msgstr ""
@@ -9739,6 +10387,9 @@ msgstr ""
msgid "SnippetsEmptyState|They can be either public or private."
msgstr ""
+msgid "Snowplow"
+msgstr ""
+
msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
msgstr ""
@@ -9922,6 +10573,9 @@ msgstr ""
msgid "SortOptions|Oldest sign in"
msgstr ""
+msgid "SortOptions|Oldest starred"
+msgstr ""
+
msgid "SortOptions|Oldest updated"
msgstr ""
@@ -9937,6 +10591,9 @@ msgstr ""
msgid "SortOptions|Recent sign in"
msgstr ""
+msgid "SortOptions|Recently starred"
+msgstr ""
+
msgid "SortOptions|Sort direction"
msgstr ""
@@ -10030,6 +10687,9 @@ msgstr ""
msgid "StarredProjectsEmptyState|You don't have starred projects yet."
msgstr ""
+msgid "Starrers"
+msgstr ""
+
msgid "Stars"
msgstr ""
@@ -10117,6 +10777,18 @@ msgstr ""
msgid "StorageSize|Unknown"
msgstr ""
+msgid "SubgroupCreationLevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Allowed to create subgroups"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Maintainers"
+msgstr ""
+
+msgid "SubgroupCreationlevel|Owners"
+msgstr ""
+
msgid "Subgroups"
msgstr ""
@@ -10156,9 +10828,18 @@ msgstr ""
msgid "Subscribed"
msgstr ""
+msgid "Subscribed to this %{quick_action_target}."
+msgstr ""
+
+msgid "Subscribes to this %{quick_action_target}."
+msgstr ""
+
msgid "Subscription"
msgstr ""
+msgid "Subtracted"
+msgstr ""
+
msgid "Subtracts"
msgstr ""
@@ -10255,6 +10936,9 @@ msgstr ""
msgid "Sunday"
msgstr ""
+msgid "Support"
+msgstr ""
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
@@ -10306,6 +10990,12 @@ msgstr ""
msgid "Tag this commit."
msgstr ""
+msgid "Tagged this commit to %{tag_name} with \"%{message}\"."
+msgstr ""
+
+msgid "Tagged this commit to %{tag_name}."
+msgstr ""
+
msgid "Tags"
msgstr ""
@@ -10527,9 +11217,6 @@ msgstr ""
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
-msgid "The instance-level Kubernetes service integration is disabled. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>."
-msgstr ""
-
msgid "The invitation could not be accepted."
msgstr ""
@@ -10629,7 +11316,7 @@ msgstr ""
msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
msgstr ""
-msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
+msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>."
msgstr ""
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
@@ -10893,9 +11580,6 @@ msgstr ""
msgid "This issue is locked."
msgstr ""
-msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments"
-msgstr ""
-
msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered"
msgstr ""
@@ -10950,6 +11634,9 @@ msgstr ""
msgid "This job requires a manual action"
msgstr ""
+msgid "This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes."
+msgstr ""
+
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
msgstr ""
@@ -10974,9 +11661,6 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run on the source branch"
-msgstr ""
-
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
msgstr ""
@@ -11007,6 +11691,9 @@ msgstr ""
msgid "This setting can be overridden in each project."
msgstr ""
+msgid "This setting will override user notification preferences for all project members."
+msgstr ""
+
msgid "This setting will update the hostname that is used to generate private commit emails. %{learn_more}"
msgstr ""
@@ -11332,6 +12019,9 @@ msgstr ""
msgid "Today"
msgstr ""
+msgid "Toggle Markdown preview"
+msgstr ""
+
msgid "Toggle Sidebar"
msgstr ""
@@ -11356,6 +12046,9 @@ msgstr ""
msgid "Toggle sidebar"
msgstr ""
+msgid "Toggle the Performance Bar"
+msgstr ""
+
msgid "Toggle thread"
msgstr ""
@@ -11365,6 +12058,9 @@ msgstr ""
msgid "ToggleButton|Toggle Status: ON"
msgstr ""
+msgid "Toggled :%{name}: emoji award."
+msgstr ""
+
msgid "Toggles :%{name}: emoji award."
msgstr ""
@@ -11449,9 +12145,6 @@ msgstr ""
msgid "Trigger was created successfully."
msgstr ""
-msgid "Trigger was re-assigned."
-msgstr ""
-
msgid "Trigger was successfully updated."
msgstr ""
@@ -11521,6 +12214,9 @@ msgstr ""
msgid "Unable to resolve"
msgstr ""
+msgid "Unable to save your changes. Please try again."
+msgstr ""
+
msgid "Unable to schedule a pipeline to run immediately"
msgstr ""
@@ -11572,7 +12268,16 @@ msgstr ""
msgid "Unlocked"
msgstr ""
-msgid "Unlocks the discussion"
+msgid "Unlocked the discussion."
+msgstr ""
+
+msgid "Unlocks the discussion."
+msgstr ""
+
+msgid "Unmarked this %{noun} as Work In Progress."
+msgstr ""
+
+msgid "Unmarks this %{noun} as Work In Progress."
msgstr ""
msgid "Unresolve discussion"
@@ -11617,6 +12322,12 @@ msgstr ""
msgid "Unsubscribe from %{type}"
msgstr ""
+msgid "Unsubscribed from this %{quick_action_target}."
+msgstr ""
+
+msgid "Unsubscribes from this %{quick_action_target}."
+msgstr ""
+
msgid "Until"
msgstr ""
@@ -11638,6 +12349,9 @@ msgstr ""
msgid "Update failed"
msgstr ""
+msgid "Update it"
+msgstr ""
+
msgid "Update now"
msgstr ""
@@ -11824,6 +12538,12 @@ msgstr ""
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
msgstr ""
+msgid "UserProfile|Star projects to track their progress and show your appreciation."
+msgstr ""
+
+msgid "UserProfile|Starred projects"
+msgstr ""
+
msgid "UserProfile|Subscribe"
msgstr ""
@@ -11836,6 +12556,9 @@ msgstr ""
msgid "UserProfile|This user hasn't contributed to any projects"
msgstr ""
+msgid "UserProfile|This user hasn't starred any projects"
+msgstr ""
+
msgid "UserProfile|View all"
msgstr ""
@@ -11863,6 +12586,9 @@ msgstr ""
msgid "Username is available."
msgstr ""
+msgid "Username or email"
+msgstr ""
+
msgid "Users"
msgstr ""
@@ -12058,6 +12784,9 @@ msgstr ""
msgid "Welcome to your Issue Board!"
msgstr ""
+msgid "What are you searching for?"
+msgstr ""
+
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
@@ -12075,6 +12804,9 @@ msgstr[1] ""
msgid "When:"
msgstr ""
+msgid "Whitelist to allow requests to the local network from hooks and services"
+msgstr ""
+
msgid "Who can see this group?"
msgstr ""
@@ -12084,6 +12816,9 @@ msgstr ""
msgid "Wiki"
msgstr ""
+msgid "Wiki pages"
+msgstr ""
+
msgid "Wiki was successfully updated."
msgstr ""
@@ -12156,15 +12891,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 ""
@@ -12180,19 +12909,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"
@@ -12213,6 +12939,9 @@ msgstr ""
msgid "Wiki|Page history"
msgstr ""
+msgid "Wiki|Page title"
+msgstr ""
+
msgid "Wiki|Page version"
msgstr ""
@@ -12378,6 +13107,9 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot access the raw file. Please wait a minute."
+msgstr ""
+
msgid "You cannot impersonate a blocked user"
msgstr ""
@@ -12396,9 +13128,6 @@ msgstr ""
msgid "You could not create a new trigger."
msgstr ""
-msgid "You could not take ownership of trigger."
-msgstr ""
-
msgid "You do not have any subscriptions yet"
msgstr ""
@@ -12675,6 +13404,9 @@ msgstr ""
msgid "added %{created_at_timeago}"
msgstr ""
+msgid "added a Zoom call to this issue"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -12714,6 +13446,9 @@ msgstr ""
msgid "cannot include leading slash or directory traversal."
msgstr ""
+msgid "cannot merge"
+msgstr ""
+
msgid "comment"
msgstr ""
@@ -12770,6 +13505,15 @@ msgstr ""
msgid "encrypted: needs to be a :required, :optional or :migrating!"
msgstr ""
+msgid "entries cannot be larger than 255 characters"
+msgstr ""
+
+msgid "entries cannot be nil"
+msgstr ""
+
+msgid "entries cannot contain HTML tags"
+msgstr ""
+
msgid "error"
msgstr ""
@@ -12845,6 +13589,9 @@ msgstr ""
msgid "is not an email you own"
msgstr ""
+msgid "is too long (maximum is 1000 entries)"
+msgstr ""
+
msgid "issue"
msgstr ""
@@ -13129,6 +13876,9 @@ msgstr ""
msgid "no contributions"
msgstr ""
+msgid "no one can merge"
+msgstr ""
+
msgid "none"
msgstr ""
@@ -13199,6 +13949,9 @@ msgstr ""
msgid "remove due date"
msgstr ""
+msgid "removed a Zoom call from this issue"
+msgstr ""
+
msgid "rendered diff"
msgstr ""
@@ -13275,9 +14028,15 @@ msgstr ""
msgid "triggered"
msgstr ""
+msgid "unicode domains should use IDNA encoding"
+msgstr ""
+
msgid "updated"
msgstr ""
+msgid "updated %{time_ago}"
+msgstr ""
+
msgid "user avatar"
msgstr ""
diff --git a/package.json b/package.json
index 4ba9a0d9a1b..97ea872ee61 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"eslint": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue .",
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
+ "file-coverage": "scripts/frontend/file_test_coverage.js",
"prejest": "yarn check-dependencies",
"jest": "jest",
"jest-debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
@@ -20,7 +21,7 @@
"prettier-staged-save": "node ./scripts/frontend/prettier.js save",
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
- "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/** --custom-formatter node_modules/stylelint-error-string-formatter",
+ "stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* ee/app/assets/stylesheets/**/*.* !**/vendors/**",
"stylelint-file": "node node_modules/stylelint/bin/stylelint.js",
"stylelint-create-utility-map": "node scripts/frontend/stylelint/stylelint-utility-map.js",
"test": "node scripts/frontend/test",
@@ -29,16 +30,16 @@
"webpack-vrt": "NODE_OPTIONS=\"--max-old-space-size=3584\" NODE_ENV=production webpack --config config/webpack.config.review_toolbar.js"
},
"dependencies": {
- "@babel/core": "^7.4.4",
- "@babel/plugin-proposal-class-properties": "^7.4.4",
+ "@babel/core": "^7.5.5",
+ "@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-json-strings": "^7.2.0",
"@babel/plugin-proposal-private-methods": "^7.4.4",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-syntax-import-meta": "^7.2.0",
- "@babel/preset-env": "^7.4.4",
+ "@babel/preset-env": "^7.5.5",
"@gitlab/csslab": "^1.9.0",
- "@gitlab/svgs": "^1.67.0",
- "@gitlab/ui": "^5.6.0",
+ "@gitlab/svgs": "^1.68.0",
+ "@gitlab/ui": "5.15.0",
"apollo-cache-inmemory": "^1.5.1",
"apollo-client": "^2.5.1",
"apollo-link": "^1.2.11",
@@ -56,6 +57,7 @@
"clipboard": "^1.7.1",
"codesandbox-api": "^0.0.20",
"compression-webpack-plugin": "^2.0.0",
+ "copy-webpack-plugin": "^5.0.4",
"core-js": "^3.1.3",
"cropper": "^2.3.0",
"css-loader": "^1.0.0",
@@ -75,7 +77,6 @@
"diff": "^3.4.0",
"document-register-element": "1.13.1",
"dropzone": "^4.2.0",
- "echarts": "^4.2.0-rc.2",
"emoji-regex": "^7.0.3",
"emoji-unicode-version": "^0.2.1",
"exports-loader": "^0.7.0",
@@ -96,12 +97,12 @@
"jszip-utils": "^0.0.2",
"katex": "^0.10.0",
"marked": "^0.3.12",
- "mermaid": "^8.1.0",
+ "mermaid": "^8.2.3",
"monaco-editor": "^0.15.6",
"monaco-editor-webpack-plugin": "^1.7.0",
"mousetrap": "^1.4.6",
"pdfjs-dist": "^2.0.943",
- "pikaday": "^1.6.1",
+ "pikaday": "^1.8.0",
"popper.js": "^1.14.7",
"prismjs": "^1.6.0",
"prosemirror-markdown": "^1.3.0",
@@ -117,7 +118,6 @@
"sql.js": "^0.4.0",
"stickyfilljs": "^2.0.5",
"style-loader": "^0.23.1",
- "stylelint-error-string-formatter": "1.0.2",
"svg4everybody": "2.1.9",
"three": "^0.84.0",
"three-orbit-controls": "^82.1.0",
@@ -138,16 +138,17 @@
"vue-virtual-scroll-list": "^1.3.1",
"vuex": "^3.1.0",
"webpack": "^4.29.0",
- "webpack-bundle-analyzer": "^3.0.3",
+ "webpack-bundle-analyzer": "^3.3.2",
"webpack-cli": "^3.2.1",
"webpack-stats-plugin": "^0.2.1",
"worker-loader": "^2.0.0",
"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",
"@vue/test-utils": "^1.0.0-beta.25",
"axios-mock-adapter": "^1.15.0",
"babel-jest": "^24.1.0",
@@ -177,14 +178,14 @@
"jest-util": "^24.0.0",
"jsdoc": "^3.5.5",
"jsdoc-vue": "^1.0.0",
- "karma": "^3.0.0",
- "karma-chrome-launcher": "^2.2.0",
- "karma-coverage-istanbul-reporter": "^2.0.4",
+ "karma": "^4.2.0",
+ "karma-chrome-launcher": "^3.0.0",
+ "karma-coverage-istanbul-reporter": "^2.1.0",
"karma-jasmine": "^1.1.2",
"karma-junit-reporter": "^1.2.0",
"karma-mocha-reporter": "^2.2.5",
"karma-sourcemap-loader": "^0.3.7",
- "karma-webpack": "^4.0.0-beta.0",
+ "karma-webpack": "^4.0.2",
"md5": "^2.2.1",
"node-sass": "^4.12.0",
"nodemon": "^1.18.9",
@@ -192,9 +193,9 @@
"postcss": "^7.0.14",
"prettier": "1.18.2",
"readdir-enhanced": "^2.2.4",
- "stylelint": "^9.10.1",
- "stylelint-config-recommended": "^2.1.0",
- "stylelint-scss": "^3.5.4",
+ "stylelint": "^10.1.0",
+ "stylelint-config-recommended": "^2.2.0",
+ "stylelint-scss": "^3.9.2",
"vue-jest": "^4.0.0-beta.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.1.1"
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 c46be8a0362..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'
@@ -8,8 +9,9 @@ gem 'rake', '~> 12.3.0'
gem 'rspec', '~> 3.7'
gem 'selenium-webdriver', '~> 3.12'
gem 'airborne', '~> 0.2.13'
-gem 'nokogiri', '~> 1.10.3'
+gem 'nokogiri', '~> 1.10.4'
gem 'rspec-retry', '~> 0.6.1'
+gem 'rspec_junit_formatter', '~> 0.4.1'
gem 'faker', '~> 1.6', '>= 1.6.6'
gem 'knapsack', '~> 1.17'
gem 'parallel_tests', '~> 2.29'
diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock
index 73aabf2c6ad..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,9 +50,9 @@ 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.3)
+ nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
parallel (1.17.0)
parallel_tests (2.29.0)
@@ -87,12 +87,14 @@ GEM
rspec-retry (0.6.1)
rspec-core (> 3.3)
rspec-support (3.7.0)
+ rspec_junit_formatter (0.4.1)
+ rspec-core (>= 2, < 4, != 2.12.0)
rubyzip (1.2.2)
selenium-webdriver (3.141.0)
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
@@ -104,18 +106,20 @@ PLATFORMS
ruby
DEPENDENCIES
+ activesupport (= 5.2.3)
airborne (~> 0.2.13)
capybara (~> 2.16.1)
capybara-screenshot (~> 1.0.18)
faker (~> 1.6, >= 1.6.6)
gitlab-qa
knapsack (~> 1.17)
- nokogiri (~> 1.10.3)
+ nokogiri (~> 1.10.4)
parallel_tests (~> 2.29)
pry-byebug (~> 3.5.1)
rake (~> 12.3.0)
rspec (~> 3.7)
rspec-retry (~> 0.6.1)
+ rspec_junit_formatter (~> 0.4.1)
selenium-webdriver (~> 3.12)
BUNDLED WITH
diff --git a/qa/README.md b/qa/README.md
index bab19665dac..97555f8d0c2 100644
--- a/qa/README.md
+++ b/qa/README.md
@@ -123,10 +123,11 @@ To set multiple cookies, separate them with the `;` character, for example: `QA_
Once you have made changes to the CE/EE repositories, you may want to build a
Docker image to test locally instead of waiting for the `gitlab-ce-qa` or
-`gitlab-ee-qa` nightly builds. To do that, you can run from this directory:
+`gitlab-ee-qa` nightly builds. To do that, you can run **from the top `gitlab`
+directory** (one level up from this directory):
```sh
-docker build -t gitlab/gitlab-ce-qa:nightly .
+docker build -t gitlab/gitlab-ce-qa:nightly --file ./qa/Dockerfile ./
```
[GDK]: https://gitlab.com/gitlab-org/gitlab-development-kit/
diff --git a/qa/qa.rb b/qa/qa.rb
index be73776425b..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.
@@ -314,6 +317,10 @@ module QA
autoload :Login, 'qa/page/mattermost/login'
end
+ module Search
+ autoload :Results, 'qa/page/search/results'
+ end
+
##
# Classes describing components that are used by several pages.
#
diff --git a/qa/qa/ce/knapsack/nightly_master_report.json b/qa/qa/ce/knapsack/nightly_master_report.json
new file mode 100644
index 00000000000..08694f706de
--- /dev/null
+++ b/qa/qa/ce/knapsack/nightly_master_report.json
@@ -0,0 +1,46 @@
+{
+ "qa/specs/features/api/1_manage/users_spec.rb": 0.6089541912078857,
+ "qa/specs/features/api/3_create/repository/files_spec.rb": 5.015859127044678,
+ "qa/specs/features/api/3_create/repository/project_archive_compare_spec.rb": 1.0199065208435059,
+ "qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb": 33.54091453552246,
+ "qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb": 3.438166856765747,
+ "qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb": 20.58603596687317,
+ "qa/specs/features/browser_ui/1_manage/login/register_spec.rb": 22.320587396621704,
+ "qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb": 8.490083694458008,
+ "qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb": 10.214765310287476,
+ "qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb": 100.28881478309631,
+ "qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb": 7.882027864456177,
+ "qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb": 13.739388942718506,
+ "qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb": 13.403101205825806,
+ "qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb": 10.989444971084595,
+ "qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb": 10.811973810195923,
+ "qa/specs/features/browser_ui/2_plan/issue/filter_issue_comments_spec.rb": 12.63524317741394,
+ "qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb": 11.280649185180664,
+ "qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb": 41.76726770401001,
+ "qa/specs/features/browser_ui/3_create/merge_request/merge_merge_request_from_fork_spec.rb": 32.5517954826355,
+ "qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb": 46.54227638244629,
+ "qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb": 27.943300485610962,
+ "qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb": 3.705310821533203,
+ "qa/specs/features/browser_ui/3_create/repository/add_file_template_spec.rb": 40.09336972236633,
+ "qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb": 31.49540114402771,
+ "qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb": 16.18057894706726,
+ "qa/specs/features/browser_ui/3_create/repository/clone_spec.rb": 0.7397980690002441,
+ "qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb": 18.047621726989746,
+ "qa/specs/features/browser_ui/3_create/repository/push_http_private_token_spec.rb": 9.48607873916626,
+ "qa/specs/features/browser_ui/3_create/repository/push_mirroring_over_http_spec.rb": 23.710937023162842,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb": 19.459370374679565,
+ "qa/specs/features/browser_ui/3_create/repository/push_over_http_spec.rb": 7.730542182922363,
+ "qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb": 29.76174831390381,
+ "qa/specs/features/browser_ui/3_create/repository/use_ssh_key_spec.rb": 22.800872802734375,
+ "qa/specs/features/browser_ui/3_create/repository/user_views_commit_diff_patch_spec.rb": 6.731764793395996,
+ "qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb": 5.812374591827393,
+ "qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb": 25.460349321365356,
+ "qa/specs/features/browser_ui/3_create/wiki/create_edit_clone_push_wiki_spec.rb": 17.273863554000854,
+ "qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb": 8.31815505027771,
+ "qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb": 18.679633855819702,
+ "qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb": 15.342933893203735,
+ "qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb": 92.46774697303772,
+ "qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb": 20.252174615859985,
+ "qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb": 8.281434059143066,
+ "qa/specs/features/browser_ui/non_devops/performance_bar_spec.rb": 8.810423135757446
+} \ No newline at end of file
diff --git a/qa/qa/page/admin/menu.rb b/qa/qa/page/admin/menu.rb
index eea5717f5a7..61ec9854726 100644
--- a/qa/qa/page/admin/menu.rb
+++ b/qa/qa/page/admin/menu.rb
@@ -13,6 +13,10 @@ module QA
element :admin_settings_metrics_and_profiling_item
end
+ view 'app/views/layouts/nav/sidebar/_admin.html.haml' do
+ element :integration_settings_link
+ end
+
def go_to_repository_settings
hover_settings do
within_submenu do
@@ -21,6 +25,14 @@ module QA
end
end
+ def go_to_integration_settings
+ hover_settings do
+ within_submenu do
+ click_element :integration_settings_link
+ end
+ end
+ end
+
def go_to_general_settings
hover_settings do
within_submenu do
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 80ce23d5c38..45496c6b245 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -181,11 +181,11 @@ module QA
return ["Page class does not have views / elements defined!"]
end
- views.map(&:errors).flatten
+ views.flat_map(&:errors)
end
def self.elements
- views.map(&:elements).flatten
+ views.flat_map(&:elements)
end
def send_keys_to_element(name, keys)
diff --git a/qa/qa/page/component/note.rb b/qa/qa/page/component/note.rb
index fe324574f4d..34fde045091 100644
--- a/qa/qa/page/component/note.rb
+++ b/qa/qa/page/component/note.rb
@@ -20,13 +20,17 @@ module QA
end
base.view 'app/assets/javascripts/notes/components/discussion_actions.vue' do
- element :discussion_reply
+ element :discussion_reply_tab
end
base.view 'app/assets/javascripts/notes/components/toggle_replies_widget.vue' do
element :expand_replies
element :collapse_replies
end
+
+ base.view 'app/assets/javascripts/diffs/components/diff_file_header.vue' do
+ element :toggle_comments_button
+ end
end
def start_discussion(text)
@@ -36,8 +40,12 @@ module QA
click_element :comment_button
end
+ def toggle_comments
+ all_elements(:toggle_comments_button).last.click
+ end
+
def type_reply_to_discussion(reply_text)
- all_elements(:discussion_reply).last.click
+ all_elements(:discussion_reply_tab).last.click
fill_element :reply_input, reply_text
end
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 85d4abcde9b..d05c44d22b2 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -8,6 +8,10 @@ module QA
find('.select2-result-label', text: item_text, match: :prefer_exact).click
end
+ def current_selection
+ find('.select2-chosen').text
+ end
+
def clear_current_selection_if_present
if has_css?('a > abbr.select2-search-choice-close', wait: 1.0)
find('a > abbr.select2-search-choice-close').click
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index 8970eeb6678..94245bbfcba 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -6,7 +6,7 @@ module QA
class Login < Page::Base
view 'app/views/devise/passwords/edit.html.haml' do
element :password_field
- element :password_confirmation
+ element :password_confirmation_field
element :change_password_button
end
@@ -44,7 +44,7 @@ module QA
def sign_in_using_credentials(user = nil)
# Don't try to log-in if we're already logged-in
- return if Page::Main::Menu.act { has_personal_area?(wait: 0) }
+ return if Page::Main::Menu.perform { |menu| menu.has_personal_area?(wait: 0) }
using_wait_time 0 do
set_initial_password_if_present
@@ -52,13 +52,11 @@ module QA
raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given?
if Runtime::User.ldap_user?
- sign_in_using_ldap_credentials
+ sign_in_using_ldap_credentials(user || Runtime::User)
else
sign_in_using_gitlab_credentials(user || Runtime::User)
end
end
-
- Page::Main::Menu.act { has_personal_area? }
end
def sign_in_using_admin_credentials
@@ -73,7 +71,26 @@ module QA
sign_in_using_gitlab_credentials(admin)
end
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
+ end
+
+ def sign_in_using_ldap_credentials(user)
+ # Log out if already logged in
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+
+ using_wait_time 0 do
+ set_initial_password_if_present
+
+ switch_to_ldap_tab
+
+ fill_element :username_field, user.ldap_username
+ fill_element :password_field, user.ldap_password
+ click_element :sign_in_button
+ end
+
+ Page::Main::Menu.perform(&:has_personal_area?)
end
def self.path
@@ -133,14 +150,6 @@ module QA
private
- def sign_in_using_ldap_credentials
- switch_to_ldap_tab
-
- fill_element :username_field, Runtime::User.ldap_username
- fill_element :password_field, Runtime::User.ldap_password
- click_element :sign_in_button
- end
-
def sign_in_using_gitlab_credentials(user)
switch_to_sign_in_tab if has_sign_in_tab?
switch_to_standard_tab if has_standard_tab?
@@ -154,7 +163,7 @@ module QA
return unless has_content?('Change your password')
fill_element :password_field, Runtime::User.password
- fill_element :password_confirmation, Runtime::User.password
+ fill_element :password_confirmation_field, Runtime::User.password
click_element :change_password_button
end
end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index c98d85d7911..e3039149ab4 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -6,7 +6,7 @@ module QA
class Menu < Page::Base
view 'app/views/layouts/header/_current_user_dropdown.html.haml' do
element :sign_out_link
- element :settings_link, 'link_to s_("CurrentUser|Settings")' # rubocop:disable QA/ElementWithPattern
+ element :settings_link
end
view 'app/views/layouts/header/_default.html.haml' do
@@ -27,6 +27,10 @@ module QA
element :your_projects_link
end
+ view 'app/views/layouts/_search.html.haml' do
+ element :search_term_field
+ end
+
def go_to_groups
within_top_menu do
click_element :groups_dropdown
@@ -71,6 +75,10 @@ module QA
click_element :snippets_link
end
+ def search_for(term)
+ fill_element :search_term_field, "#{term}\n"
+ end
+
def has_personal_area?(wait: Capybara.default_max_wait_time)
has_element?(:user_avatar, wait: wait)
end
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 0d138417176..65c02801d67 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -6,7 +6,7 @@ module QA
module Issue
class New < Page::Base
view 'app/views/shared/issuable/_form.html.haml' do
- element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern
+ element :issuable_create_button
end
view 'app/views/shared/issuable/form/_title.html.haml' do
@@ -26,7 +26,7 @@ module QA
end
def create_new_issue
- click_on 'Submit issue'
+ click_element :issuable_create_button, Page::Project::Issue::Show
end
end
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index b59540d0377..45dad9bc0ae 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -14,7 +14,7 @@ module QA
end
view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
- element :discussion_filter
+ element :discussion_filter, required: true
element :filter_options
end
@@ -70,7 +70,10 @@ module QA
end
def select_labels_and_refresh(labels)
- click_element(:edit_link_labels)
+ Support::Retrier.retry_until do
+ click_element(:edit_link_labels)
+ has_element?(:dropdown_menu_labels, text: labels.first)
+ end
labels.each do |label|
within_element(:dropdown_menu_labels, text: label) do
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 3fe048f752a..838d59b59cb 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -5,7 +5,7 @@ module QA
module Project
class Menu < Page::Base
include SubMenus::Common
-
+ include SubMenus::Project
include SubMenus::CiCd
include SubMenus::Issues
include SubMenus::Operations
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 0918445d119..64aab9be056 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -59,7 +59,7 @@ module QA
end
def set_visibility(visibility)
- choose visibility
+ choose visibility.capitalize
end
def click_github_link
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index b8c5c563da6..ae826fb3a32 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -8,33 +8,32 @@ module QA
include Common
view 'app/views/projects/settings/ci_cd/show.html.haml' do
- element :autodevops_settings
- element :runners_settings
- element :variables_settings
+ element :autodevops_settings_content
+ element :runners_settings_content
+ element :variables_settings_content
end
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
- element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern
- element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern
- element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
+ element :enable_autodevops_checkbox
+ element :save_changes_button
end
def expand_runners_settings(&block)
- expand_section(:runners_settings) do
+ expand_section(:runners_settings_content) do
Settings::Runners.perform(&block)
end
end
def expand_ci_variables(&block)
- expand_section(:variables_settings) do
+ expand_section(:variables_settings_content) do
Settings::CiVariables.perform(&block)
end
end
def enable_auto_devops
- expand_section(:autodevops_settings) do
- check 'Default to Auto DevOps pipeline'
- click_on 'Save changes'
+ expand_section(:autodevops_settings_content) do
+ check_element :enable_autodevops_checkbox
+ click_element :save_changes_button
end
end
end
diff --git a/qa/qa/page/project/sub_menus/project.rb b/qa/qa/page/project/sub_menus/project.rb
index 5e0ee3c274a..6f1bc131f84 100644
--- a/qa/qa/page/project/sub_menus/project.rb
+++ b/qa/qa/page/project/sub_menus/project.rb
@@ -10,7 +10,7 @@ module QA
def self.included(base)
base.class_eval do
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
- element :link_project
+ element :project_link
end
end
end
@@ -18,7 +18,7 @@ module QA
def click_project
retry_on_exception do
within_sidebar do
- click_element(:link_project)
+ click_element(:project_link)
end
end
end
diff --git a/qa/qa/page/search/results.rb b/qa/qa/page/search/results.rb
new file mode 100644
index 00000000000..b9b18abf660
--- /dev/null
+++ b/qa/qa/page/search/results.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module QA::Page
+ module Search
+ class Results < QA::Page::Base
+ view 'app/views/search/_category.html.haml' do
+ element :code_tab
+ end
+
+ view 'app/views/search/results/_blob_data.html.haml' do
+ element :result_item_content
+ element :file_title_content
+ element :file_text_content
+ end
+
+ def switch_to_code
+ click_element(:code_tab)
+ end
+
+ def has_file_in_project?(file_name, project_name)
+ has_element? :result_item_content, text: "#{project_name}: #{file_name}"
+ end
+
+ def has_file_with_content?(file_name, file_text)
+ within_element_by_index :result_item_content, 0 do
+ false unless has_element? :file_title_content, text: file_name
+
+ has_element? :file_text_content, text: file_text
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb
index edd12665f1e..9b2d0a1a41d 100644
--- a/qa/qa/page/validator.rb
+++ b/qa/qa/page/validator.rb
@@ -22,16 +22,14 @@ module QA
end
def descendants
- @descendants ||= constants.map do |const|
+ @descendants ||= constants.flat_map do |const|
case const
when Class
const if const < Page::Base
when Module
Page::Validator.new(const).descendants
end
- end
-
- @descendants.flatten.compact
+ end.compact
end
def errors
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 7969de726e4..45ab2396a04 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -5,7 +5,8 @@ require 'securerandom'
module QA
module Resource
class MergeRequest < Base
- attr_accessor :title,
+ attr_accessor :id,
+ :title,
:description,
:source_branch,
:target_branch,
@@ -74,6 +75,28 @@ module QA
page.create_merge_request
end
end
+
+ def fabricate_via_api!
+ populate(:target, :source)
+ super
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/merge_requests/#{id}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/merge_requests"
+ end
+
+ def api_post_body
+ {
+ description: @description,
+ source_branch: @source_branch,
+ target_branch: @target_branch,
+ title: @title
+ }
+ end
end
end
end
diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb
index c0a6004fe27..93a82094776 100644
--- a/qa/qa/resource/project.rb
+++ b/qa/qa/resource/project.rb
@@ -8,6 +8,7 @@ module QA
include Events::Project
attr_writer :initialize_with_readme
+ attr_writer :visibility
attribute :id
attribute :name
@@ -44,6 +45,7 @@ module QA
@standalone = false
@description = 'My awesome project'
@initialize_with_readme = false
+ @visibility = 'public'
end
def name=(raw_name)
@@ -60,7 +62,7 @@ module QA
page.choose_test_namespace
page.choose_name(@name)
page.add_description(@description)
- page.set_visibility('Public')
+ page.set_visibility(@visibility)
page.enable_initialize_with_readme if @initialize_with_readme
page.create_new_project
end
@@ -88,7 +90,7 @@ module QA
post_body = {
name: name,
description: description,
- visibility: 'public',
+ visibility: @visibility,
initialize_with_readme: @initialize_with_readme
}
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index e78b9bece19..b184eeb1701 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -181,6 +181,10 @@ module QA
ENV.fetch('GCLOUD_REGION')
end
+ def gcloud_num_nodes
+ ENV.fetch('GCLOUD_NUM_NODES', 1)
+ end
+
def has_gcloud_credentials?
%w[GCLOUD_ACCOUNT_KEY GCLOUD_ACCOUNT_EMAIL].none? { |var| ENV[var].to_s.empty? }
end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index e05269e8d55..632a0f5f2a9 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -7,14 +7,16 @@ module QA
class Selectors < Scenario::Template
include Scenario::Bootable
- PAGES = [QA::Page].freeze
+ def pages
+ @pages ||= [QA::Page]
+ end
def perform(*)
- validators = PAGES.map do |pages|
- Page::Validator.new(pages)
+ validators = pages.map do |page|
+ Page::Validator.new(page)
end
- validators.map(&:errors).flatten.tap do |errors|
+ validators.flat_map(&:errors).tap do |errors|
break if errors.none?
warn <<~EOS
diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb
index 40263e94065..ac0b6313167 100644
--- a/qa/qa/service/kubernetes_cluster.rb
+++ b/qa/qa/service/kubernetes_cluster.rb
@@ -29,6 +29,8 @@ module QA
#{auth_options}
--enable-basic-auth
--region #{Runtime::Env.gcloud_region}
+ --disk-size 10GB
+ --num-nodes #{Runtime::Env.gcloud_num_nodes}
&& gcloud container clusters
get-credentials
--region #{Runtime::Env.gcloud_region}
diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb
index b54fd5628f2..c5cddff56cd 100644
--- a/qa/qa/service/omnibus.rb
+++ b/qa/qa/service/omnibus.rb
@@ -11,11 +11,12 @@ module QA
end
def gitlab_ctl(command, input: nil)
- if input.nil?
- shell "docker exec #{@name} gitlab-ctl #{command}"
- else
- shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'"
- end
+ docker_exec("gitlab-ctl #{command}", input: input)
+ end
+
+ def docker_exec(command, input: nil)
+ command = "#{input} | #{command}" if input
+ shell "docker exec #{@name} bash -c '#{command}'"
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb
new file mode 100644
index 00000000000..94d20106de4
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/create_group_with_mattermost_team_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Configure', :orchestrated, :mattermost do
+ describe 'Mattermost support' do
+ it 'user creates a group with a mattermost team' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_groups)
+
+ Page::Dashboard::Groups.perform do |page|
+ page.click_new_group
+
+ expect(page).to have_content(
+ /Create a Mattermost team for this group/
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
index 2363836d5e3..c9acd7df776 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/group/transfer_project_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Project transfer between groups' do
it 'user transfers a project between groups' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
source_group = Resource::Group.fabricate_via_api! do |group|
group.path = 'source-group'
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
index 09d1d3fe76e..6556c28ccab 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_in_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'basic user login' do
it 'user logs in using basic credentials and logs out' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
index 72dde4e5bd8..10cd8470a8f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_gitlab_via_ldap_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'LDAP login' do
it 'user logs into GitLab using LDAP credentials' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Page::Main::Menu.perform do |menu|
expect(menu).to have_personal_area
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
index 67610b62ed7..0a999cf00fa 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/log_into_mattermost_via_gitlab_spec.rb
@@ -7,11 +7,13 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
- Page::Mattermost::Login.perform(&:sign_in_using_oauth)
+ Support::Retrier.retry_on_exception do
+ Runtime::Browser.visit(:mattermost, Page::Mattermost::Login)
+ Page::Mattermost::Login.perform(&:sign_in_using_oauth)
- Page::Mattermost::Main.perform do |page|
- expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ Page::Mattermost::Main.perform do |page|
+ expect(page).to have_content(/(Welcome to: Mattermost|Logout GitLab Mattermost)/)
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
index 87f0e9030d2..101143399f6 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_instance_wide_saml_sso_spec.rb
@@ -6,9 +6,9 @@ module QA
it 'User logs in to gitlab with SAML SSO' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_with_saml }
+ Page::Main::Login.perform(&:sign_in_with_saml)
- Vendor::SAMLIdp::Page::Login.act { login }
+ Vendor::SAMLIdp::Page::Login.perform(&:login)
expect(page).to have_content('Welcome to GitLab')
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
index a118176eb8a..db99488160b 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/login/login_via_oauth_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :orchestrated, :oauth do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/121
+ context 'Manage', :orchestrated, :oauth, :quarantine do
describe 'OAuth login' do
it 'User logs in to GitLab with GitHub OAuth' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 6632c2977ef..fbe857dc2a5 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
created_project = Resource::Project.fabricate_via_browser_ui! do |project|
project.name = 'awesome-project'
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
index a9eafd61a91..4f8c46cbd5f 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/import_github_repo_spec.rb
@@ -24,16 +24,16 @@ module QA
it 'user imports a GitHub repo' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
imported_project # import the project
- Page::Main::Menu.act { go_to_projects }
+ Page::Main::Menu.perform(&:go_to_projects)
Page::Dashboard::Projects.perform do |dashboard|
dashboard.go_to_project(imported_project.name)
end
- Page::Project::Show.act { wait_for_import }
+ Page::Project::Show.perform(&:wait_for_import)
verify_repository_import
verify_issues_import
@@ -50,7 +50,7 @@ module QA
def verify_issues_import
QA::Support::Retrier.retry_on_exception do
- Page::Project::Menu.act { click_issues }
+ Page::Project::Menu.perform(&:click_issues)
expect(page).to have_content('This is a sample issue')
click_link 'This is a sample issue'
@@ -73,7 +73,7 @@ module QA
end
def verify_merge_requests_import
- Page::Project::Menu.act { click_merge_requests }
+ Page::Project::Menu.perform(&:click_merge_requests)
expect(page).to have_content('Improve README.md')
click_link 'Improve README.md'
@@ -108,7 +108,7 @@ module QA
end
def verify_wiki_import
- Page::Project::Menu.act { click_wiki }
+ Page::Project::Menu.perform(&:click_wiki)
expect(page).to have_content('Welcome to the test-project wiki!')
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
index 5eceeb9661c..70c03e10449 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
@@ -3,17 +3,27 @@
module QA
context 'Plan' do
describe 'check xss occurence in @mentions in issues' do
- let(:issue_title) { 'issue title' }
-
it 'user mentions a user in comment' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.perform(&:sign_in_using_credentials)
+ QA::Runtime::Env.personal_access_token = QA::Runtime::Env.admin_personal_access_token
+
+ unless QA::Runtime::Env.personal_access_token
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_admin_credentials)
+ end
user = Resource::User.fabricate_via_api! do |user|
user.name = "eve <img src=x onerror=alert(2)&lt;img src=x onerror=alert(1)&gt;"
user.password = "test1234"
end
+ QA::Runtime::Env.personal_access_token = nil
+
+ Page::Main::Menu.perform(&:sign_out) if Page::Main::Menu.perform { |p| p.has_personal_area?(wait: 0) }
+
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'xss-test-for-mentions-project'
end
@@ -25,17 +35,17 @@ module QA
end
issue = Resource::Issue.fabricate_via_api! do |issue|
- issue.title = issue_title
+ issue.title = 'issue title'
issue.project = project
end
issue.visit!
- Page::Project::Issue::Show.perform do |show_page|
- show_page.select_all_activities_filter
- show_page.comment('cc-ing you here @eve')
+ Page::Project::Issue::Show.perform do |show|
+ show.select_all_activities_filter
+ show.comment('cc-ing you here @eve')
expect do
- expect(show_page).to have_content("cc-ing you here")
+ expect(show).to have_content("cc-ing you here")
end.not_to raise_error # Selenium::WebDriver::Error::UnhandledAlertError
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
index 2101311f065..ad70f6813fb 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/collapse_comments_in_discussions_spec.rb
@@ -3,39 +3,40 @@
module QA
context 'Plan' do
describe 'collapse comments in issue discussions' do
- let(:issue_title) { 'issue title' }
+ let(:my_first_reply) { 'My first reply' }
- it 'user collapses reply for comments in an issue' do
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
issue = Resource::Issue.fabricate_via_api! do |issue|
- issue.title = issue_title
+ issue.title = 'issue title'
end
issue.visit!
- expect(page).to have_content(issue_title)
+ Page::Project::Issue::Show.perform do |show|
+ my_first_discussion = 'My first discussion'
- Page::Project::Issue::Show.perform do |show_page|
- my_first_discussion = "My first discussion"
- my_first_reply = "My First Reply"
- one_reply = "1 reply"
-
- show_page.select_all_activities_filter
- show_page.start_discussion(my_first_discussion)
- expect(show_page).to have_content(my_first_discussion)
+ show.select_all_activities_filter
+ show.start_discussion(my_first_discussion)
+ page.assert_text(my_first_discussion)
+ show.reply_to_discussion(my_first_reply)
+ page.assert_text(my_first_reply)
+ end
+ end
- show_page.reply_to_discussion(my_first_reply)
- expect(show_page).to have_content(my_first_reply)
+ it 'user collapses and expands reply for comments in an issue' do
+ Page::Project::Issue::Show.perform do |show|
+ one_reply = "1 reply"
- show_page.collapse_replies
- expect(show_page).to have_content(one_reply)
- expect(show_page).not_to have_content(my_first_reply)
+ show.collapse_replies
+ expect(show).to have_content(one_reply)
+ expect(show).not_to have_content(my_first_reply)
- show_page.expand_replies
- expect(show_page).to have_content(my_first_reply)
- expect(show_page).not_to have_content(one_reply)
+ show.expand_replies
+ expect(show).to have_content(my_first_reply)
+ expect(show).not_to have_content(one_reply)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
index a62a51b11f4..0b1bd00ac8d 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/comment_issue_spec.rb
@@ -3,27 +3,29 @@
module QA
context 'Plan' do
describe 'Issue comments' do
- it 'user comments on an issue and edits the comment' do
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'issue title'
end
issue.visit!
+ end
- Page::Project::Issue::Show.perform do |issue_show_page|
+ it 'user comments on an issue and edits the comment' do
+ Page::Project::Issue::Show.perform do |show|
first_version_of_comment = 'First version of the comment'
second_version_of_comment = 'Second version of the comment'
- issue_show_page.comment(first_version_of_comment)
+ show.comment(first_version_of_comment)
- expect(issue_show_page).to have_content(first_version_of_comment)
+ expect(show).to have_content(first_version_of_comment)
- issue_show_page.edit_comment(second_version_of_comment)
+ show.edit_comment(second_version_of_comment)
- expect(issue_show_page).to have_content(second_version_of_comment)
- expect(issue_show_page).not_to have_content(first_version_of_comment)
+ expect(show).to have_content(second_version_of_comment)
+ expect(show).not_to have_content(first_version_of_comment)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 1eea3efec7f..04ae4963d3a 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -5,23 +5,35 @@ module QA
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ end
+
it 'user creates an issue' do
- create_issue
+ Resource::Issue.fabricate_via_browser_ui! do |issue|
+ issue.title = issue_title
+ end
- Page::Project::Menu.act { click_issues }
+ Page::Project::Menu.perform(&:click_issues)
expect(page).to have_content(issue_title)
end
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/101
- context 'when using attachments in comments', :object_storage, :quarantine do
+ context 'when using attachments in comments', :object_storage do
let(:file_to_attach) do
File.absolute_path(File.join('spec', 'fixtures', 'banana_sample.gif'))
end
- it 'user comments on an issue with an attachment' do
- create_issue
+ before do
+ issue = Resource::Issue.fabricate_via_api! do |issue|
+ issue.title = issue_title
+ end
+
+ issue.visit!
+ end
+ it 'user comments on an issue with an attachment' do
Page::Project::Issue::Show.perform do |show|
show.comment('See attached banana for scale', attachment: file_to_attach)
@@ -37,15 +49,6 @@ module QA
end
end
end
-
- def create_issue
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- Resource::Issue.fabricate_via_browser_ui! do |issue|
- issue.title = issue_title
- end
- end
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 301836f5ce8..317e31feea8 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
@@ -5,37 +5,37 @@ module QA
describe 'filter issue comments activities' do
let(:issue_title) { 'issue title' }
- it 'user filters comments and activities in an issue' do
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.title = issue_title
end
issue.visit!
+ end
- expect(page).to have_content(issue_title)
-
- Page::Project::Issue::Show.perform do |show_page|
+ it 'user filters comments and activities in an issue' do
+ Page::Project::Issue::Show.perform do |show|
my_own_comment = "My own comment"
made_the_issue_confidential = "made the issue confidential"
- show_page.comment('/confidential', filter: :comments_only)
- show_page.comment(my_own_comment, filter: :comments_only)
+ show.comment('/confidential', filter: :comments_only)
+ show.comment(my_own_comment, filter: :comments_only)
- expect(show_page).not_to have_content(made_the_issue_confidential)
- expect(show_page).to have_content(my_own_comment)
+ expect(show).not_to have_content(made_the_issue_confidential)
+ expect(show).to have_content(my_own_comment)
- show_page.select_all_activities_filter
+ show.select_all_activities_filter
- expect(show_page).to have_content(made_the_issue_confidential)
- expect(show_page).to have_content(my_own_comment)
+ expect(show).to have_content(made_the_issue_confidential)
+ expect(show).to have_content(my_own_comment)
- show_page.select_history_only_filter
+ show.select_history_only_filter
- expect(show_page).to have_content(made_the_issue_confidential)
- expect(show_page).not_to have_content(my_own_comment)
+ expect(show).to have_content(made_the_issue_confidential)
+ expect(show).not_to have_content(my_own_comment)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
index 24dcb32f63f..c42c2cedde0 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'issue suggestions' do
let(:issue_title) { 'Issue Lists are awesome' }
- it 'user sees issue suggestions when creating a new issue' do
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
@@ -20,14 +20,16 @@ module QA
end
project.visit!
+ end
+ it 'user sees issue suggestions when creating a new issue' do
Page::Project::Show.perform(&:go_to_new_issue)
- Page::Project::Issue::New.perform do |new_issue_page|
- new_issue_page.add_title("issue")
- expect(new_issue_page).to have_content(issue_title)
+ Page::Project::Issue::New.perform do |new|
+ new.add_title("issue")
+ expect(new).to have_content(issue_title)
- new_issue_page.add_title("Issue Board")
- expect(new_issue_page).not_to have_content(issue_title)
+ new.add_title("Issue Board")
+ expect(new).not_to have_content(issue_title)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
index 7a36f9ea420..3ce291bf8bc 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/66
+ context 'Create', :quarantine do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
index 9e48ee7ca2a..891cef6c420 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
@@ -7,13 +7,18 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- @merge_request = Resource::MergeRequest.fabricate! do |merge_request|
+ project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project'
+ end
+
+ @merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.project = project
merge_request.title = 'This is a merge request'
- merge_request.description = 'For downloading patches and diffs'
+ merge_request.description = '... for downloading patches and diffs'
end
end
- it 'user views merge request email patches' do
+ it 'views the merge request email patches' do
@merge_request.visit!
Page::MergeRequest::Show.perform(&:view_email_patches)
@@ -22,7 +27,7 @@ module QA
expect(page).to have_content('diff --git a/added_file.txt b/added_file.txt')
end
- it 'user views merge request plain diff' do
+ it 'views the merge request plain diff' do
@merge_request.visit!
Page::MergeRequest::Show.perform(&:view_plain_diff)
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/add_ssh_key_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
index f41240b7605..56a7a04e840 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_ssh_key_spec.rb
@@ -7,7 +7,7 @@ module QA
it 'user adds and then removes an SSH key', :smoke do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
key = Resource::SSHKey.fabricate! do |resource|
resource.title = key_title
@@ -16,8 +16,8 @@ module QA
expect(page).to have_content("Title: #{key_title}")
expect(page).to have_content(key.fingerprint)
- Page::Main::Menu.act { click_settings_link }
- Page::Profile::Menu.act { click_ssh_keys }
+ Page::Main::Menu.perform(&:click_settings_link)
+ Page::Profile::Menu.perform(&:click_ssh_keys)
Page::Profile::SSHKeys.perform do |ssh_keys|
ssh_keys.remove_key(key_title)
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
index d345fbfe995..51a1c19f0f7 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/create_edit_delete_file_via_web_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Files management' do
it 'user creates, edits and deletes a file via the Web' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
# Create
file_name = 'QA Test - File name'
@@ -27,7 +27,7 @@ module QA
updated_file_content = 'QA Test - Updated file content'
commit_message_for_update = 'QA Test - Update file'
- Page::File::Show.act { click_edit }
+ Page::File::Show.perform(&:click_edit)
Page::File::Form.act do
remove_content
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
index 11fd570d131..2027a3c16aa 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_over_http_file_size_spec.rb
@@ -1,8 +1,7 @@
# frozen_string_literal: true
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/113
- context 'Create', :requires_admin, :quarantine do
+ context 'Create', :requires_admin do
describe 'push after setting the file size limit via admin/application_settings' do
before(:context) do
@project = Resource::Project.fabricate_via_api! do |p|
@@ -21,14 +20,21 @@ module QA
it 'push successful when the file size is under the limit' do
set_file_size_limit(5)
- push = push_new_file('oversize_file_1.bin', wait_for_push: true)
- expect(push.output).not_to have_content 'remote: fatal: pack exceeds maximum allowed size'
+
+ retry_on_fail do
+ push = push_new_file('oversize_file_1.bin', wait_for_push: true)
+
+ expect(push.output).not_to have_content 'remote: fatal: pack exceeds maximum allowed size'
+ end
end
it 'push fails when the file size is above the limit' do
set_file_size_limit(1)
- expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
- .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
+
+ retry_on_fail do
+ expect { push_new_file('oversize_file_2.bin', wait_for_push: false) }
+ .to raise_error(QA::Git::Repository::RepositoryCommandError, /remote: fatal: pack exceeds maximum allowed size/)
+ end
end
def set_file_size_limit(limit)
@@ -54,6 +60,22 @@ module QA
output
end
+
+ # Application settings are cached for up to a minute. So when we change
+ # the `receive_max_input_size` setting, the setting might not be applied
+ # for minute. This caused the tests to intermittently fail.
+ # See https://gitlab.com/gitlab-org/quality/nightly/issues/113
+ #
+ # Instead of waiting a minute after changing the setting, we retry the
+ # attempt to push if it fails. Most of the time the setting is updated in
+ # under a minute, i.e., in fewer than 6 attempts with a 10 second sleep
+ # between attempts.
+ # See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/30233#note_188616863
+ def retry_on_fail
+ Support::Retrier.retry_on_exception(max_attempts: 6, reload_page: nil, sleep_interval: 10) do
+ yield
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
index 6aebd04af03..e159e517cbb 100644
--- a/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/push_protected_branch_spec.rb
@@ -13,7 +13,7 @@ module QA
before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
end
after do
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 680c5e21fa4..f915d412bf3 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
@@ -2,8 +2,7 @@
module QA
context 'Create' do
- # failure reported: https://gitlab.com/gitlab-org/quality/nightly/issues/42
- describe 'Commit data', :quarantine do
+ describe 'Commit data' do
before(:context) do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index c09c65a57a5..9ff7919f199 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ context 'Create', :quarantine do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/127
describe 'Web IDE file templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
index 3af7db751e7..33744236dd4 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/runner/register_runner_spec.rb
@@ -11,7 +11,7 @@ module QA
it 'user registers a new specific runner' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
Resource::Runner.fabricate! do |runner|
runner.name = executor
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 2fe4e4d9d1f..f6411d8c5ad 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,36 +3,29 @@
require 'digest/sha1'
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/70
- context 'Release', :docker, :quarantine do
+ context 'Release', :docker do
describe 'Git clone using a deploy key' do
- def login
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- end
-
- before(:all) do
- login
@runner_name = "qa-runner-#{Time.now.to_i}"
- @project = Resource::Project.fabricate! do |resource|
+ @project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'deploy-key-clone-project'
end
@repository_location = @project.repository_ssh_location
- Resource::Runner.fabricate! do |resource|
+ Resource::Runner.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
resource.image = 'gitlab/gitlab-runner:ubuntu'
end
-
- Page::Main::Menu.perform(&:sign_out)
end
- after(:all) do
+ after do
Service::Runner.new(@runner_name).remove!
end
@@ -46,9 +39,7 @@ module QA
it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do
key = key_class.new(*bits)
- login
-
- Resource::DeployKey.fabricate! do |resource|
+ Resource::DeployKey.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
@@ -56,7 +47,7 @@ module QA
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
- Resource::CiVariable.fabricate! do |resource|
+ Resource::CiVariable.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
index 791dc62e32f..ec0c45652fd 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_token/add_deploy_token_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Deploy token creation' do
it 'user adds a deploy token' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
deploy_token_name = 'deploy token name'
one_week_from_now = Date.today + 7
diff --git a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
deleted file mode 100644
index 8383dcdb983..00000000000
--- a/qa/qa/specs/features/browser_ui/7_configure/mattermost/create_group_with_mattermost_team_spec.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- context 'Configure', :orchestrated, :mattermost do
- describe 'Mattermost support' do
- it 'user creates a group with a mattermost team' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
- Page::Main::Menu.act { go_to_groups }
-
- Page::Dashboard::Groups.perform do |page|
- page.click_new_group
-
- expect(page).to have_content(
- /Create a Mattermost team for this group/
- )
- end
- end
- end
- end
-end
diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb
index 9c1f9904a7a..1b8c926532a 100644
--- a/qa/qa/vendor/saml_idp/page/login.rb
+++ b/qa/qa/vendor/saml_idp/page/login.rb
@@ -12,6 +12,14 @@ module QA
fill_in 'password', with: 'user1pass'
click_on 'Login'
end
+
+ def login_if_required
+ login if login_required?
+ end
+
+ def login_required?
+ page.has_text?('Enter your username and password')
+ end
end
end
end
diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb
index bf3ebce0cfe..2f9e4958ae1 100644
--- a/qa/spec/resource/repository/push_spec.rb
+++ b/qa/spec/resource/repository/push_spec.rb
@@ -19,7 +19,11 @@ describe QA::Resource::Repository::Push do
expect { subject.files = [] }.to raise_error(ArgumentError)
end
- it 'does not raise if files is an array' do
+ it 'raises an error if files is not an array of hashes with :name and :content keys' do
+ expect { subject.files = [{ foo: 'foo' }] }.to raise_error(ArgumentError)
+ end
+
+ it 'does not raise if files is an array of hashes with :name and :content keys' do
expect { subject.files = files }.not_to raise_error
end
end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index caf96a213e1..340831aa06d 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -192,6 +192,30 @@ describe QA::Runtime::Env do
end
end
+ describe '.knapsack?' do
+ it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do
+ stub_env('KNAPSACK_GENERATE_REPORT', 'true')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_REPORT_PATH is defined' do
+ stub_env('KNAPSACK_REPORT_PATH', '/a/path')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do
+ stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do
+ expect(described_class.knapsack?).to be_falsey
+ end
+ end
+
describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 21bfd2876a9..363980acc33 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -46,7 +46,7 @@ RSpec.configure do |config|
if ENV['CI']
config.around do |example|
- retry_times = example.metadata.keys.include?(:quarantine) ? 1 : 2
+ retry_times = example.metadata.key?(:quarantine) ? 1 : 2
example.run_with_retry retry: retry_times
end
end
diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb
index 6c533c6dc7d..3d98f03a982 100644
--- a/qa/spec/specs/runner_spec.rb
+++ b/qa/spec/specs/runner_spec.rb
@@ -125,9 +125,9 @@ describe QA::Specs::Runner do
end
def excluded_feature_tags_except(tag)
- QA::Runtime::Env.supported_features.except(tag).map do |tag, _|
+ QA::Runtime::Env.supported_features.except(tag).flat_map do |tag, _|
['--tag', "~requires_#{tag}"]
- end.flatten
+ end
end
def expect_rspec_runner_arguments(arguments)
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index 1d37b1bd12d..e0e1b2d6c7d 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -6,23 +6,39 @@ module RuboCop
# the last line of a file. Injecting a module in the middle of a file will
# cause merge conflicts, while placing it on the last line will not.
class InjectEnterpriseEditionModule < RuboCop::Cop::Cop
- MSG = 'Injecting EE modules must be done on the last line of this file' \
- ', outside of any class or module definitions'
+ INVALID_LINE = 'Injecting EE modules must be done on the last line of this file' \
+ ', outside of any class or module definitions'
- METHODS = Set.new(%i[include extend prepend]).freeze
+ DISALLOWED_METHOD =
+ 'EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`'
+
+ INVALID_ARGUMENT = 'EE modules to inject must be specified as a String'
+
+ CHECK_LINE_METHODS =
+ Set.new(%i[include_if_ee extend_if_ee prepend_if_ee]).freeze
+
+ DISALLOW_METHODS = Set.new(%i[include extend prepend]).freeze
def ee_const?(node)
line = node.location.expression.source_line
# We use `match?` here instead of RuboCop's AST matching, as this makes
# it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
- line.match?(/(\s|\()(::)?EE::/)
+ line.match?(/(\s|\()('|")?(::)?EE::/)
end
def on_send(node)
- return unless METHODS.include?(node.children[1])
- return unless ee_const?(node.children[2])
+ return unless check_method?(node)
+ if DISALLOW_METHODS.include?(node.children[1])
+ add_offense(node, message: DISALLOWED_METHOD)
+ else
+ verify_line_number(node)
+ verify_argument_type(node)
+ end
+ end
+
+ def verify_line_number(node)
line = node.location.line
buffer = node.location.expression.source_buffer
last_line = buffer.last_line
@@ -32,7 +48,25 @@ module RuboCop
# the expression is the last line _of code_.
last_line -= 1 if buffer.source.end_with?("\n")
- add_offense(node) if line < last_line
+ add_offense(node, message: INVALID_LINE) if line < last_line
+ end
+
+ def verify_argument_type(node)
+ argument = node.children[2]
+
+ return if argument.str_type?
+
+ add_offense(argument, message: INVALID_ARGUMENT)
+ end
+
+ def check_method?(node)
+ name = node.children[1]
+
+ if CHECK_LINE_METHODS.include?(name) || DISALLOW_METHODS.include?(name)
+ ee_const?(node.children[2])
+ else
+ false
+ end
end
# Automatically correcting these offenses is not always possible, as
diff --git a/rubocop/cop/rspec/top_level_describe_path.rb b/rubocop/cop/rspec/top_level_describe_path.rb
new file mode 100644
index 00000000000..61796e23af0
--- /dev/null
+++ b/rubocop/cop/rspec/top_level_describe_path.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'rubocop/rspec/top_level_describe'
+
+module RuboCop
+ module Cop
+ module RSpec
+ class TopLevelDescribePath < RuboCop::Cop::Cop
+ include RuboCop::RSpec::TopLevelDescribe
+
+ MESSAGE = 'A file with a top-level `describe` must end in _spec.rb.'
+ SHARED_EXAMPLES = %i[shared_examples shared_examples_for].freeze
+
+ def on_top_level_describe(node, args)
+ return if acceptable_file_path?(processed_source.buffer.name)
+ return if shared_example?(node)
+
+ add_offense(node, message: MESSAGE)
+ end
+
+ private
+
+ def acceptable_file_path?(path)
+ File.fnmatch?('*_spec.rb', path) || File.fnmatch?('*/frontend/fixtures/*', path)
+ end
+
+ def shared_example?(node)
+ node.ancestors.any? do |node|
+ node.respond_to?(:method_name) && SHARED_EXAMPLES.include?(node.method_name)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index ba61a634d97..58a7ead6f13 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -32,6 +32,7 @@ require_relative 'cop/migration/update_large_table'
require_relative 'cop/project_path_helper'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
+require_relative 'cop/rspec/top_level_describe_path'
require_relative 'cop/qa/element_with_pattern'
require_relative 'cop/sidekiq_options_queue'
require_relative 'cop/destroy_all'
diff --git a/scripts/create_mysql_user.sh b/scripts/create_mysql_user.sh
deleted file mode 100644
index 35f68c581f3..00000000000
--- a/scripts/create_mysql_user.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/bash
-
-mysql --user=root --host=mysql <<EOF
-CREATE USER IF NOT EXISTS 'gitlab'@'%';
-GRANT ALL PRIVILEGES ON gitlabhq_test.* TO 'gitlab'@'%';
-FLUSH PRIVILEGES;
-EOF
diff --git a/scripts/frontend/file_test_coverage.js b/scripts/frontend/file_test_coverage.js
new file mode 100755
index 00000000000..7d1eb45d4bc
--- /dev/null
+++ b/scripts/frontend/file_test_coverage.js
@@ -0,0 +1,88 @@
+#!/usr/bin/env node
+
+/**
+ * Counts the number of frontend test files and compares them against the number of application files.
+ *
+ * Example output:
+ *
+ * Source files: 1551
+ * Test files: 716
+ * Coverage: 46.16%
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+const sourceDirectories = ['app/assets/javascripts'];
+const testDirectories = ['spec/javascripts', 'spec/frontend'];
+
+if (fs.existsSync('ee')) {
+ sourceDirectories.forEach(dir => {
+ sourceDirectories.push(`ee/${dir}`);
+ });
+
+ testDirectories.forEach(dir => {
+ testDirectories.push(`ee/${dir}`);
+ });
+}
+
+let numSourceFiles = 0;
+let numTestFiles = 0;
+
+const isVerbose = process.argv.some(arg => arg === '-v');
+
+const countSourceFiles = path =>
+ forEachFileIn(path, fileName => {
+ if (fileName.endsWith('.vue') || fileName.endsWith('.js')) {
+ if (isVerbose) {
+ console.log(`source file: ${fileName}`);
+ }
+
+ numSourceFiles += 1;
+ }
+ });
+
+const countTestFiles = path =>
+ forEachFileIn(path, fileName => {
+ if (fileName.endsWith('_spec.js')) {
+ if (isVerbose) {
+ console.log(`test file: ${fileName}`);
+ }
+
+ numTestFiles += 1;
+ }
+ });
+
+function forEachFileIn(dirPath, callback) {
+ fs.readdir(dirPath, (err, files) => {
+ if (err) {
+ console.error(err);
+ }
+
+ if (!files) {
+ return;
+ }
+
+ files.forEach(fileName => {
+ const absolutePath = path.join(dirPath, fileName);
+ const stats = fs.statSync(absolutePath);
+ if (stats.isFile()) {
+ callback(absolutePath);
+ } else if (stats.isDirectory()) {
+ forEachFileIn(absolutePath, callback);
+ }
+ });
+ });
+}
+
+console.log(`Source directories: ${sourceDirectories.join(', ')}`);
+console.log(`Test directories: ${testDirectories.join(', ')}`);
+
+sourceDirectories.forEach(countSourceFiles);
+testDirectories.forEach(countTestFiles);
+
+process.on('exit', () => {
+ console.log(`Source files: ${numSourceFiles}`);
+ console.log(`Test files: ${numTestFiles}`);
+ console.log(`Coverage: ${((100 * numTestFiles) / numSourceFiles).toFixed(2)}%`);
+});
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
index b34379764e0..88c9d8c12b1 100755
--- a/scripts/insert-rspec-profiling-data
+++ b/scripts/insert-rspec-profiling-data
@@ -14,10 +14,6 @@ module RspecProfiling
Result.establish_connection(results_url)
end
- def prepared?
- connection.data_source_exists?(table)
- end
-
def results_url
ENV['RSPEC_PROFILING_POSTGRES_URL']
end
diff --git a/scripts/lint-changelog-yaml b/scripts/lint-changelog-yaml
deleted file mode 100755
index 06d502c4676..00000000000
--- a/scripts/lint-changelog-yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-#!/usr/bin/env ruby
-
-require 'yaml'
-
-invalid_changelogs = Dir['changelogs/**/*'].reject do |changelog|
- next true if changelog =~ /((README|archive)\.md|unreleased(-ee)?)$/
- next false unless changelog.end_with?('.yml')
-
- begin
- YAML.load_file(changelog)
- rescue => exception
- puts exception
- end
-end
-
-if invalid_changelogs.any?
- puts
- puts "Invalid changelogs found!\n"
- puts invalid_changelogs.sort
- exit 1
-else
- puts "All changelogs are valid YAML.\n"
- exit 0
-end
diff --git a/scripts/lint-rugged b/scripts/lint-rugged
index 9466c62a415..1b3fb54f70b 100755
--- a/scripts/lint-rugged
+++ b/scripts/lint-rugged
@@ -1,6 +1,9 @@
#!/usr/bin/env ruby
ALLOWED = [
+ # https://gitlab.com/gitlab-org/gitaly/issues/760
+ 'lib/elasticsearch/git/repository.rb',
+
# Needed to handle repositories that are not in any storage
'lib/gitlab/bare_repository_import/repository.rb',
@@ -10,7 +13,15 @@ ALLOWED = [
# Reverted Rugged calls due to Gitaly atop NFS performance
# See https://docs.gitlab.com/ee/development/gitaly.html#legacy-rugged-code.
'lib/gitlab/git/rugged_impl/',
- 'lib/gitlab/gitaly_client/storage_settings.rb'
+ 'lib/gitlab/gitaly_client/storage_settings.rb',
+
+ # Needed for logging
+ 'config/initializers/peek.rb',
+ 'config/initializers/lograge.rb',
+ 'lib/gitlab/grape_logging/loggers/perf_logger.rb',
+ 'lib/gitlab/instrumentation_helper.rb',
+ 'lib/gitlab/rugged_instrumentation.rb',
+ 'lib/peek/views/rugged.rb'
].freeze
rugged_lines = IO.popen(%w[git grep -i -n rugged -- app config lib], &:read).lines
diff --git a/scripts/merge-html-reports b/scripts/merge-html-reports
new file mode 100755
index 00000000000..7d1e15186c8
--- /dev/null
+++ b/scripts/merge-html-reports
@@ -0,0 +1,84 @@
+#!/usr/bin/env ruby
+
+require 'nokogiri'
+
+main_report_file = ARGV.shift
+unless main_report_file
+ puts 'usage: merge-html-reports <main-report> <base-artifact-url> [parallel reports...]'
+ exit 1
+end
+
+base_artifact_url = ARGV.shift
+unless base_artifact_url
+ puts 'usage: merge-html-reports <main-report> <base-artifact-url> [parallel reports...]'
+ exit 1
+end
+
+# Create the base report with empty body tag
+new_report = Nokogiri::HTML.parse(File.read(ARGV[0]))
+new_report.at_css('body').remove
+empty_body = Nokogiri::XML::Node.new('body', new_report)
+new_report.at_css('head').add_next_sibling(empty_body)
+
+ARGV.each do |report_file|
+ report = Nokogiri::HTML.parse(File.read(report_file))
+
+ report.css('a').each do |link|
+ link_suffix = link['href'].slice(19..-1)
+ link['href'] = base_artifact_url + link_suffix
+ end
+
+ header = report.css('div #rspec-header')
+ tests = report.css('dt[id^="example_group_"]')
+
+ tests.each do |test|
+ title = test.parent
+ group = title.parent
+ script = title.css('script')
+
+ if script.inner_html.include? 'makeYellow'
+ test.remove_class('passed')
+ test.add_class('not_implemented')
+
+ group.remove_class('passed')
+ group.add_class('not_implemented')
+ header.add_class('not_implemented')
+
+ script.remove
+ test.next_sibling.remove
+ test.next_sibling.remove
+
+ elsif script.inner_html.include? 'makeRed'
+ test.remove_class('passed')
+ test.add_class('failed')
+
+ group.remove_class('passed')
+ group.add_class('failed')
+ header.add_class('failed')
+
+ script.remove
+ test.next_sibling.remove
+ test.next_sibling.remove
+ end
+ end
+
+ duration = report.at_css('p#duration')
+ totals = report.at_css('p#totals')
+
+ duration_script = report.css('div.results script')[-2]
+ totals_script = report.css('div.results script')[-1]
+
+ duration_text = duration_script.text.slice(49..-3)
+ totals_text = totals_script.text.slice(47..-3)
+
+ duration.inner_html = duration_text
+ totals.inner_html = totals_text
+
+ duration_script.remove
+ totals_script.remove
+
+ # Add the new result after the last one to keep the test order
+ new_report.css('body')[-1].add_next_sibling(report.at_css('body'))
+end
+
+File.write(main_report_file, new_report)
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index bc47884ee45..4935c1342a3 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -52,7 +52,7 @@ function delete() {
function get_pod() {
local app_name="${1}"
local status="${2-Running}"
- get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name"
+ get_pod_cmd="kubectl get pods -n ${KUBE_NAMESPACE} --field-selector=status.phase=${status} -lapp=${app_name},release=${CI_ENVIRONMENT_SLUG} --no-headers -o=custom-columns=NAME:.metadata.name | tail -n 1"
echoinfo "Waiting till '${app_name}' pod is ready" true
echoinfo "Running '${get_pod_cmd}'"
@@ -69,7 +69,6 @@ function get_pod() {
break
fi
- printf "."
let "elapsed_seconds+=interval"
sleep ${interval}
done
@@ -190,18 +189,12 @@ function deploy() {
gitlab_shell_image_repository="${IMAGE_REPOSITORY}/gitlab-shell"
gitlab_workhorse_image_repository="${IMAGE_REPOSITORY}/gitlab-workhorse-${IMAGE_VERSION}"
- # Cleanup and previous installs, as FAILED and PENDING_UPGRADE will cause errors with `upgrade`
- if [ "$CI_ENVIRONMENT_SLUG" != "production" ] && previous_deploy_failed "$CI_ENVIRONMENT_SLUG" ; then
- echo "Deployment in bad state, cleaning up $CI_ENVIRONMENT_SLUG"
- delete
- fi
-
create_application_secret
HELM_CMD=$(cat << EOF
helm upgrade --install \
- --wait \
- --timeout 600 \
+ --atomic \
+ --timeout 900 \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set global.appConfig.enableUsagePing=false \
--set global.imagePullPolicy=Always \
@@ -347,32 +340,6 @@ function display_deployment_debug() {
fi
}
-function wait_for_review_app_to_be_accessible() {
- echoinfo "Waiting for the Review App at ${CI_ENVIRONMENT_URL} to be accessible..." true
-
- local interval=5
- local elapsed_seconds=0
- local max_seconds=$((2 * 60))
- while true; do
- local review_app_http_code
- review_app_http_code=$(curl --silent --output /dev/null --max-time 5 --write-out "%{http_code}" "${CI_ENVIRONMENT_URL}/users/sign_in")
- if [[ "${review_app_http_code}" -eq "200" ]] || [[ "${elapsed_seconds}" -gt "${max_seconds}" ]]; then
- break
- fi
-
- printf "."
- let "elapsed_seconds+=interval"
- sleep ${interval}
- done
-
- if [[ "${review_app_http_code}" -eq "200" ]]; then
- echoinfo "The Review App at ${CI_ENVIRONMENT_URL} is ready after ${elapsed_seconds} seconds!"
- else
- echoerr "The Review App at ${CI_ENVIRONMENT_URL} isn't ready after ${max_seconds} seconds of polling..."
- exit 1
- fi
-}
-
function add_license() {
if [ -z "${REVIEW_APPS_EE_LICENSE}" ]; then echo "License not found" && return; fi
diff --git a/scripts/static-analysis b/scripts/static-analysis
index 642c50ec0a8..6fd64fbf9da 100755
--- a/scripts/static-analysis
+++ b/scripts/static-analysis
@@ -1,6 +1,7 @@
#!/usr/bin/env ruby
# We don't have auto-loading here
+require_relative '../lib/gitlab'
require_relative '../lib/gitlab/popen'
require_relative '../lib/gitlab/popen/runner'
@@ -36,6 +37,10 @@ tasks = [
%w[scripts/lint-rugged]
]
+if Gitlab.ee?
+ tasks.unshift(%w[ruby -rbundler/setup scripts/ee_specific_check/ee_specific_check.rb])
+end
+
static_analysis = Gitlab::Popen::Runner.new
static_analysis.run(tasks) do |cmd, &run|
diff --git a/scripts/utils.sh b/scripts/utils.sh
index 4a6567b8a62..f0f08e2e1c5 100644
--- a/scripts/utils.sh
+++ b/scripts/utils.sh
@@ -29,6 +29,8 @@ function setup_db() {
if [ "$GITLAB_DATABASE" = "mysql" ]; then
bundle exec rake add_limits_mysql
fi
+
+ bundle exec rake gitlab:db:setup_ee
}
function install_api_client_dependencies_with_apk() {
diff --git a/spec/bin/changelog_spec.rb b/spec/bin/changelog_spec.rb
index 84bbe0525e5..c3a5abf2b7a 100644
--- a/spec/bin/changelog_spec.rb
+++ b/spec/bin/changelog_spec.rb
@@ -15,7 +15,7 @@ describe 'bin/changelog' do
allow(entry).to receive(:branch_name).and_return('long-branch-' * 100)
file_path = entry.send(:file_path)
- expect(file_path.length).to eq(140)
+ expect(file_path.length).to eq(99)
end
end
diff --git a/spec/controllers/admin/clusters/applications_controller_spec.rb b/spec/controllers/admin/clusters/applications_controller_spec.rb
index cf202d88acc..9d6edcd80c0 100644
--- a/spec/controllers/admin/clusters/applications_controller_spec.rb
+++ b/spec/controllers/admin/clusters/applications_controller_spec.rb
@@ -84,7 +84,7 @@ describe Admin::Clusters::ApplicationsController do
patch :update, params: params
end
- let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_cert_manager, :installed, cluster: cluster) }
let(:application_name) { application.name }
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
diff --git a/spec/controllers/admin/groups_controller_spec.rb b/spec/controllers/admin/groups_controller_spec.rb
index 509d8944e3a..1123563c1e3 100644
--- a/spec/controllers/admin/groups_controller_spec.rb
+++ b/spec/controllers/admin/groups_controller_spec.rb
@@ -68,5 +68,13 @@ describe Admin::GroupsController do
post :update, params: { id: group.to_param, group: { project_creation_level: ::Gitlab::Access::NO_ONE_PROJECT_ACCESS } }
end.to change { group.reload.project_creation_level }.to(::Gitlab::Access::NO_ONE_PROJECT_ACCESS)
end
+
+ it 'updates the subgroup_creation_level successfully' do
+ expect do
+ post :update,
+ params: { id: group.to_param,
+ group: { subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS } }
+ end.to change { group.reload.subgroup_creation_level }.to(::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
end
end
diff --git a/spec/controllers/admin/requests_profiles_controller_spec.rb b/spec/controllers/admin/requests_profiles_controller_spec.rb
index 10850cb4603..345f7720c25 100644
--- a/spec/controllers/admin/requests_profiles_controller_spec.rb
+++ b/spec/controllers/admin/requests_profiles_controller_spec.rb
@@ -10,38 +10,63 @@ describe Admin::RequestsProfilesController do
end
describe '#show' do
- let(:basename) { "profile_#{Time.now.to_i}.html" }
let(:tmpdir) { Dir.mktmpdir('profiler-test') }
let(:test_file) { File.join(tmpdir, basename) }
- let(:profile) { Gitlab::RequestProfiler::Profile.new(basename) }
- let(:sample_data) do
- <<~HTML
- <!DOCTYPE html>
- <html>
- <body>
- <h1>My First Heading</h1>
- <p>My first paragraph.</p>
- </body>
- </html>
- HTML
+
+ subject do
+ get :show, params: { name: basename }
end
before do
stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
- output = File.open(test_file, 'w')
- output.write(sample_data)
- output.close
+ File.write(test_file, sample_data)
end
after do
- File.unlink(test_file)
+ FileUtils.rm_rf(tmpdir)
end
- it 'loads an HTML profile' do
- get :show, params: { name: basename }
+ context 'when loading HTML profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_execution.html" }
+
+ let(:sample_data) do
+ '<html> <body> <h1>Heading</h1> <p>paragraph.</p> </body> </html>'
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading TXT profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_memory.txt" }
+
+ let(:sample_data) do
+ <<~TXT
+ Total allocated: 112096396 bytes (1080431 objects)
+ Total retained: 10312598 bytes (53567 objects)
+ TXT
+ end
+
+ it 'renders the data' do
+ subject
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to eq(sample_data)
+ end
+ end
+
+ context 'when loading PDF profile' do
+ let(:basename) { "profile_#{Time.now.to_i}_anything.pdf" }
+
+ let(:sample_data) { 'mocked pdf content' }
- expect(response).to have_gitlab_http_status(200)
- expect(response.body).to eq(sample_data)
+ it 'fails to render the data' do
+ expect { subject }.to raise_error(ActionController::UrlGenerationError, /No route matches.*unmatched constraints:/)
+ end
end
end
end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 89a0eba66f7..d7428f8b52c 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -279,6 +279,12 @@ describe Admin::UsersController do
expect(warden.user).to eq(user)
end
+ it 'logs the beginning of the impersonation event' do
+ expect(Gitlab::AppLogger).to receive(:info).with("User #{admin.username} has started impersonating #{user.username}").and_call_original
+
+ post :impersonate, params: { id: user.username }
+ end
+
it "redirects to root" do
post :impersonate, params: { id: user.username }
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 84bbbac39b0..0b3833e6515 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -641,24 +641,32 @@ describe ApplicationController do
end
end
- it 'does not set a custom header' do
+ it 'sets a custom header' do
get :index, format: :json
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
- end
- context 'given a json response for an html request' do
- controller do
- def index
- render json: {}, status: :unprocessable_entity
+ context 'for html request' do
+ it 'sets a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
end
- it 'does not set a custom header' do
- get :index
+ context 'for 200 response' do
+ controller do
+ def index
+ render json: {}, status: :ok
+ end
+ end
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ it 'does not set a custom header' do
+ get :index, format: :json
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
end
end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 3f1c0ae8ac4..6cdd61e7abd 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -222,6 +222,20 @@ describe AutocompleteController do
expect(response_user_ids).to contain_exactly(non_member.id)
end
end
+
+ context 'merge_request_iid parameter included' do
+ before do
+ sign_in(user)
+ end
+
+ it 'includes can_merge option to users' do
+ merge_request = create(:merge_request, source_project: project)
+
+ get(:users, params: { merge_request_iid: merge_request.iid, project_id: project.id })
+
+ expect(json_response.first).to have_key('can_merge')
+ end
+ end
end
context 'GET projects' do
@@ -295,28 +309,6 @@ describe AutocompleteController do
end
end
- context 'authorized projects with offset' do
- before do
- authorized_project2 = create(:project)
- authorized_project3 = create(:project)
-
- authorized_project.add_maintainer(user)
- authorized_project2.add_maintainer(user)
- authorized_project3.add_maintainer(user)
- end
-
- describe 'GET #projects with project ID and offset_id' do
- before do
- get(:projects, params: { project_id: project.id, offset_id: authorized_project.id })
- end
-
- it 'returns projects' do
- expect(json_response).to be_kind_of(Array)
- expect(json_response.size).to eq 2 # Of a total of 3
- end
- end
- end
-
context 'authorized projects without admin_issue ability' do
before do
authorized_project.add_guest(user)
diff --git a/spec/controllers/boards/issues_controller_spec.rb b/spec/controllers/boards/issues_controller_spec.rb
index 0db58fbefc1..d54f7ad33cf 100644
--- a/spec/controllers/boards/issues_controller_spec.rb
+++ b/spec/controllers/boards/issues_controller_spec.rb
@@ -85,7 +85,7 @@ describe Boards::IssuesController do
expect { list_issues(user: user, board: group_board, list: list3) }.not_to exceed_query_limit(control_count + (2 * 8 - 1))
end
- it 'avoids N+1 database queries when adding a subgroup, project, and issue', :nested_groups do
+ it 'avoids N+1 database queries when adding a subgroup, project, and issue' do
create(:project, group: sub_group_1)
create(:labeled_issue, project: project, labels: [development])
control_count = ActiveRecord::QueryRecorder.new { list_issues(user: user, board: group_board, list: list3) }.count
diff --git a/spec/controllers/chaos_controller_spec.rb b/spec/controllers/chaos_controller_spec.rb
new file mode 100644
index 00000000000..bafd4a70862
--- /dev/null
+++ b/spec/controllers/chaos_controller_spec.rb
@@ -0,0 +1,127 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ChaosController do
+ describe '#leakmem' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:leak_mem).with(100, 30.seconds)
+
+ get :leakmem
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'call synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:leak_mem).with(1, 2.seconds)
+
+ get :leakmem, params: { memory_mb: 1, duration_s: 2 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::LeakMemWorker).to receive(:perform_async).with(100, 30.seconds)
+
+ get :leakmem, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#cpu_spin' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:cpu_spin).with(30.seconds)
+
+ get :cpu_spin
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:cpu_spin).with(3.seconds)
+
+ get :cpu_spin, params: { duration_s: 3 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::CpuSpinWorker).to receive(:perform_async).with(30.seconds)
+
+ get :cpu_spin, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#db_spin' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:db_spin).with(30.seconds, 1.second)
+
+ get :db_spin
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:db_spin).with(4.seconds, 5.seconds)
+
+ get :db_spin, params: { duration_s: 4, interval_s: 5 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::DbSpinWorker).to receive(:perform_async).with(30.seconds, 1.second)
+
+ get :db_spin, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#sleep' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:sleep).with(30.seconds)
+
+ get :sleep
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls synchronously with params' do
+ expect(Gitlab::Chaos).to receive(:sleep).with(5.seconds)
+
+ get :sleep, params: { duration_s: 5 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::SleepWorker).to receive(:perform_async).with(30.seconds)
+
+ get :sleep, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ describe '#kill' do
+ it 'calls synchronously' do
+ expect(Gitlab::Chaos).to receive(:kill).with(no_args)
+
+ get :kill
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'calls asynchronously' do
+ expect(Chaos::KillWorker).to receive(:perform_async).with(no_args)
+
+ get :kill, params: { async: 1 }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+end
diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb
new file mode 100644
index 00000000000..0c598a360af
--- /dev/null
+++ b/spec/controllers/concerns/confirm_email_warning_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ConfirmEmailWarning do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ controller(ApplicationController) do
+ # `described_class` is not available in this context
+ include ConfirmEmailWarning # rubocop:disable RSpec/DescribedClass
+
+ def index
+ head :ok
+ end
+ end
+
+ RSpec::Matchers.define :set_confirm_warning_for do |email|
+ match do |response|
+ expect(response).to set_flash.now[:warning].to include("Please check your email (#{email}) to verify that you own this address.")
+ end
+ end
+
+ describe 'confirm email flash warning' do
+ context 'when not signed in' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'with a confirmed user' do
+ let(:user) { create(:user) }
+
+ before do
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'with an unconfirmed user' do
+ let(:user) { create(:user, confirmed_at: nil) }
+
+ context 'when executing a peek request' do
+ before do
+ request.path = '/-/peek'
+ get :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a json request' do
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a post request' do
+ before do
+ post :index
+ end
+
+ it { is_expected.not_to set_confirm_warning_for(user.email) }
+ end
+
+ context 'when executing a get request' do
+ before do
+ get :index
+ end
+
+ context 'with an unconfirmed email address present' do
+ let(:user) { create(:user, confirmed_at: nil, unconfirmed_email: 'unconfirmed@gitlab.com') }
+
+ it { is_expected.to set_confirm_warning_for(user.unconfirmed_email) }
+ end
+
+ context 'without an unconfirmed email address present' do
+ it { is_expected.to set_confirm_warning_for(user.email) }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/controllers/concerns/group_tree_spec.rb b/spec/controllers/concerns/group_tree_spec.rb
index aa3cd690e3f..835c3d9b3af 100644
--- a/spec/controllers/concerns/group_tree_spec.rb
+++ b/spec/controllers/concerns/group_tree_spec.rb
@@ -30,7 +30,7 @@ describe GroupTree do
expect(assigns(:groups)).to contain_exactly(other_group)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
it 'only renders root groups when no parent was given' do
create(:group, :public, parent: group)
@@ -85,7 +85,7 @@ describe GroupTree do
expect(json_response.first['id']).to eq(group.id)
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
it 'expands the tree when filtering' do
subgroup = create(:group, :public, parent: group, name: 'filter')
diff --git a/spec/controllers/concerns/issuable_actions_spec.rb b/spec/controllers/concerns/issuable_actions_spec.rb
new file mode 100644
index 00000000000..7b0b4497f3f
--- /dev/null
+++ b/spec/controllers/concerns/issuable_actions_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IssuableActions do
+ let(:project) { double('project') }
+ let(:user) { double('user') }
+ let(:issuable) { double('issuable') }
+ let(:finder_params_for_issuable) { {} }
+ let(:notes_result) { double('notes_result') }
+ let(:discussion_serializer) { double('discussion_serializer') }
+
+ let(:controller) do
+ klass = Class.new do
+ attr_reader :current_user, :project, :issuable
+
+ def self.before_action(action, params = nil)
+ end
+
+ include IssuableActions
+
+ def initialize(issuable, project, user, finder_params)
+ @issuable = issuable
+ @project = project
+ @current_user = user
+ @finder_params = finder_params
+ end
+
+ def finder_params_for_issuable
+ @finder_params
+ end
+
+ def params
+ {
+ notes_filter: 1
+ }
+ end
+
+ def prepare_notes_for_rendering(notes)
+ []
+ end
+
+ def render(options)
+ end
+ end
+
+ klass.new(issuable, project, user, finder_params_for_issuable)
+ end
+
+ describe '#discussions' do
+ before do
+ allow(user).to receive(:set_notes_filter)
+ allow(user).to receive(:user_preference)
+ allow(discussion_serializer).to receive(:represent)
+ end
+
+ it 'instantiates and calls NotesFinder as expected' do
+ expect(Discussion).to receive(:build_collection).and_return([])
+ expect(DiscussionSerializer).to receive(:new).and_return(discussion_serializer)
+ expect(NotesFinder).to receive(:new).with(user, finder_params_for_issuable).and_call_original
+
+ expect_any_instance_of(NotesFinder).to receive(:execute).and_return(notes_result)
+
+ expect(notes_result).to receive_messages(inc_relations_for_view: notes_result, includes: notes_result, fresh: notes_result)
+
+ controller.discussions
+ 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/groups_controller_spec.rb b/spec/controllers/dashboard/groups_controller_spec.rb
index 48373d29412..20a0951423b 100644
--- a/spec/controllers/dashboard/groups_controller_spec.rb
+++ b/spec/controllers/dashboard/groups_controller_spec.rb
@@ -26,7 +26,7 @@ describe Dashboard::GroupsController do
expect(assigns(:groups)).to contain_exactly(member_of_group)
end
- context 'when rendering an expanded hierarchy with public groups you are not a member of', :nested_groups do
+ context 'when rendering an expanded hierarchy with public groups you are not a member of' do
let!(:top_level_result) { create(:group, name: 'chef-top') }
let!(:top_level_a) { create(:group, name: 'top-a') }
let!(:sub_level_result_a) { create(:group, name: 'chef-sub-a', parent: top_level_a) }
diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb
index 4de537ae6f8..67939aa4e6a 100644
--- a/spec/controllers/dashboard/milestones_controller_spec.rb
+++ b/spec/controllers/dashboard/milestones_controller_spec.rb
@@ -47,6 +47,8 @@ describe Dashboard::MilestonesController do
describe "#index" do
let(:public_group) { create(:group, :public) }
let!(:public_milestone) { create(:milestone, group: public_group) }
+ let!(:closed_group_milestone) { create(:milestone, group: group, state: 'closed') }
+ let!(:closed_project_milestone) { create(:milestone, project: project, state: 'closed') }
render_views
@@ -59,6 +61,15 @@ describe Dashboard::MilestonesController do
expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
end
+ it 'returns closed group and project milestones to which the user belongs' do
+ get :index, params: { state: 'closed' }, format: :json
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |i| i["name"] }).to match_array([closed_group_milestone.name, closed_project_milestone.name])
+ expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name)
+ end
+
it 'searches legacy project milestones by title when search_title is given' do
project_milestone = create(:milestone, title: 'Project milestone title', project: project)
@@ -77,11 +88,11 @@ describe Dashboard::MilestonesController do
expect(response.body).not_to include(project_milestone.title)
end
- it 'shows counts of group and project milestones to which the user belongs to' do
+ it 'shows counts of open and closed group and project milestones to which the user belongs to' do
get :index
expect(response.body).to include("Open\n<span class=\"badge badge-pill\">2</span>")
- expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>")
+ expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">2</span>")
end
context 'external authorization' do
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/graphql_controller_spec.rb b/spec/controllers/graphql_controller_spec.rb
index c19a752b07b..9937bdf4061 100644
--- a/spec/controllers/graphql_controller_spec.rb
+++ b/spec/controllers/graphql_controller_spec.rb
@@ -7,6 +7,27 @@ describe GraphqlController do
stub_feature_flags(graphql: true)
end
+ describe 'ArgumentError' do
+ let(:user) { create(:user) }
+ let(:message) { 'green ideas sleep furiously' }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'handles argument errors' do
+ allow(subject).to receive(:execute) do
+ raise Gitlab::Graphql::Errors::ArgumentError, message
+ end
+
+ post :execute
+
+ expect(json_response).to include(
+ 'errors' => include(a_hash_including('message' => message))
+ )
+ end
+ end
+
describe 'POST #execute' do
context 'when user is logged in' do
let(:user) { create(:user) }
diff --git a/spec/controllers/groups/children_controller_spec.rb b/spec/controllers/groups/children_controller_spec.rb
index 02fb971bd9a..bced300a24c 100644
--- a/spec/controllers/groups/children_controller_spec.rb
+++ b/spec/controllers/groups/children_controller_spec.rb
@@ -46,7 +46,7 @@ describe Groups::ChildrenController do
end
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let!(:public_subgroup) { create(:group, :public, parent: group) }
let!(:private_subgroup) { create(:group, :private, parent: group) }
let!(:public_project) { create(:project, :public, namespace: group) }
@@ -292,7 +292,7 @@ describe Groups::ChildrenController do
end
end
- context 'with subgroups and projects', :nested_groups do
+ context 'with subgroups and projects' do
let!(:first_page_subgroups) { create_list(:group, per_page, :public, parent: group) }
let!(:other_subgroup) { create(:group, :public, parent: group) }
let!(:next_page_projects) { create_list(:project, per_page, :public, namespace: group) }
diff --git a/spec/controllers/groups/clusters/applications_controller_spec.rb b/spec/controllers/groups/clusters/applications_controller_spec.rb
index 16a63536ea6..21533d1c89a 100644
--- a/spec/controllers/groups/clusters/applications_controller_spec.rb
+++ b/spec/controllers/groups/clusters/applications_controller_spec.rb
@@ -91,7 +91,7 @@ describe Groups::Clusters::ApplicationsController do
patch :update, params: params.merge(group_id: group)
end
- let!(:application) { create(:clusters_applications_cert_managers, :installed, cluster: cluster) }
+ let!(:application) { create(:clusters_applications_cert_manager, :installed, cluster: cluster) }
let(:application_name) { application.name }
let(:params) { { application: application_name, id: cluster.id, email: "new-email@example.com" } }
diff --git a/spec/controllers/groups/group_members_controller_spec.rb b/spec/controllers/groups/group_members_controller_spec.rb
index 413598ddde0..0c3dd971582 100644
--- a/spec/controllers/groups/group_members_controller_spec.rb
+++ b/spec/controllers/groups/group_members_controller_spec.rb
@@ -16,6 +16,39 @@ describe Groups::GroupMembersController do
expect(response).to have_gitlab_http_status(200)
expect(response).to render_template(:index)
end
+
+ context 'user with owner access' do
+ let!(:invited) { create_list(:group_member, 3, :invited, group: group) }
+
+ before do
+ group.add_owner(user)
+ sign_in(user)
+ end
+
+ it 'assigns invited members' do
+ get :index, params: { group_id: group }
+
+ expect(assigns(:invited_members).map(&:invite_email)).to match_array(invited.map(&:invite_email))
+ end
+
+ it 'restricts search to one email' do
+ get :index, params: { group_id: group, search_invited: invited.first.invite_email }
+
+ expect(assigns(:invited_members).map(&:invite_email)).to match_array(invited.first.invite_email)
+ end
+
+ it 'paginates invited list' do
+ stub_const('Groups::GroupMembersController::MEMBER_PER_PAGE_LIMIT', 2)
+
+ get :index, params: { group_id: group, invited_members_page: 1 }
+
+ expect(assigns(:invited_members).count).to eq(2)
+
+ get :index, params: { group_id: group, invited_members_page: 2 }
+
+ expect(assigns(:invited_members).count).to eq(1)
+ end
+ end
end
describe 'POST create' do
@@ -139,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/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb
index 3cc6fc6f066..98a4c50fc49 100644
--- a/spec/controllers/groups/labels_controller_spec.rb
+++ b/spec/controllers/groups/labels_controller_spec.rb
@@ -24,7 +24,7 @@ describe Groups::LabelsController do
expect(label_ids).to match_array([label_1.title, group_label_1.title])
end
- context 'with ancestor group', :nested_groups do
+ context 'with ancestor group' do
set(:subgroup) { create(:group, parent: group) }
set(:subgroup_label_1) { create(:group_label, group: subgroup, title: 'subgroup_label_1') }
@@ -32,7 +32,7 @@ describe Groups::LabelsController do
subgroup.add_owner(user)
end
- it 'returns ancestor group labels', :nested_groups do
+ it 'returns ancestor group labels' do
get :index, params: { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true }, format: :json
label_ids = json_response.map {|label| label['title']}
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/groups/uploads_controller_spec.rb b/spec/controllers/groups/uploads_controller_spec.rb
index 0f99a957581..60342bf8e3d 100644
--- a/spec/controllers/groups/uploads_controller_spec.rb
+++ b/spec/controllers/groups/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Groups::UploadsController do
{ group_id: model }
end
+ let(:other_model) { create(:group, :public) }
+ let(:other_params) do
+ { group_id: other_model }
+ end
+
it_behaves_like 'handle uploads' do
let(:uploader_class) { NamespaceFileUploader }
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index d2faef5b12b..404e61c5271 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -89,7 +89,7 @@ describe GroupsController do
end
describe 'GET #new' do
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
before do
@@ -166,7 +166,7 @@ describe GroupsController do
end
end
- context 'when creating subgroups', :nested_groups do
+ context 'when creating subgroups' do
[true, false].each do |can_create_group_status|
context "and can_create_group is #{can_create_group_status}" do
context 'and logged in as Owner' do
@@ -584,7 +584,7 @@ describe GroupsController do
end
end
- describe 'PUT transfer', :postgresql do
+ describe 'PUT transfer' do
before do
sign_in(user)
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 dbfacf4e42e..03b6e85b653 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -111,10 +111,10 @@ describe HelpController do
it 'renders the raw file' do
get :show,
params: {
- path: 'user/project/img/labels_default'
+ 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/ide_controller_spec.rb b/spec/controllers/ide_controller_spec.rb
new file mode 100644
index 00000000000..0462f9520d5
--- /dev/null
+++ b/spec/controllers/ide_controller_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IdeController do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'increases the views counter' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_views_count)
+
+ get :index
+ end
+end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index 64a66502732..38388c21749 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -231,7 +231,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -250,7 +250,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -281,7 +281,7 @@ describe Import::BitbucketController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index b89d7317b9c..e1aeab46fca 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -134,6 +134,8 @@ describe Import::BitbucketServerController do
describe 'GET status' do
render_views
+ let(:repos) { instance_double(BitbucketServer::Collection) }
+
before do
allow(controller).to receive(:bitbucket_client).and_return(client)
@@ -145,7 +147,6 @@ describe Import::BitbucketServerController do
it 'assigns repository categories' do
created_project = create(:project, :import_finished, import_type: 'bitbucket_server', creator_id: user.id, import_source: @created_repo.browse_url)
- repos = instance_double(BitbucketServer::Collection)
expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
expect(repos).to receive(:current_page).and_return(1)
@@ -159,6 +160,17 @@ describe Import::BitbucketServerController do
expect(assigns(:repos)).to eq([@repo])
expect(assigns(:incompatible_repos)).to eq([@invalid_repo])
end
+
+ context 'when filtering' do
+ let(:filter) { 'test' }
+
+ it 'passes filter param to bitbucket client' do
+ expect(repos).to receive(:partition).and_return([[@repo, @created_repo], [@invalid_repo]])
+ expect(client).to receive(:repos).with(filter: filter, limit: 25, page_offset: 0).and_return(repos)
+
+ get :status, params: { filter: filter }, as: :json
+ end
+ end
end
describe 'GET jobs' do
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 059354870b5..5675798ac33 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -33,6 +33,16 @@ describe Import::GithubController do
expect(response).to have_http_status(200)
end
+
+ context 'when importing a CI/CD project' do
+ it 'always prompts for an access token' do
+ allow(controller).to receive(:github_import_configured?).and_return(true)
+
+ get :new, params: { ci_cd_only: true }
+
+ expect(response).to render_template(:new)
+ end
+ end
end
describe "GET callback" do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 5af7572e74e..e465eca6c71 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -197,7 +197,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen an existing nested namespace for the project', :postgresql do
+ context 'user has chosen an existing nested namespace for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
@@ -215,7 +215,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen a non-existent nested namespaces for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -246,7 +246,7 @@ describe Import::GitlabController do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/controllers/metrics_controller_spec.rb b/spec/controllers/metrics_controller_spec.rb
index 84027119491..7fb3578cd0a 100644
--- a/spec/controllers/metrics_controller_spec.rb
+++ b/spec/controllers/metrics_controller_spec.rb
@@ -5,12 +5,19 @@ require 'spec_helper'
describe MetricsController do
include StubENV
- let(:metrics_multiproc_dir) { Dir.mktmpdir }
+ let(:metrics_multiproc_dir) { @metrics_multiproc_dir }
let(:whitelisted_ip) { '127.0.0.1' }
let(:whitelisted_ip_range) { '10.0.0.0/24' }
let(:ip_in_whitelisted_range) { '10.0.0.1' }
let(:not_whitelisted_ip) { '10.0.1.1' }
+ around do |example|
+ Dir.mktmpdir do |path|
+ @metrics_multiproc_dir = path
+ example.run
+ end
+ end
+
before do
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(metrics_multiproc_dir)
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index 228c97d591d..df836c2c3e3 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -17,7 +17,7 @@ describe Oauth::ApplicationsController do
expect(response).to have_gitlab_http_status(200)
end
- it 'shows list of applications' do
+ it 'redirects back to profile page if OAuth applications are disabled' do
disable_user_oauth
get :index
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/badges_controller_spec.rb b/spec/controllers/projects/badges_controller_spec.rb
index 5ec8d8d41d7..4ae29ba7f54 100644
--- a/spec/controllers/projects/badges_controller_spec.rb
+++ b/spec/controllers/projects/badges_controller_spec.rb
@@ -7,51 +7,115 @@ describe Projects::BadgesController do
let!(:pipeline) { create(:ci_empty_pipeline) }
let(:user) { create(:user) }
- before do
- project.add_maintainer(user)
- sign_in(user)
- end
+ shared_examples 'a badge resource' do |badge_type|
+ context 'when pipelines are public' do
+ before do
+ project.update!(public_builds: true)
+ end
+
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it "returns the #{badge_type} badge to unauthenticated users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when project is restricted' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it "returns the #{badge_type} badge to guest users" do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
- it 'requests the pipeline badge successfully' do
- get_badge(:pipeline)
+ context 'format' do
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ it 'renders the `flat` badge layout by default' do
+ get_badge(badge_type)
- it 'requests the coverage badge successfully' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
- expect(response).to have_gitlab_http_status(:ok)
- end
+ context 'when style param is set to `flat`' do
+ it 'renders the `flat` badge layout' do
+ get_badge(badge_type, 'flat')
- it 'renders the `flat` badge layout by default' do
- get_badge(:coverage)
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- expect(response).to render_template('projects/badges/badge')
- end
+ context 'when style param is set to an invalid type' do
+ it 'renders the `flat` (default) badge layout' do
+ get_badge(badge_type, 'xxx')
+
+ expect(response).to render_template('projects/badges/badge')
+ end
+ end
- context 'when style param is set to `flat`' do
- it 'renders the `flat` badge layout' do
- get_badge(:coverage, 'flat')
+ context 'when style param is set to `flat-square`' do
+ it 'renders the `flat-square` badge layout' do
+ get_badge(badge_type, 'flat-square')
- expect(response).to render_template('projects/badges/badge')
+ expect(response).to render_template('projects/badges/badge_flat-square')
+ end
+ end
end
- end
- context 'when style param is set to an invalid type' do
- it 'renders the `flat` (default) badge layout' do
- get_badge(:coverage, 'xxx')
+ context 'when pipelines are not public' do
+ before do
+ project.update!(public_builds: false)
+ end
- expect(response).to render_template('projects/badges/badge')
+ context 'when project is public' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
+ end
+
+ it 'returns 404 to unauthenticated users' do
+ get_badge(badge_type)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when project is restricted to the user' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ project.add_guest(user)
+ sign_in(user)
+ end
+
+ it 'defaults to project permissions' do
+ get_badge(:coverage)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
end
end
- context 'when style param is set to `flat-square`' do
- it 'renders the `flat-square` badge layout' do
- get_badge(:coverage, 'flat-square')
+ describe '#pipeline' do
+ it_behaves_like 'a badge resource', :pipeline
+ end
- expect(response).to render_template('projects/badges/badge_flat-square')
- end
+ describe '#coverage' do
+ it_behaves_like 'a badge resource', :coverage
end
def get_badge(badge, style = nil)
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
new file mode 100644
index 00000000000..b828c678d0c
--- /dev/null
+++ b/spec/controllers/projects/cycle_analytics/events_controller_spec.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::CycleAnalytics::EventsController do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ project.add_maintainer(user)
+ end
+
+ describe 'cycle analytics not set up flag' do
+ context 'with no data' do
+ it 'is empty' do
+ get_issue
+
+ expect(response).to be_successful
+ expect(JSON.parse(response.body)['events']).to be_empty
+ end
+ end
+
+ context 'with data' do
+ let(:milestone) { create(:milestone, project: project, created_at: 10.days.ago) }
+ let(:issue) { create(:issue, project: project, created_at: 9.days.ago) }
+
+ before do
+ issue.update(milestone: milestone)
+ end
+
+ it 'is not empty' do
+ get_issue
+
+ expect(response).to be_successful
+ end
+
+ it 'contains event detais' do
+ get_issue
+
+ events = JSON.parse(response.body)['events']
+
+ expect(events).not_to be_empty
+ expect(events.first).to include('title', 'author', 'iid', 'total_time', 'created_at', 'url')
+ expect(events.first['title']).to eq(issue.title)
+ end
+
+ context 'with data older than start date' do
+ it 'is empty' do
+ get_issue(additional_params: { cycle_analytics: { start_date: 7 } })
+
+ expect(response).to be_successful
+
+ expect(JSON.parse(response.body)['events']).to be_empty
+ end
+ end
+ end
+ end
+
+ def get_issue(additional_params: {})
+ params = additional_params.merge(namespace_id: project.namespace, project_id: project)
+ get(:issue, params: params, format: :json)
+ end
+end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 2dc97e18113..65eee7b8ead 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -11,6 +11,20 @@ describe Projects::CycleAnalyticsController do
project.add_maintainer(user)
end
+ context "counting page views for 'show'" do
+ it 'increases the counter' do
+ expect(Gitlab::UsageDataCounters::CycleAnalyticsCounter).to receive(:count).with(:views)
+
+ get(:show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
+
+ expect(response).to be_successful
+ end
+ end
+
describe 'cycle analytics not set up flag' do
context 'with no data' do
it 'is true' do
@@ -20,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
@@ -41,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/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb
index ebbbebf1bc0..71ee1fd03bf 100644
--- a/spec/controllers/projects/environments_controller_spec.rb
+++ b/spec/controllers/projects/environments_controller_spec.rb
@@ -518,10 +518,10 @@ describe Projects::EnvironmentsController do
end
end
- shared_examples_for 'the default dynamic dashboard' do
+ shared_examples_for 'specified dashboard embed' do |expected_titles|
it_behaves_like '200 response'
- it 'contains only the Memory and CPU charts' do
+ it 'contains only the specified charts' do
get :metrics_dashboard, params: environment_params(dashboard_params)
dashboard = json_response['dashboard']
@@ -531,10 +531,14 @@ describe Projects::EnvironmentsController do
expect(dashboard['dashboard']).to be_nil
expect(dashboard['panel_groups'].length).to eq 1
expect(panel_group['group']).to be_nil
- expect(titles).to eq ['Memory Usage (Total)', 'Core Usage (Total)']
+ expect(titles).to eq expected_titles
end
end
+ shared_examples_for 'the default dynamic dashboard' do
+ it_behaves_like 'specified dashboard embed', ['Memory Usage (Total)', 'Core Usage (Total)']
+ end
+
shared_examples_for 'dashboard can be specified' do
context 'when dashboard is specified' do
let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
@@ -551,7 +555,7 @@ describe Projects::EnvironmentsController do
end
context 'when the specified dashboard is the default dashboard' do
- let(:dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH }
+ let(:dashboard_path) { system_dashboard_path }
it_behaves_like 'the default dashboard'
end
@@ -564,12 +568,40 @@ describe Projects::EnvironmentsController do
it_behaves_like 'the default dynamic dashboard'
- context 'when the dashboard is specified' do
- let(:dashboard_params) { { format: :json, embedded: true, dashboard: '.gitlab/dashboards/fake.yml' } }
+ context 'when incomplete dashboard params are provided' do
+ let(:dashboard_params) { { format: :json, embedded: true, title: 'Title' } }
+
+ # The title param should be ignored.
+ it_behaves_like 'the default dynamic dashboard'
+ end
+
+ context 'when invalid params are provided' do
+ let(:dashboard_params) { { format: :json, embedded: true, metric_id: 16 } }
- # The dashboard param should be ignored.
+ # The superfluous param should be ignored.
it_behaves_like 'the default dynamic dashboard'
end
+
+ context 'when the dashboard is correctly specified' do
+ let(:dashboard_params) do
+ {
+ format: :json,
+ embedded: true,
+ dashboard: system_dashboard_path,
+ group: business_metric_title,
+ title: 'title',
+ y_label: 'y_label'
+ }
+ end
+
+ it_behaves_like 'error response', :not_found
+
+ context 'and exists' do
+ let!(:metric) { create(:prometheus_metric, project: project) }
+
+ it_behaves_like 'specified dashboard embed', ['title']
+ end
+ end
end
end
@@ -581,31 +613,13 @@ describe Projects::EnvironmentsController do
end
end
- shared_examples_for 'dashboard cannot be embedded' do
- context 'when the embedded flag is included' do
- let(:dashboard_params) { { format: :json, embedded: true } }
-
- it_behaves_like 'the default dashboard'
- end
- end
-
let(:dashboard_params) { { format: :json } }
it_behaves_like 'the default dashboard'
it_behaves_like 'dashboard can be specified'
it_behaves_like 'dashboard can be embedded'
- context 'when multiple dashboards is enabled and embedding metrics is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
-
- it_behaves_like 'the default dashboard'
- it_behaves_like 'dashboard can be specified'
- it_behaves_like 'dashboard cannot be embedded'
- end
-
- context 'when multiple dashboards is disabled and embedding metrics is enabled' do
+ context 'when multiple dashboards is disabled' do
before do
stub_feature_flags(environment_metrics_show_multiple_dashboards: false)
end
@@ -614,19 +628,6 @@ describe Projects::EnvironmentsController do
it_behaves_like 'dashboard cannot be specified'
it_behaves_like 'dashboard can be embedded'
end
-
- context 'when multiple dashboards and embedding metrics are disabled' do
- before do
- stub_feature_flags(
- environment_metrics_show_multiple_dashboards: false,
- gfm_embedded_metrics: false
- )
- end
-
- it_behaves_like 'the default dashboard'
- it_behaves_like 'dashboard cannot be specified'
- it_behaves_like 'dashboard cannot be embedded'
- end
end
describe 'GET #search' do
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 844c61f1ace..d11ef24ef96 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe Projects::ErrorTrackingController do
set(:project) { create(:project) }
diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb
index bf099e8deeb..88fa2236e33 100644
--- a/spec/controllers/projects/git_http_controller_spec.rb
+++ b/spec/controllers/projects/git_http_controller_spec.rb
@@ -12,4 +12,15 @@ describe Projects::GitHttpController do
expect(response.status).to eq(403)
end
end
+
+ describe 'GET #info_refs' do
+ it 'returns 401 for unauthenticated requests to public repositories when http protocol is disabled' do
+ stub_application_setting(enabled_git_access_protocol: 'ssh')
+ project = create(:project, :public, :repository)
+
+ get :info_refs, params: { service: 'git-upload-pack', namespace_id: project.namespace.to_param, project_id: project.path + '.git' }
+
+ expect(response.status).to eq(401)
+ end
+ end
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 32d14dce936..187c7864ad7 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -251,15 +251,13 @@ describe Projects::IssuesController do
end
end
- describe 'Redirect after sign in' do
+ # This spec runs as a request-style spec in order to invoke the
+ # Rails router. A controller-style spec matches the wrong route, and
+ # session['user_return_to'] becomes incorrect.
+ describe 'Redirect after sign in', type: :request do
context 'with an AJAX request' do
it 'does not store the visited URL' do
- get :show, params: {
- format: :json,
- namespace_id: project.namespace,
- project_id: project,
- id: issue.iid
- }, xhr: true
+ get project_issue_path(project, issue), xhr: true
expect(session['user_return_to']).to be_blank
end
@@ -267,14 +265,9 @@ describe Projects::IssuesController do
context 'without an AJAX request' do
it 'stores the visited URL' do
- get :show,
- params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.iid
- }
+ get project_issue_path(project, issue)
- expect(session['user_return_to']).to eq("/#{project.namespace.to_param}/#{project.to_param}/issues/#{issue.iid}")
+ expect(session['user_return_to']).to eq(project_issue_path(project, issue))
end
end
end
@@ -1111,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
@@ -1260,6 +1274,28 @@ describe Projects::IssuesController do
sign_in(user)
end
+ context do
+ it_behaves_like 'discussions provider' do
+ let!(:author) { create(:user) }
+ let!(:project) { create(:project) }
+
+ let!(:issue) { create(:issue, project: project, author: user) }
+
+ let!(:note_on_issue1) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
+ let!(:note_on_issue2) { create(:discussion_note_on_issue, noteable: issue, project: issue.project, author: create(:user)) }
+
+ let(:requested_iid) { issue.iid }
+ let(:expected_discussion_count) { 3 }
+ let(:expected_discussion_ids) do
+ [
+ issue.notes.first.discussion_id,
+ note_on_issue1.discussion_id,
+ note_on_issue2.discussion_id
+ ]
+ end
+ end
+ end
+
it 'returns discussion json' do
get :discussions, params: { namespace_id: project.namespace, project_id: project, id: issue.iid }
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 901402aa5fd..f076a5e769f 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -158,7 +158,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
get_show_json
json_response.dig('pipeline', 'details', 'stages').tap do |stages|
- expect(stages.map(&:keys).flatten)
+ expect(stages.flat_map(&:keys))
.to eq %w[name title status path dropdown_path]
end
end
@@ -546,7 +546,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['id']).to eq job.id
expect(json_response['status']).to eq job.status
- expect(json_response['html']).to eq('<span class="">BUILD TRACE</span>')
+ expect(json_response['html']).to eq('<span>BUILD TRACE</span>')
end
end
@@ -676,6 +676,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
describe 'POST play' do
+ let(:variable_attributes) { [] }
+
before do
project.add_developer(user)
@@ -698,6 +700,14 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
it 'transits to pending' do
expect(job.reload).to be_pending
end
+
+ context 'when job variables are specified' do
+ let(:variable_attributes) { [{ key: 'first', secret_value: 'first' }] }
+
+ it 'assigns the job variables' do
+ expect(job.reload.job_variables.map(&:key)).to contain_exactly('first')
+ end
+ end
end
context 'when job is not playable' do
@@ -712,7 +722,8 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
post :play, params: {
namespace_id: project.namespace,
project_id: project,
- id: job.id
+ id: job.id,
+ job_variables_attributes: variable_attributes
}
end
end
diff --git a/spec/controllers/projects/merge_requests/content_controller_spec.rb b/spec/controllers/projects/merge_requests/content_controller_spec.rb
index 2879e06aee4..818cf794ec6 100644
--- a/spec/controllers/projects/merge_requests/content_controller_spec.rb
+++ b/spec/controllers/projects/merge_requests/content_controller_spec.rb
@@ -11,8 +11,8 @@ describe Projects::MergeRequests::ContentController do
sign_in(user)
end
- def do_request
- get :widget, params: {
+ def do_request(action = :cached_widget)
+ get action, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: merge_request.iid,
@@ -20,41 +20,65 @@ describe Projects::MergeRequests::ContentController do
}
end
- describe 'GET widget' do
- context 'user has access to the project' do
- before do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ context 'user has access to the project' do
+ before do
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
- project.add_maintainer(user)
- end
+ project.add_maintainer(user)
+ end
+ describe 'GET cached_widget' do
it 'renders widget MR entity as json' do
do_request
- expect(response).to match_response_schema('entities/merge_request_widget')
+ expect(response).to match_response_schema('entities/merge_request_poll_cached_widget')
end
+ it 'closes an MR with moved source project' do
+ merge_request.update_column(:source_project_id, nil)
+
+ expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ end
+ end
+
+ describe 'GET widget' do
it 'checks whether the MR can be merged' do
controller.instance_variable_set(:@merge_request, merge_request)
expect(merge_request).to receive(:check_mergeability)
- do_request
+ do_request(:widget)
end
- it 'closes an MR with moved source project' do
- merge_request.update_column(:source_project_id, nil)
+ context 'merged merge request' do
+ let(:merge_request) do
+ create(:merged_merge_request, :with_test_reports, target_project: project, source_project: project)
+ end
- expect { do_request }.to change { merge_request.reload.open? }.from(true).to(false)
+ it 'renders widget MR entity as json' do
+ do_request(:widget)
+
+ expect(response).to match_response_schema('entities/merge_request_poll_widget')
+ end
end
end
+ end
- context 'user does not have access to the project' do
- it 'renders widget MR entity as json' do
+ context 'user does not have access to the project' do
+ describe 'GET cached_widget' do
+ it 'returns 404' do
do_request
expect(response).to have_http_status(:not_found)
end
end
+
+ describe 'GET widget' do
+ it 'returns 404' do
+ do_request(:widget)
+
+ expect(response).to have_http_status(:not_found)
+ end
+ end
end
end
diff --git a/spec/controllers/projects/merge_requests/creations_controller_spec.rb b/spec/controllers/projects/merge_requests/creations_controller_spec.rb
index 5fefad86ef3..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
@@ -212,4 +212,46 @@ describe Projects::MergeRequests::CreationsController do
expect(response).to have_gitlab_http_status(200)
end
end
+
+ describe 'POST create' do
+ let(:params) do
+ {
+ namespace_id: fork_project.namespace.to_param,
+ project_id: fork_project,
+ merge_request: {
+ title: 'Test merge request',
+ source_branch: 'remove-submodule',
+ target_branch: 'master'
+ }
+ }
+ end
+
+ it 'creates merge request' do
+ expect do
+ post_request(params)
+ end.to change { MergeRequest.count }.by(1)
+ end
+
+ context 'when the merge request is not created from the web ide' do
+ it 'counter is not increased' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_merge_requests_count)
+
+ post_request(params)
+ end
+ end
+
+ context 'when the merge request is created from the web ide' do
+ let(:nav_source) { { nav_source: 'webide' } }
+
+ it 'counter is increased' do
+ expect(Gitlab::UsageDataCounters::WebIdeCounter).to receive(:increment_merge_requests_count)
+
+ post_request(params.merge(nav_source))
+ end
+ end
+
+ def post_request(merge_request_params)
+ post :create, params: merge_request_params
+ end
+ 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 f11880122b1..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
@@ -621,10 +621,100 @@ describe Projects::MergeRequestsController do
format: :json
end
- it 'responds with serialized pipelines' do
- expect(json_response['pipelines']).not_to be_empty
- expect(json_response['count']['all']).to eq 1
- expect(response).to include_pagination_headers
+ context 'with "enabled" builds on a public project' do
+ let(:project) { create(:project, :repository, :public) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with private builds on a public project' do
+ let(:project) { create(:project, :repository, :public, :builds_private) }
+
+ context 'for a project owner' do
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for an unassociated user' do
+ let(:user) { create :user }
+
+ it 'responds with no pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'from a project fork' do
+ let(:fork_user) { create :user }
+ let(:forked_project) { fork_project(project, fork_user, repository: true) } # Forked project carries over :builds_private
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: forked_project) }
+
+ context 'with private builds' do
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_empty
+ expect(json_response['count']['all']).to eq(0)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+
+ context 'with public builds' do
+ let(:forked_project) do
+ fork_project(project, fork_user, repository: true).tap do |new_project|
+ new_project.project_feature.update(builds_access_level: ProjectFeature::ENABLED)
+ end
+ end
+
+ context 'for the target project member' do
+ it 'does not respond with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+
+ context 'for the source project member' do
+ let(:user) { fork_user }
+
+ it 'responds with serialized pipelines' do
+ expect(json_response['pipelines']).to be_present
+ expect(json_response['count']['all']).to eq(1)
+ expect(response).to include_pagination_headers
+ end
+ end
+ end
+ end
end
end
@@ -885,10 +975,9 @@ describe Projects::MergeRequestsController do
environment2 = create(:environment, project: forked)
create(:deployment, :succeed, environment: environment2, sha: sha, ref: 'master', deployable: build)
- # TODO address the last 11 queries
+ # TODO address the last 5 queries
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/63952 (5 queries)
- # And https://gitlab.com/gitlab-org/gitlab-ce/issues/64105 (6 queries)
- leeway = 11
+ leeway = 5
expect { get_ci_environments_status }.not_to exceed_all_query_limit(control_count + leeway)
end
end
@@ -1121,6 +1210,22 @@ describe Projects::MergeRequestsController do
end
end
end
+
+ context do
+ it_behaves_like 'discussions provider' do
+ let!(:author) { create(:user) }
+ let!(:project) { create(:project) }
+
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+
+ let!(:mr_note1) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
+ let!(:mr_note2) { create(:discussion_note_on_merge_request, noteable: merge_request, project: project) }
+
+ let(:requested_iid) { merge_request.iid }
+ let(:expected_discussion_count) { 2 }
+ let(:expected_discussion_ids) { [mr_note1.discussion_id, mr_note2.discussion_id] }
+ end
+ end
end
describe 'GET edit' do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
index 767cee7d54a..cbf9d437909 100644
--- a/spec/controllers/projects/milestones_controller_spec.rb
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -115,7 +115,7 @@ describe Projects::MilestonesController do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
@@ -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 98aea9056dc..4500c412521 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -43,7 +43,7 @@ describe Projects::NotesController do
request.headers['X-Last-Fetched-At'] = last_fetched_at
expect(NotesFinder).to receive(:new)
- .with(anything, anything, hash_including(last_fetched_at: last_fetched_at))
+ .with(anything, hash_including(last_fetched_at: last_fetched_at))
.and_call_original
get :index, params: request_params
@@ -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..089d06e11eb 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -177,18 +177,22 @@ describe Projects::PipelinesController do
end
it 'does not perform N + 1 queries' do
+ # Set up all required variables
+ get_pipeline_json
+
control_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- create_build('test', 1, 'rspec 1')
- create_build('test', 1, 'spinach 0')
- create_build('test', 1, 'spinach 1')
- create_build('test', 1, 'audit')
- create_build('post deploy', 3, 'pages 1')
- create_build('post deploy', 3, 'pages 2')
+ first_build = pipeline.builds.first
+ first_build.tag_list << [:hello, :world]
+ create(:deployment, deployable: first_build)
+
+ second_build = pipeline.builds.second
+ second_build.tag_list << [:docker, :ruby]
+ create(:deployment, deployable: second_build)
new_count = ActiveRecord::QueryRecorder.new { get_pipeline_json }.count
- expect(new_count).to be_within(12).of(control_count)
+ expect(new_count).to be_within(1).of(control_count)
end
end
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index 4141e41c7a7..5130e26c928 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -158,7 +158,7 @@ describe Projects::ProjectMembersController do
id: member
}, xhr: true
- expect(response).to be_success
+ expect(response).to be_successful
expect(project.members).not_to include member
end
end
diff --git a/spec/controllers/projects/raw_controller_spec.rb b/spec/controllers/projects/raw_controller_spec.rb
index 97acd47b4da..8b43d1264b2 100644
--- a/spec/controllers/projects/raw_controller_spec.rb
+++ b/spec/controllers/projects/raw_controller_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::RawController do
+ include RepoHelpers
+
let(:project) { create(:project, :public, :repository) }
describe 'GET #show' do
@@ -46,5 +48,98 @@ describe Projects::RawController do
let(:filename) { 'lfs_object.iso' }
let(:filepath) { "be93687/files/lfs/#{filename}" }
end
+
+ context 'when the endpoint receives requests above the limit', :clean_gitlab_redis_cache do
+ let(:file_path) { 'master/README.md' }
+
+ before do
+ stub_application_setting(raw_blob_request_limit: 5)
+ end
+
+ it 'prevents from accessing the raw file' do
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+ end
+
+ it 'logs the event on auth.log' do
+ attributes = {
+ message: 'Action_Rate_Limiter_Request',
+ env: :raw_blob_request_limit,
+ remote_ip: '0.0.0.0',
+ request_method: 'GET',
+ path: "/#{project.full_path}/raw/#{file_path}"
+ }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
+
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+ end
+
+ context 'when the request uses a different version of a commit' do
+ it 'prevents from accessing the raw file' do
+ # 3 times with the normal sha
+ commit_sha = project.repository.commit.sha
+ file_path = "#{commit_sha}/README.md"
+
+ execute_raw_requests(requests: 3, project: project, file_path: file_path)
+
+ # 3 times with the modified version
+ modified_sha = commit_sha.gsub(commit_sha[0..5], commit_sha[0..5].upcase)
+ modified_path = "#{modified_sha}/README.md"
+
+ execute_raw_requests(requests: 3, project: project, file_path: modified_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+ end
+ end
+
+ context 'when the throttling has been disabled' do
+ before do
+ stub_application_setting(raw_blob_request_limit: 0)
+ end
+
+ it 'does not prevent from accessing the raw file' do
+ execute_raw_requests(requests: 10, project: project, file_path: file_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ context 'with case-sensitive files' do
+ it 'prevents from accessing the specific file' do
+ create_file_in_repo(project, 'master', 'master', 'readme.md', 'Add readme.md')
+ create_file_in_repo(project, 'master', 'master', 'README.md', 'Add README.md')
+
+ commit_sha = project.repository.commit.sha
+ file_path = "#{commit_sha}/readme.md"
+
+ # Accessing downcase version of readme
+ execute_raw_requests(requests: 6, project: project, file_path: file_path)
+
+ expect(flash[:alert]).to eq('You cannot access the raw file. Please wait a minute.')
+ expect(response).to have_gitlab_http_status(429)
+
+ # Accessing upcase version of readme
+ file_path = "#{commit_sha}/README.md"
+
+ execute_raw_requests(requests: 1, project: project, file_path: file_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+ end
+
+ def execute_raw_requests(requests:, project:, file_path:)
+ requests.times do
+ get :show, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: file_path
+ }
+ end
end
end
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/registry/tags_controller_spec.rb b/spec/controllers/projects/registry/tags_controller_spec.rb
index ff35139ae2e..c6e063d8229 100644
--- a/spec/controllers/projects/registry/tags_controller_spec.rb
+++ b/spec/controllers/projects/registry/tags_controller_spec.rb
@@ -113,4 +113,37 @@ describe Projects::Registry::TagsController do
format: :json
end
end
+
+ describe 'POST bulk_destroy' do
+ context 'when user has access to registry' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'when there is matching tag present' do
+ before do
+ stub_container_registry_tags(repository: repository.path, tags: %w[rc1 test.])
+ end
+
+ it 'makes it possible to delete tags in bulk' do
+ allow_any_instance_of(ContainerRegistry::Tag).to receive(:delete) { |*args| ContainerRegistry::Tag.delete(*args) }
+ expect(ContainerRegistry::Tag).to receive(:delete).exactly(2).times
+
+ bulk_destroy_tags(['rc1', 'test.'])
+ end
+ end
+ end
+
+ private
+
+ def bulk_destroy_tags(names)
+ post :bulk_destroy, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ repository_id: repository,
+ ids: names
+ },
+ format: :json
+ end
+ end
end
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index 8fca9e680dd..fcab4d73dca 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -77,6 +77,53 @@ describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(404)
end
end
+
+ describe 'caching' do
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, private')
+ end
+
+ context 'when project is public' do
+ let(:project) { create(:project, :repository, :public) }
+
+ it 'sets appropriate caching headers' do
+ get_archive
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to include('max-age=60, public')
+ end
+ end
+
+ context 'when ref is a commit SHA' do
+ it 'max-age is set to 3600 in Cache-Control header' do
+ get_archive('ddd0f15ae83993f5cb66a927a28673882e99100b')
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.header['Cache-Control']).to include('max-age=3600')
+ end
+ end
+
+ context 'when If-None-Modified header is set' do
+ it 'returns a 304 status' do
+ # Get the archive cached first
+ get_archive
+
+ request.headers['If-None-Match'] = response.headers['ETag']
+ get_archive
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+ end
+
+ def get_archive(id = 'feature')
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: id }, format: 'zip'
+ end
+ end
end
end
end
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 18c594acae0..9f1ef3a4be8 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -10,12 +10,16 @@ describe Projects::Serverless::FunctionsController do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: cluster.cluster_project.project,
+ environment: environment)
end
before do
@@ -47,12 +51,11 @@ describe Projects::Serverless::FunctionsController do
end
context 'when cache is ready' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
let(:knative_state) { true }
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
stub_kubeclient_service_pods(
@@ -107,12 +110,12 @@ describe Projects::Serverless::FunctionsController do
context 'valid data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
end
it 'has a valid function name' do
@@ -140,12 +143,12 @@ describe Projects::Serverless::FunctionsController do
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
before do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
end
it 'has data' do
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
new file mode 100644
index 00000000000..5774ff7c576
--- /dev/null
+++ b/spec/controllers/projects/starrers_controller_spec.rb
@@ -0,0 +1,196 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::StarrersController do
+ let(:user_1) { create(:user, name: 'John') }
+ let(:user_2) { create(:user, name: 'Michael') }
+ let(:private_user) { create(:user, name: 'Michael Douglas', private_profile: true) }
+ let(:admin) { create(:user, admin: true) }
+ let(:project) { create(:project, :public) }
+
+ before do
+ user_1.toggle_star(project)
+ user_2.toggle_star(project)
+ private_user.toggle_star(project)
+ end
+
+ describe 'GET index' do
+ def get_starrers(search: nil)
+ get :index, params: { namespace_id: project.namespace, project_id: project, search: search }
+ end
+
+ def user_ids
+ assigns[:starrers].map { |s| s['user_id'] }
+ end
+
+ shared_examples 'starrers counts' do
+ it 'starrers counts are correct' do
+ expect(assigns[:total_count]).to eq(3)
+ expect(assigns[:public_count]).to eq(2)
+ expect(assigns[:private_count]).to eq(1)
+ end
+ end
+
+ context 'N+1 queries' do
+ render_views
+
+ it 'avoids N+1s loading users', :request_store do
+ get_starrers
+
+ control_count = ActiveRecord::QueryRecorder.new { get_starrers }.count
+
+ create_list(:user, 5).each { |user| user.toggle_star(project) }
+
+ expect { get_starrers }.not_to exceed_query_limit(control_count)
+ end
+ end
+
+ context 'when project is public' do
+ before do
+ project.update_attribute(:visibility_level, Project::PUBLIC)
+ end
+
+ context 'when no user is logged in' do
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when public user is logged in' do
+ before do
+ sign_in(user_1)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when private user is logged in' do
+ before do
+ sign_in(private_user)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'their star is also visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+
+ context 'when admin is logged in' do
+ before do
+ sign_in(admin)
+ end
+
+ context 'with no searching' do
+ before do
+ get_starrers
+ end
+
+ it 'all users are visible' do
+ expect(user_ids).to include(user_1.id, user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+
+ context 'when searching by user' do
+ before do
+ get_starrers(search: 'Michael')
+ end
+
+ it 'public and private starrers are visible' do
+ expect(user_ids).to contain_exactly(user_2.id, private_user.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+ end
+
+ context 'when project is private' do
+ before do
+ project.update(visibility_level: Project::PRIVATE)
+ end
+
+ it 'starrers are not visible for non logged in users' do
+ get_starrers
+
+ expect(assigns[:starrers]).to be_blank
+ end
+
+ context 'when user is logged in' do
+ before do
+ sign_in(project.creator)
+ get_starrers
+ end
+
+ it 'only users with public profiles are visible' do
+ expect(user_ids).to contain_exactly(user_1.id, user_2.id)
+ end
+
+ include_examples 'starrers counts'
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb
index 776c1270977..661ed9840b1 100644
--- a/spec/controllers/projects/uploads_controller_spec.rb
+++ b/spec/controllers/projects/uploads_controller_spec.rb
@@ -10,6 +10,11 @@ describe Projects::UploadsController do
{ namespace_id: model.namespace.to_param, project_id: model }
end
+ let(:other_model) { create(:project, :public) }
+ let(:other_params) do
+ { namespace_id: other_model.namespace.to_param, project_id: other_model }
+ end
+
it_behaves_like 'handle uploads'
context 'when the URL the old style, without /-/system' do
diff --git a/spec/controllers/projects/variables_controller_spec.rb b/spec/controllers/projects/variables_controller_spec.rb
index a2a09e2580f..21e106660d0 100644
--- a/spec/controllers/projects/variables_controller_spec.rb
+++ b/spec/controllers/projects/variables_controller_spec.rb
@@ -36,5 +36,70 @@ describe Projects::VariablesController do
end
include_examples 'PATCH #update updates variables'
+
+ context 'with environment scope' do
+ let!(:variable) { create(:ci_variable, project: project, environment_scope: 'custom_scope') }
+
+ let(:variable_attributes) do
+ { id: variable.id,
+ key: variable.key,
+ secret_value: variable.value,
+ protected: variable.protected?.to_s,
+ environment_scope: variable.environment_scope }
+ end
+ let(:new_variable_attributes) do
+ { key: 'new_key',
+ secret_value: 'dummy_value',
+ protected: 'false',
+ environment_scope: 'new_scope' }
+ end
+
+ context 'with same key and different environment scope' do
+ let(:variables_attributes) do
+ [
+ variable_attributes,
+ new_variable_attributes.merge(key: variable.key)
+ ]
+ end
+
+ it 'does not update the existing variable' do
+ expect { subject }.not_to change { variable.reload.value }
+ end
+
+ it 'creates the new variable' do
+ expect { subject }.to change { owner.variables.count }.by(1)
+ end
+
+ it 'returns a successful response including all variables' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('variables')
+ end
+ end
+
+ context 'with same key and same environment scope' do
+ let(:variables_attributes) do
+ [
+ variable_attributes,
+ new_variable_attributes.merge(key: variable.key, environment_scope: variable.environment_scope)
+ ]
+ end
+
+ it 'does not update the existing variable' do
+ expect { subject }.not_to change { variable.reload.value }
+ end
+
+ it 'does not create the new variable' do
+ expect { subject }.not_to change { owner.variables.count }
+ end
+
+ it 'returns a bad request response' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/projects/wikis_controller_spec.rb b/spec/controllers/projects/wikis_controller_spec.rb
index a7e5a79b51d..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 } }
@@ -31,43 +46,106 @@ describe Projects::WikisController do
end
end
+ describe 'GET #history' do
+ before do
+ allow(controller)
+ .to receive(:can?)
+ .with(any_args)
+ .and_call_original
+
+ # The :create_wiki permission is irrelevant to reading history.
+ expect(controller)
+ .not_to receive(:can?)
+ .with(anything, :create_wiki, any_args)
+
+ allow(controller)
+ .to receive(:can?)
+ .with(anything, :read_wiki, any_args)
+ .and_return(allow_read_wiki)
+ end
+
+ shared_examples 'fetching history' do |expected_status|
+ before do
+ get :history, params: { namespace_id: project.namespace, project_id: project, id: wiki_title }
+ end
+
+ it "returns status #{expected_status}" do
+ expect(response).to have_http_status(expected_status)
+ end
+ end
+
+ it_behaves_like 'fetching history', :ok do
+ let(:allow_read_wiki) { true }
+
+ it 'assigns @page_versions' do
+ expect(assigns(:page_versions)).to be_present
+ end
+ end
+
+ it_behaves_like 'fetching history', :not_found do
+ let(:allow_read_wiki) { false }
+ end
+ end
+
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(response.body).to include(wiki_title)
+ 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)
+
+ subject
+
+ expect(response).to have_http_status(:ok)
+ expect(flash[:notice]).to eq('The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.')
+ end
+ end
end
- context 'when page content encoding is invalid' do
- it 'sets flash error' do
- allow(controller).to receive(:valid_encoding?).and_return(false)
+ context 'when the page does not exist' do
+ let(:id) { 'does not exist' }
+ before do
subject
+ end
- expect(response).to have_http_status(:ok)
- expect(flash[:notice]).to eq 'The content of this page is not encoded in UTF-8. Edits can only be made via the Git repository.'
+ it 'builds a new wiki page with the id as the title' do
+ expect(assigns(:page).title).to eq(id)
+ end
+
+ context 'when a random_title param is present' do
+ let(:random_title) { true }
+
+ it 'builds a new wiki page with no title' do
+ expect(assigns(:page).title).to be_empty
+ end
end
end
context 'when page is a file' do
include WikiHelpers
- let(:path) { upload_file_to_wiki(project, user, file_name) }
+ let(:id) { upload_file_to_wiki(project, user, file_name) }
before do
- get :show, params: { namespace_id: project.namespace, project_id: project, id: path }
+ subject
end
context 'when file is an image' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index faf3c990cb2..35487682462 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
describe RegistrationsController do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
describe '#create' do
let(:base_user_params) { { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
let(:user_params) { { user: base_user_params } }
@@ -26,13 +30,36 @@ describe RegistrationsController do
end
context 'when send_user_confirmation_email is true' do
- it 'does not authenticate user and sends confirmation email' do
+ before do
stub_application_setting(send_user_confirmation_email: true)
+ end
+
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- post(:create, params: user_params)
+ it 'does not authenticate the user and sends a confirmation email' do
+ post(:create, params: user_params)
- expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
- expect(subject.current_user).to be_nil
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(subject.current_user).to be_nil
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'authenticates the user and sends a confirmation email' do
+ post(:create, params: user_params)
+
+ expect(ActionMailer::Base.deliveries.last.to.first).to eq(user_params[:user][:email])
+ expect(response).to redirect_to(dashboard_projects_path)
+ end
end
end
@@ -88,6 +115,88 @@ describe RegistrationsController do
end
end
+ context 'when invisible captcha is enabled' do
+ before do
+ stub_feature_flags(invisible_captcha: true)
+ InvisibleCaptcha.timestamp_threshold = treshold
+ end
+
+ let(:treshold) { 4 }
+ let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } }
+ let(:form_rendered_time) { Time.current }
+ let(:submit_time) { form_rendered_time + treshold }
+ let(:auth_log_attributes) do
+ {
+ message: auth_log_message,
+ env: :invisible_captcha_signup_bot_detected,
+ remote_ip: '0.0.0.0',
+ request_method: 'POST',
+ path: '/users'
+ }
+ end
+
+ describe 'the honeypot has not been filled and the signup form has not been submitted too quickly' do
+ it 'creates an account' do
+ travel_to(submit_time) do
+ expect { post(:create, params: user_params, session: session_params) }.to change(User, :count).by(1)
+ end
+ end
+ end
+
+ describe 'honeypot spam detection' do
+ let(:user_params) { super().merge(firstname: 'Roy', lastname: 'Batty') }
+ let(:auth_log_message) { 'Invisible_Captcha_Honeypot_Request' }
+
+ it 'logs the request, refuses to create an account and renders an empty body' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_honeypot, 'Counter of blocked sign up attempts with filled honeypot')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ describe 'timestamp spam detection' do
+ let(:auth_log_message) { 'Invisible_Captcha_Timestamp_Request' }
+
+ context 'the sign up form has been submitted without the invisible_captcha_timestamp parameter' do
+ let(:session_params) { nil }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+
+ context 'the sign up form has been submitted too quickly' do
+ let(:submit_time) { form_rendered_time }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+ end
+ end
+
context 'when terms are enforced' do
before do
enforce_terms
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
index 5a5c0a1f6ac..3e0d53a6573 100644
--- a/spec/controllers/search_controller_spec.rb
+++ b/spec/controllers/search_controller_spec.rb
@@ -11,151 +11,173 @@ describe SearchController do
sign_in(user)
end
- context 'uses the right partials depending on scope' do
- using RSpec::Parameterized::TableSyntax
- render_views
-
- set(:project) { create(:project, :public, :repository, :wiki_repo) }
-
+ shared_examples_for 'when the user cannot read cross project' do |action, params|
before do
- expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?)
+ .with(user, :read_cross_project, :global) { false }
end
- subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
+ it 'blocks access without a project_id' do
+ get action, params: params
- where(:partial, :scope) do
- '_blob' | :blobs
- '_wiki_blob' | :wiki_blobs
- '_commit' | :commits
+ expect(response).to have_gitlab_http_status(403)
end
- with_them do
- it do
- project_wiki = create(:project_wiki, project: project, user: user)
- create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' })
+ it 'allows access with a project_id' do
+ get action, params: params.merge(project_id: create(:project, :public).id)
- expect(subject).to render_template("search/results/#{partial}")
- end
+ expect(response).to have_gitlab_http_status(200)
end
end
- context 'global search' do
- render_views
-
- it 'omits pipeline status from load' do
- project = create(:project, :public)
- expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
-
- get :show, params: { scope: 'projects', search: project.name }
+ shared_examples_for 'with external authorization service enabled' do |action, params|
+ let(:project) { create(:project, namespace: user.namespace) }
+ let(:note) { create(:note_on_issue, project: project) }
- expect(assigns[:search_objects].first).to eq project
+ before do
+ enable_external_authorization_service_check
end
- end
-
- it 'finds issue comments' do
- project = create(:project, :public)
- note = create(:note_on_issue, project: project)
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ it 'renders a 403 when no project is given' do
+ get action, params: params
- expect(assigns[:search_objects].first).to eq note
- end
-
- context 'when the user cannot read cross project' do
- before do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?)
- .with(user, :read_cross_project, :global) { false }
+ expect(response).to have_gitlab_http_status(403)
end
- it 'still allows accessing the search page' do
- get :show
+ it 'renders a 200 when a project was set' do
+ get action, params: params.merge(project_id: project.id)
expect(response).to have_gitlab_http_status(200)
end
+ end
- it 'still blocks searches without a project_id' do
- get :show, params: { search: 'hello' }
+ describe 'GET #show' do
+ it_behaves_like 'when the user cannot read cross project', :show, { search: 'hello' } do
+ it 'still allows accessing the search page' do
+ get :show
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(200)
+ end
end
- it 'allows searches with a project_id' do
- get :show, params: { search: 'hello', project_id: create(:project, :public).id }
+ it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
- expect(response).to have_gitlab_http_status(200)
- end
- end
+ context 'uses the right partials depending on scope' do
+ using RSpec::Parameterized::TableSyntax
+ render_views
+
+ set(:project) { create(:project, :public, :repository, :wiki_repo) }
- context 'on restricted projects' do
- context 'when signed out' do
before do
- sign_out(user)
+ expect(::Gitlab::GitalyClient).to receive(:allow_ref_name_caching).and_call_original
end
- it "doesn't expose comments on issues" do
- project = create(:project, :public, :issues_private)
- note = create(:note_on_issue, project: project)
+ subject { get(:show, params: { project_id: project.id, scope: scope, search: 'merge' }) }
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ where(:partial, :scope) do
+ '_blob' | :blobs
+ '_wiki_blob' | :wiki_blobs
+ '_commit' | :commits
+ end
- expect(assigns[:search_objects].count).to eq(0)
+ with_them do
+ it do
+ project_wiki = create(:project_wiki, project: project, user: user)
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'merge', content: 'merge' })
+
+ expect(subject).to render_template("search/results/#{partial}")
+ end
end
end
- it "doesn't expose comments on merge_requests" do
- project = create(:project, :public, :merge_requests_private)
- note = create(:note_on_merge_request, project: project)
+ context 'global search' do
+ render_views
- get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+ it 'omits pipeline status from load' do
+ project = create(:project, :public)
+ expect(Gitlab::Cache::Ci::ProjectPipelineStatus).not_to receive(:load_in_batch_for_projects)
+
+ get :show, params: { scope: 'projects', search: project.name }
- expect(assigns[:search_objects].count).to eq(0)
+ expect(assigns[:search_objects].first).to eq project
+ end
end
- it "doesn't expose comments on snippets" do
- project = create(:project, :public, :snippets_private)
- note = create(:note_on_project_snippet, project: project)
+ it 'finds issue comments' do
+ project = create(:project, :public)
+ note = create(:note_on_issue, project: project)
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(assigns[:search_objects].count).to eq(0)
+ expect(assigns[:search_objects].first).to eq note
end
- end
- context 'with external authorization service enabled' do
- let(:project) { create(:project, namespace: user.namespace) }
- let(:note) { create(:note_on_issue, project: project) }
+ context 'on restricted projects' do
+ context 'when signed out' do
+ before do
+ sign_out(user)
+ end
- before do
- enable_external_authorization_service_check
- end
+ it "doesn't expose comments on issues" do
+ project = create(:project, :public, :issues_private)
+ note = create(:note_on_issue, project: project)
- describe 'GET #show' do
- it 'renders a 403 when no project is given' do
- get :show, params: { scope: 'notes', search: note.note }
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(response).to have_gitlab_http_status(403)
+ expect(assigns[:search_objects].count).to eq(0)
+ end
end
- it 'renders a 200 when a project was set' do
+ it "doesn't expose comments on merge_requests" do
+ project = create(:project, :public, :merge_requests_private)
+ note = create(:note_on_merge_request, project: project)
+
get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
- expect(response).to have_gitlab_http_status(200)
+ expect(assigns[:search_objects].count).to eq(0)
end
- end
- describe 'GET #autocomplete' do
- it 'renders a 403 when no project is given' do
- get :autocomplete, params: { term: 'hello' }
+ it "doesn't expose comments on snippets" do
+ project = create(:project, :public, :snippets_private)
+ note = create(:note_on_project_snippet, project: project)
- expect(response).to have_gitlab_http_status(403)
+ get :show, params: { project_id: project.id, scope: 'notes', search: note.note }
+
+ expect(assigns[:search_objects].count).to eq(0)
end
+ end
+ end
- it 'renders a 200 when a project was set' do
- get :autocomplete, params: { project_id: project.id, term: 'hello' }
+ describe 'GET #count' do
+ it_behaves_like 'when the user cannot read cross project', :count, { search: 'hello', scope: 'projects' }
+ it_behaves_like 'with external authorization service enabled', :count, { search: 'hello', scope: 'projects' }
- expect(response).to have_gitlab_http_status(200)
- end
+ it 'returns the result count for the given term and scope' do
+ create(:project, :public, name: 'hello world')
+ create(:project, :public, name: 'foo bar')
+
+ get :count, params: { search: 'hello', scope: 'projects' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to eq({ 'count' => '1' })
+ end
+
+ it 'raises an error if search term is missing' do
+ expect do
+ get :count, params: { scope: 'projects' }
+ end.to raise_error(ActionController::ParameterMissing)
end
+
+ it 'raises an error if search scope is missing' do
+ expect do
+ get :count, params: { search: 'hello' }
+ end.to raise_error(ActionController::ParameterMissing)
+ end
+ end
+
+ describe 'GET #autocomplete' do
+ it_behaves_like 'when the user cannot read cross project', :autocomplete, { term: 'hello' }
+ it_behaves_like 'with external authorization service enabled', :autocomplete, { term: 'hello' }
end
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/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb
index babc93a83e5..07eaff2da09 100644
--- a/spec/controllers/user_callouts_controller_spec.rb
+++ b/spec/controllers/user_callouts_controller_spec.rb
@@ -13,7 +13,7 @@ describe UserCalloutsController do
subject { post :create, params: { feature_name: feature_name }, format: :json }
context 'with valid feature name' do
- let(:feature_name) { UserCallout.feature_names.keys.first }
+ let(:feature_name) { UserCallout.feature_names.first.first }
context 'when callout entry does not exist' do
it 'creates a callout entry with dismissed state' do
@@ -28,7 +28,7 @@ describe UserCalloutsController do
end
context 'when callout entry already exists' do
- let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
+ let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.first.first, user: user) }
it 'returns success' do
subject
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/db/schema_spec.rb b/spec/db/schema_spec.rb
index 6cfec5f4017..232890b1bba 100644
--- a/spec/db/schema_spec.rb
+++ b/spec/db/schema_spec.rb
@@ -1,12 +1,14 @@
# frozen_string_literal: true
require 'spec_helper'
+require Rails.root.join('ee', 'spec', 'db', 'schema_support') if Gitlab.ee?
describe 'Database schema' do
let(:connection) { ActiveRecord::Base.connection }
let(:tables) { connection.tables }
# Use if you are certain that this column should not have a foreign key
+ # EE: edit the ee/spec/db/schema_support.rb
IGNORED_FK_COLUMNS = {
abuse_reports: %w[reporter_id user_id],
application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_site_id],
@@ -25,7 +27,7 @@ describe 'Database schema' do
cluster_providers_gcp: %w[gcp_project_id operation_id],
deploy_keys_projects: %w[deploy_key_id],
deployments: %w[deployable_id environment_id user_id],
- draft_notes: %w[discussion_id],
+ draft_notes: %w[discussion_id commit_id],
emails: %w[user_id],
events: %w[target_id],
epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id],
diff --git a/spec/factories/abuse_reports.rb b/spec/factories/abuse_reports.rb
index 021971ac421..578af9ed895 100644
--- a/spec/factories/abuse_reports.rb
+++ b/spec/factories/abuse_reports.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :abuse_report do
reporter factory: :user
diff --git a/spec/factories/appearances.rb b/spec/factories/appearances.rb
index dd5129229d4..bdd5964fb93 100644
--- a/spec/factories/appearances.rb
+++ b/spec/factories/appearances.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/application_settings.rb b/spec/factories/application_settings.rb
index 00c063c49f8..90b6b9e648a 100644
--- a/spec/factories/application_settings.rb
+++ b/spec/factories/application_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :application_setting do
default_projects_limit 42
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index 43753fa650c..a8bb806381e 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :award_emoji do
name "thumbsup"
diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb
index b87ece946cb..1d4e29014cc 100644
--- a/spec/factories/badge.rb
+++ b/spec/factories/badge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_badge do
link_url { generate(:url) }
diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb
index 6524bb4830c..a5aff5c7504 100644
--- a/spec/factories/boards.rb
+++ b/spec/factories/boards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :board do
transient do
diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb
index 1a2be5e9552..2a30e2034b1 100644
--- a/spec/factories/broadcast_messages.rb
+++ b/spec/factories/broadcast_messages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :broadcast_message do
message "MyText"
diff --git a/spec/factories/chat_names.rb b/spec/factories/chat_names.rb
index 56993e5da18..07bf990162f 100644
--- a/spec/factories/chat_names.rb
+++ b/spec/factories/chat_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_name, class: ChatName do
user factory: :user
diff --git a/spec/factories/chat_teams.rb b/spec/factories/chat_teams.rb
index d048c46d6b6..52628e6d53d 100644
--- a/spec/factories/chat_teams.rb
+++ b/spec/factories/chat_teams.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :chat_team, class: ChatTeam do
sequence(:team_id) { |n| "abcdefghijklm#{n}" }
diff --git a/spec/factories/ci/bridge.rb b/spec/factories/ci/bridge.rb
index 7cb5900f2b7..b1b714277e4 100644
--- a/spec/factories/ci/bridge.rb
+++ b/spec/factories/ci/bridge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_bridge, class: Ci::Bridge do
name 'bridge'
@@ -6,7 +8,7 @@ FactoryBot.define do
ref 'master'
tag false
created_at 'Di 29. Okt 09:50:00 CET 2013'
- status :success
+ status :created
pipeline factory: :ci_pipeline
@@ -15,6 +17,7 @@ FactoryBot.define do
end
transient { downstream nil }
+ transient { upstream nil }
after(:build) do |bridge, evaluator|
bridge.project ||= bridge.pipeline.project
@@ -24,6 +27,12 @@ FactoryBot.define do
trigger: { project: evaluator.downstream.full_path }
)
end
+
+ if evaluator.upstream.present?
+ bridge.options = bridge.options.to_h.merge(
+ bridge_needs: { pipeline: evaluator.upstream.full_path }
+ )
+ end
end
end
end
diff --git a/spec/factories/ci/build_need.rb b/spec/factories/ci/build_need.rb
new file mode 100644
index 00000000000..568aff45a91
--- /dev/null
+++ b/spec/factories/ci/build_need.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_build_need, class: Ci::BuildNeed do
+ build factory: :ci_build
+ sequence(:name) { |n| "build_#{n}" }
+ end
+end
diff --git a/spec/factories/ci/build_trace_chunks.rb b/spec/factories/ci/build_trace_chunks.rb
index 3e8e2736423..492dc47f083 100644
--- a/spec/factories/ci/build_trace_chunks.rb
+++ b/spec/factories/ci/build_trace_chunks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_chunk, class: Ci::BuildTraceChunk do
build factory: :ci_build
diff --git a/spec/factories/ci/build_trace_section_names.rb b/spec/factories/ci/build_trace_section_names.rb
index ce07e716dde..e52694ef3dc 100644
--- a/spec/factories/ci/build_trace_section_names.rb
+++ b/spec/factories/ci/build_trace_section_names.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_build_trace_section_name, class: Ci::BuildTraceSectionName do
sequence(:name) { |n| "section_#{n}" }
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 5f7c75a3a92..e3b7c64176a 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/ci/group_variables.rb b/spec/factories/ci/group_variables.rb
index 9bf520a2c0a..13c2b78e61b 100644
--- a/spec/factories/ci/group_variables.rb
+++ b/spec/factories/ci/group_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_group_variable, class: Ci::GroupVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/job_artifacts.rb b/spec/factories/ci/job_artifacts.rb
index 542fa9775cd..2d68a8e9fe3 100644
--- a/spec/factories/ci/job_artifacts.rb
+++ b/spec/factories/ci/job_artifacts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
diff --git a/spec/factories/ci/job_variables.rb b/spec/factories/ci/job_variables.rb
new file mode 100644
index 00000000000..d664b763abd
--- /dev/null
+++ b/spec/factories/ci/job_variables.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :ci_job_variable, class: Ci::JobVariable do
+ sequence(:key) { |n| "VARIABLE_#{n}" }
+ value 'VARIABLE_VALUE'
+
+ job factory: :ci_build
+ end
+end
diff --git a/spec/factories/ci/pipeline_schedule.rb b/spec/factories/ci/pipeline_schedule.rb
index 4b83ba2ac1b..8fae6986869 100644
--- a/spec/factories/ci/pipeline_schedule.rb
+++ b/spec/factories/ci/pipeline_schedule.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule, class: Ci::PipelineSchedule do
cron '0 1 * * *'
diff --git a/spec/factories/ci/pipeline_schedule_variables.rb b/spec/factories/ci/pipeline_schedule_variables.rb
index c85b97fbfc7..fd7cfada65b 100644
--- a/spec/factories/ci/pipeline_schedule_variables.rb
+++ b/spec/factories/ci/pipeline_schedule_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_schedule_variable, class: Ci::PipelineScheduleVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipeline_variables.rb b/spec/factories/ci/pipeline_variables.rb
index b18055d7b3a..af0982124d7 100644
--- a/spec/factories/ci/pipeline_variables.rb
+++ b/spec/factories/ci/pipeline_variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_pipeline_variable, class: Ci::PipelineVariable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index aa5ccbda6cd..9652b0000a9 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_empty_pipeline, class: Ci::Pipeline do
source :push
diff --git a/spec/factories/ci/runner_projects.rb b/spec/factories/ci/runner_projects.rb
index ec15972c423..bc28544a839 100644
--- a/spec/factories/ci/runner_projects.rb
+++ b/spec/factories/ci/runner_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner_project, class: Ci::RunnerProject do
runner factory: [:ci_runner, :project]
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index 24e70913b87..1e4344b814d 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_runner, class: Ci::Runner do
sequence(:description) { |n| "My runner#{n}" }
diff --git a/spec/factories/ci/stages.rb b/spec/factories/ci/stages.rb
index ce61e6bf759..88ff8d7dc53 100644
--- a/spec/factories/ci/stages.rb
+++ b/spec/factories/ci/stages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_stage, class: Ci::LegacyStage do
skip_create
diff --git a/spec/factories/ci/trigger_requests.rb b/spec/factories/ci/trigger_requests.rb
index 0e9fc3d0014..d63bf9868c9 100644
--- a/spec/factories/ci/trigger_requests.rb
+++ b/spec/factories/ci/trigger_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_request, class: Ci::TriggerRequest do
trigger factory: :ci_trigger
diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb
index 742d9efba2d..6f628ed5435 100644
--- a/spec/factories/ci/triggers.rb
+++ b/spec/factories/ci/triggers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_trigger_without_token, class: Ci::Trigger do
owner
diff --git a/spec/factories/ci/variables.rb b/spec/factories/ci/variables.rb
index 97a7c9ba252..55d11085040 100644
--- a/spec/factories/ci/variables.rb
+++ b/spec/factories/ci/variables.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :ci_variable, class: Ci::Variable do
sequence(:key) { |n| "VARIABLE_#{n}" }
diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb
index d78f01828d7..89f7bc15217 100644
--- a/spec/factories/clusters/applications/helm.rb
+++ b/spec/factories/clusters/applications/helm.rb
@@ -1,7 +1,23 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :clusters_applications_helm, class: Clusters::Applications::Helm do
cluster factory: %i(cluster provided_by_gcp)
+ before(:create) do
+ allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root)
+ .and_return(
+ double(
+ key_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_key.key')),
+ cert_string: File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
+ )
+ )
+ end
+
+ after(:create) do
+ allow(Gitlab::Kubernetes::Helm::Certificate).to receive(:generate_root).and_call_original
+ end
+
trait :not_installable do
status(-2)
end
@@ -58,7 +74,7 @@ FactoryBot.define do
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
- factory :clusters_applications_cert_managers, class: Clusters::Applications::CertManager do
+ factory :clusters_applications_cert_manager, class: Clusters::Applications::CertManager do
email 'admin@example.com'
cluster factory: %i(cluster with_installed_helm provided_by_gcp)
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index ab332fc238b..d294e6d055e 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -1,9 +1,14 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster, class: Clusters::Cluster do
user
name 'test-cluster'
cluster_type :project_type
managed true
+ namespace_per_environment true
+
+ factory :cluster_for_group, traits: [:provided_by_gcp, :group]
trait :instance do
cluster_type { Clusters::Cluster.cluster_types[:instance_type] }
@@ -25,6 +30,10 @@ FactoryBot.define do
end
end
+ trait :namespace_per_environment_disabled do
+ namespace_per_environment false
+ end
+
trait :provided_by_user do
provider_type :user
platform_type :kubernetes
diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb
index 042be7b4c4a..8d6ad1b9f79 100644
--- a/spec/factories/clusters/kubernetes_namespaces.rb
+++ b/spec/factories/clusters/kubernetes_namespaces.rb
@@ -5,12 +5,21 @@ FactoryBot.define do
association :cluster, :project, :provided_by_gcp
after(:build) do |kubernetes_namespace|
- if kubernetes_namespace.cluster.project_type?
- cluster_project = kubernetes_namespace.cluster.cluster_project
+ cluster = kubernetes_namespace.cluster
+
+ if cluster.project_type?
+ cluster_project = cluster.cluster_project
kubernetes_namespace.project = cluster_project.project
kubernetes_namespace.cluster_project = cluster_project
end
+
+ kubernetes_namespace.namespace ||=
+ Gitlab::Kubernetes::DefaultNamespace.new(
+ cluster,
+ project: kubernetes_namespace.project
+ ).from_environment_slug(kubernetes_namespace.environment&.slug)
+ kubernetes_namespace.service_account_name ||= "#{kubernetes_namespace.namespace}-service-account"
end
trait :with_token do
diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb
index bf30a9c3a61..d5dc288fddb 100644
--- a/spec/factories/clusters/platforms/kubernetes.rb
+++ b/spec/factories/clusters/platforms/kubernetes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_platform_kubernetes, class: Clusters::Platforms::Kubernetes do
cluster
diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb
index 186c7c8027c..22462651b6a 100644
--- a/spec/factories/clusters/providers/gcp.rb
+++ b/spec/factories/clusters/providers/gcp.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :cluster_provider_gcp, class: Clusters::Providers::Gcp do
cluster
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 848a31e96c1..a76da30217e 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :commit_status, class: CommitStatus do
name 'default'
diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb
index 2bcc4b6cf52..d1554426a76 100644
--- a/spec/factories/commits.rb
+++ b/spec/factories/commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
FactoryBot.define do
diff --git a/spec/factories/container_repositories.rb b/spec/factories/container_repositories.rb
index 00fad7975c9..0b756220d68 100644
--- a/spec/factories/container_repositories.rb
+++ b/spec/factories/container_repositories.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :container_repository do
- name 'test_image'
+ sequence(:name) { |n| "test_image_#{n}" }
project
transient do
diff --git a/spec/factories/conversational_development_index_metrics.rb b/spec/factories/conversational_development_index_metrics.rb
index abf37fb861e..ea5816684c6 100644
--- a/spec/factories/conversational_development_index_metrics.rb
+++ b/spec/factories/conversational_development_index_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :conversational_development_index_metric, class: ConversationalDevelopmentIndex::Metric do
leader_issues 9.256
diff --git a/spec/factories/deploy_keys_projects.rb b/spec/factories/deploy_keys_projects.rb
index 4350652fb79..7f82902dee7 100644
--- a/spec/factories/deploy_keys_projects.rb
+++ b/spec/factories/deploy_keys_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_keys_project do
deploy_key
diff --git a/spec/factories/deploy_tokens.rb b/spec/factories/deploy_tokens.rb
index 017e866e69c..a96258f5cbe 100644
--- a/spec/factories/deploy_tokens.rb
+++ b/spec/factories/deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deploy_token do
token { SecureRandom.hex(50) }
diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb
index 1c7787bc1a6..89ff1c527df 100644
--- a/spec/factories/deployments.rb
+++ b/spec/factories/deployments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :deployment, class: Deployment do
sha 'b83d6e391c22777fca1ed3012fce84f633d7fed0'
diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb
index feacd5ccf15..284ba631c37 100644
--- a/spec/factories/emails.rb
+++ b/spec/factories/emails.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :email do
user
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 9d9e3d693b8..b5c8f0ca4f0 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :environment, class: Environment do
sequence(:name) { |n| "environment#{n}" }
diff --git a/spec/factories/events.rb b/spec/factories/events.rb
index bf8411b1894..b15eb1592fc 100644
--- a/spec/factories/events.rb
+++ b/spec/factories/events.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :event do
project
diff --git a/spec/factories/file_uploaders.rb b/spec/factories/file_uploaders.rb
index 8404985bfea..ec8f5c9af2d 100644
--- a/spec/factories/file_uploaders.rb
+++ b/spec/factories/file_uploaders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :file_uploader do
skip_create
diff --git a/spec/factories/fork_network_members.rb b/spec/factories/fork_network_members.rb
index 850e3f158f1..d34de72ab60 100644
--- a/spec/factories/fork_network_members.rb
+++ b/spec/factories/fork_network_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network_member do
association :project
diff --git a/spec/factories/fork_networks.rb b/spec/factories/fork_networks.rb
index 813b1943eb2..12ea688bbce 100644
--- a/spec/factories/fork_networks.rb
+++ b/spec/factories/fork_networks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :fork_network do
association :root_project, factory: :project
diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb
index 5034c3d0e48..954b5338846 100644
--- a/spec/factories/gitaly/commit.rb
+++ b/spec/factories/gitaly/commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:gitaly_commit_id) { Digest::SHA1.hexdigest(Time.now.to_f.to_s) }
diff --git a/spec/factories/gitaly/commit_author.rb b/spec/factories/gitaly/commit_author.rb
index aaf634ce08b..51dcd8a623b 100644
--- a/spec/factories/gitaly/commit_author.rb
+++ b/spec/factories/gitaly/commit_author.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_commit_author, class: Gitaly::CommitAuthor do
skip_create
diff --git a/spec/factories/gitaly/tag.rb b/spec/factories/gitaly/tag.rb
index e99776d524a..a7a84753090 100644
--- a/spec/factories/gitaly/tag.rb
+++ b/spec/factories/gitaly/tag.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gitaly_tag, class: Gitaly::Tag do
skip_create
diff --git a/spec/factories/gpg_key_subkeys.rb b/spec/factories/gpg_key_subkeys.rb
index 6c7db5379a9..9daf26d930d 100644
--- a/spec/factories/gpg_key_subkeys.rb
+++ b/spec/factories/gpg_key_subkeys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_key_subkey do
gpg_key
diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb
index 3c0f43cc1b6..9f321643174 100644
--- a/spec/factories/gpg_keys.rb
+++ b/spec/factories/gpg_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/gpg_helpers'
FactoryBot.define do
diff --git a/spec/factories/gpg_signature.rb b/spec/factories/gpg_signature.rb
index b89e6ffc4b3..a0fc1740d77 100644
--- a/spec/factories/gpg_signature.rb
+++ b/spec/factories/gpg_signature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :gpg_signature do
commit_sha { Digest::SHA1.hexdigest(SecureRandom.hex) }
diff --git a/spec/factories/group_custom_attributes.rb b/spec/factories/group_custom_attributes.rb
index d2f45d5d3ce..895bc42c84b 100644
--- a/spec/factories/group_custom_attributes.rb
+++ b/spec/factories/group_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_custom_attribute do
group
diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb
index 077c6ddc5ae..4c875935d82 100644
--- a/spec/factories/group_members.rb
+++ b/spec/factories/group_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group_member do
access_level { GroupMember::OWNER }
@@ -14,7 +16,13 @@ FactoryBot.define do
trait(:invited) do
user_id nil
invite_token 'xxx'
- invite_email 'email@email.com'
+ sequence :invite_email do |n|
+ "email#{n}@email.com"
+ end
+ end
+
+ trait(:ldap) do
+ ldap true
end
end
end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 18a0c2ec731..334c0f369cd 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :group, class: Group, parent: :namespace do
sequence(:name) { |n| "group#{n}" }
@@ -45,5 +47,9 @@ FactoryBot.define do
trait :auto_devops_disabled do
auto_devops_enabled false
end
+
+ trait :owner_subgroup_creation_only do
+ subgroup_creation_level ::Gitlab::Access::OWNER_SUBGROUP_ACCESS
+ end
end
end
diff --git a/spec/factories/identities.rb b/spec/factories/identities.rb
index 122d0d42938..21cfe7fe623 100644
--- a/spec/factories/identities.rb
+++ b/spec/factories/identities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :identity do
provider 'ldapmain'
diff --git a/spec/factories/import_export_uploads.rb b/spec/factories/import_export_uploads.rb
index 7750d49b1d0..8521411e0e8 100644
--- a/spec/factories/import_export_uploads.rb
+++ b/spec/factories/import_export_uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_export_upload do
project { create(:project) }
diff --git a/spec/factories/import_states.rb b/spec/factories/import_states.rb
index d6de26dccbc..8e778200389 100644
--- a/spec/factories/import_states.rb
+++ b/spec/factories/import_states.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :import_state, class: ProjectImportState do
status :none
diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb
index 31866a9c221..daa0be3f404 100644
--- a/spec/factories/instance_configuration.rb
+++ b/spec/factories/instance_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :instance_configuration do
skip_create
diff --git a/spec/factories/internal_ids.rb b/spec/factories/internal_ids.rb
index fbde07a391a..df5c5beeb42 100644
--- a/spec/factories/internal_ids.rb
+++ b/spec/factories/internal_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :internal_id do
project
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index ab27fee33dc..434225f7022 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :issue do
title { generate(:title) }
diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb
index 3f0c60f32b7..087d2521836 100644
--- a/spec/factories/keys.rb
+++ b/spec/factories/keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/key_generator_helper'
FactoryBot.define do
diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb
index 007847d9cf4..9b7c0a158bd 100644
--- a/spec/factories/label_links.rb
+++ b/spec/factories/label_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_link do
label
diff --git a/spec/factories/label_priorities.rb b/spec/factories/label_priorities.rb
index c4824faad53..2f9f2ddfa89 100644
--- a/spec/factories/label_priorities.rb
+++ b/spec/factories/label_priorities.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :label_priority do
project
diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb
index f759b6d499d..3eed750be03 100644
--- a/spec/factories/labels.rb
+++ b/spec/factories/labels.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
trait :base_label do
title { generate(:label_title) }
diff --git a/spec/factories/lfs_file_locks.rb b/spec/factories/lfs_file_locks.rb
index b9d24f82b65..73675d076ab 100644
--- a/spec/factories/lfs_file_locks.rb
+++ b/spec/factories/lfs_file_locks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_file_lock do
user
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index c909471bb55..631d87cfb12 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
include ActionDispatch::TestProcess
FactoryBot.define do
@@ -14,6 +16,7 @@ FactoryBot.define do
# objects, so the test needs to decide which (if any) object gets it
trait :correct_oid do
oid 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75'
+ size 1062
end
trait :object_storage do
diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb
index 4804d0bb884..7b55cc57f75 100644
--- a/spec/factories/lfs_objects_projects.rb
+++ b/spec/factories/lfs_objects_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :lfs_objects_project do
lfs_object
diff --git a/spec/factories/lists.rb b/spec/factories/lists.rb
index 210c58b21e9..e68611ec518 100644
--- a/spec/factories/lists.rb
+++ b/spec/factories/lists.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :list do
board
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 0b6a43b13a9..3d12ff98257 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_request do
title { generate(:title) }
diff --git a/spec/factories/merge_requests_closing_issues.rb b/spec/factories/merge_requests_closing_issues.rb
index ee0606a72b6..94f8b27358c 100644
--- a/spec/factories/merge_requests_closing_issues.rb
+++ b/spec/factories/merge_requests_closing_issues.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :merge_requests_closing_issues do
issue
diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb
index 027349b2f8e..7d623000fc9 100644
--- a/spec/factories/milestones.rb
+++ b/spec/factories/milestones.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :milestone do
title
diff --git a/spec/factories/namespaces.rb b/spec/factories/namespaces.rb
index 0cfc6e3aa46..09dbe16ef9e 100644
--- a/spec/factories/namespaces.rb
+++ b/spec/factories/namespaces.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :namespace do
sequence(:name) { |n| "namespace#{n}" }
diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb
index c51f2f958f9..5b9a7e6f864 100644
--- a/spec/factories/notes.rb
+++ b/spec/factories/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/repo_helpers'
include ActionDispatch::TestProcess
diff --git a/spec/factories/notification_settings.rb b/spec/factories/notification_settings.rb
index 5116ef33f5d..c16b0e456ba 100644
--- a/spec/factories/notification_settings.rb
+++ b/spec/factories/notification_settings.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :notification_setting do
source factory: :project
diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb
index 02c51cd9899..7a46ae6d71d 100644
--- a/spec/factories/oauth_access_grants.rb
+++ b/spec/factories/oauth_access_grants.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_grant do
resource_owner_id { create(:user).id }
diff --git a/spec/factories/oauth_access_tokens.rb b/spec/factories/oauth_access_tokens.rb
index eabfd6cd830..8d1075dacbb 100644
--- a/spec/factories/oauth_access_tokens.rb
+++ b/spec/factories/oauth_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_access_token do
resource_owner
diff --git a/spec/factories/oauth_applications.rb b/spec/factories/oauth_applications.rb
index 4427da1d6c7..4748b320298 100644
--- a/spec/factories/oauth_applications.rb
+++ b/spec/factories/oauth_applications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :oauth_application, class: 'Doorkeeper::Application', aliases: [:application] do
sequence(:name) { |n| "OAuth App #{n}" }
diff --git a/spec/factories/pages_domains.rb b/spec/factories/pages_domains.rb
index 3e0baab04ce..ee5be82cd19 100644
--- a/spec/factories/pages_domains.rb
+++ b/spec/factories/pages_domains.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pages_domain, class: 'PagesDomain' do
sequence(:domain) { |n| "my#{n}.domain.com" }
@@ -166,6 +168,90 @@ pu/xO28QOG8=
-----END CERTIFICATE-----'
end
+ trait :with_trusted_expired_chain do
+ # This contains
+ # Let's Encrypt Authority X3
+ # DST Root CA X3
+ certificate '-----BEGIN CERTIFICATE-----
+MIIFSjCCBDKgAwIBAgISAw24xGWrFotvTBa6AZI/pzq1MA0GCSqGSIb3DQEBCwUA
+MEoxCzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MSMwIQYDVQQD
+ExpMZXQncyBFbmNyeXB0IEF1dGhvcml0eSBYMzAeFw0xOTAzMDcxNzU5NTZaFw0x
+OTA2MDUxNzU5NTZaMBQxEjAQBgNVBAMTCXN5dHNlLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALtIpQuqeZN6OgEE+y2UoGC/31Vt9NAeQWvTuWWO
+nMn/MvDJiw8731Dx4DDbMjhF50UBE20a9iAu2nhlxcsuuIITk2MXKMEgPtqSbwM7
+Mg0/WvgrBOWnF9CpdD3qcsjtstT6Djij06VfMfUrRZzMkGgbGzudR0cShKPmkBVU
+LgB6crFmSQ/qHt5PzBivdexCUpz5WzSKU5UWYFx2UnkSLykvEJuUr3Nn4/o9oyKw
+Qoiq354S262mFuMW+s6wQdMNNkwj41OqCwAGbqq7YUYLDc8OQiRC2LcqSO5yYnnA
+0lNfbEatZ1BzHiDjTH7wMUtwcLGHsZ1C5ZmORD2s2gtGiRkCAwEAAaOCAl4wggJa
+MA4GA1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIw
+DAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUAMn3t1s4zXdOQbJFOP1riSwjuGkwHwYD
+VR0jBBgwFoAUqEpqYwR93brm0Tm3pkVl7/Oo7KEwbwYIKwYBBQUHAQEEYzBhMC4G
+CCsGAQUFBzABhiJodHRwOi8vb2NzcC5pbnQteDMubGV0c2VuY3J5cHQub3JnMC8G
+CCsGAQUFBzAChiNodHRwOi8vY2VydC5pbnQteDMubGV0c2VuY3J5cHQub3JnLzAU
+BgNVHREEDTALgglzeXRzZS5jb20wTAYDVR0gBEUwQzAIBgZngQwBAgEwNwYLKwYB
+BAGC3xMBAQEwKDAmBggrBgEFBQcCARYaaHR0cDovL2Nwcy5sZXRzZW5jcnlwdC5v
+cmcwggEEBgorBgEEAdZ5AgQCBIH1BIHyAPAAdQB0ftqDMa0zEJEhnM4lT0Jwwr/9
+XkIgCMY3NXnmEHvMVgAAAWlZhr4pAAAEAwBGMEQCIBEA+3oiM1UJKY1kajBO5Aoz
+9AZMMlImaR1X5hFIPr95AiBXGIACuXUDLchB0kT8VIG/jM4f9iuXMoYCoKNJggNM
+/gB3ACk8UZZUyDlluqpQ/FgH1Ldvv1h6KXLcpMMM9OVFR/R4AAABaVmGv/AAAAQD
+AEgwRgIhANeTA7H51SZUmcT2ldtumFYX6/OkOr0fdvze72U0j9U9AiEAjSOSVQmi
+ZdYK6u3JYkDVOWsEzyKwjPWod8UN5K3ej0EwDQYJKoZIhvcNAQELBQADggEBAJev
+ArtxZVVTmLghV0O7471J1mN1fVC2p6b3AsK/TqrI7aiq8XuQq76KmUsB+U05MTXH
+3sYiHm+/RJ7+ljiKVIC8ZfbQsHo5I+F1CNMo6JB6z8Z+bOeRkoves5FNYmiJnUjO
+uoGzt//CyldbX1dEPVNuU7P0s2wZ6Bubump2LoapGIiGxQJfeb0vj0TQzfRacTIZ
+x9U5E/D0y0iewX4kPHK17QDBsSL9WlqsRzFAkQjJ9XWUVn3BO7JG3WU47iOuykby
+y2HmOWUxjv1Yf/H/OYRBiuSCR4LhrE5Ze4tTo2AByrXQ5h7ezjDJQqnKBP5NuwIq
+7NuX+D2esUNos/D6uJg=
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
+SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
+GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
+AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
+q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
+SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
+Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
+a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
+/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
+AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
+CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
+bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
+c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
+VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
+ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
+MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
+Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
+AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
+uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
+wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
+X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
+PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
+KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
+-----END CERTIFICATE-----
+-----BEGIN CERTIFICATE-----
+MIIDSjCCAjKgAwIBAgIQRK+wgNajJ7qJMDmGLvhAazANBgkqhkiG9w0BAQUFADA/
+MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
+DkRTVCBSb290IENBIFgzMB4XDTAwMDkzMDIxMTIxOVoXDTIxMDkzMDE0MDExNVow
+PzEkMCIGA1UEChMbRGlnaXRhbCBTaWduYXR1cmUgVHJ1c3QgQ28uMRcwFQYDVQQD
+Ew5EU1QgUm9vdCBDQSBYMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
+AN+v6ZdQCINXtMxiZfaQguzH0yxrMMpb7NnDfcdAwRgUi+DoM3ZJKuM/IUmTrE4O
+rz5Iy2Xu/NMhD2XSKtkyj4zl93ewEnu1lcCJo6m67XMuegwGMoOifooUMM0RoOEq
+OLl5CjH9UL2AZd+3UWODyOKIYepLYYHsUmu5ouJLGiifSKOeDNoJjj4XLh7dIN9b
+xiqKqy69cK3FCxolkHRyxXtqqzTWMIn/5WgTe1QLyNau7Fqckh49ZLOMxt+/yUFw
+7BZy1SbsOFU5Q9D8/RhcQPGX69Wam40dutolucbY38EVAjqr2m7xPi71XAicPNaD
+aeQQmxkqtilX4+U9m5/wAl0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAOBgNV
+HQ8BAf8EBAMCAQYwHQYDVR0OBBYEFMSnsaR7LHH62+FLkHX/xBVghYkQMA0GCSqG
+SIb3DQEBBQUAA4IBAQCjGiybFwBcqR7uKGY3Or+Dxz9LwwmglSBd49lZRNI+DT69
+ikugdB/OEIKcdBodfpga3csTS7MgROSR6cz8faXbauX+5v3gTt23ADq1cEmv8uXr
+AvHRAosZy5Q6XkjEGB5YGV8eAlrwDPGxrancWYaLbumR9YbK+rlmM6pZW87ipxZz
+R8srzJmwN0jP41ZL9c8PDHIyh8bwRLtTcm1D9SZImlJnt1ir/md2cXjbDaJWFBM5
+JDGFoqgCWjBH4d1QB7wCCZAA62RjYJsWvIjJEubSfZGL+T0yjWW06XyxV3bqxbYo
+Ob8VZRzI9neWagqNdwvYkQsEjgfbKbYK7p2CNTUQ
+-----END CERTIFICATE-----'
+ end
+
trait :with_expired_certificate do
certificate '-----BEGIN CERTIFICATE-----
MIIBsDCCARmgAwIBAgIBATANBgkqhkiG9w0BAQUFADAeMRwwGgYDVQQDExNleHBp
diff --git a/spec/factories/personal_access_tokens.rb b/spec/factories/personal_access_tokens.rb
index e7fd22a96b2..cc9b2328705 100644
--- a/spec/factories/personal_access_tokens.rb
+++ b/spec/factories/personal_access_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :personal_access_token do
user
diff --git a/spec/factories/pool_repositories.rb b/spec/factories/pool_repositories.rb
index 8cac666069c..7a8946d47b0 100644
--- a/spec/factories/pool_repositories.rb
+++ b/spec/factories/pool_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :pool_repository do
shard { Shard.by_name("default") }
diff --git a/spec/factories/programming_languages.rb b/spec/factories/programming_languages.rb
index d3511d42b6c..ee8e7765ec9 100644
--- a/spec/factories/programming_languages.rb
+++ b/spec/factories/programming_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :programming_language do
name 'Ruby'
diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb
index 1de42512402..4cc59c7095c 100644
--- a/spec/factories/project_auto_devops.rb
+++ b/spec/factories/project_auto_devops.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_auto_devops do
project
diff --git a/spec/factories/project_custom_attributes.rb b/spec/factories/project_custom_attributes.rb
index 099d2d7ff19..c9ed86cc0ac 100644
--- a/spec/factories/project_custom_attributes.rb
+++ b/spec/factories/project_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_custom_attribute do
project
diff --git a/spec/factories/project_deploy_tokens.rb b/spec/factories/project_deploy_tokens.rb
index 4866cb58d88..fbf5454b0fe 100644
--- a/spec/factories/project_deploy_tokens.rb
+++ b/spec/factories/project_deploy_tokens.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_deploy_token do
project
diff --git a/spec/factories/project_group_links.rb b/spec/factories/project_group_links.rb
index 59c77627ee5..b02d167a950 100644
--- a/spec/factories/project_group_links.rb
+++ b/spec/factories/project_group_links.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_group_link do
project
diff --git a/spec/factories/project_hooks.rb b/spec/factories/project_hooks.rb
index a448d565e4b..96c9742c7d0 100644
--- a/spec/factories/project_hooks.rb
+++ b/spec/factories/project_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_hook do
url { generate(:url) }
diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb
index c72e0487895..6dcac0400ca 100644
--- a/spec/factories/project_members.rb
+++ b/spec/factories/project_members.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_member do
user
diff --git a/spec/factories/project_statistics.rb b/spec/factories/project_statistics.rb
index 3d4174eb852..f084d9d5cbf 100644
--- a/spec/factories/project_statistics.rb
+++ b/spec/factories/project_statistics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_statistics do
project
diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb
index 4d21ed47f39..401402614f4 100644
--- a/spec/factories/project_wikis.rb
+++ b/spec/factories/project_wikis.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :project_wiki do
skip_create
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 0e8810b73a1..ea89555b0d5 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative '../support/helpers/test_env'
FactoryBot.define do
@@ -323,10 +325,6 @@ FactoryBot.define do
jira_service
end
- factory :kubernetes_project, parent: :project do
- kubernetes_service
- end
-
factory :mock_deployment_project, parent: :project do
mock_deployment_service
end
diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb
index 5457c0d2a8f..741615bc0d3 100644
--- a/spec/factories/protected_branches.rb
+++ b/spec/factories/protected_branches.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_branch do
name
diff --git a/spec/factories/protected_tags.rb b/spec/factories/protected_tags.rb
index 2b81d089549..6ff2a245b58 100644
--- a/spec/factories/protected_tags.rb
+++ b/spec/factories/protected_tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :protected_tag do
name
diff --git a/spec/factories/redirect_routes.rb b/spec/factories/redirect_routes.rb
index 774232d0b34..8fa0aec007c 100644
--- a/spec/factories/redirect_routes.rb
+++ b/spec/factories/redirect_routes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :redirect_route do
sequence(:path) { |n| "redirect#{n}" }
diff --git a/spec/factories/releases.rb b/spec/factories/releases.rb
index 4cacc77c182..34794f57284 100644
--- a/spec/factories/releases.rb
+++ b/spec/factories/releases.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :release do
tag "v1.1.0"
diff --git a/spec/factories/remote_mirrors.rb b/spec/factories/remote_mirrors.rb
index adc7da27522..ff1d751c86c 100644
--- a/spec/factories/remote_mirrors.rb
+++ b/spec/factories/remote_mirrors.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :remote_mirror, class: 'RemoteMirror' do
association :project, :repository
diff --git a/spec/factories/repository_languages.rb b/spec/factories/repository_languages.rb
index 1757ba6766c..b2b17ebbce2 100644
--- a/spec/factories/repository_languages.rb
+++ b/spec/factories/repository_languages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :repository_language do
project
diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb
index b0174dd06b7..11116af7dbb 100644
--- a/spec/factories/sent_notifications.rb
+++ b/spec/factories/sent_notifications.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :sent_notification do
project
diff --git a/spec/factories/sequences.rb b/spec/factories/sequences.rb
index f2b6e7a11f9..b6f2d6d8389 100644
--- a/spec/factories/sequences.rb
+++ b/spec/factories/sequences.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
sequence(:username) { |n| "user#{n}" }
sequence(:name) { |n| "John Doe#{n}" }
diff --git a/spec/factories/service_hooks.rb b/spec/factories/service_hooks.rb
index c907862b4f6..ff819f4f8d0 100644
--- a/spec/factories/service_hooks.rb
+++ b/spec/factories/service_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service_hook do
url { generate(:url) }
diff --git a/spec/factories/services.rb b/spec/factories/services.rb
index daf842e3075..b2d6ada91fa 100644
--- a/spec/factories/services.rb
+++ b/spec/factories/services.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :service do
project
@@ -14,16 +16,17 @@ FactoryBot.define do
)
end
- factory :kubernetes_service do
+ factory :emails_on_push_service do
project
- type 'KubernetesService'
+ type 'EmailsOnPushService'
active true
- properties({
- api_url: 'https://kubernetes.example.com',
- token: 'a' * 40
- })
-
- skip_deprecation_validation true
+ push_events true
+ tag_push_events true
+ properties(
+ recipients: 'test@example.com',
+ disable_diffs: true,
+ send_from_committer_email: true
+ )
end
factory :mock_deployment_service do
diff --git a/spec/factories/shards.rb b/spec/factories/shards.rb
index c095fa5f0a0..c30a38180e8 100644
--- a/spec/factories/shards.rb
+++ b/spec/factories/shards.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :shard do
name "default"
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index dc12b562108..9c3a0fbe9b3 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :snippet do
author
diff --git a/spec/factories/spam_logs.rb b/spec/factories/spam_logs.rb
index a467f850a80..42a856832e7 100644
--- a/spec/factories/spam_logs.rb
+++ b/spec/factories/spam_logs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :spam_log do
user
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index 8f7ab74ec70..2b652cd57bf 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :subscription do
project
diff --git a/spec/factories/system_hooks.rb b/spec/factories/system_hooks.rb
index 9e00eeb6ef1..a2d163a060f 100644
--- a/spec/factories/system_hooks.rb
+++ b/spec/factories/system_hooks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_hook do
url { generate(:url) }
diff --git a/spec/factories/system_note_metadata.rb b/spec/factories/system_note_metadata.rb
index e913068da40..8cd4b77799c 100644
--- a/spec/factories/system_note_metadata.rb
+++ b/spec/factories/system_note_metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :system_note_metadata do
note
diff --git a/spec/factories/term_agreements.rb b/spec/factories/term_agreements.rb
index 3c4eebd0196..b7e259bd44b 100644
--- a/spec/factories/term_agreements.rb
+++ b/spec/factories/term_agreements.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term_agreement do
term
diff --git a/spec/factories/terms.rb b/spec/factories/terms.rb
index 5ffca365a5f..b890261d293 100644
--- a/spec/factories/terms.rb
+++ b/spec/factories/terms.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :term, class: ApplicationSetting::Term do
terms "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb
index b45f06b9a0a..056a8833c46 100644
--- a/spec/factories/timelogs.rb
+++ b/spec/factories/timelogs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Read about factories at https://github.com/thoughtbot/factory_bot
FactoryBot.define do
diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb
index ed3d87eb76b..2ff024112a4 100644
--- a/spec/factories/todos.rb
+++ b/spec/factories/todos.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :todo do
project
diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb
index f7c634fd21f..354b8c1e002 100644
--- a/spec/factories/trending_project.rb
+++ b/spec/factories/trending_project.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
# TrendingProject
factory :trending_project, class: 'TrendingProject' do
diff --git a/spec/factories/u2f_registrations.rb b/spec/factories/u2f_registrations.rb
index 26090b08966..c968468834b 100644
--- a/spec/factories/u2f_registrations.rb
+++ b/spec/factories/u2f_registrations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :u2f_registration do
certificate { FFaker::BaconIpsum.characters(728) }
diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb
index 426abdc2a6c..3f6326114c9 100644
--- a/spec/factories/uploads.rb
+++ b/spec/factories/uploads.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :upload do
model { build(:project) }
@@ -54,10 +56,7 @@ FactoryBot.define do
end
trait :attachment_upload do
- transient do
- mount_point :attachment
- end
-
+ mount_point :attachment
model { build(:note) }
uploader "AttachmentUploader"
end
diff --git a/spec/factories/user_agent_details.rb b/spec/factories/user_agent_details.rb
index 7183a8e1140..055aea50585 100644
--- a/spec/factories/user_agent_details.rb
+++ b/spec/factories/user_agent_details.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_agent_detail do
ip_address '127.0.0.1'
diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb
index 528e442c14b..c4a217fd357 100644
--- a/spec/factories/user_callouts.rb
+++ b/spec/factories/user_callouts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_callout do
feature_name :gke_cluster_integration
diff --git a/spec/factories/user_custom_attributes.rb b/spec/factories/user_custom_attributes.rb
index a184a2e0f17..7bd5c06f4ef 100644
--- a/spec/factories/user_custom_attributes.rb
+++ b/spec/factories/user_custom_attributes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user_custom_attribute do
user
diff --git a/spec/factories/users.rb b/spec/factories/users.rb
index 4f3392cdcbf..b2c8bdab013 100644
--- a/spec/factories/users.rb
+++ b/spec/factories/users.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
diff --git a/spec/factories/users_star_projects.rb b/spec/factories/users_star_projects.rb
index 6afd08a2084..a1299024fe4 100644
--- a/spec/factories/users_star_projects.rb
+++ b/spec/factories/users_star_projects.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :users_star_project do
project
diff --git a/spec/factories/web_hook_log.rb b/spec/factories/web_hook_log.rb
index 17837260a4b..5750af85662 100644
--- a/spec/factories/web_hook_log.rb
+++ b/spec/factories/web_hook_log.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :web_hook_log do
web_hook factory: :project_hook
diff --git a/spec/factories/wiki_directories.rb b/spec/factories/wiki_directories.rb
index b105c82b19d..de23cf110b5 100644
--- a/spec/factories/wiki_directories.rb
+++ b/spec/factories/wiki_directories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
FactoryBot.define do
factory :wiki_directory do
skip_create
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index ae257d769e8..761ba58edb2 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
FactoryBot.define do
diff --git a/spec/fast_spec_helper.rb b/spec/fast_spec_helper.rb
index 91ef7653822..f5a487b4d57 100644
--- a/spec/fast_spec_helper.rb
+++ b/spec/fast_spec_helper.rb
@@ -4,6 +4,7 @@ ENV['GITLAB_ENV'] = 'test'
ENV['IN_MEMORY_APPLICATION_SETTINGS'] = 'true'
require 'active_support/dependencies'
+require_relative '../config/initializers/0_inject_enterprise_edition_module'
require_relative '../config/settings'
require_relative 'support/rspec'
require 'active_support/all'
diff --git a/spec/features/abuse_report_spec.rb b/spec/features/abuse_report_spec.rb
index eb12eebe8e6..b1573bfb270 100644
--- a/spec/features/abuse_report_spec.rb
+++ b/spec/features/abuse_report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Abuse reports' do
diff --git a/spec/features/admin/admin_abuse_reports_spec.rb b/spec/features/admin/admin_abuse_reports_spec.rb
index 3ff1a66b0b2..48fff9e57d3 100644
--- a/spec/features/admin/admin_abuse_reports_spec.rb
+++ b/spec/features/admin/admin_abuse_reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::AbuseReports", :js do
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index f6dc499df29..dfc7c89840a 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Broadcast Messages' do
diff --git a/spec/features/admin/admin_browse_spam_logs_spec.rb b/spec/features/admin/admin_browse_spam_logs_spec.rb
index 4645fde7522..c79524a7fb3 100644
--- a/spec/features/admin/admin_browse_spam_logs_spec.rb
+++ b/spec/features/admin/admin_browse_spam_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin browse spam logs' do
diff --git a/spec/features/admin/admin_browses_logs_spec.rb b/spec/features/admin/admin_browses_logs_spec.rb
index 1f83d04d9aa..2b97362c8e9 100644
--- a/spec/features/admin/admin_browses_logs_spec.rb
+++ b/spec/features/admin/admin_browses_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin browses logs' do
@@ -9,7 +11,7 @@ describe 'Admin browses logs' do
visit admin_logs_path
expect(page).to have_link 'application.log'
- expect(page).to have_link 'githost.log'
+ expect(page).to have_link 'git_json.log'
expect(page).to have_link 'test.log'
expect(page).to have_link 'sidekiq.log'
expect(page).to have_link 'repocheck.log'
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 51b42d1b43b..afdf8eb0cca 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Builds' do
diff --git a/spec/features/admin/admin_deploy_keys_spec.rb b/spec/features/admin/admin_deploy_keys_spec.rb
index cb96830cb7c..2039a6ff1ee 100644
--- a/spec/features/admin/admin_deploy_keys_spec.rb
+++ b/spec/features/admin/admin_deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin deploy keys' do
diff --git a/spec/features/admin/admin_disables_git_access_protocol_spec.rb b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
index f066b088800..ca8874a24f9 100644
--- a/spec/features/admin/admin_disables_git_access_protocol_spec.rb
+++ b/spec/features/admin/admin_disables_git_access_protocol_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin disables Git access protocol', :js do
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
index e41835b4f24..6e6a4964541 100644
--- a/spec/features/admin/admin_disables_two_factor_spec.rb
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin disables 2FA for a user' do
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 735ca60f7da..34356a2ee90 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin Groups' do
@@ -90,6 +92,7 @@ describe 'Admin Groups' do
visit admin_group_path(group)
expect(page).to have_content("Group: #{group.name}")
+ expect(page).to have_content("ID: #{group.id}")
end
end
@@ -102,6 +105,14 @@ describe 'Admin Groups' do
expect_selected_visibility(group.visibility_level)
end
+ it 'shows the subgroup creation level dropdown populated with the group subgroup_creation_level value' do
+ group = create(:group, :private, :owner_subgroup_creation_only)
+
+ visit admin_group_edit_path(group)
+
+ expect(page).to have_content('Allowed to create subgroups')
+ end
+
it 'edit group path does not change group name', :js do
group = create(:group, :private)
diff --git a/spec/features/admin/admin_health_check_spec.rb b/spec/features/admin/admin_health_check_spec.rb
index 790051dd933..97c34d55d73 100644
--- a/spec/features/admin/admin_health_check_spec.rb
+++ b/spec/features/admin/admin_health_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin Health Check", :feature do
diff --git a/spec/features/admin/admin_hook_logs_spec.rb b/spec/features/admin/admin_hook_logs_spec.rb
index 928f97b6d29..98900142353 100644
--- a/spec/features/admin/admin_hook_logs_spec.rb
+++ b/spec/features/admin/admin_hook_logs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::HookLogs' do
diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb
index ce780789f5a..0fd748af3ce 100644
--- a/spec/features/admin/admin_hooks_spec.rb
+++ b/spec/features/admin/admin_hooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::Hooks' do
diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb
index 238ea2a25bd..35638e0829b 100644
--- a/spec/features/admin/admin_labels_spec.rb
+++ b/spec/features/admin/admin_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin issues labels' do
diff --git a/spec/features/admin/admin_manage_applications_spec.rb b/spec/features/admin/admin_manage_applications_spec.rb
index a4904272706..dd4d4b1a426 100644
--- a/spec/features/admin/admin_manage_applications_spec.rb
+++ b/spec/features/admin/admin_manage_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'admin manage applications' do
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index 2b6bfa40beb..058e548208f 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Projects" do
@@ -60,6 +62,7 @@ describe "Admin::Projects" do
expect(page).to have_content(project.name)
expect(page).to have_content(project.full_name)
expect(page).to have_content(project.creator.name)
+ expect(page).to have_content(project.id)
end
end
diff --git a/spec/features/admin/admin_requests_profiles_spec.rb b/spec/features/admin/admin_requests_profiles_spec.rb
index 2503fc9067d..0400b89dbfd 100644
--- a/spec/features/admin/admin_requests_profiles_spec.rb
+++ b/spec/features/admin/admin_requests_profiles_spec.rb
@@ -1,13 +1,17 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin::RequestsProfilesController' do
+ let(:tmpdir) { Dir.mktmpdir('profiler-test') }
+
before do
- FileUtils.mkdir_p(Gitlab::RequestProfiler::PROFILES_DIR)
+ stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
sign_in(create(:admin))
end
after do
- Gitlab::RequestProfiler.remove_all_profiles
+ FileUtils.rm_rf(tmpdir)
end
describe 'GET /admin/requests_profiles' do
@@ -19,42 +23,103 @@ describe 'Admin::RequestsProfilesController' do
expect(page).to have_content("X-Profile-Token: #{Gitlab::RequestProfiler.profile_token}")
end
- it 'lists all available profiles' do
- time1 = 1.hour.ago
- time2 = 2.hours.ago
- time3 = 3.hours.ago
- profile1 = "|gitlab-org|gitlab-ce_#{time1.to_i}.html"
- profile2 = "|gitlab-org|gitlab-ce_#{time2.to_i}.html"
- profile3 = "|gitlab-com|infrastructure_#{time3.to_i}.html"
-
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile1}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile2}")
- FileUtils.touch("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile3}")
-
- visit admin_requests_profiles_path
+ context 'when having multiple profiles' do
+ let(:time1) { 1.hour.ago }
+ let(:time2) { 2.hours.ago }
+
+ let(:profiles) do
+ [
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_execution.html",
+ created: time2,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time1.to_i}_memory.html",
+ created: time1,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/gitlab-ce',
+ name: "|gitlab-org|gitlab-ce_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time1.to_i}_execution.html",
+ created: time1,
+ profile_mode: 'Execution'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time2.to_i}_memory.html",
+ created: time2,
+ profile_mode: 'Memory'
+ },
+ {
+ request_path: '/gitlab-org/infrastructure',
+ name: "|gitlab-org|infrastructure_#{time2.to_i}.html",
+ created: time2,
+ profile_mode: 'Unknown'
+ }
+ ]
+ end
- within('.card', text: '/gitlab-org/gitlab-ce') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile1)}']", text: time1.to_s(:long))
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile2)}']", text: time2.to_s(:long))
+ before do
+ profiles.each do |profile|
+ FileUtils.touch(File.join(Gitlab::RequestProfiler::PROFILES_DIR, profile[:name]))
+ end
end
- within('.card', text: '/gitlab-com/infrastructure') do
- expect(page).to have_selector("a[href='#{admin_requests_profile_path(profile3)}']", text: time3.to_s(:long))
+ it 'lists all available profiles' do
+ visit admin_requests_profiles_path
+
+ profiles.each do |profile|
+ within('.card', text: profile[:request_path]) do
+ expect(page).to have_selector(
+ "a[href='#{admin_requests_profile_path(profile[:name])}']",
+ text: "#{profile[:created].to_s(:long)} #{profile[:profile_mode]}")
+ end
+ end
end
end
end
describe 'GET /admin/requests_profiles/:profile' do
context 'when a profile exists' do
- it 'displays the content of the profile' do
- content = 'This is a request profile'
- profile = "|gitlab-org|gitlab-ce_#{Time.now.to_i}.html"
-
+ before do
File.write("#{Gitlab::RequestProfiler::PROFILES_DIR}/#{profile}", content)
+ end
+
+ context 'when is valid call stack profile' do
+ let(:content) { 'This is a call stack request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_execution.html" }
+
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
+
+ expect(page).to have_content(content)
+ end
+ end
+
+ context 'when is valid memory profile' do
+ let(:content) { 'This is a memory request profile' }
+ let(:profile) { "|gitlab-org|gitlab-ce_#{Time.now.to_i}_memory.txt" }
- visit admin_requests_profile_path(profile)
+ it 'displays the content' do
+ visit admin_requests_profile_path(profile)
- expect(page).to have_content(content)
+ expect(page).to have_content(content)
+ end
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 97b432a6751..4ad90c96558 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin Runners" do
diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb
index 518b3625348..ddd87404003 100644
--- a/spec/features/admin/admin_settings_spec.rb
+++ b/spec/features/admin/admin_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin updates settings' do
@@ -336,14 +338,17 @@ describe 'Admin updates settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
- check 'Allow requests to the local network from hooks and services'
+ check 'Allow requests to the local network from web hooks and services'
+ # Enabled by default
+ uncheck 'Allow requests to the local network from system hooks'
# Enabled by default
uncheck 'Enforce DNS rebinding attack protection'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
- expect(current_settings.allow_local_requests_from_hooks_and_services).to be true
+ expect(current_settings.allow_local_requests_from_web_hooks_and_services).to be true
+ expect(current_settings.allow_local_requests_from_system_hooks).to be false
expect(current_settings.dns_rebinding_protection_enabled).to be false
end
end
@@ -354,16 +359,18 @@ describe 'Admin updates settings' do
end
it 'Change Help page' do
+ new_support_url = 'http://example.com/help'
+
page.within('.as-help-page') do
fill_in 'Help page text', with: 'Example text'
check 'Hide marketing-related entries from help'
- fill_in 'Support page URL', with: 'http://example.com/help'
+ fill_in 'Support page URL', with: new_support_url
click_button 'Save changes'
end
expect(current_settings.help_page_text).to eq "Example text"
expect(current_settings.help_page_hide_commercial_content).to be_truthy
- expect(current_settings.help_page_support_url).to eq "http://example.com/help"
+ expect(current_settings.help_page_support_url).to eq new_support_url
expect(page).to have_content "Application settings saved successfully"
end
@@ -413,6 +420,34 @@ describe 'Admin updates settings' do
end
end
+ context 'Nav bar' do
+ it 'Shows default help links in nav' do
+ default_support_url = 'https://about.gitlab.com/getting-help/'
+
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Help', href: help_path)
+ expect(page).to have_link(text: 'Support', href: default_support_url)
+ end
+ end
+
+ it 'Shows custom support url in nav when set' do
+ new_support_url = 'http://example.com/help'
+ stub_application_setting(help_page_support_url: new_support_url)
+
+ visit root_dashboard_path
+
+ find('.header-help-dropdown-toggle').click
+
+ page.within '.header-help' do
+ expect(page).to have_link(text: 'Support', href: new_support_url)
+ end
+ end
+ end
+
def check_all_events
page.check('Active')
page.check('Push')
diff --git a/spec/features/admin/admin_system_info_spec.rb b/spec/features/admin/admin_system_info_spec.rb
index 5a989319d5b..3dacf63e25a 100644
--- a/spec/features/admin/admin_system_info_spec.rb
+++ b/spec/features/admin/admin_system_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin System Info' do
diff --git a/spec/features/admin/admin_users_impersonation_tokens_spec.rb b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
index c7860bebb06..27f2436108c 100644
--- a/spec/features/admin/admin_users_impersonation_tokens_spec.rb
+++ b/spec/features/admin/admin_users_impersonation_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin > Users > Impersonation Tokens', :js do
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index dafec29dfcc..ebf71d8c9da 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Users" do
diff --git a/spec/features/admin/admin_uses_repository_checks_spec.rb b/spec/features/admin/admin_uses_repository_checks_spec.rb
index d04bb9acd9e..8c1ec183286 100644
--- a/spec/features/admin/admin_uses_repository_checks_spec.rb
+++ b/spec/features/admin/admin_uses_repository_checks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Admin uses repository checks' do
diff --git a/spec/features/admin/dashboard_spec.rb b/spec/features/admin/dashboard_spec.rb
index a6ca0803469..e204e0a515d 100644
--- a/spec/features/admin/dashboard_spec.rb
+++ b/spec/features/admin/dashboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'admin visits dashboard' do
@@ -15,10 +17,8 @@ describe 'admin visits dashboard' do
# Make sure the fork_networks & fork_networks reltuples have been updated
# to get a correct count on postgresql
- if Gitlab::Database.postgresql?
- ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
- ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
- end
+ ActiveRecord::Base.connection.execute('ANALYZE fork_networks')
+ ActiveRecord::Base.connection.execute('ANALYZE fork_network_members')
visit admin_root_path
diff --git a/spec/features/admin/services/admin_activates_prometheus_spec.rb b/spec/features/admin/services/admin_activates_prometheus_spec.rb
index 904fe5b406b..64c57cd425b 100644
--- a/spec/features/admin/services/admin_activates_prometheus_spec.rb
+++ b/spec/features/admin/services/admin_activates_prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Admin activates Prometheus' do
diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb
index 947587220a9..7df0e47cd14 100644
--- a/spec/features/atom/dashboard_spec.rb
+++ b/spec/features/atom/dashboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard Feed" do
diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb
index 714a9885caa..c0413f0f352 100644
--- a/spec/features/atom/issues_spec.rb
+++ b/spec/features/atom/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issues Feed' do
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 7de8bea5049..6f013e13ae4 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "User Feed" do
diff --git a/spec/features/boards/add_issues_modal_spec.rb b/spec/features/boards/add_issues_modal_spec.rb
index 030993462b5..6e477a93293 100644
--- a/spec/features/boards/add_issues_modal_spec.rb
+++ b/spec/features/boards/add_issues_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards add issue modal', :js do
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 4c6175f5590..4e7b25115d7 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
diff --git a/spec/features/boards/issue_ordering_spec.rb b/spec/features/boards/issue_ordering_spec.rb
index 21779336559..c8ea202169c 100644
--- a/spec/features/boards/issue_ordering_spec.rb
+++ b/spec/features/boards/issue_ordering_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb
index d820a59aa16..5f3a2c409ed 100644
--- a/spec/features/boards/keyboard_shortcut_spec.rb
+++ b/spec/features/boards/keyboard_shortcut_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards shortcut', :js do
diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb
index e42d18b457e..93de2750466 100644
--- a/spec/features/boards/modal_filter_spec.rb
+++ b/spec/features/boards/modal_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards add issue modal filtering', :js do
diff --git a/spec/features/boards/multiple_boards_spec.rb b/spec/features/boards/multiple_boards_spec.rb
new file mode 100644
index 00000000000..9a2b7a80498
--- /dev/null
+++ b/spec/features/boards/multiple_boards_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Multiple Issue Boards', :js do
+ set(:user) { create(:user) }
+ set(:project) { create(:project, :public) }
+ set(:planning) { create(:label, project: project, name: 'Planning') }
+ set(:board) { create(:board, name: 'board1', project: project) }
+ set(:board2) { create(:board, name: 'board2', project: project) }
+ let(:parent) { project }
+ let(:boards_path) { project_boards_path(project) }
+
+ it_behaves_like 'multiple issue boards'
+end
diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb
index d0c4534e317..abbec0ea810 100644
--- a/spec/features/boards/new_issue_spec.rb
+++ b/spec/features/boards/new_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards new issue', :js do
diff --git a/spec/features/boards/reload_boards_on_browser_back_spec.rb b/spec/features/boards/reload_boards_on_browser_back_spec.rb
index 4b4bd705a77..752c8c1052d 100644
--- a/spec/features/boards/reload_boards_on_browser_back_spec.rb
+++ b/spec/features/boards/reload_boards_on_browser_back_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Ensure Boards do not show stale data on browser back', :js do
diff --git a/spec/features/boards/sidebar_spec.rb b/spec/features/boards/sidebar_spec.rb
index 6c9ae343e01..7ee3a839293 100644
--- a/spec/features/boards/sidebar_spec.rb
+++ b/spec/features/boards/sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Boards', :js do
diff --git a/spec/features/boards/sub_group_project_spec.rb b/spec/features/boards/sub_group_project_spec.rb
index de2cb4c335e..264e5e85505 100644
--- a/spec/features/boards/sub_group_project_spec.rb
+++ b/spec/features/boards/sub_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Sub-group project issue boards', :js do
diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb
index 8cb9b57a049..235b6d0fd40 100644
--- a/spec/features/calendar_spec.rb
+++ b/spec/features/calendar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Contributions Calendar', :js do
diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb
index a2dd34e7f7c..96d8da845cb 100644
--- a/spec/features/commits_spec.rb
+++ b/spec/features/commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Commits' do
diff --git a/spec/features/container_registry_spec.rb b/spec/features/container_registry_spec.rb
index 1b5943bd5d8..aefdc4d6d4f 100644
--- a/spec/features/container_registry_spec.rb
+++ b/spec/features/container_registry_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe "Container Registry", :js do
+describe 'Container Registry', :js do
let(:user) { create(:user) }
let(:project) { create(:project) }
@@ -38,8 +40,7 @@ describe "Container Registry", :js do
it 'user removes entire container repository' do
visit_container_registry
- expect_any_instance_of(ContainerRepository)
- .to receive(:delete_tags!).and_return(true)
+ expect_any_instance_of(ContainerRepository).to receive(:delete_tags!).and_return(true)
click_on(class: 'js-remove-repo')
expect(find('.modal .modal-title')).to have_content 'Remove repository'
@@ -52,10 +53,9 @@ describe "Container Registry", :js do
find('.js-toggle-repo').click
wait_for_requests
- expect_any_instance_of(ContainerRegistry::Tag)
- .to receive(:delete).and_return(true)
+ expect_any_instance_of(ContainerRegistry::Tag).to receive(:delete).and_return(true)
- click_on(class: 'js-delete-registry')
+ click_on(class: 'js-delete-registry-row', visible: false)
expect(find('.modal .modal-title')).to have_content 'Remove image'
find('.modal .modal-footer .btn-danger').click
end
diff --git a/spec/features/contextual_sidebar_spec.rb b/spec/features/contextual_sidebar_spec.rb
index 88da1b7966b..e250e8cc90a 100644
--- a/spec/features/contextual_sidebar_spec.rb
+++ b/spec/features/contextual_sidebar_spec.rb
@@ -16,21 +16,21 @@ describe 'Contextual sidebar', :js do
it 'shows flyout navs when collapsed or expanded apart from on the active item when expanded' do
expect(page).not_to have_selector('.js-sidebar-collapsed')
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('.qa-link-project').hover
+ find('.rspec-project-link').hover
expect(page).not_to have_selector('.is-showing-fly-out')
- find('.qa-toggle-sidebar').click
+ find('.rspec-toggle-sidebar').click
- find('.qa-link-pipelines').hover
+ find('.rspec-link-pipelines').hover
expect(page).to have_selector('.is-showing-fly-out')
- find('.qa-link-project').hover
+ find('.rspec-project-link').hover
expect(page).to have_selector('.is-showing-fly-out')
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index 4108a0f370d..07f0864fb3b 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Cycle Analytics', :js do
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
index f4d0f82d248..92d0c0c9260 100644
--- a/spec/features/dashboard/active_tab_spec.rb
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Active Tab', :js do
diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb
index c55dc4523f7..0a3b550c0c4 100644
--- a/spec/features/dashboard/activity_spec.rb
+++ b/spec/features/dashboard/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > Activity' do
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
index d31df322d10..9965229a539 100644
--- a/spec/features/dashboard/archived_projects_spec.rb
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Archived Project' do
diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb
index f44bd55ecf6..56b47b74626 100644
--- a/spec/features/dashboard/datetime_on_tooltips_spec.rb
+++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Tooltips on .timeago dates', :js do
diff --git a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb
index 4098dd02141..aebc2eb1916 100644
--- a/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb
+++ b/spec/features/dashboard/group_dashboard_with_external_authorization_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'The group dashboard' do
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
index 259f220c68b..653a16bbbcc 100644
--- a/spec/features/dashboard/group_spec.rb
+++ b/spec/features/dashboard/group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Group' do
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index fb76e2b0014..76785534ec7 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Groups page', :js do
@@ -27,7 +29,7 @@ describe 'Dashboard Groups page', :js do
expect(page).not_to have_content(another_group.name)
end
- it 'shows subgroups the user is member of', :nested_groups do
+ it 'shows subgroups the user is member of' do
group.add_owner(user)
nested_group.add_owner(user)
@@ -40,7 +42,7 @@ describe 'Dashboard Groups page', :js do
expect(page).to have_content(nested_group.name)
end
- context 'when filtering groups', :nested_groups do
+ context 'when filtering groups' do
before do
group.add_owner(user)
nested_group.add_owner(user)
@@ -79,7 +81,7 @@ describe 'Dashboard Groups page', :js do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let!(:subgroup) { create(:group, :public, parent: group) }
before do
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 467a503a62d..3f425001447 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Help' do
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index 50b71368e13..eca78749171 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Navigation bar counter', :use_clean_rails_memory_store_caching do
diff --git a/spec/features/dashboard/issues_filter_spec.rb b/spec/features/dashboard/issues_filter_spec.rb
index c0434f767bb..1352e1bd8fc 100644
--- a/spec/features/dashboard/issues_filter_spec.rb
+++ b/spec/features/dashboard/issues_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Issues filtering', :js do
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 9957bec0f0b..cb055ff8416 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe 'Dashboard Issues' do
diff --git a/spec/features/dashboard/label_filter_spec.rb b/spec/features/dashboard/label_filter_spec.rb
index 2d4659d380f..630b2b636b4 100644
--- a/spec/features/dashboard/label_filter_spec.rb
+++ b/spec/features/dashboard/label_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > label filter', :js do
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 0c6713f623c..0c1e1d5910b 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Merge Requests' do
diff --git a/spec/features/dashboard/milestone_tabs_spec.rb b/spec/features/dashboard/milestone_tabs_spec.rb
index 21de7c2f06f..a83e4c1f7c9 100644
--- a/spec/features/dashboard/milestone_tabs_spec.rb
+++ b/spec/features/dashboard/milestone_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard milestone tabs', :js do
diff --git a/spec/features/dashboard/milestones_spec.rb b/spec/features/dashboard/milestones_spec.rb
index c3310a4a132..c21bc922de7 100644
--- a/spec/features/dashboard/milestones_spec.rb
+++ b/spec/features/dashboard/milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > Milestones' do
diff --git a/spec/features/dashboard/project_member_activity_index_spec.rb b/spec/features/dashboard/project_member_activity_index_spec.rb
index 16919fe63ad..8e7a0b2a611 100644
--- a/spec/features/dashboard/project_member_activity_index_spec.rb
+++ b/spec/features/dashboard/project_member_activity_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project member activity', :js do
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index d1ed64cce7f..e2100c8562b 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Projects' do
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index 254bb12573c..3a47475da2b 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard shortcuts', :js do
@@ -49,7 +51,7 @@ describe 'Dashboard shortcuts', :js do
find('body').send_keys([:shift, 'P'])
find('.nothing-here-block')
- expect(page).to have_content('This user doesn\'t have any personal projects')
+ expect(page).to have_content('Explore public groups to find projects to contribute to.')
end
end
diff --git a/spec/features/dashboard/snippets_spec.rb b/spec/features/dashboard/snippets_spec.rb
index 0e248c8732d..4fb01995cb0 100644
--- a/spec/features/dashboard/snippets_spec.rb
+++ b/spec/features/dashboard/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard snippets' do
diff --git a/spec/features/dashboard/todos/target_state_spec.rb b/spec/features/dashboard/todos/target_state_spec.rb
index d55c32b3082..931fc16a5eb 100644
--- a/spec/features/dashboard/todos/target_state_spec.rb
+++ b/spec/features/dashboard/todos/target_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dashboard > Todo target states' do
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 85f865321cf..f273e416597 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -1,26 +1,41 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User filters todos', :js do
let(:user_1) { create(:user, username: 'user_1', name: 'user_1') }
let(:user_2) { create(:user, username: 'user_2', name: 'user_2') }
- let(:project_1) { create(:project, name: 'project_1') }
- let(:project_2) { create(:project, name: 'project_2') }
+ let(:group1) { create(:group) }
+ let(:group2) { create(:group) }
+
+ let(:project_1) { create(:project, name: 'project_1', namespace: group1) }
+ let(:project_2) { create(:project, name: 'project_2', namespace: group1) }
+ let(:project_3) { create(:project, name: 'project_3', namespace: group2) }
- let(:issue) { create(:issue, title: 'issue', project: project_1) }
+ let(:issue1) { create(:issue, title: 'issue', project: project_1) }
+ let(:issue2) { create(:issue, title: 'issue', project: project_3) }
let!(:merge_request) { create(:merge_request, source_project: project_2, title: 'merge_request') }
before do
- create(:todo, user: user_1, author: user_2, project: project_1, target: issue, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_1, target: issue1, action: 1)
+ create(:todo, user: user_1, author: user_2, project: project_3, target: issue2, action: 1)
create(:todo, user: user_1, author: user_1, project: project_2, target: merge_request, action: 2)
project_1.add_developer(user_1)
project_2.add_developer(user_1)
+ project_3.add_developer(user_1)
sign_in(user_1)
visit dashboard_todos_path
end
+ it 'displays all todos without a filter' do
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).to have_content issue2.to_reference(full: true)
+ end
+
it 'filters by project' do
click_button 'Project'
within '.dropdown-menu-project' do
@@ -34,6 +49,20 @@ describe 'Dashboard > User filters todos', :js do
expect(page).not_to have_content project_2.full_name
end
+ it 'filters by group' do
+ click_button 'Group'
+ within '.dropdown-menu-group' do
+ fill_in 'Search groups', with: group1.full_name
+ click_link group1.full_name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_content issue1.to_reference(full: true)
+ expect(page).to have_content merge_request.to_reference(full: true)
+ expect(page).not_to have_content issue2.to_reference(full: true)
+ end
+
context 'Author filter' do
it 'filters by author' do
click_button 'Author'
@@ -63,7 +92,7 @@ describe 'Dashboard > User filters todos', :js do
it 'shows only authors of existing done todos' do
user_3 = create :user
user_4 = create :user
- create(:todo, user: user_1, author: user_3, project: project_1, target: issue, action: 1, state: :done)
+ create(:todo, user: user_1, author: user_3, project: project_1, target: issue1, action: 1, state: :done)
create(:todo, user: user_1, author: user_4, project: project_2, target: merge_request, action: 2, state: :done)
project_1.add_developer(user_3)
@@ -92,14 +121,15 @@ describe 'Dashboard > User filters todos', :js do
wait_for_requests
- expect(find('.todos-list')).to have_content issue.to_reference
+ expect(find('.todos-list')).to have_content issue1.to_reference
+ expect(find('.todos-list')).to have_content issue2.to_reference
expect(find('.todos-list')).not_to have_content merge_request.to_reference
end
describe 'filter by action' do
before do
create(:todo, :build_failed, user: user_1, author: user_2, project: project_1)
- create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue)
+ create(:todo, :marked, user: user_1, author: user_2, project: project_1, target: issue1)
end
it 'filters by Assigned' do
diff --git a/spec/features/dashboard/todos/todos_sorting_spec.rb b/spec/features/dashboard/todos/todos_sorting_spec.rb
index b87caaa1c07..3870c661784 100644
--- a/spec/features/dashboard/todos/todos_sorting_spec.rb
+++ b/spec/features/dashboard/todos/todos_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User sorts todos' do
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index c48229fc0a0..b98a04b0bda 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Todos' do
diff --git a/spec/features/dashboard/user_filters_projects_spec.rb b/spec/features/dashboard/user_filters_projects_spec.rb
index 4410c8f887f..c4e4eb8affe 100644
--- a/spec/features/dashboard/user_filters_projects_spec.rb
+++ b/spec/features/dashboard/user_filters_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard > User filters projects' do
diff --git a/spec/features/discussion_comments/commit_spec.rb b/spec/features/discussion_comments/commit_spec.rb
index b3f1731ec95..0362ddbae82 100644
--- a/spec/features/discussion_comments/commit_spec.rb
+++ b/spec/features/discussion_comments/commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Thread Comments Commit', :js do
diff --git a/spec/features/discussion_comments/issue_spec.rb b/spec/features/discussion_comments/issue_spec.rb
index d71a1ee4731..fbceb2a51ae 100644
--- a/spec/features/discussion_comments/issue_spec.rb
+++ b/spec/features/discussion_comments/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Thread Comments Issue', :js do
diff --git a/spec/features/discussion_comments/merge_request_spec.rb b/spec/features/discussion_comments/merge_request_spec.rb
index 86e3507a3ee..96184593655 100644
--- a/spec/features/discussion_comments/merge_request_spec.rb
+++ b/spec/features/discussion_comments/merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Thread Comments Merge Request', :js do
diff --git a/spec/features/discussion_comments/snippets_spec.rb b/spec/features/discussion_comments/snippets_spec.rb
index 29aa3e4366c..082f35050c0 100644
--- a/spec/features/discussion_comments/snippets_spec.rb
+++ b/spec/features/discussion_comments/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Thread Comments Snippet', :js do
diff --git a/spec/features/display_system_header_and_footer_bar_spec.rb b/spec/features/display_system_header_and_footer_bar_spec.rb
index af9d9a5834f..e32da1a02bc 100644
--- a/spec/features/display_system_header_and_footer_bar_spec.rb
+++ b/spec/features/display_system_header_and_footer_bar_spec.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-require 'rails_helper'
+require 'spec_helper'
describe 'Display system header and footer bar' do
let(:header_message) { "Foo" }
diff --git a/spec/features/error_pages_spec.rb b/spec/features/error_pages_spec.rb
index cd7bcf29cc9..562277388bb 100644
--- a/spec/features/error_pages_spec.rb
+++ b/spec/features/error_pages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Error Pages' do
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index b2b3382666a..4bd2a305dfa 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Expand and collapse diffs', :js do
diff --git a/spec/features/explore/groups_list_spec.rb b/spec/features/explore/groups_list_spec.rb
index 56f6b1f7eaf..9686eee18fc 100644
--- a/spec/features/explore/groups_list_spec.rb
+++ b/spec/features/explore/groups_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Explore Groups page', :js do
diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb
index e4ef47d88dd..81c77a29ecd 100644
--- a/spec/features/explore/groups_spec.rb
+++ b/spec/features/explore/groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Explore Groups', :js do
diff --git a/spec/features/explore/user_explores_projects_spec.rb b/spec/features/explore/user_explores_projects_spec.rb
index c724c3d17f8..9c3686dba2d 100644
--- a/spec/features/explore/user_explores_projects_spec.rb
+++ b/spec/features/explore/user_explores_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User explores projects' do
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index f2ab5373d3d..a7ccc6f7d7b 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Global search' do
@@ -7,6 +9,15 @@ describe 'Global search' do
before do
project.add_maintainer(user)
sign_in(user)
+
+ visit dashboard_projects_path
+ end
+
+ it 'increases usage ping searches counter' do
+ expect(Gitlab::UsageDataCounters::SearchCounter).to receive(:increment_navbar_searches_count)
+
+ fill_in "search", with: "foobar"
+ click_button "Go"
end
describe 'I search through the issues and I see pagination' do
@@ -16,8 +27,6 @@ describe 'Global search' do
end
it "has a pagination" do
- visit dashboard_projects_path
-
fill_in "search", with: "initial"
click_button "Go"
@@ -27,8 +36,6 @@ describe 'Global search' do
end
it 'closes the dropdown on blur', :js do
- visit dashboard_projects_path
-
fill_in 'search', with: "a"
dropdown = find('.js-dashboard-search-options')
diff --git a/spec/features/group_variables_spec.rb b/spec/features/group_variables_spec.rb
index fc5777e8c7c..2b8d37bd629 100644
--- a/spec/features/group_variables_spec.rb
+++ b/spec/features/group_variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group variables', :js do
diff --git a/spec/features/groups/activity_spec.rb b/spec/features/groups/activity_spec.rb
index 88fc12ae1e4..c102e19d477 100644
--- a/spec/features/groups/activity_spec.rb
+++ b/spec/features/groups/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group activity page' do
diff --git a/spec/features/groups/board_spec.rb b/spec/features/groups/board_spec.rb
index 86a4a016f3d..ca33dbb7a33 100644
--- a/spec/features/groups/board_spec.rb
+++ b/spec/features/groups/board_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group Boards' do
diff --git a/spec/features/groups/empty_states_spec.rb b/spec/features/groups/empty_states_spec.rb
index e4eb0d355d1..bbc80b7eec4 100644
--- a/spec/features/groups/empty_states_spec.rb
+++ b/spec/features/groups/empty_states_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group empty states' do
@@ -110,7 +112,7 @@ describe 'Group empty states' do
end
context 'group without a project' do
- context 'group has a subgroup', :nested_groups do
+ context 'group has a subgroup' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, namespace: subgroup) }
diff --git a/spec/features/groups/group_settings_spec.rb b/spec/features/groups/group_settings_spec.rb
index 5cef5f0521f..41ecd21a386 100644
--- a/spec/features/groups/group_settings_spec.rb
+++ b/spec/features/groups/group_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit group settings' do
@@ -85,6 +87,14 @@ describe 'Edit group settings' do
end
end
+ describe 'subgroup creation level menu' do
+ it 'shows the selection menu' do
+ visit edit_group_path(group)
+
+ expect(page).to have_content('Allowed to create subgroups')
+ end
+ end
+
describe 'edit group avatar' do
before do
visit edit_group_path(group)
@@ -113,7 +123,7 @@ describe 'Edit group settings' do
expect(find(:css, '.group-root-path').text).to eq(root_url)
end
- it 'has a parent group URL label for a subgroup group', :postgresql do
+ it 'has a parent group URL label for a subgroup group' do
subgroup = create(:group, parent: group)
visit edit_group_path(subgroup)
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 0ada530781c..0cb24ef856b 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group issues page' do
@@ -50,7 +52,7 @@ describe 'Group issues page' do
end
end
- context 'issues list', :nested_groups do
+ context 'issues list' do
let(:subgroup) { create(:group, parent: group) }
let(:subgroup_project) { create(:project, :public, group: subgroup)}
let(:user_in_group) { create(:group_member, :maintainer, user: create(:user), group: group ).user }
diff --git a/spec/features/groups/labels/edit_spec.rb b/spec/features/groups/labels/edit_spec.rb
index 7cfc27a8905..43f067b89d6 100644
--- a/spec/features/groups/labels/edit_spec.rb
+++ b/spec/features/groups/labels/edit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit group label' do
diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb
index 22b51b297a6..cbccf4f3880 100644
--- a/spec/features/groups/labels/subscription_spec.rb
+++ b/spec/features/groups/labels/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Labels subscription' do
diff --git a/spec/features/groups/labels/user_sees_links_to_issuables.rb b/spec/features/groups/labels/user_sees_links_to_issuables.rb
deleted file mode 100644
index 1fdba78fa6c..00000000000
--- a/spec/features/groups/labels/user_sees_links_to_issuables.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe 'Groups > Labels > User sees links to issuables' do
- set(:group) { create(:group, :public) }
-
- before do
- create(:group_label, group: group, title: 'bug')
- visit group_labels_path(group)
- end
-
- it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
-end
diff --git a/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
new file mode 100644
index 00000000000..6199b566ebc
--- /dev/null
+++ b/spec/features/groups/labels/user_sees_links_to_issuables_spec.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Groups > Labels > User sees links to issuables' do
+ set(:group) { create(:group, :public) }
+
+ before do
+ create(:group_label, group: group, title: 'bug')
+ visit group_labels_path(group)
+ end
+
+ it 'shows links to MRs and issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+end
diff --git a/spec/features/groups/members/filter_members_spec.rb b/spec/features/groups/members/filter_members_spec.rb
index 386d81546d7..fc62c92db4e 100644
--- a/spec/features/groups/members/filter_members_spec.rb
+++ b/spec/features/groups/members/filter_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Filter members' do
@@ -17,7 +19,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user.name)
expect(second_member).to include(user_with_2fa.name)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Everyone')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Everyone')
end
it 'shows only 2FA members' do
@@ -25,7 +27,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user_with_2fa.name)
expect(members_list.size).to eq(1)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Enabled')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Enabled')
end
it 'shows only non 2FA members' do
@@ -33,7 +35,7 @@ describe 'Groups > Members > Filter members' do
expect(first_member).to include(user.name)
expect(members_list.size).to eq(1)
- expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: '2FA: Disabled')
+ expect(page).to have_css('.member-filter-2fa-dropdown .dropdown-toggle-text', text: 'Disabled')
end
def visit_members_list(options = {})
diff --git a/spec/features/groups/members/leave_group_spec.rb b/spec/features/groups/members/leave_group_spec.rb
index 439803f9255..df15f995be4 100644
--- a/spec/features/groups/members/leave_group_spec.rb
+++ b/spec/features/groups/members/leave_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Leave group' do
diff --git a/spec/features/groups/members/list_members_spec.rb b/spec/features/groups/members/list_members_spec.rb
index 4ba7161601e..8df807186be 100644
--- a/spec/features/groups/members/list_members_spec.rb
+++ b/spec/features/groups/members/list_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > List members' do
@@ -13,7 +15,7 @@ describe 'Groups > Members > List members' do
sign_in(user1)
end
- it 'show members from current group and parent', :nested_groups do
+ it 'show members from current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user2)
@@ -23,7 +25,7 @@ describe 'Groups > Members > List members' do
expect(second_row.text).to include(user2.name)
end
- it 'show user once if member of both current group and parent', :nested_groups do
+ it 'show user once if member of both current group and parent' do
group.add_developer(user1)
nested_group.add_developer(user1)
diff --git a/spec/features/groups/members/manage_members_spec.rb b/spec/features/groups/members/manage_members_spec.rb
index e2b4a491a13..cdd16ae9441 100644
--- a/spec/features/groups/members/manage_members_spec.rb
+++ b/spec/features/groups/members/manage_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Manage members' do
@@ -96,7 +98,9 @@ describe 'Groups > Members > Manage members' do
add_user('test@example.com', 'Reporter')
- page.within(second_row) do
+ click_link('Pending')
+
+ page.within('.content-list.members-list') do
expect(page).to have_content('test@example.com')
expect(page).to have_content('Invited')
expect(page).to have_button('Reporter')
diff --git a/spec/features/groups/members/master_manages_access_requests_spec.rb b/spec/features/groups/members/master_manages_access_requests_spec.rb
index bd615c99412..454da126c81 100644
--- a/spec/features/groups/members/master_manages_access_requests_spec.rb
+++ b/spec/features/groups/members/master_manages_access_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Maintainer manages access requests' do
diff --git a/spec/features/groups/members/request_access_spec.rb b/spec/features/groups/members/request_access_spec.rb
index 94510f917a3..0d5321709ae 100644
--- a/spec/features/groups/members/request_access_spec.rb
+++ b/spec/features/groups/members/request_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Request access' do
diff --git a/spec/features/groups/members/search_members_spec.rb b/spec/features/groups/members/search_members_spec.rb
index e7efdf7dfef..9c17aac09e8 100644
--- a/spec/features/groups/members/search_members_spec.rb
+++ b/spec/features/groups/members/search_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Search group member' do
@@ -17,9 +19,9 @@ describe 'Search group member' do
end
it 'renders member users' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: member.name
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
group_members_list = find(".card .content-list")
diff --git a/spec/features/groups/members/sort_members_spec.rb b/spec/features/groups/members/sort_members_spec.rb
index ee32f6d77fe..76709199942 100644
--- a/spec/features/groups/members/sort_members_spec.rb
+++ b/spec/features/groups/members/sort_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > Members > Sort members' do
@@ -17,7 +19,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by access level ascending' do
@@ -25,7 +27,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do
@@ -33,7 +35,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do
@@ -41,7 +43,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do
@@ -49,7 +51,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do
@@ -57,7 +59,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do
@@ -65,7 +67,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
@@ -73,7 +75,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(owner.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
@@ -81,7 +83,7 @@ describe 'Groups > Members > Sort members' do
expect(first_member).to include(developer.name)
expect(second_member).to include(owner.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end
def visit_members_list(sort:)
diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb
index d57eb87ca77..7f0155b63e0 100644
--- a/spec/features/groups/milestone_spec.rb
+++ b/spec/features/groups/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group milestones' do
diff --git a/spec/features/groups/milestones_sorting_spec.rb b/spec/features/groups/milestones_sorting_spec.rb
index 7bc015ea28f..d27511be0b0 100644
--- a/spec/features/groups/milestones_sorting_spec.rb
+++ b/spec/features/groups/milestones_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Milestones sorting', :js do
diff --git a/spec/features/groups/settings/group_badges_spec.rb b/spec/features/groups/settings/group_badges_spec.rb
index a5c8dbf18d0..9236a50cce5 100644
--- a/spec/features/groups/settings/group_badges_spec.rb
+++ b/spec/features/groups/settings/group_badges_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group Badges' do
diff --git a/spec/features/groups/share_lock_spec.rb b/spec/features/groups/share_lock_spec.rb
index 704d9f12888..777f5d98720 100644
--- a/spec/features/groups/share_lock_spec.rb
+++ b/spec/features/groups/share_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group share with group lock' do
@@ -9,7 +11,7 @@ describe 'Group share with group lock' do
sign_in(root_owner)
end
- context 'with a subgroup', :nested_groups do
+ context 'with a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
context 'when enabling the parent group share with group lock' do
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 9671a4d8c49..bcaed2a5f18 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group show page' do
@@ -56,32 +58,69 @@ describe 'Group show page' do
end
context 'subgroup support' do
- let(:user) { create(:user) }
+ let(:restricted_group) do
+ create(:group, subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
- before do
- group.add_owner(user)
- sign_in(user)
+ let(:relaxed_group) do
+ create(:group, subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
end
- context 'when subgroups are supported', :js, :nested_groups do
+ let(:owner) { create(:user) }
+ let(:maintainer) { create(:user) }
+
+ context 'for owners' do
+ let(:path) { group_path(restricted_group) }
+
before do
- allow(Group).to receive(:supports_nested_objects?) { true }
- visit path
+ restricted_group.add_owner(owner)
+ sign_in(owner)
end
- it 'allows creating subgroups' do
- expect(page).to have_css("li[data-text='New subgroup']", visible: false)
+ context 'when subgroups are supported' do
+ it 'allows creating subgroups' do
+ visit path
+
+ expect(page)
+ .to have_css("li[data-text='New subgroup']", visible: false)
+ end
end
end
- context 'when subgroups are not supported' do
+ context 'for maintainers' do
before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- visit path
+ sign_in(maintainer)
end
- it 'allows creating subgroups' do
- expect(page).not_to have_selector("li[data-text='New subgroup']", visible: false)
+ context 'when subgroups are supported' do
+ context 'when subgroup_creation_level is set to maintainers' do
+ before do
+ relaxed_group.add_maintainer(maintainer)
+ end
+
+ it 'allows creating subgroups' do
+ path = group_path(relaxed_group)
+ visit path
+
+ expect(page)
+ .to have_css("li[data-text='New subgroup']", visible: false)
+ end
+ end
+
+ context 'when subgroup_creation_level is set to owners' do
+ before do
+ restricted_group.add_maintainer(maintainer)
+ end
+
+ it 'does not allow creating subgroups' do
+ path = group_path(restricted_group)
+ visit path
+
+ expect(page)
+ .not_to have_selector("li[data-text='New subgroup']",
+ visible: false)
+ end
+ end
end
end
end
@@ -122,4 +161,27 @@ describe 'Group show page' do
expect(find('.group-row:nth-child(3) .namespace-title > a')).to have_content(project3.title)
end
end
+
+ context 'notification button', :js do
+ let(:maintainer) { create(:user) }
+ let!(:project) { create(:project, namespace: group) }
+
+ before do
+ group.add_maintainer(maintainer)
+ sign_in(maintainer)
+ end
+
+ it 'is enabled by default' do
+ visit path
+
+ expect(page).to have_selector('.notifications-btn:not(.disabled)', visible: true)
+ end
+
+ it 'is disabled if emails are disabled' do
+ group.update_attribute(:emails_disabled, true)
+ visit path
+
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/groups/user_browse_projects_group_page_spec.rb b/spec/features/groups/user_browse_projects_group_page_spec.rb
index 916363c41dd..075bc1128ca 100644
--- a/spec/features/groups/user_browse_projects_group_page_spec.rb
+++ b/spec/features/groups/user_browse_projects_group_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User browse group projects page' do
diff --git a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
index 6d6f206d761..742021ae4a1 100644
--- a/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
+++ b/spec/features/groups/user_sees_users_dropdowns_in_issuables_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Groups > User sees users dropdowns in issuables list' do
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index 8e7f78cab81..ca994c95df8 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group' do
@@ -105,7 +107,7 @@ describe 'Group' do
end
end
- describe 'create a nested group', :nested_groups, :js do
+ describe 'create a nested group', :js do
let(:group) { create(:group, path: 'foo') }
context 'as admin' do
@@ -231,7 +233,7 @@ describe 'Group' do
end
end
- describe 'group page with nested groups', :nested_groups, :js do
+ describe 'group page with nested groups', :js do
let!(:group) { create(:group) }
let!(:nested_group) { create(:group, parent: group) }
let!(:project) { create(:project, namespace: group) }
diff --git a/spec/features/ics/dashboard_issues_spec.rb b/spec/features/ics/dashboard_issues_spec.rb
index b74bbf848ac..bde5488f375 100644
--- a/spec/features/ics/dashboard_issues_spec.rb
+++ b/spec/features/ics/dashboard_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dashboard Issues Calendar Feed' do
@@ -91,7 +93,7 @@ describe 'Dashboard Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ics/group_issues_spec.rb b/spec/features/ics/group_issues_spec.rb
index 86da720c8be..0b317095678 100644
--- a/spec/features/ics/group_issues_spec.rb
+++ b/spec/features/ics/group_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Group Issues Calendar Feed' do
@@ -66,7 +68,7 @@ describe 'Group Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ics/project_issues_spec.rb b/spec/features/ics/project_issues_spec.rb
index 37b90c666bc..3c940149670 100644
--- a/spec/features/ics/project_issues_spec.rb
+++ b/spec/features/ics/project_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Issues Calendar Feed' do
@@ -65,7 +67,7 @@ describe 'Project Issues Calendar Feed' do
expect(body).to have_text("SUMMARY:test title (in #{project.full_path})")
# line length for ics is 75 chars
- expected_description = "DESCRIPTION:Find out more at #{issue_url(issue)}".insert(75, ' ')
+ expected_description = (+"DESCRIPTION:Find out more at #{issue_url(issue)}").insert(75, ' ')
expect(body).to have_text(expected_description)
expect(body).to have_text("DTSTART;VALUE=DATE:#{Date.tomorrow.strftime('%Y%m%d')}")
expect(body).to have_text("URL:#{issue_url(issue)}")
diff --git a/spec/features/ide/user_opens_merge_request_spec.rb b/spec/features/ide/user_opens_merge_request_spec.rb
index 185349219a7..03318287db9 100644
--- a/spec/features/ide/user_opens_merge_request_spec.rb
+++ b/spec/features/ide/user_opens_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'IDE merge request', :js do
diff --git a/spec/features/ide_spec.rb b/spec/features/ide_spec.rb
index 6eb59ef72c2..73f6180d944 100644
--- a/spec/features/ide_spec.rb
+++ b/spec/features/ide_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'IDE', :js do
- describe 'sub-groups', :nested_groups do
+ describe 'sub-groups' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
diff --git a/spec/features/import/manifest_import_spec.rb b/spec/features/import/manifest_import_spec.rb
index a90cdd8d920..e9471257544 100644
--- a/spec/features/import/manifest_import_spec.rb
+++ b/spec/features/import/manifest_import_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Import multiple repositories by uploading a manifest file', :js, :postgresql do
+describe 'Import multiple repositories by uploading a manifest file', :js do
include Select2Helper
let(:user) { create(:admin) }
diff --git a/spec/features/instance_statistics/cohorts_spec.rb b/spec/features/instance_statistics/cohorts_spec.rb
index 40e65515ceb..61294ec9af2 100644
--- a/spec/features/instance_statistics/cohorts_spec.rb
+++ b/spec/features/instance_statistics/cohorts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Cohorts page' do
diff --git a/spec/features/instance_statistics/instance_statistics.rb b/spec/features/instance_statistics/instance_statistics.rb
deleted file mode 100644
index d03e6e68075..00000000000
--- a/spec/features/instance_statistics/instance_statistics.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-require 'rails_helper'
-
-describe 'Cohorts page', :js do
- before do
- sign_in(create(:admin))
- end
-
- it 'hides cohorts nav button when usage ping is disabled' do
- stub_application_setting(usage_ping_enabled: false)
-
- visit instance_statistics_root_path
-
- expect(find('.nav-sidebar')).not_to have_content('Cohorts')
- end
-
- it 'shows cohorts nav button when usage ping is enabled' do
- stub_application_setting(usage_ping_enabled: true)
-
- visit instance_statistics_root_path
-
- expect(find('.nav-sidebar')).to have_content('Cohorts')
- end
-end
diff --git a/spec/features/instance_statistics/instance_statistics_spec.rb b/spec/features/instance_statistics/instance_statistics_spec.rb
new file mode 100644
index 00000000000..40d0f1db207
--- /dev/null
+++ b/spec/features/instance_statistics/instance_statistics_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Cohorts page', :js do
+ before do
+ sign_in(create(:admin))
+ end
+
+ it 'hides cohorts nav button when usage ping is disabled' do
+ stub_application_setting(usage_ping_enabled: false)
+
+ visit instance_statistics_root_path
+
+ expect(find('.nav-sidebar')).not_to have_content('Cohorts')
+ end
+
+ it 'shows cohorts nav button when usage ping is enabled' do
+ stub_application_setting(usage_ping_enabled: true)
+
+ visit instance_statistics_root_path
+
+ expect(find('.nav-sidebar')).to have_content('Cohorts')
+ end
+end
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index 9e1a12a9c2a..1e054a7b358 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Invites' do
@@ -8,17 +10,17 @@ describe 'Invites' do
let(:group_invite) { group.group_members.invite.last }
before do
+ stub_feature_flags(invisible_captcha: false)
project.add_maintainer(owner)
group.add_user(owner, Gitlab::Access::OWNER)
group.add_developer('user@example.com', owner)
group_invite.generate_invite_token!
end
- def confirm_email_and_sign_in(new_user)
+ def confirm_email(new_user)
new_user_token = User.find_by_email(new_user.email).confirmation_token
visit user_confirmation_path(confirmation_token: new_user_token)
- fill_in_sign_in_form(new_user)
end
def fill_in_sign_up_form(new_user)
@@ -152,17 +154,41 @@ describe 'Invites' do
context 'email confirmation enabled' do
let(:send_email_confirmation) { true }
- it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ # stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(root_path)
- expect(page).to have_content(project.full_name)
- visit group_path(group)
- expect(page).to have_content(group.full_name)
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to root page with all the project/groups invitation automatically accepted' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+
+ expect(current_path).to eq(root_path)
+ expect(page).to have_content(project.full_name)
+ visit group_path(group)
+ expect(page).to have_content(group.full_name)
+ end
end
- it "doesn't accept invitations until the user confirm his email" do
+ it "doesn't accept invitations until the user confirms his email" do
fill_in_sign_up_form(new_user)
sign_in(owner)
@@ -173,11 +199,32 @@ describe 'Invites' do
context 'the user sign-up using a different email address' do
let(:invite_email) { build_stubbed(:user).email }
- it 'signs up and redirects to the invitation page' do
- fill_in_sign_up_form(new_user)
- confirm_email_and_sign_in(new_user)
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 0
+ end
- expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+ confirm_email(new_user)
+ fill_in_sign_in_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days
+ end
+
+ it 'signs up and redirects to the invitation page' do
+ fill_in_sign_up_form(new_user)
+
+ expect(current_path).to eq(invite_path(group_invite.raw_invite_token))
+ end
end
end
end
diff --git a/spec/features/issuables/close_reopen_report_toggle_spec.rb b/spec/features/issuables/close_reopen_report_toggle_spec.rb
index 26c44baeb21..8805018902f 100644
--- a/spec/features/issuables/close_reopen_report_toggle_spec.rb
+++ b/spec/features/issuables/close_reopen_report_toggle_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issuables Close/Reopen/Report toggle' do
diff --git a/spec/features/issuables/discussion_lock_spec.rb b/spec/features/issuables/discussion_lock_spec.rb
index 7ea29ff252b..0cd2c077081 100644
--- a/spec/features/issuables/discussion_lock_spec.rb
+++ b/spec/features/issuables/discussion_lock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Discussion Lock', :js do
diff --git a/spec/features/issuables/issuable_list_spec.rb b/spec/features/issuables/issuable_list_spec.rb
index 225b858742d..dc68e633e27 100644
--- a/spec/features/issuables/issuable_list_spec.rb
+++ b/spec/features/issuables/issuable_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'issuable list' do
diff --git a/spec/features/issuables/markdown_references/internal_references_spec.rb b/spec/features/issuables/markdown_references/internal_references_spec.rb
index 870e92b8de8..e84629dc17f 100644
--- a/spec/features/issuables/markdown_references/internal_references_spec.rb
+++ b/spec/features/issuables/markdown_references/internal_references_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "Internal references", :js do
diff --git a/spec/features/issuables/markdown_references/jira_spec.rb b/spec/features/issuables/markdown_references/jira_spec.rb
index 8eaccfc0949..aecdeffe4a4 100644
--- a/spec/features/issuables/markdown_references/jira_spec.rb
+++ b/spec/features/issuables/markdown_references/jira_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "Jira", :js do
diff --git a/spec/features/issuables/shortcuts_issuable_spec.rb b/spec/features/issuables/shortcuts_issuable_spec.rb
index a19101366a0..da8a0dd7b0f 100644
--- a/spec/features/issuables/shortcuts_issuable_spec.rb
+++ b/spec/features/issuables/shortcuts_issuable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob shortcuts', :js do
diff --git a/spec/features/issuables/sorting_list_spec.rb b/spec/features/issuables/sorting_list_spec.rb
index 3a46a4e0167..b4531f5da4e 100644
--- a/spec/features/issuables/sorting_list_spec.rb
+++ b/spec/features/issuables/sorting_list_spec.rb
@@ -102,7 +102,7 @@ describe 'Sort Issuable List' do
expect(first_merge_request).to include(last_updated_issuable.title)
expect(last_merge_request).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_merge_request).to include(first_updated_issuable.title)
expect(last_merge_request).to include(last_updated_issuable.title)
@@ -204,7 +204,7 @@ describe 'Sort Issuable List' do
expect(first_issue).to include(last_updated_issuable.title)
expect(last_issue).to include(first_updated_issuable.title)
- find('.issues-other-filters .filter-dropdown-container .qa-reverse-sort').click
+ find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
expect(first_issue).to include(first_updated_issuable.title)
expect(last_issue).to include(last_updated_issuable.title)
diff --git a/spec/features/issuables/user_sees_sidebar_spec.rb b/spec/features/issuables/user_sees_sidebar_spec.rb
index c6c2e58ecea..d1e10c70414 100644
--- a/spec/features/issuables/user_sees_sidebar_spec.rb
+++ b/spec/features/issuables/user_sees_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Sidebar on Mobile' do
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 7be5961af09..e8b828bd264 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issues > Labels bulk assignment' do
diff --git a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
index f6dccb5e98a..f2e482faf5f 100644
--- a/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Resolving all open threads in a merge request from an issue', :js do
diff --git a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
index 1b1a31d0723..08c98358544 100644
--- a/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
+++ b/spec/features/issues/create_issue_for_single_discussion_in_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Resolve an open thread in a merge request by creating an issue', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
index 75313442b65..2ca551cec77 100644
--- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown assignee', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
index bc8d9bc8450..b2905fc1dde 100644
--- a/spec/features/issues/filtered_search/dropdown_author_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown author', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
index a5c3ab7e7d0..45344aeb8f5 100644
--- a/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown emoji', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
index 1f4e9e79179..34313553b34 100644
--- a/spec/features/issues/filtered_search/dropdown_hint_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown hint', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
index f502061dfce..f7f9f0de4db 100644
--- a/spec/features/issues/filtered_search/dropdown_label_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Dropdown label', :js do
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
index 7a6f76cb382..8429fe44c43 100644
--- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Dropdown milestone', :js do
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
index fa8e5cb0ca9..8b5e7934ec1 100644
--- a/spec/features/issues/filtered_search/filter_issues_spec.rb
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Filter issues', :js do
diff --git a/spec/features/issues/filtered_search/recent_searches_spec.rb b/spec/features/issues/filtered_search/recent_searches_spec.rb
index 41b9ada988a..c038281d825 100644
--- a/spec/features/issues/filtered_search/recent_searches_spec.rb
+++ b/spec/features/issues/filtered_search/recent_searches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Recent searches', :js do
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
index da23aea1fc9..0fe03160e7a 100644
--- a/spec/features/issues/filtered_search/search_bar_spec.rb
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Search bar', :js do
diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb
index 9fd661d80ae..b20add1ea33 100644
--- a/spec/features/issues/filtered_search/visual_tokens_spec.rb
+++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Visual tokens', :js do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
index 597af566f9c..e840ac79916 100644
--- a/spec/features/issues/form_spec.rb
+++ b/spec/features/issues/form_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New/edit issue', :js do
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 40845ec48f9..566441dc6f6 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'GFM autocomplete', :js do
diff --git a/spec/features/issues/group_label_sidebar_spec.rb b/spec/features/issues/group_label_sidebar_spec.rb
index 9c10f78f67a..746c55b6a26 100644
--- a/spec/features/issues/group_label_sidebar_spec.rb
+++ b/spec/features/issues/group_label_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Group label on issue' do
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index 791bd003597..8f2e5b237ea 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Detail', :js do
diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb
index 321da8f44d7..b88d1bbeae5 100644
--- a/spec/features/issues/issue_sidebar_spec.rb
+++ b/spec/features/issues/issue_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue Sidebar' do
diff --git a/spec/features/issues/keyboard_shortcut_spec.rb b/spec/features/issues/keyboard_shortcut_spec.rb
index 961de9d3d25..e1325e63e6b 100644
--- a/spec/features/issues/keyboard_shortcut_spec.rb
+++ b/spec/features/issues/keyboard_shortcut_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issues shortcut', :js do
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
index 94bd7c53bb5..b80b47a4c99 100644
--- a/spec/features/issues/markdown_toolbar_spec.rb
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Issue markdown toolbar', :js do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 2abc50b04e4..9e0be7d8935 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'issue move to another project' do
diff --git a/spec/features/issues/note_polling_spec.rb b/spec/features/issues/note_polling_spec.rb
index 3cd7ce6dada..04ad012d57e 100644
--- a/spec/features/issues/note_polling_spec.rb
+++ b/spec/features/issues/note_polling_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issue notes polling', :js do
diff --git a/spec/features/issues/notes_on_issues_spec.rb b/spec/features/issues/notes_on_issues_spec.rb
index fed77453cbb..5247baa58a1 100644
--- a/spec/features/issues/notes_on_issues_spec.rb
+++ b/spec/features/issues/notes_on_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Create notes on issues', :js do
diff --git a/spec/features/issues/rss_spec.rb b/spec/features/issues/rss_spec.rb
index 0e1383cd607..d6a406f4f44 100644
--- a/spec/features/issues/rss_spec.rb
+++ b/spec/features/issues/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Issues RSS' do
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 7cce45ff206..85c72c42f45 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New issue', :js do
diff --git a/spec/features/issues/todo_spec.rb b/spec/features/issues/todo_spec.rb
index 07ae159eef4..9282c31751a 100644
--- a/spec/features/issues/todo_spec.rb
+++ b/spec/features/issues/todo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Manually create a todo item from issue', :js do
diff --git a/spec/features/issues/update_issues_spec.rb b/spec/features/issues/update_issues_spec.rb
index fd8629ae504..0e27d0231d0 100644
--- a/spec/features/issues/update_issues_spec.rb
+++ b/spec/features/issues/update_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Multiple issue updating from issues#index', :js do
diff --git a/spec/features/issues/user_comments_on_issue_spec.rb b/spec/features/issues/user_comments_on_issue_spec.rb
index b4b9a589ba3..bdaaea5bf7f 100644
--- a/spec/features/issues/user_comments_on_issue_spec.rb
+++ b/spec/features/issues/user_comments_on_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User comments on issue", :js do
@@ -41,16 +43,17 @@ describe "User comments on issue", :js do
expect(page.find('pre code').text).to eq code_block_content
end
- it "does not render html content in mermaid" do
+ it "renders escaped HTML content in Mermaid" do
html_content = "<img onerror=location=`javascript\\u003aalert\\u0028document.domain\\u0029` src=x>"
mermaid_content = "graph LR\n B-->D(#{html_content});"
+ escaped_content = CGI.escapeHTML(html_content).gsub('=', "&equals;")
comment = "```mermaid\n#{mermaid_content}\n```"
add_note(comment)
wait_for_requests
- expect(page.find('svg.mermaid')).to have_content html_content
+ expect(page.find('svg.mermaid')).to have_content escaped_content
end
end
diff --git a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
index f9103d83ba0..878a73718d7 100644
--- a/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_branch_and_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User creates branch and merge request on issue page', :js do
diff --git a/spec/features/issues/user_creates_confidential_merge_request_spec.rb b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
index 7ae4af4667b..4a32b7e2b73 100644
--- a/spec/features/issues/user_creates_confidential_merge_request_spec.rb
+++ b/spec/features/issues/user_creates_confidential_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User creates confidential merge request on issue page', :js do
diff --git a/spec/features/issues/user_creates_issue_spec.rb b/spec/features/issues/user_creates_issue_spec.rb
index 2789d574156..a71395c0e47 100644
--- a/spec/features/issues/user_creates_issue_spec.rb
+++ b/spec/features/issues/user_creates_issue_spec.rb
@@ -28,7 +28,7 @@ describe "User creates issue" do
fill_in("Title", with: issue_title)
first('.js-md').click
- first('.qa-issuable-form-description').native.send_keys('Description')
+ first('.rspec-issuable-form-description').native.send_keys('Description')
click_button("Submit issue")
diff --git a/spec/features/issues/user_edits_issue_spec.rb b/spec/features/issues/user_edits_issue_spec.rb
index 60b88ef4bdf..0afc19d9519 100644
--- a/spec/features/issues/user_edits_issue_spec.rb
+++ b/spec/features/issues/user_edits_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits issue", :js do
diff --git a/spec/features/issues/user_interacts_with_awards_spec.rb b/spec/features/issues/user_interacts_with_awards_spec.rb
index d117620a2b1..eab18b72c86 100644
--- a/spec/features/issues/user_interacts_with_awards_spec.rb
+++ b/spec/features/issues/user_interacts_with_awards_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User interacts with awards' do
diff --git a/spec/features/issues/user_sees_breadcrumb_links_spec.rb b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
index 43369f7609f..db8d3c5dd53 100644
--- a/spec/features/issues/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/issues/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New issue breadcrumb' do
diff --git a/spec/features/issues/user_sorts_issues_spec.rb b/spec/features/issues/user_sorts_issues_spec.rb
index eebd2d57cca..79938785633 100644
--- a/spec/features/issues/user_sorts_issues_spec.rb
+++ b/spec/features/issues/user_sorts_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User sorts issues" do
diff --git a/spec/features/issues/user_toggles_subscription_spec.rb b/spec/features/issues/user_toggles_subscription_spec.rb
index c2b2a193682..165d41950da 100644
--- a/spec/features/issues/user_toggles_subscription_spec.rb
+++ b/spec/features/issues/user_toggles_subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User toggles subscription", :js do
@@ -25,4 +27,14 @@ describe "User toggles subscription", :js do
# Check we're unsubscribed.
expect(subscription_button).to have_css("button:not(.is-checked)")
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project_empty_repo, :public, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_content('Notifications have been disabled by the project or group owner')
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ expect(page).not_to have_selector('.js-issuable-subscribe-button')
+ end
+ end
end
diff --git a/spec/features/issues/user_views_issue_spec.rb b/spec/features/issues/user_views_issue_spec.rb
index 330b6f0e77a..dd04ac94105 100644
--- a/spec/features/issues/user_views_issue_spec.rb
+++ b/spec/features/issues/user_views_issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views issue" do
diff --git a/spec/features/issues/user_views_issues_spec.rb b/spec/features/issues/user_views_issues_spec.rb
index 58afb4efb86..b986991f38f 100644
--- a/spec/features/issues/user_views_issues_spec.rb
+++ b/spec/features/issues/user_views_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views issues" do
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index 489651fea15..b7a45905845 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Labels Hierarchy', :js, :nested_groups do
+describe 'Labels Hierarchy', :js do
include FilteredSearchHelpers
let!(:user) { create(:user) }
diff --git a/spec/features/markdown/copy_as_gfm_spec.rb b/spec/features/markdown/copy_as_gfm_spec.rb
index c30ac9c4ae2..c098a1b3e3a 100644
--- a/spec/features/markdown/copy_as_gfm_spec.rb
+++ b/spec/features/markdown/copy_as_gfm_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Copy as GFM', :js do
@@ -416,8 +418,8 @@ describe 'Copy as GFM', :js do
html = <<~HTML
<div class="md-suggestion">
- <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header">
- <div class="qa-suggestion-diff-header font-weight-bold">
+ <div class="md-suggestion-header border-bottom-0 mt-2 qa-suggestion-diff-header js-suggestion-diff-header">
+ <div class="qa-suggestion-diff-header js-suggestion-diff-header font-weight-bold">
Suggested change
<a href="/gitlab/help/user/discussions/index.md#suggest-changes" aria-label="Help" class="js-help-btn">
<svg aria-hidden="true" class="s16 ic-question-o link-highlight">
@@ -426,7 +428,7 @@ describe 'Copy as GFM', :js do
</a>
</div>
<!---->
- <button type="button" class="btn qa-apply-btn">Apply suggestion</button>
+ <button type="button" class="btn qa-apply-btn js-apply-btn">Apply suggestion</button>
</div>
<table class="mb-3 md-suggestion-diff js-syntax-highlight code white">
<tbody>
@@ -796,7 +798,7 @@ describe 'Copy as GFM', :js do
context 'selecting one word of text' do
it 'copies as inline code' do
verify(
- '.line[id="LC27"] .s2',
+ '.line[id="LC27"] .nl',
'`"bio"`'
)
diff --git a/spec/features/markdown/gitlab_flavored_markdown_spec.rb b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
index 8fda3c7193e..f34af268630 100644
--- a/spec/features/markdown/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/markdown/gitlab_flavored_markdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "GitLab Flavored Markdown" do
diff --git a/spec/features/markdown/markdown_spec.rb b/spec/features/markdown/markdown_spec.rb
index 8815643ca96..0efeffe3232 100644
--- a/spec/features/markdown/markdown_spec.rb
+++ b/spec/features/markdown/markdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'erb'
diff --git a/spec/features/markdown/math_spec.rb b/spec/features/markdown/math_spec.rb
index 16ad0d456be..68d99b4241a 100644
--- a/spec/features/markdown/math_spec.rb
+++ b/spec/features/markdown/math_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Math rendering', :js do
diff --git a/spec/features/markdown/mermaid_spec.rb b/spec/features/markdown/mermaid_spec.rb
index e3bcaca737e..aaaceebc216 100644
--- a/spec/features/markdown/mermaid_spec.rb
+++ b/spec/features/markdown/mermaid_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Mermaid rendering', :js do
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
new file mode 100644
index 00000000000..4de67cfcdbe
--- /dev/null
+++ b/spec/features/markdown/metrics_spec.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching do
+ include PrometheusHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:prometheus_project) }
+ let(:environment) { create(:environment, project: project) }
+ let(:issue) { create(:issue, project: project, description: description) }
+ let(:description) { "See [metrics dashboard](#{metrics_url}) for info." }
+ let(:metrics_url) { metrics_project_environment_url(project, environment) }
+
+ before do
+ configure_host
+ import_common_metrics
+ stub_any_prometheus_request_with_response
+
+ project.add_developer(user)
+
+ sign_in(user)
+ end
+
+ after do
+ restore_host
+ end
+
+ it 'shows embedded metrics' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text('Memory Usage (Total)')
+ expect(page).to have_text('Core Usage (Total)')
+ end
+
+ context 'when dashboard params are in included the url' do
+ let(:metrics_url) { metrics_project_environment_url(project, environment, **chart_params) }
+
+ let(:chart_params) do
+ {
+ group: 'System metrics (Kubernetes)',
+ title: 'Memory Usage (Pod average)',
+ y_label: 'Memory Used per Pod (MB)'
+ }
+ end
+
+ it 'shows embedded metrics for the specifiec chart' do
+ visit project_issue_path(project, issue)
+
+ expect(page).to have_css('div.prometheus-graph')
+ expect(page).to have_text(chart_params[:title])
+ expect(page).to have_text(chart_params[:y_label])
+ end
+ end
+
+ def import_common_metrics
+ ::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
+ end
+
+ def configure_host
+ @original_default_host = default_url_options[:host]
+ @original_gitlab_url = Gitlab.config.gitlab[:url]
+
+ # Ensure we create a metrics url with the right host.
+ # Configure host for route helpers in specs (also updates root_url):
+ default_url_options[:host] = Capybara.server_host
+
+ # Ensure we identify urls with the appropriate host.
+ # Configure host to include port in app:
+ Gitlab.config.gitlab[:url] = root_url.chomp('/')
+ end
+
+ def restore_host
+ default_url_options[:host] = @original_default_host
+ Gitlab.config.gitlab[:url] = @original_gitlab_url
+ end
+end
diff --git a/spec/features/merge_request/maintainer_edits_fork_spec.rb b/spec/features/merge_request/maintainer_edits_fork_spec.rb
index b35f985126c..030638cba71 100644
--- a/spec/features/merge_request/maintainer_edits_fork_spec.rb
+++ b/spec/features/merge_request/maintainer_edits_fork_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'a maintainer edits files on a source-branch of an MR from a fork', :js do
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 3d029ccec1a..4d305d43351 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User accepts a merge request', :js do
diff --git a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
index b8c4a78e24f..be403abcc4d 100644
--- a/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
+++ b/spec/features/merge_request/user_allows_commits_from_memebers_who_can_merge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'create a merge request, allowing commits from members who can merge to the target branch', :js do
diff --git a/spec/features/merge_request/user_assigns_themselves_spec.rb b/spec/features/merge_request/user_assigns_themselves_spec.rb
index 41cc7cef777..d7918a9e9d7 100644
--- a/spec/features/merge_request/user_assigns_themselves_spec.rb
+++ b/spec/features/merge_request/user_assigns_themselves_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User assigns themselves' do
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 93376bc8ce0..5e9b232760b 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User awards emoji', :js do
diff --git a/spec/features/merge_request/user_closes_merge_request_spec.rb b/spec/features/merge_request/user_closes_merge_request_spec.rb
index 2d12d690151..c5125c38ed7 100644
--- a/spec/features/merge_request/user_closes_merge_request_spec.rb
+++ b/spec/features/merge_request/user_closes_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User closes a merge requests', :js do
diff --git a/spec/features/merge_request/user_comments_on_commit_spec.rb b/spec/features/merge_request/user_comments_on_commit_spec.rb
index 8ea358bcc70..6b869d93e4c 100644
--- a/spec/features/merge_request/user_comments_on_commit_spec.rb
+++ b/spec/features/merge_request/user_comments_on_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a commit', :js do
diff --git a/spec/features/merge_request/user_comments_on_diff_spec.rb b/spec/features/merge_request/user_comments_on_diff_spec.rb
index eb4b2cf5bd0..19b8a7f74b7 100644
--- a/spec/features/merge_request/user_comments_on_diff_spec.rb
+++ b/spec/features/merge_request/user_comments_on_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a diff', :js do
diff --git a/spec/features/merge_request/user_comments_on_merge_request_spec.rb b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
index e6bc3780b5c..c7845b4cce4 100644
--- a/spec/features/merge_request/user_comments_on_merge_request_spec.rb
+++ b/spec/features/merge_request/user_comments_on_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User comments on a merge request', :js do
diff --git a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
index 3a9a06a6bc3..e0724a04ea3 100644
--- a/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_creates_image_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User creates image diff notes', :js do
diff --git a/spec/features/merge_request/user_creates_merge_request_spec.rb b/spec/features/merge_request/user_creates_merge_request_spec.rb
index 2a70cbb2a72..f92791cc810 100644
--- a/spec/features/merge_request/user_creates_merge_request_spec.rb
+++ b/spec/features/merge_request/user_creates_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates a merge request", :js do
diff --git a/spec/features/merge_request/user_creates_mr_spec.rb b/spec/features/merge_request/user_creates_mr_spec.rb
index c9dedab048a..267097bd466 100644
--- a/spec/features/merge_request/user_creates_mr_spec.rb
+++ b/spec/features/merge_request/user_creates_mr_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User creates MR' do
diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index c6b11fce388..3213efcb60b 100644
--- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request < User customizes merge commit message', :js do
diff --git a/spec/features/merge_request/user_edits_merge_request_spec.rb b/spec/features/merge_request/user_edits_merge_request_spec.rb
index 7de0f9daac6..81c56855961 100644
--- a/spec/features/merge_request/user_edits_merge_request_spec.rb
+++ b/spec/features/merge_request/user_edits_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User edits a merge request', :js do
diff --git a/spec/features/merge_request/user_edits_mr_spec.rb b/spec/features/merge_request/user_edits_mr_spec.rb
index 25979513ead..e6b847c5e7f 100644
--- a/spec/features/merge_request/user_edits_mr_spec.rb
+++ b/spec/features/merge_request/user_edits_mr_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User edits MR' do
diff --git a/spec/features/merge_request/user_expands_diff_spec.rb b/spec/features/merge_request/user_expands_diff_spec.rb
index 3560b8d90bb..f7317ec5ca7 100644
--- a/spec/features/merge_request/user_expands_diff_spec.rb
+++ b/spec/features/merge_request/user_expands_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User expands diff', :js do
diff --git a/spec/features/merge_request/user_locks_discussion_spec.rb b/spec/features/merge_request/user_locks_discussion_spec.rb
index 76c759ab8d3..365e9b60d90 100644
--- a/spec/features/merge_request/user_locks_discussion_spec.rb
+++ b/spec/features/merge_request/user_locks_discussion_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User locks discussion', :js do
diff --git a/spec/features/merge_request/user_manages_subscription_spec.rb b/spec/features/merge_request/user_manages_subscription_spec.rb
index 68a835e7f77..5c6072c57ff 100644
--- a/spec/features/merge_request/user_manages_subscription_spec.rb
+++ b/spec/features/merge_request/user_manages_subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages subscription', :js do
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index 84636ae355c..2f01971c2e9 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User merges immediately', :js do
diff --git a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
index 8372b61f872..3f9c27bbf9d 100644
--- a/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_only_if_pipeline_succeeds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User merges only if pipeline succeeds', :js do
diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
index 85c4d778fd0..93938ac221a 100644
--- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
+++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User merges when pipeline succeeds', :js do
diff --git a/spec/features/merge_request/user_posts_diff_notes_spec.rb b/spec/features/merge_request/user_posts_diff_notes_spec.rb
index 19edce1b562..abae6ffbd71 100644
--- a/spec/features/merge_request/user_posts_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User posts diff notes', :js do
@@ -66,12 +68,12 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting on the left side' do
line_holder = find('.match', match: :first).find(:xpath, '..')
- should_not_allow_commenting(line_holder, 'left')
+ match_should_not_allow_commenting(line_holder)
end
it 'does not allow commenting on the right side' do
line_holder = find('.match', match: :first).find(:xpath, '..')
- should_not_allow_commenting(line_holder, 'right')
+ match_should_not_allow_commenting(line_holder)
end
end
@@ -134,7 +136,7 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
+ match_should_not_allow_commenting(find('.match', match: :first))
end
end
@@ -220,7 +222,7 @@ describe 'Merge request > User posts diff notes', :js do
context 'with a match line' do
it 'does not allow commenting' do
- should_not_allow_commenting(find('.match', match: :first))
+ match_should_not_allow_commenting(find('.match', match: :first))
end
end
end
@@ -249,6 +251,10 @@ describe 'Merge request > User posts diff notes', :js do
expect(line[:num]).not_to have_css comment_button_class
end
+ def match_should_not_allow_commenting(line_holder)
+ expect(line_holder).not_to have_css comment_button_class
+ end
+
def write_comment_on_line(line_holder, diff_side)
click_diff_line(line_holder, diff_side)
diff --git a/spec/features/merge_request/user_posts_notes_spec.rb b/spec/features/merge_request/user_posts_notes_spec.rb
index 8ff24449b39..1fad9eac1bd 100644
--- a/spec/features/merge_request/user_posts_notes_spec.rb
+++ b/spec/features/merge_request/user_posts_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User posts notes', :js do
diff --git a/spec/features/merge_request/user_rebases_merge_request_spec.rb b/spec/features/merge_request/user_rebases_merge_request_spec.rb
index 92e1c9942b1..34f009000dc 100644
--- a/spec/features/merge_request/user_rebases_merge_request_spec.rb
+++ b/spec/features/merge_request/user_rebases_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User rebases a merge request", :js do
diff --git a/spec/features/merge_request/user_reopens_merge_request_spec.rb b/spec/features/merge_request/user_reopens_merge_request_spec.rb
index 745b4537e72..6dee5770d0c 100644
--- a/spec/features/merge_request/user_reopens_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reopens_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reopens a merge requests', :js do
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 8fd44b87e5a..fb089a9ffcb 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User resolves conflicts', :js do
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 10fe60cb075..b8e4852a748 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User resolves diff notes and threads', :js do
@@ -362,14 +364,14 @@ describe 'Merge request > User resolves diff notes and threads', :js do
end
end
- it 'shows jump to next thread button except on last thread' do
+ it 'shows jump to next discussion button on all discussions' do
wait_for_requests
all_discussion_replies = page.all('.discussion-reply-holder')
expect(all_discussion_replies.count).to eq(2)
- expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(2)
- expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(2)
+ expect(all_discussion_replies.first.all('.discussion-next-btn').count).to eq(1)
+ expect(all_discussion_replies.last.all('.discussion-next-btn').count).to eq(1)
end
it 'displays next thread even if hidden' do
diff --git a/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
index 777464ef841..b40c11f0d47 100644
--- a/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
+++ b/spec/features/merge_request/user_resolves_outdated_diff_discussions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge request > User resolves outdated diff discussions', :js do
diff --git a/spec/features/merge_request/user_reverts_merge_request_spec.rb b/spec/features/merge_request/user_reverts_merge_request_spec.rb
index 67b6aefb2d8..71270b13c14 100644
--- a/spec/features/merge_request/user_reverts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_reverts_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reverts a merge request', :js do
diff --git a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
index 2f7d359575e..23b37c218e1 100644
--- a/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
+++ b/spec/features/merge_request/user_scrolls_to_note_on_load_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User scrolls to note on load', :js do
diff --git a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
index b58c433bbfe..fae81758086 100644
--- a/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_avatar_on_diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees avatars on diff notes', :js do
diff --git a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
index 18d204da17a..e1f0ddc4c6a 100644
--- a/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/merge_request/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New merge request breadcrumb' do
diff --git a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
index 86086a58f18..93ae408a173 100644
--- a/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_check_out_branch_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees check out branch modal', :js do
diff --git a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
index aa499493dbe..8a2614c53af 100644
--- a/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
+++ b/spec/features/merge_request/user_sees_cherry_pick_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User cherry-picks', :js do
diff --git a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
index d7c784b14c5..52163571175 100644
--- a/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
+++ b/spec/features/merge_request/user_sees_closing_issues_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees closing issues message', :js do
diff --git a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
index 46c21a2b155..f46921af1cd 100644
--- a/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_deleted_target_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees deleted target branch', :js do
diff --git a/spec/features/merge_request/user_sees_deployment_widget_spec.rb b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
index fe8e0b07d39..14fbfc2fd3f 100644
--- a/spec/features/merge_request/user_sees_deployment_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_deployment_widget_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees deployment widget', :js do
diff --git a/spec/features/merge_request/user_sees_diff_spec.rb b/spec/features/merge_request/user_sees_diff_spec.rb
index 8dc5912b8be..32429bac5f9 100644
--- a/spec/features/merge_request/user_sees_diff_spec.rb
+++ b/spec/features/merge_request/user_sees_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees diff', :js do
diff --git a/spec/features/merge_request/user_sees_discussions_spec.rb b/spec/features/merge_request/user_sees_discussions_spec.rb
index 39258b48295..a11baf1dca3 100644
--- a/spec/features/merge_request/user_sees_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_discussions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees threads', :js do
diff --git a/spec/features/merge_request/user_sees_empty_state_spec.rb b/spec/features/merge_request/user_sees_empty_state_spec.rb
index 012bfd6e458..f1ad5c2dffc 100644
--- a/spec/features/merge_request/user_sees_empty_state_spec.rb
+++ b/spec/features/merge_request/user_sees_empty_state_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees empty state' do
diff --git a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
index cf398a7df18..53cafb60dfb 100644
--- a/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_button_depending_on_unresolved_discussions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees merge button depending on unresolved threads', :js do
diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb
index 30e30751693..b5d0240e9de 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees merge widget', :js do
diff --git a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
index dd8900a3698..eb89d616b14 100644
--- a/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
+++ b/spec/features/merge_request/user_sees_mini_pipeline_graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request < User sees mini pipeline graph', :js do
diff --git a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
index fc4a188d4a7..80ee35f2a91 100644
--- a/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_from_deleted_forked_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees MR from deleted forked project', :js do
diff --git a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
index fd4175d5227..d9dc32d0594 100644
--- a/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
+++ b/spec/features/merge_request/user_sees_mr_with_deleted_source_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
# This test serves as a regression test for a bug that caused an error
diff --git a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
index a6118453540..195592531f2 100644
--- a/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_notes_from_forked_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees notes from forked project', :js do
diff --git a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
index 97093bbc2f6..68c9e0b123d 100644
--- a/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_from_forked_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees pipelines from forked project', :js do
diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb
index 8faddee4daa..e057f59e00c 100644
--- a/spec/features/merge_request/user_sees_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees pipelines', :js do
diff --git a/spec/features/merge_request/user_sees_system_notes_spec.rb b/spec/features/merge_request/user_sees_system_notes_spec.rb
index c6811d4161a..26cdd5ba21c 100644
--- a/spec/features/merge_request/user_sees_system_notes_spec.rb
+++ b/spec/features/merge_request/user_sees_system_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees system notes', :js do
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 6eae3fd4676..dae5845adec 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees versions', :js do
diff --git a/spec/features/merge_request/user_sees_wip_help_message_spec.rb b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
index 92cc73ddf1f..46209237faf 100644
--- a/spec/features/merge_request/user_sees_wip_help_message_spec.rb
+++ b/spec/features/merge_request/user_sees_wip_help_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees WIP help message' do
diff --git a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
index ae41cf90576..a1f027cbb1d 100644
--- a/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
+++ b/spec/features/merge_request/user_selects_branches_for_new_mr_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User selects branches for new MR', :js do
diff --git a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
index 0decdfe3a14..61b9904dde5 100644
--- a/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
+++ b/spec/features/merge_request/user_toggles_whitespace_changes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User toggles whitespace changes', :js do
diff --git a/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
new file mode 100644
index 00000000000..1ebe9e2e409
--- /dev/null
+++ b/spec/features/merge_request/user_tries_to_access_private_project_info_through_new_mr_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Merge Request > User tries to access private project information through the new mr page' do
+ let(:current_user) { create(:user) }
+ let(:private_project) do
+ create(:project, :public, :repository,
+ path: 'nothing-to-see-here',
+ name: 'nothing to see here',
+ repository_access_level: ProjectFeature::PRIVATE)
+ end
+ let(:owned_project) do
+ create(:project, :public, :repository,
+ namespace: current_user.namespace,
+ creator: current_user)
+ end
+
+ context 'when the user enters the querystring info for the other project' do
+ let(:mr_path) do
+ project_new_merge_request_diffs_path(
+ owned_project,
+ merge_request: {
+ source_project_id: private_project.id,
+ source_branch: 'feature'
+ }
+ )
+ end
+
+ before do
+ sign_in current_user
+ visit mr_path
+ end
+
+ it "does not mention the project the user can't see the repo of" do
+ expect(page).not_to have_content('nothing-to-see-here')
+ end
+
+ context 'when the user enters label information from the private project in the querystring' do
+ let(:inaccessible_label) { create(:label, project: private_project) }
+ let(:mr_path) do
+ project_new_merge_request_path(
+ owned_project,
+ merge_request: {
+ label_ids: [inaccessible_label.id],
+ source_branch: 'feature'
+ }
+ )
+ end
+
+ it 'does not expose the label name' do
+ expect(page).not_to have_content(inaccessible_label.name)
+ end
+ end
+ end
+end
diff --git a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb b/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb
deleted file mode 100644
index 9318b5f1ebb..00000000000
--- a/spec/features/merge_request/user_tries_to_access_private_repository_through_new_mr_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-require 'spec_helper'
-
-describe 'Merge Request > Tries to access private repo of public project' do
- let(:current_user) { create(:user) }
- let(:private_project) do
- create(:project, :public, :repository,
- path: 'nothing-to-see-here',
- name: 'nothing to see here',
- repository_access_level: ProjectFeature::PRIVATE)
- end
- let(:owned_project) do
- create(:project, :public, :repository,
- namespace: current_user.namespace,
- creator: current_user)
- end
-
- context 'when the user enters the querystring info for the other project' do
- let(:mr_path) do
- project_new_merge_request_diffs_path(
- owned_project,
- merge_request: {
- source_project_id: private_project.id,
- source_branch: 'feature'
- }
- )
- end
-
- before do
- sign_in current_user
- visit mr_path
- end
-
- it "does not mention the project the user can't see the repo of" do
- expect(page).not_to have_content('nothing-to-see-here')
- end
- end
-end
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 74342b16cb2..2d1eb260236 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views diffs', :js do
@@ -15,11 +17,25 @@ describe 'User views diffs', :js do
end
shared_examples 'unfold diffs' do
- it 'unfolds diffs' do
+ it 'unfolds diffs upwards' do
first('.js-unfold').click
-
expect(find('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"] .text-file')).to have_content('.bundle')
end
+
+ it 'unfolds diffs to the start' do
+ first('.js-unfold-all').click
+ expect(find('.file-holder[id="a5cc2925ca8258af241be7e5b0381edf30266302"] .text-file')).to have_content('.rbc')
+ end
+
+ it 'unfolds diffs downwards' do
+ first('.js-unfold-down').click
+ expect(find('.file-holder[id="2f6fcd96b88b36ce98c38da085c795a27d92a3dd"] .text-file')).to have_content('.popen3')
+ end
+
+ it 'unfolds diffs to the end' do
+ page.all('.js-unfold-down').last
+ expect(find('.file-holder[id="6eb14e00385d2fb284765eb1cd8d420d33d63fc9"] .text-file')).to have_content('end')
+ end
end
it 'shows diffs' do
diff --git a/spec/features/merge_request/user_views_open_merge_request_spec.rb b/spec/features/merge_request/user_views_open_merge_request_spec.rb
index 849fab62fc6..a788fc71286 100644
--- a/spec/features/merge_request/user_views_open_merge_request_spec.rb
+++ b/spec/features/merge_request/user_views_open_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views an open merge request' do
diff --git a/spec/features/merge_requests/filters_generic_behavior_spec.rb b/spec/features/merge_requests/filters_generic_behavior_spec.rb
index 0e7fac6b409..404e591cf4b 100644
--- a/spec/features/merge_requests/filters_generic_behavior_spec.rb
+++ b/spec/features/merge_requests/filters_generic_behavior_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > Filters generic behavior', :js do
diff --git a/spec/features/merge_requests/user_filters_by_assignees_spec.rb b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
index 0cbf1bcae30..b896b6392f1 100644
--- a/spec/features/merge_requests/user_filters_by_assignees_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_assignees_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by assignees', :js do
diff --git a/spec/features/merge_requests/user_filters_by_labels_spec.rb b/spec/features/merge_requests/user_filters_by_labels_spec.rb
index 08d741af93d..9fb149ca58d 100644
--- a/spec/features/merge_requests/user_filters_by_labels_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by labels', :js do
diff --git a/spec/features/merge_requests/user_filters_by_milestones_spec.rb b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
index 727a236d980..2fb3c86ea94 100644
--- a/spec/features/merge_requests/user_filters_by_milestones_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by milestones', :js do
diff --git a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
index 4627931f26a..3dd0f93ddfa 100644
--- a/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_multiple_criteria_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User filters by multiple criteria', :js do
diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
index ffbdacc68f6..b5969f656b0 100644
--- a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
+++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge Requests > User filters by target branch', :js do
diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
index 2dee0e26954..69d4650d1ac 100644
--- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User lists merge requests' do
diff --git a/spec/features/merge_requests/user_mass_updates_spec.rb b/spec/features/merge_requests/user_mass_updates_spec.rb
index c2dd105324d..48c92a87c63 100644
--- a/spec/features/merge_requests/user_mass_updates_spec.rb
+++ b/spec/features/merge_requests/user_mass_updates_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge requests > User mass updates', :js do
diff --git a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
index fa887110c13..ca3e24d7036 100644
--- a/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_sorts_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User sorts merge requests' do
diff --git a/spec/features/merge_requests/user_squashes_merge_request_spec.rb b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
index bf9c55cf22c..4fc8c71e47e 100644
--- a/spec/features/merge_requests/user_squashes_merge_request_spec.rb
+++ b/spec/features/merge_requests/user_squashes_merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User squashes a merge request', :js do
diff --git a/spec/features/merge_requests/user_views_all_merge_requests_spec.rb b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
index 6c695bd7aa9..60496d61e87 100644
--- a/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_all_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views all merge requests' do
diff --git a/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
index 853809fe87a..dc0d35e4fea 100644
--- a/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_closed_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views closed merge requests' do
diff --git a/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
index eb012694f1e..ddb354204c9 100644
--- a/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_merged_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views merged merge requests' do
diff --git a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
index 115e548b691..cefac9690ce 100644
--- a/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
+++ b/spec/features/merge_requests/user_views_open_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views open merge requests' do
diff --git a/spec/features/milestone_spec.rb b/spec/features/milestone_spec.rb
index adac59b89ef..6d5acc894b1 100644
--- a/spec/features/milestone_spec.rb
+++ b/spec/features/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Milestone' do
diff --git a/spec/features/milestones/user_creates_milestone_spec.rb b/spec/features/milestones/user_creates_milestone_spec.rb
index 5de0c381cdf..73f4f187501 100644
--- a/spec/features/milestones/user_creates_milestone_spec.rb
+++ b/spec/features/milestones/user_creates_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User creates milestone", :js do
diff --git a/spec/features/milestones/user_deletes_milestone_spec.rb b/spec/features/milestones/user_deletes_milestone_spec.rb
index f68ed1cde07..5666c1dd507 100644
--- a/spec/features/milestones/user_deletes_milestone_spec.rb
+++ b/spec/features/milestones/user_deletes_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User deletes milestone", :js do
diff --git a/spec/features/milestones/user_edits_milestone_spec.rb b/spec/features/milestones/user_edits_milestone_spec.rb
index 077295f1cc0..f8e96eac3ea 100644
--- a/spec/features/milestones/user_edits_milestone_spec.rb
+++ b/spec/features/milestones/user_edits_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User edits milestone", :js do
diff --git a/spec/features/milestones/user_promotes_milestone_spec.rb b/spec/features/milestones/user_promotes_milestone_spec.rb
index df1bc502134..acceb5cad62 100644
--- a/spec/features/milestones/user_promotes_milestone_spec.rb
+++ b/spec/features/milestones/user_promotes_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User promotes milestone' do
diff --git a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
index d3906ea73bd..f58242759cc 100644
--- a/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/milestones/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New project milestone breadcrumb' do
diff --git a/spec/features/milestones/user_views_milestone_spec.rb b/spec/features/milestones/user_views_milestone_spec.rb
index 83d8e2ff9e9..aa0cdf66b75 100644
--- a/spec/features/milestones/user_views_milestone_spec.rb
+++ b/spec/features/milestones/user_views_milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User views milestone" do
diff --git a/spec/features/milestones/user_views_milestones_spec.rb b/spec/features/milestones/user_views_milestones_spec.rb
index bebe40f73fd..6868791f584 100644
--- a/spec/features/milestones/user_views_milestones_spec.rb
+++ b/spec/features/milestones/user_views_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "rails_helper"
describe "User views milestones" do
diff --git a/spec/features/oauth_login_spec.rb b/spec/features/oauth_login_spec.rb
index 86331728f88..a47eaa9bda7 100644
--- a/spec/features/oauth_login_spec.rb
+++ b/spec/features/oauth_login_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'OAuth Login', :js, :allow_forgery_protection do
@@ -34,6 +36,7 @@ describe 'OAuth Login', :js, :allow_forgery_protection do
before do
stub_omniauth_config(provider)
+ expect(ActiveSession).to receive(:cleanup).with(user).at_least(:once).and_call_original
end
context 'when two-factor authentication is disabled' do
diff --git a/spec/features/participants_autocomplete_spec.rb b/spec/features/participants_autocomplete_spec.rb
index 134731a4639..312285ca2cb 100644
--- a/spec/features/participants_autocomplete_spec.rb
+++ b/spec/features/participants_autocomplete_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Member autocomplete', :js do
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index dcc63dff9f5..8ceca1e3175 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Password reset' do
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 2e0753c3bfb..e80a3cd32cc 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile account page', :js do
diff --git a/spec/features/profiles/account_spec.rb b/spec/features/profiles/account_spec.rb
index 90d0e9bb77c..741e41adbf1 100644
--- a/spec/features/profiles/account_spec.rb
+++ b/spec/features/profiles/account_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Account', :js do
diff --git a/spec/features/profiles/active_sessions_spec.rb b/spec/features/profiles/active_sessions_spec.rb
index 2aa0177af5d..709cca7d178 100644
--- a/spec/features/profiles/active_sessions_spec.rb
+++ b/spec/features/profiles/active_sessions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Active Sessions', :clean_gitlab_redis_shared_state do
diff --git a/spec/features/profiles/chat_names_spec.rb b/spec/features/profiles/chat_names_spec.rb
index c72069f6262..0219dacbc38 100644
--- a/spec/features/profiles/chat_names_spec.rb
+++ b/spec/features/profiles/chat_names_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Chat' do
diff --git a/spec/features/profiles/emails_spec.rb b/spec/features/profiles/emails_spec.rb
index bc6d54b5ed7..40e2988730b 100644
--- a/spec/features/profiles/emails_spec.rb
+++ b/spec/features/profiles/emails_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > Emails' do
diff --git a/spec/features/profiles/gpg_keys_spec.rb b/spec/features/profiles/gpg_keys_spec.rb
index ec3ec795b63..4237f037c27 100644
--- a/spec/features/profiles/gpg_keys_spec.rb
+++ b/spec/features/profiles/gpg_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > GPG Keys' do
diff --git a/spec/features/profiles/keys_spec.rb b/spec/features/profiles/keys_spec.rb
index e6586fc8a0a..c1b142c4e12 100644
--- a/spec/features/profiles/keys_spec.rb
+++ b/spec/features/profiles/keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Profile > SSH Keys' do
diff --git a/spec/features/profiles/oauth_applications_spec.rb b/spec/features/profiles/oauth_applications_spec.rb
index 7d204f89fba..94c9897a7a9 100644
--- a/spec/features/profiles/oauth_applications_spec.rb
+++ b/spec/features/profiles/oauth_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Applications' do
diff --git a/spec/features/profiles/password_spec.rb b/spec/features/profiles/password_spec.rb
index 5e3569e4beb..8c0c426f689 100644
--- a/spec/features/profiles/password_spec.rb
+++ b/spec/features/profiles/password_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Password' do
diff --git a/spec/features/profiles/personal_access_tokens_spec.rb b/spec/features/profiles/personal_access_tokens_spec.rb
index dee213a11d4..22f9c8d8afc 100644
--- a/spec/features/profiles/personal_access_tokens_spec.rb
+++ b/spec/features/profiles/personal_access_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Personal Access Tokens', :js do
diff --git a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
index f618bc330ea..5af48c4503d 100644
--- a/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
+++ b/spec/features/profiles/user_changes_notified_of_own_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Profile > Notifications > User changes notified_of_own_activity setting', :js do
diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb
index a53da94ef7d..1ab7742b36e 100644
--- a/spec/features/profiles/user_edit_profile_spec.rb
+++ b/spec/features/profiles/user_edit_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User edit profile' do
@@ -40,7 +42,7 @@ describe 'User edit profile' do
simulate_input('#user_name', 'Martin 😀')
submit_settings
- page.within('.qa-full-name') do
+ page.within('.rspec-full-name') do
expect(page).to have_css '.gl-field-error-outline'
expect(find('.gl-field-error')).not_to have_selector('.hidden')
expect(find('.gl-field-error')).to have_content('Using emojis in names seems fun, but please try to set a status message instead')
diff --git a/spec/features/profiles/user_manages_applications_spec.rb b/spec/features/profiles/user_manages_applications_spec.rb
index 34aaab240cc..7a961855c92 100644
--- a/spec/features/profiles/user_manages_applications_spec.rb
+++ b/spec/features/profiles/user_manages_applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages applications' do
diff --git a/spec/features/profiles/user_manages_emails_spec.rb b/spec/features/profiles/user_manages_emails_spec.rb
index 7283c76eb54..09da819a187 100644
--- a/spec/features/profiles/user_manages_emails_spec.rb
+++ b/spec/features/profiles/user_manages_emails_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User manages emails' do
diff --git a/spec/features/profiles/user_visits_notifications_tab_spec.rb b/spec/features/profiles/user_visits_notifications_tab_spec.rb
index db797bb586f..d788c0574e2 100644
--- a/spec/features/profiles/user_visits_notifications_tab_spec.rb
+++ b/spec/features/profiles/user_visits_notifications_tab_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the notifications tab', :js do
@@ -18,4 +20,12 @@ describe 'User visits the notifications tab', :js do
expect(page).to have_selector('#notifications-button', text: 'On mention')
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, emails_disabled: true) }
+
+ it 'notification button is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/profiles/user_visits_profile_account_page_spec.rb b/spec/features/profiles/user_visits_profile_account_page_spec.rb
index a8c08a680d7..f576a2bf9e1 100644
--- a/spec/features/profiles/user_visits_profile_account_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_account_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile account page' do
diff --git a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
index 0f419c3c2c0..2f6f8ebee9c 100644
--- a/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
+++ b/spec/features/profiles/user_visits_profile_authentication_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the authentication log' do
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 1b3718968b9..4dbdea02e27 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile preferences page' do
@@ -36,7 +38,7 @@ describe 'User visits the profile preferences page' do
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
@@ -45,7 +47,7 @@ describe 'User visits the profile preferences page' do
end
it 'updates their preference' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
diff --git a/spec/features/profiles/user_visits_profile_spec.rb b/spec/features/profiles/user_visits_profile_spec.rb
index 2dc4547b2d8..1c90a794099 100644
--- a/spec/features/profiles/user_visits_profile_spec.rb
+++ b/spec/features/profiles/user_visits_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits their profile' do
diff --git a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
index 685bf44619d..05ad9096f65 100644
--- a/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_ssh_keys_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User visits the profile SSH keys page' do
diff --git a/spec/features/project_variables_spec.rb b/spec/features/project_variables_spec.rb
index 95685a3c7ff..9e3f8a843a1 100644
--- a/spec/features/project_variables_spec.rb
+++ b/spec/features/project_variables_spec.rb
@@ -17,4 +17,27 @@ describe 'Project variables', :js do
end
it_behaves_like 'variable list'
+
+ it 'adds new variable with a special environment scope' do
+ page.within('.js-ci-variable-list-section .js-row:last-child') do
+ find('.js-ci-variable-input-key').set('somekey')
+ find('.js-ci-variable-input-value').set('somevalue')
+
+ find('.js-variable-environment-toggle').click
+ find('.js-variable-environment-dropdown-wrapper .dropdown-input-field').set('review/*')
+ find('.js-variable-environment-dropdown-wrapper .js-dropdown-create-new-item').click
+
+ expect(find('input[name="variables[variables_attributes][][environment_scope]"]', visible: false).value).to eq('review/*')
+ end
+
+ click_button('Save variables')
+ wait_for_requests
+
+ visit page_path
+
+ page.within('.js-ci-variable-list-section .js-row:nth-child(2)') do
+ expect(find('.js-ci-variable-input-key').value).to eq('somekey')
+ expect(page).to have_content('review/*')
+ end
+ end
end
diff --git a/spec/features/projects/activity/rss_spec.rb b/spec/features/projects/activity/rss_spec.rb
index 411134e7b8e..e1efe6ca64d 100644
--- a/spec/features/projects/activity/rss_spec.rb
+++ b/spec/features/projects/activity/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Activity RSS' do
diff --git a/spec/features/projects/activity/user_sees_activity_spec.rb b/spec/features/projects/activity/user_sees_activity_spec.rb
index bb4b2abc3c7..664002d909c 100644
--- a/spec/features/projects/activity/user_sees_activity_spec.rb
+++ b/spec/features/projects/activity/user_sees_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Activity > User sees activity' do
diff --git a/spec/features/projects/activity/user_sees_private_activity_spec.rb b/spec/features/projects/activity/user_sees_private_activity_spec.rb
index 61ec2ce9d29..0ec4752d418 100644
--- a/spec/features/projects/activity/user_sees_private_activity_spec.rb
+++ b/spec/features/projects/activity/user_sees_private_activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project > Activity > User sees private activity', :js do
diff --git a/spec/features/projects/actve_tabs_spec.rb b/spec/features/projects/actve_tabs_spec.rb
index 7c6110c533b..56f587f23ee 100644
--- a/spec/features/projects/actve_tabs_spec.rb
+++ b/spec/features/projects/actve_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project active tab' do
diff --git a/spec/features/projects/artifacts/file_spec.rb b/spec/features/projects/artifacts/file_spec.rb
index 993d0040434..f7eaae12072 100644
--- a/spec/features/projects/artifacts/file_spec.rb
+++ b/spec/features/projects/artifacts/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Artifact file', :js do
diff --git a/spec/features/projects/artifacts/raw_spec.rb b/spec/features/projects/artifacts/raw_spec.rb
index d8ee9adda6b..0606ab0ed08 100644
--- a/spec/features/projects/artifacts/raw_spec.rb
+++ b/spec/features/projects/artifacts/raw_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Raw artifact', :js do
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index a1fcd4024c0..ecc07181d09 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User browses artifacts" do
diff --git a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
index 5cb015e80be..254ebfb839a 100644
--- a/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_downloads_artifacts_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User downloads artifacts" do
- set(:project) { create(:project, :public) }
- set(:pipeline) { create(:ci_empty_pipeline, status: :success, project: project) }
+ set(:project) { create(:project, :repository, :public) }
+ set(:pipeline) { create(:ci_empty_pipeline, status: :success, sha: project.commit.id, project: project) }
set(:job) { create(:ci_build, :artifacts, :success, pipeline: pipeline) }
shared_examples "downloading" do
diff --git a/spec/features/projects/badges/coverage_spec.rb b/spec/features/projects/badges/coverage_spec.rb
index 8522ea747fa..46aa104fdd7 100644
--- a/spec/features/projects/badges/coverage_spec.rb
+++ b/spec/features/projects/badges/coverage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'test coverage badge' do
diff --git a/spec/features/projects/badges/list_spec.rb b/spec/features/projects/badges/list_spec.rb
index e30b908c60d..7a90457c942 100644
--- a/spec/features/projects/badges/list_spec.rb
+++ b/spec/features/projects/badges/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'list of badges' do
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
index 4ac4e8f0fcb..f2c57d702a5 100644
--- a/spec/features/projects/badges/pipeline_badge_spec.rb
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline Badge' do
diff --git a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
index 96f514f4f04..1fc490ecbfe 100644
--- a/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
+++ b/spec/features/projects/blobs/blob_line_permalink_updater_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob button line permalinks (BlobLinePermalinkUpdater)', :js do
diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb
index aa2e538cc8e..af6bb8c271f 100644
--- a/spec/features/projects/blobs/blob_show_spec.rb
+++ b/spec/features/projects/blobs/blob_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'File blob', :js do
diff --git a/spec/features/projects/blobs/edit_spec.rb b/spec/features/projects/blobs/edit_spec.rb
index 57d21f3e182..3b32d213754 100644
--- a/spec/features/projects/blobs/edit_spec.rb
+++ b/spec/features/projects/blobs/edit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Editing file blob', :js do
diff --git a/spec/features/projects/blobs/shortcuts_blob_spec.rb b/spec/features/projects/blobs/shortcuts_blob_spec.rb
index 3925de6cfb9..bc12a8ff007 100644
--- a/spec/features/projects/blobs/shortcuts_blob_spec.rb
+++ b/spec/features/projects/blobs/shortcuts_blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Blob shortcuts', :js do
diff --git a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
index 8a0b92190dd..b90129d6176 100644
--- a/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
+++ b/spec/features/projects/blobs/user_creates_new_blob_in_new_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User creates blob in new project', :js do
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 3e75890725e..401425187b0 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Download buttons in branches page' do
diff --git a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
index 0faf73db7da..4b6b07f6cda 100644
--- a/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
+++ b/spec/features/projects/branches/new_branch_ref_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'New Branch Ref Dropdown', :js do
diff --git a/spec/features/projects/branches/user_creates_branch_spec.rb b/spec/features/projects/branches/user_creates_branch_spec.rb
index b706ad64954..156edb973cd 100644
--- a/spec/features/projects/branches/user_creates_branch_spec.rb
+++ b/spec/features/projects/branches/user_creates_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates branch", :js do
diff --git a/spec/features/projects/branches/user_deletes_branch_spec.rb b/spec/features/projects/branches/user_deletes_branch_spec.rb
index 96f215e1606..1f053b69646 100644
--- a/spec/features/projects/branches/user_deletes_branch_spec.rb
+++ b/spec/features/projects/branches/user_deletes_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User deletes branch", :js do
diff --git a/spec/features/projects/branches/user_views_branches_spec.rb b/spec/features/projects/branches/user_views_branches_spec.rb
index 777d30fdffd..f3810611094 100644
--- a/spec/features/projects/branches/user_views_branches_spec.rb
+++ b/spec/features/projects/branches/user_views_branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User views branches" do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index ee71c843b80..b35067d0f4d 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Branches' do
diff --git a/spec/features/projects/ci/lint_spec.rb b/spec/features/projects/ci/lint_spec.rb
index 313950072e7..521cfb54cd2 100644
--- a/spec/features/projects/ci/lint_spec.rb
+++ b/spec/features/projects/ci/lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'CI Lint', :js do
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index c75259d1b0c..3d15095e2da 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Clusters Applications', :js do
@@ -20,9 +22,8 @@ describe 'Clusters Applications', :js do
let(:cluster) { create(:cluster, :providing_by_gcp, projects: [project]) }
it 'user is unable to install applications' do
- page.within('.js-cluster-application-row-helm') do
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Install')
- end
+ expect(page).not_to have_css('.js-cluster-application-row-helm')
+ expect(page).not_to have_css('.js-cluster-application-install-button')
end
end
@@ -61,7 +62,8 @@ describe 'Clusters Applications', :js do
Clusters::Cluster.last.application_helm.make_installed!
- expect(page).to have_css('.js-cluster-application-install-button[disabled]', exact_text: 'Installed')
+ expect(page).not_to have_css('.js-cluster-application-install-button')
+ expect(page).to have_css('.js-cluster-application-uninstall-button:not([disabled])', exact_text: 'Uninstall')
end
expect(page).to have_content('Helm Tiller was successfully installed on your Kubernetes cluster')
@@ -124,7 +126,7 @@ describe 'Clusters Applications', :js do
it 'shows status transition' do
page.within('.js-cluster-application-row-knative') do
expect(domainname_form_value).to eq('domain.example.org')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
expect(page).to have_content('Knative was successfully installed on your Kubernetes cluster')
@@ -181,7 +183,7 @@ describe 'Clusters Applications', :js do
Clusters::Cluster.last.application_cert_manager.make_installed!
expect(email_form_value).to eq('new_email@example.org')
- expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
+ expect(page).to have_css('.js-cluster-application-uninstall-button', exact_text: 'Uninstall')
end
expect(page).to have_content('Cert-Manager was successfully installed on your Kubernetes cluster')
diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb
index 974e0f84681..820ce48e52c 100644
--- a/spec/features/projects/clusters/gcp_spec.rb
+++ b/spec/features/projects/clusters/gcp_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Gcp Cluster', :js do
diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb
index 31cc09ae911..3899aab8170 100644
--- a/spec/features/projects/clusters/user_spec.rb
+++ b/spec/features/projects/clusters/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User Cluster', :js do
diff --git a/spec/features/projects/commit/builds_spec.rb b/spec/features/projects/commit/builds_spec.rb
index d72476f36a9..d28ff5d3b5f 100644
--- a/spec/features/projects/commit/builds_spec.rb
+++ b/spec/features/projects/commit/builds_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'project commit pipelines', :js do
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index acfb582dba9..46a6f62ba14 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Cherry-pick Commits' do
diff --git a/spec/features/projects/commit/comments/user_adds_comment_spec.rb b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
index 586e2e33112..bae8e6dc827 100644
--- a/spec/features/projects/commit/comments/user_adds_comment_spec.rb
+++ b/spec/features/projects/commit/comments/user_adds_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User adds a comment on a commit", :js do
diff --git a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
index a727cab4ac7..2993a402e37 100644
--- a/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
+++ b/spec/features/projects/commit/comments/user_deletes_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User deletes comments on a commit", :js do
diff --git a/spec/features/projects/commit/comments/user_edits_comments_spec.rb b/spec/features/projects/commit/comments/user_edits_comments_spec.rb
index 75bccd99f59..0fa2b2ff232 100644
--- a/spec/features/projects/commit/comments/user_edits_comments_spec.rb
+++ b/spec/features/projects/commit/comments/user_edits_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits a comment on a commit", :js do
diff --git a/spec/features/projects/commit/diff_notes_spec.rb b/spec/features/projects/commit/diff_notes_spec.rb
index e2aefa35fad..04bd66df28d 100644
--- a/spec/features/projects/commit/diff_notes_spec.rb
+++ b/spec/features/projects/commit/diff_notes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Commit diff', :js do
diff --git a/spec/features/projects/commit/mini_pipeline_graph_spec.rb b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
index 614f11c8392..1199a3bd226 100644
--- a/spec/features/projects/commit/mini_pipeline_graph_spec.rb
+++ b/spec/features/projects/commit/mini_pipeline_graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Mini Pipeline Graph in Commit View', :js do
diff --git a/spec/features/projects/commit/user_comments_on_commit_spec.rb b/spec/features/projects/commit/user_comments_on_commit_spec.rb
index 73ce8d2b996..f4fea9b9ae0 100644
--- a/spec/features/projects/commit/user_comments_on_commit_spec.rb
+++ b/spec/features/projects/commit/user_comments_on_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User comments on commit", :js do
diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb
index 42844a03ea6..39ee72a4a99 100644
--- a/spec/features/projects/commit/user_reverts_commit_spec.rb
+++ b/spec/features/projects/commit/user_reverts_commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User reverts a commit', :js do
diff --git a/spec/features/projects/commits/rss_spec.rb b/spec/features/projects/commits/rss_spec.rb
index cfc2637f1b2..0266df48d4a 100644
--- a/spec/features/projects/commits/rss_spec.rb
+++ b/spec/features/projects/commits/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Commits RSS' do
diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb
index fc74a370e72..085d8d63d52 100644
--- a/spec/features/projects/commits/user_browses_commits_spec.rb
+++ b/spec/features/projects/commits/user_browses_commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses commits' do
@@ -13,7 +15,7 @@ describe 'User browses commits' do
it 'renders commit' do
visit project_commit_path(project, sample_commit.id)
- expect(page).to have_content(sample_commit.message.gsub!(/\s+/, ' '))
+ expect(page).to have_content(sample_commit.message.gsub(/\s+/, ' '))
.and have_content("Showing #{sample_commit.files_changed_count} changed files")
.and have_content('Side-by-side')
end
diff --git a/spec/features/projects/compare_spec.rb b/spec/features/projects/compare_spec.rb
index 5f7cf68987e..2674617bcfc 100644
--- a/spec/features/projects/compare_spec.rb
+++ b/spec/features/projects/compare_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "Compare", :js do
diff --git a/spec/features/projects/deploy_keys_spec.rb b/spec/features/projects/deploy_keys_spec.rb
index 1fa9babaff5..0f3e7646673 100644
--- a/spec/features/projects/deploy_keys_spec.rb
+++ b/spec/features/projects/deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project deploy keys', :js do
diff --git a/spec/features/projects/diffs/diff_show_spec.rb b/spec/features/projects/diffs/diff_show_spec.rb
index df05625d105..df94d6debd6 100644
--- a/spec/features/projects/diffs/diff_show_spec.rb
+++ b/spec/features/projects/diffs/diff_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Diff file viewer', :js do
diff --git a/spec/features/projects/environments/environment_metrics_spec.rb b/spec/features/projects/environments/environment_metrics_spec.rb
index b08ccdc2a7c..c027b776d67 100644
--- a/spec/features/projects/environments/environment_metrics_spec.rb
+++ b/spec/features/projects/environments/environment_metrics_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environment > Metrics' do
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index fbaf12be64e..497880a7835 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environment' do
diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb
index 1b5d9083932..1a2302b3d0c 100644
--- a/spec/features/projects/environments/environments_spec.rb
+++ b/spec/features/projects/environments/environments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Environments page', :js do
diff --git a/spec/features/projects/features_visibility_spec.rb b/spec/features/projects/features_visibility_spec.rb
index 254e885ce46..ca383da5f5c 100644
--- a/spec/features/projects/features_visibility_spec.rb
+++ b/spec/features/projects/features_visibility_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Edit Project Settings' do
diff --git a/spec/features/projects/files/dockerfile_dropdown_spec.rb b/spec/features/projects/files/dockerfile_dropdown_spec.rb
index 004585f7c9e..2e0c589e168 100644
--- a/spec/features/projects/files/dockerfile_dropdown_spec.rb
+++ b/spec/features/projects/files/dockerfile_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a Dockerfile file' do
diff --git a/spec/features/projects/files/download_buttons_spec.rb b/spec/features/projects/files/download_buttons_spec.rb
index 111972a6b00..a4889f8d4c4 100644
--- a/spec/features/projects/files/download_buttons_spec.rb
+++ b/spec/features/projects/files/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Download buttons in files tree' do
diff --git a/spec/features/projects/files/edit_file_soft_wrap_spec.rb b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
index 41af70d8ebc..df6bc6883a9 100644
--- a/spec/features/projects/files/edit_file_soft_wrap_spec.rb
+++ b/spec/features/projects/files/edit_file_soft_wrap_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User uses soft wrap whilst editing file', :js do
diff --git a/spec/features/projects/files/editing_a_file_spec.rb b/spec/features/projects/files/editing_a_file_spec.rb
index 4074e67e2d2..085276f96a8 100644
--- a/spec/features/projects/files/editing_a_file_spec.rb
+++ b/spec/features/projects/files/editing_a_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to edit a file' do
diff --git a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
index 51c884201a6..0e43f2fd26b 100644
--- a/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
+++ b/spec/features/projects/files/files_sort_submodules_with_folders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User views files page' do
diff --git a/spec/features/projects/files/find_file_keyboard_spec.rb b/spec/features/projects/files/find_file_keyboard_spec.rb
index cd0235f2b9e..b680be09444 100644
--- a/spec/features/projects/files/find_file_keyboard_spec.rb
+++ b/spec/features/projects/files/find_file_keyboard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Find file keyboard shortcuts', :js do
diff --git a/spec/features/projects/files/gitignore_dropdown_spec.rb b/spec/features/projects/files/gitignore_dropdown_spec.rb
index 9fa4c053a40..dcb960b880a 100644
--- a/spec/features/projects/files/gitignore_dropdown_spec.rb
+++ b/spec/features/projects/files/gitignore_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a .gitignore file' do
diff --git a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
index 53aff183562..875ae5d34d1 100644
--- a/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
+++ b/spec/features/projects/files/gitlab_ci_yml_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User wants to add a .gitlab-ci.yml file' do
diff --git a/spec/features/projects/files/project_owner_creates_license_file_spec.rb b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
index 44715261b8b..2944089358f 100644
--- a/spec/features/projects/files/project_owner_creates_license_file_spec.rb
+++ b/spec/features/projects/files/project_owner_creates_license_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Project owner creates a license file', :js do
diff --git a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
index 0b8474fb87a..556b7227403 100644
--- a/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
+++ b/spec/features/projects/files/project_owner_sees_link_to_create_license_file_in_empty_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Project owner sees a link to create a license file in empty project', :js do
diff --git a/spec/features/projects/files/template_selector_menu_spec.rb b/spec/features/projects/files/template_selector_menu_spec.rb
index 6b313824acd..838a484d62e 100644
--- a/spec/features/projects/files/template_selector_menu_spec.rb
+++ b/spec/features/projects/files/template_selector_menu_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Template selector menu', :js do
diff --git a/spec/features/projects/files/template_type_dropdown_spec.rb b/spec/features/projects/files/template_type_dropdown_spec.rb
index 342a93b328f..8b385185e2e 100644
--- a/spec/features/projects/files/template_type_dropdown_spec.rb
+++ b/spec/features/projects/files/template_type_dropdown_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Template type dropdown selector', :js do
diff --git a/spec/features/projects/files/undo_template_spec.rb b/spec/features/projects/files/undo_template_spec.rb
index 91618145391..d3f8d36a0a9 100644
--- a/spec/features/projects/files/undo_template_spec.rb
+++ b/spec/features/projects/files/undo_template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > Template Undo Button', :js do
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
deleted file mode 100644
index 2d67837763c..00000000000
--- a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
-describe 'Projects > Files > User browses a tree with a folder containing only a folder' do
- let(:project) { create(:project, :empty_repo) }
- let(:user) { project.owner }
-
- before do
- # We need to disable the tree.flat_path provided by Gitaly to reproduce the issue
- allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false)
-
- project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
- sign_in(user)
- visit(project_tree_path(project, project.repository.root_ref))
- end
-
- it 'shows the nested folder on a single row' do
- expect(page).to have_content('foo/bar')
- end
-end
diff --git a/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
new file mode 100644
index 00000000000..6bd569e5ee2
--- /dev/null
+++ b/spec/features/projects/files/user_browses_a_tree_with_a_folder_containing_only_a_folder_spec.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+# This is a regression test for https://gitlab.com/gitlab-org/gitlab-ce/issues/37569
+describe 'Projects > Files > User browses a tree with a folder containing only a folder', :js do
+ let(:project) { create(:project, :empty_repo) }
+ let(:user) { project.owner }
+
+ before do
+ project.repository.create_dir(user, 'foo/bar', branch_name: 'master', message: 'Add the foo/bar folder')
+ sign_in(user)
+ visit(project_tree_path(project, project.repository.root_ref))
+ wait_for_requests
+ end
+
+ it 'shows the nested folder on a single row' do
+ expect(page).to have_content('foo/bar')
+ end
+end
diff --git a/spec/features/projects/files/user_browses_files_spec.rb b/spec/features/projects/files/user_browses_files_spec.rb
index a5d849db8a3..a090461261b 100644
--- a/spec/features/projects/files/user_browses_files_spec.rb
+++ b/spec/features/projects/files/user_browses_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User browses files" do
diff --git a/spec/features/projects/files/user_browses_lfs_files_spec.rb b/spec/features/projects/files/user_browses_lfs_files_spec.rb
index d5cb8f9212d..08ebeed2cdd 100644
--- a/spec/features/projects/files/user_browses_lfs_files_spec.rb
+++ b/spec/features/projects/files/user_browses_lfs_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User browses LFS files' do
diff --git a/spec/features/projects/files/user_creates_directory_spec.rb b/spec/features/projects/files/user_creates_directory_spec.rb
index e29e867492e..19d95c87c6c 100644
--- a/spec/features/projects/files/user_creates_directory_spec.rb
+++ b/spec/features/projects/files/user_creates_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User creates a directory', :js do
diff --git a/spec/features/projects/files/user_creates_files_spec.rb b/spec/features/projects/files/user_creates_files_spec.rb
index 264b288ab38..74c037641cd 100644
--- a/spec/features/projects/files/user_creates_files_spec.rb
+++ b/spec/features/projects/files/user_creates_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User creates files' do
diff --git a/spec/features/projects/files/user_deletes_files_spec.rb b/spec/features/projects/files/user_deletes_files_spec.rb
index 11ee87f245b..fd4783cfb6b 100644
--- a/spec/features/projects/files/user_deletes_files_spec.rb
+++ b/spec/features/projects/files/user_deletes_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User deletes files', :js do
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index e4b02408b49..56430721ed6 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User edits files', :js do
diff --git a/spec/features/projects/files/user_find_file_spec.rb b/spec/features/projects/files/user_find_file_spec.rb
index e2d881b34d2..72f6ccb20d6 100644
--- a/spec/features/projects/files/user_find_file_spec.rb
+++ b/spec/features/projects/files/user_find_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User find project file' do
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
index 5bce96d9b80..15f8fa7438d 100644
--- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'user reads pipeline status', :js do
diff --git a/spec/features/projects/files/user_replaces_files_spec.rb b/spec/features/projects/files/user_replaces_files_spec.rb
index bfd612e4cc8..d50bc0a7d18 100644
--- a/spec/features/projects/files/user_replaces_files_spec.rb
+++ b/spec/features/projects/files/user_replaces_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User replaces files', :js do
diff --git a/spec/features/projects/files/user_searches_for_files_spec.rb b/spec/features/projects/files/user_searches_for_files_spec.rb
index a90e4918fb1..e82f54fbe50 100644
--- a/spec/features/projects/files/user_searches_for_files_spec.rb
+++ b/spec/features/projects/files/user_searches_for_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User searches for files' do
diff --git a/spec/features/projects/files/user_uploads_files_spec.rb b/spec/features/projects/files/user_uploads_files_spec.rb
index 25ff3fdf411..74b5d7c5041 100644
--- a/spec/features/projects/files/user_uploads_files_spec.rb
+++ b/spec/features/projects/files/user_uploads_files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Files > User uploads files' do
diff --git a/spec/features/projects/fork_spec.rb b/spec/features/projects/fork_spec.rb
index 26b94bf58b2..2aed402652b 100644
--- a/spec/features/projects/fork_spec.rb
+++ b/spec/features/projects/fork_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project fork' do
diff --git a/spec/features/projects/forks/fork_list_spec.rb b/spec/features/projects/forks/fork_list_spec.rb
index 2c41c61a660..2dbe3d90bad 100644
--- a/spec/features/projects/forks/fork_list_spec.rb
+++ b/spec/features/projects/forks/fork_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'listing forks of a project' do
diff --git a/spec/features/projects/gfm_autocomplete_load_spec.rb b/spec/features/projects/gfm_autocomplete_load_spec.rb
index 1c988726ae6..ad39dec0a43 100644
--- a/spec/features/projects/gfm_autocomplete_load_spec.rb
+++ b/spec/features/projects/gfm_autocomplete_load_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'GFM autocomplete loading', :js do
diff --git a/spec/features/projects/graph_spec.rb b/spec/features/projects/graph_spec.rb
index e1bc18519a2..6082eb03374 100644
--- a/spec/features/projects/graph_spec.rb
+++ b/spec/features/projects/graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Graph', :js do
diff --git a/spec/features/projects/hook_logs/user_reads_log_spec.rb b/spec/features/projects/hook_logs/user_reads_log_spec.rb
index 086cd4b9f03..8c666f5d67a 100644
--- a/spec/features/projects/hook_logs/user_reads_log_spec.rb
+++ b/spec/features/projects/hook_logs/user_reads_log_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Hook logs' do
diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb
index c71a778fc84..a1002f38936 100644
--- a/spec/features/projects/import_export/export_file_spec.rb
+++ b/spec/features/projects/import_export/export_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Integration test that exports a file using the Import/Export feature
diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb
index 8d2b1fc7e30..a12fc8b18ed 100644
--- a/spec/features/projects/import_export/import_file_spec.rb
+++ b/spec/features/projects/import_export/import_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Import/Export - project import integration test', :js do
diff --git a/spec/features/projects/issuable_templates_spec.rb b/spec/features/projects/issuable_templates_spec.rb
index a57edc394f9..18eadb7c4a3 100644
--- a/spec/features/projects/issuable_templates_spec.rb
+++ b/spec/features/projects/issuable_templates_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'issuable templates', :js do
diff --git a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
index a8612d77a5e..6c8f4b51ea0 100644
--- a/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
+++ b/spec/features/projects/issues/viewing_issues_with_external_authorization_enabled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'viewing an issue with cross project references' do
diff --git a/spec/features/projects/jobs/permissions_spec.rb b/spec/features/projects/jobs/permissions_spec.rb
index b5e711997a0..44309a9c4bf 100644
--- a/spec/features/projects/jobs/permissions_spec.rb
+++ b/spec/features/projects/jobs/permissions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Jobs Permissions' do
diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb
index fbe765d4c44..1b277e17b0c 100644
--- a/spec/features/projects/jobs/user_browses_job_spec.rb
+++ b/spec/features/projects/jobs/user_browses_job_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses a job', :js do
@@ -48,6 +50,20 @@ describe 'User browses a job', :js do
expect(page).not_to have_content(text_to_hide)
expect(page).to have_content(text_to_show)
end
+
+ it 'collapses the section header clicked' do
+ wait_for_requests
+ text_to_hide = "Cloning into '/nolith/ci-tests'"
+ text_to_show = 'Waiting for pod'
+
+ expect(page).to have_content(text_to_hide)
+ expect(page).to have_content(text_to_show)
+
+ first('.js-section-header.js-s-get-sources').click
+
+ expect(page).not_to have_content(text_to_hide)
+ expect(page).to have_content(text_to_show)
+ end
end
context 'when job trace contains sections' do
diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb
index ebc20d15d67..44709cb1230 100644
--- a/spec/features/projects/jobs/user_browses_jobs_spec.rb
+++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User browses jobs' do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index f4ed89adc0f..8ed420300af 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -701,12 +701,12 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it 'shows manual action empty state', :js do
expect(page).to have_content(job.detailed_status(user).illustration[:title])
expect(page).to have_content('This job requires a manual action')
- expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
- expect(page).to have_link('Trigger this manual action')
+ expect(page).to have_content('This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.')
+ expect(page).to have_button('Trigger this manual action')
end
it 'plays manual action and shows pending status', :js do
- click_link 'Trigger this manual action'
+ click_button 'Trigger this manual action'
wait_for_requests
expect(page).to have_content('This job has not started yet')
@@ -734,8 +734,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
wait_for_requests
expect(page).to have_content('This job requires a manual action')
- expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments')
- expect(page).to have_link('Trigger this manual action')
+ expect(page).to have_content('This job requires manual intervention to start. Before starting this job, you can add variables below for last-minute configuration changes.')
+ expect(page).to have_button('Trigger this manual action')
end
end
diff --git a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
index 25417cf4955..55629376007 100644
--- a/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
+++ b/spec/features/projects/labels/issues_sorted_by_priority_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Issue prioritization' do
diff --git a/spec/features/projects/labels/subscription_spec.rb b/spec/features/projects/labels/subscription_spec.rb
index 49227eebf3d..1d0f9e73a1b 100644
--- a/spec/features/projects/labels/subscription_spec.rb
+++ b/spec/features/projects/labels/subscription_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Labels subscription' do
diff --git a/spec/features/projects/labels/update_prioritization_spec.rb b/spec/features/projects/labels/update_prioritization_spec.rb
index f32b155790f..3a37ee6623d 100644
--- a/spec/features/projects/labels/update_prioritization_spec.rb
+++ b/spec/features/projects/labels/update_prioritization_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Prioritize labels' do
diff --git a/spec/features/projects/labels/user_creates_labels_spec.rb b/spec/features/projects/labels/user_creates_labels_spec.rb
index c71b04fea09..257e064ae3d 100644
--- a/spec/features/projects/labels/user_creates_labels_spec.rb
+++ b/spec/features/projects/labels/user_creates_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates labels" do
diff --git a/spec/features/projects/labels/user_edits_labels_spec.rb b/spec/features/projects/labels/user_edits_labels_spec.rb
index 0708bbd40ce..da33ae3af3a 100644
--- a/spec/features/projects/labels/user_edits_labels_spec.rb
+++ b/spec/features/projects/labels/user_edits_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User edits labels" do
diff --git a/spec/features/projects/labels/user_removes_labels_spec.rb b/spec/features/projects/labels/user_removes_labels_spec.rb
index 7f49ddf560f..459adeeec30 100644
--- a/spec/features/projects/labels/user_removes_labels_spec.rb
+++ b/spec/features/projects/labels/user_removes_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User removes labels" do
diff --git a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
index 0c0501f438a..35c84204910 100644
--- a/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
+++ b/spec/features/projects/labels/user_sees_breadcrumb_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'New project label breadcrumb' do
diff --git a/spec/features/projects/labels/user_sees_links_to_issuables.rb b/spec/features/projects/labels/user_sees_links_to_issuables.rb
deleted file mode 100644
index c404fc8d66f..00000000000
--- a/spec/features/projects/labels/user_sees_links_to_issuables.rb
+++ /dev/null
@@ -1,75 +0,0 @@
-require 'spec_helper'
-
-describe 'Projects > Labels > User sees links to issuables' do
- set(:user) { create(:user) }
-
- before do
- label # creates the label
- project.add_developer(user)
- sign_in user
- visit project_labels_path(project)
- end
-
- context 'with a project label' do
- let(:label) { create(:label, project: project, title: 'bug') }
-
- context 'when merge requests and issues are enabled for the project' do
- let(:project) { create(:project, :public) }
-
- it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
- end
-
- context 'when issues are disabled for the project' do
- let(:project) { create(:project, :public, issues_access_level: ProjectFeature::DISABLED) }
-
- it 'shows links to MRs but not to issues' do
- expect(page).to have_link('view merge requests')
- expect(page).not_to have_link('view open issues')
- end
- end
-
- context 'when merge requests are disabled for the project' do
- let(:project) { create(:project, :public, merge_requests_access_level: ProjectFeature::DISABLED) }
-
- it 'shows links to issues but not to MRs' do
- expect(page).not_to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
- end
- end
-
- context 'with a group label' do
- set(:group) { create(:group) }
- let(:label) { create(:group_label, group: group, title: 'bug') }
-
- context 'when merge requests and issues are enabled for the project' do
- let(:project) { create(:project, :public, namespace: group) }
-
- it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
- end
-
- context 'when issues are disabled for the project' do
- let(:project) { create(:project, :public, namespace: group, issues_access_level: ProjectFeature::DISABLED) }
-
- it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
- end
-
- context 'when merge requests are disabled for the project' do
- let(:project) { create(:project, :public, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
-
- it 'shows links to MRs and issues' do
- expect(page).to have_link('view merge requests')
- expect(page).to have_link('view open issues')
- end
- end
- end
-end
diff --git a/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
new file mode 100644
index 00000000000..7a9b9e6eac2
--- /dev/null
+++ b/spec/features/projects/labels/user_sees_links_to_issuables_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Labels > User sees links to issuables' do
+ set(:user) { create(:user) }
+
+ before do
+ label # creates the label
+ project.add_developer(user)
+ sign_in user
+ visit project_labels_path(project)
+ end
+
+ context 'with a project label' do
+ let(:label) { create(:label, project: project, title: 'bug') }
+
+ context 'when merge requests and issues are enabled for the project' do
+ let(:project) { create(:project, :public) }
+
+ it 'shows links to MRs and issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+ end
+
+ context 'when issues are disabled for the project' do
+ let(:project) { create(:project, :public, issues_access_level: ProjectFeature::DISABLED) }
+
+ it 'shows links to MRs but not to issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).not_to have_link('Issues')
+ end
+ end
+ end
+
+ context 'when merge requests are disabled for the project' do
+ let(:project) { create(:project, :public, merge_requests_access_level: ProjectFeature::DISABLED) }
+
+ it 'shows links to issues but not to MRs' do
+ page.within('.labels-container') do
+ expect(page).not_to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+ end
+ end
+
+ context 'with a group label' do
+ set(:group) { create(:group) }
+ let(:label) { create(:group_label, group: group, title: 'bug') }
+
+ context 'when merge requests and issues are enabled for the project' do
+ let(:project) { create(:project, :public, namespace: group) }
+
+ it 'shows links to MRs and issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+ end
+
+ context 'when issues are disabled for the project' do
+ let(:project) { create(:project, :public, namespace: group, issues_access_level: ProjectFeature::DISABLED) }
+
+ it 'shows links to MRs and issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+ end
+
+ context 'when merge requests are disabled for the project' do
+ let(:project) { create(:project, :public, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
+
+ it 'shows links to MRs and issues' do
+ page.within('.labels-container') do
+ expect(page).to have_link('Merge requests')
+ expect(page).to have_link('Issues')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index b3ed725f602..096cf97551a 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Anonymous user sees members' do
diff --git a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
index a645b917568..b6f6e2ca85f 100644
--- a/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_leave_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group member cannot leave group project' do
diff --git a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
index bb475ea95e5..1f4d555c6ae 100644
--- a/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
+++ b/spec/features/projects/members/group_member_cannot_request_access_to_his_group_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group member cannot request access to his group project' do
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 0b2cd13b8ec..dd5fc82e058 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects members' do
diff --git a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
index ea3894c92bd..fb4238f0a1f 100644
--- a/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
+++ b/spec/features/projects/members/group_requester_cannot_request_access_to_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Group requester cannot request access to project', :js do
diff --git a/spec/features/projects/members/groups_with_access_list_spec.rb b/spec/features/projects/members/groups_with_access_list_spec.rb
index c0b5d943e96..6e8d1a945e1 100644
--- a/spec/features/projects/members/groups_with_access_list_spec.rb
+++ b/spec/features/projects/members/groups_with_access_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Groups with access list', :js do
@@ -50,18 +52,18 @@ describe 'Projects > Members > Groups with access list', :js do
context 'search in existing members (yes, this filters the groups list as well)' do
it 'finds no results' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: 'testing 123'
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
expect(page).not_to have_selector('.group_member')
end
it 'finds results' do
- page.within '.member-search-form' do
+ page.within '.user-search-form' do
fill_in 'search', with: group.name
- find('.member-search-btn').click
+ find('.user-search-btn').click
end
expect(page).to have_selector('.group_member', count: 1)
diff --git a/spec/features/projects/members/invite_group_spec.rb b/spec/features/projects/members/invite_group_spec.rb
index 7432c600c1e..e76637039c6 100644
--- a/spec/features/projects/members/invite_group_spec.rb
+++ b/spec/features/projects/members/invite_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project > Members > Invite group', :js do
@@ -58,7 +60,7 @@ describe 'Project > Members > Invite group', :js do
end
end
- context 'for a project in a subgroup', :nested_groups do
+ context 'for a project in a subgroup' do
let!(:group_to_share_with) { create(:group) }
let(:root_group) { create(:group) }
let(:subgroup) { create(:group, parent: root_group) }
@@ -181,7 +183,7 @@ describe 'Project > Members > Invite group', :js do
group_to_share_with.add_maintainer(maintainer)
end
- it 'the groups dropdown does not show ancestors', :nested_groups do
+ it 'the groups dropdown does not show ancestors' do
visit project_settings_members_path(project)
click_on 'invite-group-tab'
diff --git a/spec/features/projects/members/list_spec.rb b/spec/features/projects/members/list_spec.rb
index cf309492808..6d92c777033 100644
--- a/spec/features/projects/members/list_spec.rb
+++ b/spec/features/projects/members/list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project members list' do
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index 26de6fb33fd..501dd05300a 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Maintainer adds member with expiration date', :js do
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index adc8202cde7..17d6efbcaa5 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Maintainer manages access requests' do
diff --git a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
index f612ad8d551..606444b36a2 100644
--- a/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/member_cannot_request_access_to_his_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Member cannot request access to his project' do
diff --git a/spec/features/projects/members/member_leaves_project_spec.rb b/spec/features/projects/members/member_leaves_project_spec.rb
index bd2ef9c07c4..fb1165838c7 100644
--- a/spec/features/projects/members/member_leaves_project_spec.rb
+++ b/spec/features/projects/members/member_leaves_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Member leaves project' do
diff --git a/spec/features/projects/members/owner_cannot_leave_project_spec.rb b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
index 0aa005adb4d..781c584796d 100644
--- a/spec/features/projects/members/owner_cannot_leave_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_leave_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Owner cannot leave project' do
diff --git a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
index eb1b720af05..2fb76da36ad 100644
--- a/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
+++ b/spec/features/projects/members/owner_cannot_request_access_to_his_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Owner cannot request access to his project' do
diff --git a/spec/features/projects/members/sorting_spec.rb b/spec/features/projects/members/sorting_spec.rb
index 220775b514d..12f485317d8 100644
--- a/spec/features/projects/members/sorting_spec.rb
+++ b/spec/features/projects/members/sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > Sorting' do
@@ -16,7 +18,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by access level ascending' do
@@ -24,7 +26,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, ascending')
end
it 'sorts by access level descending' do
@@ -32,7 +34,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Access level, descending')
end
it 'sorts by last joined' do
@@ -40,7 +42,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Last joined')
end
it 'sorts by oldest joined' do
@@ -48,7 +50,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest joined')
end
it 'sorts by name ascending' do
@@ -56,7 +58,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, ascending')
end
it 'sorts by name descending' do
@@ -64,7 +66,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Name, descending')
end
it 'sorts by recent sign in', :clean_gitlab_redis_shared_state do
@@ -72,7 +74,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(maintainer.name)
expect(second_member).to include(developer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Recent sign in')
end
it 'sorts by oldest sign in', :clean_gitlab_redis_shared_state do
@@ -80,7 +82,7 @@ describe 'Projects > Members > Sorting' do
expect(first_member).to include(developer.name)
expect(second_member).to include(maintainer.name)
- expect(page).to have_css('.member-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
+ expect(page).to have_css('.qa-user-sort-dropdown .dropdown-toggle-text', text: 'Oldest sign in')
end
def visit_members_list(sort:)
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index f26941ab567..9f7327cd6e4 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Members > User requests access', :js do
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 69561b4d733..950af8b0ae0 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Merge Request button' do
diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb
index ff31092b910..5e94b2f721e 100644
--- a/spec/features/projects/milestones/milestone_spec.rb
+++ b/spec/features/projects/milestones/milestone_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project milestone' do
diff --git a/spec/features/projects/milestones/milestones_sorting_spec.rb b/spec/features/projects/milestones/milestones_sorting_spec.rb
index dc711377e6e..77cf696fb7c 100644
--- a/spec/features/projects/milestones/milestones_sorting_spec.rb
+++ b/spec/features/projects/milestones/milestones_sorting_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Milestones sorting', :js do
diff --git a/spec/features/projects/milestones/new_spec.rb b/spec/features/projects/milestones/new_spec.rb
index 0b5ab547dce..b1b74bed59d 100644
--- a/spec/features/projects/milestones/new_spec.rb
+++ b/spec/features/projects/milestones/new_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Creating a new project milestone', :js do
diff --git a/spec/features/projects/milestones/user_interacts_with_labels_spec.rb b/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
index a6d58be7b13..0177871599a 100644
--- a/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
+++ b/spec/features/projects/milestones/user_interacts_with_labels_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User interacts with labels' do
diff --git a/spec/features/projects/network_graph_spec.rb b/spec/features/projects/network_graph_spec.rb
index 9f9a7787093..2f6a2e90ab9 100644
--- a/spec/features/projects/network_graph_spec.rb
+++ b/spec/features/projects/network_graph_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Network Graph', :js do
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index fe7b4b759a8..010a5de6930 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'New project' do
@@ -62,13 +64,7 @@ describe 'New project' do
find('#import-project-tab').click
end
- context 'when using postgres', :postgresql do
- it { expect(page).to have_link('Manifest file') }
- end
-
- context 'when using mysql', :mysql do
- it { expect(page).not_to have_link('Manifest file') }
- end
+ it { expect(page).to have_link('Manifest file') }
end
context 'Visibility level selector', :js do
@@ -300,7 +296,7 @@ describe 'New project' do
end
end
- context 'from manifest file', :postgresql do
+ context 'from manifest file' do
before do
first('.import_manifest').click
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/pipeline_schedules_spec.rb b/spec/features/projects/pipeline_schedules_spec.rb
index 24041a51383..c21b1e36f9a 100644
--- a/spec/features/projects/pipeline_schedules_spec.rb
+++ b/spec/features/projects/pipeline_schedules_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipeline Schedules', :js do
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 885d5f85989..4fb72eb8737 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Pipelines', :js do
diff --git a/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb b/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb
new file mode 100644
index 00000000000..6d587053b4f
--- /dev/null
+++ b/spec/features/projects/raw/user_interacts_with_raw_endpoint_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Projects > Raw > User interacts with raw endpoint' do
+ include RepoHelpers
+
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :repository, :public) }
+ let(:file_path) { 'master/README.md' }
+
+ before do
+ stub_application_setting(raw_blob_request_limit: 3)
+ project.add_developer(user)
+ create_file_in_repo(project, 'master', 'master', 'README.md', 'readme content')
+
+ sign_in(user)
+ end
+
+ context 'when user access a raw file' do
+ it 'renders the page successfully' do
+ visit project_raw_url(project, file_path)
+
+ expect(source).to eq('') # Body is filled in by gitlab-workhorse
+ end
+ end
+
+ context 'when user goes over the rate requests limit' do
+ it 'returns too many requests' do
+ 4.times do
+ visit project_raw_url(project, file_path)
+ end
+
+ expect(source).to have_content('You are being redirected')
+ click_link('redirected')
+ expect(page).to have_content('You cannot access the raw file. Please wait a minute.')
+ end
+ end
+end
diff --git a/spec/features/projects/releases/user_views_releases_spec.rb b/spec/features/projects/releases/user_views_releases_spec.rb
index 725d7173bce..a9b8ff9dc4d 100644
--- a/spec/features/projects/releases/user_views_releases_spec.rb
+++ b/spec/features/projects/releases/user_views_releases_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views releases', :js do
diff --git a/spec/features/projects/remote_mirror_spec.rb b/spec/features/projects/remote_mirror_spec.rb
index 33e9b73efe8..d357aabead7 100644
--- a/spec/features/projects/remote_mirror_spec.rb
+++ b/spec/features/projects/remote_mirror_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project remote mirror', :feature do
diff --git a/spec/features/projects/serverless/functions_spec.rb b/spec/features/projects/serverless/functions_spec.rb
index 9865dbbfb3c..e82e5b81021 100644
--- a/spec/features/projects/serverless/functions_spec.rb
+++ b/spec/features/projects/serverless/functions_spec.rb
@@ -39,17 +39,19 @@ describe 'Functions', :js do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, cluster: cluster, environment: environment) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: cluster.cluster_project.project,
+ environment: environment)
end
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
stub_kubeclient_knative_services(stub_get_services_options)
diff --git a/spec/features/projects/services/disable_triggers_spec.rb b/spec/features/projects/services/disable_triggers_spec.rb
index 1a13fe03a67..2785f74bee2 100644
--- a/spec/features/projects/services/disable_triggers_spec.rb
+++ b/spec/features/projects/services/disable_triggers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Disable individual triggers' do
diff --git a/spec/features/projects/services/user_activates_asana_spec.rb b/spec/features/projects/services/user_activates_asana_spec.rb
index c44e07dd3b4..b07c77da554 100644
--- a/spec/features/projects/services/user_activates_asana_spec.rb
+++ b/spec/features/projects/services/user_activates_asana_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Asana' do
diff --git a/spec/features/projects/services/user_activates_assembla_spec.rb b/spec/features/projects/services/user_activates_assembla_spec.rb
index 9c3884a7c74..56f7beb8f4b 100644
--- a/spec/features/projects/services/user_activates_assembla_spec.rb
+++ b/spec/features/projects/services/user_activates_assembla_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Assembla' do
diff --git a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb b/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb
index 19573565265..c694eeb1cc2 100644
--- a/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb
+++ b/spec/features/projects/services/user_activates_atlassian_bamboo_ci_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Atlassian Bamboo CI' do
diff --git a/spec/features/projects/services/user_activates_emails_on_push_spec.rb b/spec/features/projects/services/user_activates_emails_on_push_spec.rb
index cc55f7b2060..2015b658295 100644
--- a/spec/features/projects/services/user_activates_emails_on_push_spec.rb
+++ b/spec/features/projects/services/user_activates_emails_on_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Emails on push' do
diff --git a/spec/features/projects/services/user_activates_flowdock_spec.rb b/spec/features/projects/services/user_activates_flowdock_spec.rb
index f981b7e9da9..fc8e75daa0d 100644
--- a/spec/features/projects/services/user_activates_flowdock_spec.rb
+++ b/spec/features/projects/services/user_activates_flowdock_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Flowdock' do
diff --git a/spec/features/projects/services/user_activates_irker_spec.rb b/spec/features/projects/services/user_activates_irker_spec.rb
index 4c8e321b411..898e16ce835 100644
--- a/spec/features/projects/services/user_activates_irker_spec.rb
+++ b/spec/features/projects/services/user_activates_irker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Irker (IRC gateway)' do
diff --git a/spec/features/projects/services/user_activates_issue_tracker_spec.rb b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
index 74b9a2b20cd..5f3bb794b48 100644
--- a/spec/features/projects/services/user_activates_issue_tracker_spec.rb
+++ b/spec/features/projects/services/user_activates_issue_tracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates issue tracker', :js do
@@ -59,7 +61,7 @@ describe 'User activates issue tracker', :js do
context 'when the connection test fails' do
it 'activates the service' do
- stub_request(:head, url).to_raise(HTTParty::Error)
+ stub_request(:head, url).to_raise(Gitlab::HTTP::Error)
click_link(tracker)
diff --git a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
index c50fd93e4cb..9842141285a 100644
--- a/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
+++ b/spec/features/projects/services/user_activates_jetbrains_teamcity_ci_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates JetBrains TeamCity CI' do
diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb
index c52f38e2806..7847b7d5177 100644
--- a/spec/features/projects/services/user_activates_jira_spec.rb
+++ b/spec/features/projects/services/user_activates_jira_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Jira', :js do
diff --git a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
index 70f3a812ee9..f8179979018 100644
--- a/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_mattermost_slash_command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Set up Mattermost slash commands', :js do
diff --git a/spec/features/projects/services/user_activates_packagist_spec.rb b/spec/features/projects/services/user_activates_packagist_spec.rb
index 756e9b33c07..85bd15adbe5 100644
--- a/spec/features/projects/services/user_activates_packagist_spec.rb
+++ b/spec/features/projects/services/user_activates_packagist_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Packagist' do
diff --git a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb b/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
index 1d6b19e0b0c..67ff99c0295 100644
--- a/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
+++ b/spec/features/projects/services/user_activates_pivotaltracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates PivotalTracker' do
diff --git a/spec/features/projects/services/user_activates_prometheus_spec.rb b/spec/features/projects/services/user_activates_prometheus_spec.rb
index 61361c8a2e3..a83d3e2e8be 100644
--- a/spec/features/projects/services/user_activates_prometheus_spec.rb
+++ b/spec/features/projects/services/user_activates_prometheus_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Prometheus' do
diff --git a/spec/features/projects/services/user_activates_pushover_spec.rb b/spec/features/projects/services/user_activates_pushover_spec.rb
index 24612ee1457..34e1cf33f36 100644
--- a/spec/features/projects/services/user_activates_pushover_spec.rb
+++ b/spec/features/projects/services/user_activates_pushover_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Pushover' do
diff --git a/spec/features/projects/services/user_activates_slack_notifications_spec.rb b/spec/features/projects/services/user_activates_slack_notifications_spec.rb
index 24b5d5259db..f23b1d3102a 100644
--- a/spec/features/projects/services/user_activates_slack_notifications_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_notifications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates Slack notifications' do
diff --git a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
index 08cfddf7993..752ef8d592d 100644
--- a/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
+++ b/spec/features/projects/services/user_activates_slack_slash_command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Slack slash commands' do
diff --git a/spec/features/projects/services/user_activates_youtrack_spec.rb b/spec/features/projects/services/user_activates_youtrack_spec.rb
index bb6a030c1cf..8fdeddfdfb4 100644
--- a/spec/features/projects/services/user_activates_youtrack_spec.rb
+++ b/spec/features/projects/services/user_activates_youtrack_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User activates issue tracker', :js do
@@ -46,7 +48,7 @@ describe 'User activates issue tracker', :js do
context 'when the connection test fails' do
it 'activates the service' do
- stub_request(:head, url).to_raise(HTTParty::Error)
+ stub_request(:head, url).to_raise(Gitlab::HTTP::Error)
click_link(tracker)
fill_form
diff --git a/spec/features/projects/services/user_views_services_spec.rb b/spec/features/projects/services/user_views_services_spec.rb
index e9c8cf0fe34..d9358a40602 100644
--- a/spec/features/projects/services/user_views_services_spec.rb
+++ b/spec/features/projects/services/user_views_services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views services' do
diff --git a/spec/features/projects/settings/forked_project_settings_spec.rb b/spec/features/projects/settings/forked_project_settings_spec.rb
index df33d215602..a2c6dd8e288 100644
--- a/spec/features/projects/settings/forked_project_settings_spec.rb
+++ b/spec/features/projects/settings/forked_project_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > For a forked project', :js do
diff --git a/spec/features/projects/settings/integration_settings_spec.rb b/spec/features/projects/settings/integration_settings_spec.rb
index 016ccf63f58..26ea4ec5944 100644
--- a/spec/features/projects/settings/integration_settings_spec.rb
+++ b/spec/features/projects/settings/integration_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Integration settings' do
diff --git a/spec/features/projects/settings/lfs_settings_spec.rb b/spec/features/projects/settings/lfs_settings_spec.rb
index befb306b48d..56606df5a78 100644
--- a/spec/features/projects/settings/lfs_settings_spec.rb
+++ b/spec/features/projects/settings/lfs_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Settings > LFS settings' do
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index bf0c0de89b2..23358d5cd67 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Projects > Settings > Pipelines settings" do
@@ -126,7 +128,7 @@ describe "Projects > Settings > Pipelines settings" do
end
end
- context 'when auto devops is turned on group parent level', :nested_groups do
+ context 'when auto devops is turned on group parent level' do
before do
group = create(:group, parent: create(:group, :auto_devops_enabled))
project.update!(namespace: group)
diff --git a/spec/features/projects/settings/project_badges_spec.rb b/spec/features/projects/settings/project_badges_spec.rb
index 42b5547d43b..5791e30a495 100644
--- a/spec/features/projects/settings/project_badges_spec.rb
+++ b/spec/features/projects/settings/project_badges_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Badges' do
diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb
index 1edfee705c8..1294c8822b6 100644
--- a/spec/features/projects/settings/repository_settings_spec.rb
+++ b/spec/features/projects/settings/repository_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Repository settings' do
@@ -248,11 +250,11 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
- mirror = find('.qa-mirrored-repository-row')
+ mirror = find('.rspec-mirrored-repository-row')
- expect(mirror).to have_selector('.qa-delete-mirror')
- expect(mirror).to have_selector('.qa-disabled-mirror-badge')
- expect(mirror).not_to have_selector('.qa-update-now-button')
+ expect(mirror).to have_selector('.rspec-delete-mirror')
+ expect(mirror).to have_selector('.rspec-disabled-mirror-badge')
+ expect(mirror).not_to have_selector('.rspec-update-now-button')
end
end
end
diff --git a/spec/features/projects/settings/user_archives_project_spec.rb b/spec/features/projects/settings/user_archives_project_spec.rb
index 5008eab4d39..7667fad7b03 100644
--- a/spec/features/projects/settings/user_archives_project_spec.rb
+++ b/spec/features/projects/settings/user_archives_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User archives a project' do
diff --git a/spec/features/projects/settings/user_changes_avatar_spec.rb b/spec/features/projects/settings/user_changes_avatar_spec.rb
index 64335163016..67789b869da 100644
--- a/spec/features/projects/settings/user_changes_avatar_spec.rb
+++ b/spec/features/projects/settings/user_changes_avatar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User changes avatar' do
diff --git a/spec/features/projects/settings/user_changes_default_branch_spec.rb b/spec/features/projects/settings/user_changes_default_branch_spec.rb
index 7dc18601f50..411fc0c7e07 100644
--- a/spec/features/projects/settings/user_changes_default_branch_spec.rb
+++ b/spec/features/projects/settings/user_changes_default_branch_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User changes default branch' do
diff --git a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
index ecfb49b9efe..cd9299150b2 100644
--- a/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
+++ b/spec/features/projects/settings/user_interacts_with_deploy_keys_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User interacts with deploy keys", :js do
diff --git a/spec/features/projects/settings/user_manages_group_links_spec.rb b/spec/features/projects/settings/user_manages_group_links_spec.rb
index e5a58c44e41..7df0bbb9d02 100644
--- a/spec/features/projects/settings/user_manages_group_links_spec.rb
+++ b/spec/features/projects/settings/user_manages_group_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User manages group links' do
diff --git a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
index 0739726f52c..9f09c5c4501 100644
--- a/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
+++ b/spec/features/projects/settings/user_manages_merge_requests_settings_spec.rb
@@ -94,7 +94,7 @@ describe 'Projects > Settings > User manages merge request settings' do
it 'when unchecked sets :printing_merge_request_link_enabled to false' do
uncheck('project_printing_merge_request_link_enabled')
within('.merge-request-settings-form') do
- find('.qa-save-merge-request-changes')
+ find('.rspec-save-merge-request-changes')
click_on('Save changes')
end
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index b8ca11d53f0..6d94388a6e2 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User manages project members' do
diff --git a/spec/features/projects/settings/user_renames_a_project_spec.rb b/spec/features/projects/settings/user_renames_a_project_spec.rb
index d3979b79910..d2daf8b922d 100644
--- a/spec/features/projects/settings/user_renames_a_project_spec.rb
+++ b/spec/features/projects/settings/user_renames_a_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User renames a project' do
diff --git a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
index 069704a1305..9c77a08718e 100644
--- a/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
+++ b/spec/features/projects/settings/user_sees_revoke_deploy_token_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Repository Settings > User sees revoke deploy token modal', :js do
diff --git a/spec/features/projects/settings/user_tags_project_spec.rb b/spec/features/projects/settings/user_tags_project_spec.rb
index e3f06c042b9..a919dd0e4af 100644
--- a/spec/features/projects/settings/user_tags_project_spec.rb
+++ b/spec/features/projects/settings/user_tags_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User tags a project' do
diff --git a/spec/features/projects/settings/user_transfers_a_project_spec.rb b/spec/features/projects/settings/user_transfers_a_project_spec.rb
index 2fdbc04fa62..8989eac77b5 100644
--- a/spec/features/projects/settings/user_transfers_a_project_spec.rb
+++ b/spec/features/projects/settings/user_transfers_a_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > User transfers a project', :js do
@@ -68,7 +70,7 @@ describe 'Projects > Settings > User transfers a project', :js do
end
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'allows transferring a project to a subgroup' do
subgroup = create(:group, parent: group)
diff --git a/spec/features/projects/settings/visibility_settings_spec.rb b/spec/features/projects/settings/visibility_settings_spec.rb
index 1fbc108697f..0e757e647a0 100644
--- a/spec/features/projects/settings/visibility_settings_spec.rb
+++ b/spec/features/projects/settings/visibility_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Settings > Visibility settings', :js do
@@ -57,6 +59,12 @@ describe 'Projects > Settings > Visibility settings', :js do
end
end
end
+
+ context 'disable email notifications' do
+ it 'is visible' do
+ expect(page).to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
context 'as maintainer' do
@@ -74,5 +82,11 @@ describe 'Projects > Settings > Visibility settings', :js do
expect(visibility_select_container).to have_selector 'select[name="project[visibility_level]"]:disabled'
expect(visibility_select_container).to have_content 'The project can be accessed by anyone, regardless of authentication.'
end
+
+ context 'disable email notifications' do
+ it 'is not available' do
+ expect(page).not_to have_selector('.js-emails-disabled', visible: true)
+ end
+ end
end
end
diff --git a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
index 8ba91fe7fd7..6f176c260a2 100644
--- a/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
+++ b/spec/features/projects/show/developer_views_empty_project_instructions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Show > Developer views empty project instructions' do
diff --git a/spec/features/projects/show/download_buttons_spec.rb b/spec/features/projects/show/download_buttons_spec.rb
index fee5f8001b0..5e7453bcdb7 100644
--- a/spec/features/projects/show/download_buttons_spec.rb
+++ b/spec/features/projects/show/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Download buttons' do
diff --git a/spec/features/projects/show/no_password_spec.rb b/spec/features/projects/show/no_password_spec.rb
index 8259d482fd9..0048b1bf017 100644
--- a/spec/features/projects/show/no_password_spec.rb
+++ b/spec/features/projects/show/no_password_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'No Password Alert' do
diff --git a/spec/features/projects/show/redirects_spec.rb b/spec/features/projects/show/redirects_spec.rb
index 8d41c547d77..1b579ab0121 100644
--- a/spec/features/projects/show/redirects_spec.rb
+++ b/spec/features/projects/show/redirects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Redirects' do
diff --git a/spec/features/projects/show/rss_spec.rb b/spec/features/projects/show/rss_spec.rb
index 4d9135b9677..4fe1fde5bdd 100644
--- a/spec/features/projects/show/rss_spec.rb
+++ b/spec/features/projects/show/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > RSS' do
diff --git a/spec/features/projects/show/user_interacts_with_stars_spec.rb b/spec/features/projects/show/user_interacts_with_stars_spec.rb
index ba28c0e1b8a..e4cd8294f7a 100644
--- a/spec/features/projects/show/user_interacts_with_stars_spec.rb
+++ b/spec/features/projects/show/user_interacts_with_stars_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User interacts with project stars' do
diff --git a/spec/features/projects/show/user_manages_notifications_spec.rb b/spec/features/projects/show/user_manages_notifications_spec.rb
index e9dd1dc0f66..851a09cf28a 100644
--- a/spec/features/projects/show/user_manages_notifications_spec.rb
+++ b/spec/features/projects/show/user_manages_notifications_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User manages notifications', :js do
@@ -63,4 +65,12 @@ describe 'Projects > Show > User manages notifications', :js do
end
end
end
+
+ context 'when project emails are disabled' do
+ let(:project) { create(:project, :public, :repository, emails_disabled: true) }
+
+ it 'is disabled' do
+ expect(page).to have_selector('.notifications-btn.disabled', visible: true)
+ end
+ end
end
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
index 46586b891e7..bbb3a066ed5 100644
--- a/spec/features/projects/show/user_sees_collaboration_links_spec.rb
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > Collaboration links' do
diff --git a/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb
index d9d57298929..bdd0ab688f0 100644
--- a/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb
+++ b/spec/features/projects/show/user_sees_deletion_failure_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees a deletion failure message' do
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index 0d59ef4a727..dde9490a5e1 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees Git instructions' do
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 89ce4b50781..a1cad261875 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees last commit CI status' do
diff --git a/spec/features/projects/show/user_sees_readme_spec.rb b/spec/features/projects/show/user_sees_readme_spec.rb
index d80606c1c23..98906de4620 100644
--- a/spec/features/projects/show/user_sees_readme_spec.rb
+++ b/spec/features/projects/show/user_sees_readme_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees README' do
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index 58bd20d7551..c136d7607fd 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Show > User sees setup shortcut buttons' do
diff --git a/spec/features/projects/snippets/create_snippet_spec.rb b/spec/features/projects/snippets/create_snippet_spec.rb
index 6d8a72dd6a3..430883fdf29 100644
--- a/spec/features/projects/snippets/create_snippet_spec.rb
+++ b/spec/features/projects/snippets/create_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Projects > Snippets > Create Snippet', :js do
diff --git a/spec/features/projects/snippets/show_spec.rb b/spec/features/projects/snippets/show_spec.rb
index f3dc13fb52f..e448309356d 100644
--- a/spec/features/projects/snippets/show_spec.rb
+++ b/spec/features/projects/snippets/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > Project snippet', :js do
diff --git a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
index 4e1e2f330ec..239d19d35d1 100644
--- a/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_comments_on_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User comments on a snippet', :js do
diff --git a/spec/features/projects/snippets/user_deletes_snippet_spec.rb b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
index 2bd8bb9d551..1b56d7bf26d 100644
--- a/spec/features/projects/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_deletes_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User deletes a snippet' do
diff --git a/spec/features/projects/snippets/user_updates_snippet_spec.rb b/spec/features/projects/snippets/user_updates_snippet_spec.rb
index 33f77d55f89..c7ff4f89fd6 100644
--- a/spec/features/projects/snippets/user_updates_snippet_spec.rb
+++ b/spec/features/projects/snippets/user_updates_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User updates a snippet' do
diff --git a/spec/features/projects/snippets/user_views_snippets_spec.rb b/spec/features/projects/snippets/user_views_snippets_spec.rb
index 1243db9d9f7..59869244b4a 100644
--- a/spec/features/projects/snippets/user_views_snippets_spec.rb
+++ b/spec/features/projects/snippets/user_views_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Snippets > User views snippets' do
diff --git a/spec/features/projects/sub_group_issuables_spec.rb b/spec/features/projects/sub_group_issuables_spec.rb
index 50e7e934cf6..d6faec2078d 100644
--- a/spec/features/projects/sub_group_issuables_spec.rb
+++ b/spec/features/projects/sub_group_issuables_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe 'Subgroup Issuables', :js, :nested_groups do
+describe 'Subgroup Issuables', :js do
let!(:group) { create(:group, name: 'group') }
let!(:subgroup) { create(:group, parent: group, name: 'subgroup') }
let!(:project) { create(:project, namespace: subgroup, name: 'project') }
diff --git a/spec/features/projects/tags/download_buttons_spec.rb b/spec/features/projects/tags/download_buttons_spec.rb
index 4c8ec53836a..76b2704ae49 100644
--- a/spec/features/projects/tags/download_buttons_spec.rb
+++ b/spec/features/projects/tags/download_buttons_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Download buttons in tags page' do
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
index 2cb2a23b7be..7ac5da86702 100644
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ b/spec/features/projects/tree/create_directory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor new directory', :js do
@@ -47,7 +49,9 @@ describe 'Multi-file editor new directory', :js do
fill_in('commit-message', with: 'commit message ide')
- click_button('Commit')
+ page.within '.multi-file-commit-form' do
+ click_button('Commit')
+ 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 9f5524da8e9..00eefe9db42 100644
--- a/spec/features/projects/tree/create_file_spec.rb
+++ b/spec/features/projects/tree/create_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor new file', :js do
@@ -39,7 +41,9 @@ describe 'Multi-file editor new file', :js do
fill_in('commit-message', with: 'commit message ide')
- click_button('Commit')
+ page.within '.multi-file-commit-form' do
+ click_button('Commit')
+ end
expect(page).to have_content('file name')
end
diff --git a/spec/features/projects/tree/rss_spec.rb b/spec/features/projects/tree/rss_spec.rb
index 022167d9c5f..4300574210f 100644
--- a/spec/features/projects/tree/rss_spec.rb
+++ b/spec/features/projects/tree/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Project Tree RSS' do
diff --git a/spec/features/projects/tree/tree_show_spec.rb b/spec/features/projects/tree/tree_show_spec.rb
index 3ccea2db705..ca616be341d 100644
--- a/spec/features/projects/tree/tree_show_spec.rb
+++ b/spec/features/projects/tree/tree_show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects tree', :js do
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
index e5dd2f40fdf..38c29263b1e 100644
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ b/spec/features/projects/tree/upload_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Multi-file editor upload file', :js do
diff --git a/spec/features/projects/user_creates_project_spec.rb b/spec/features/projects/user_creates_project_spec.rb
index c0932539131..361367f1a3d 100644
--- a/spec/features/projects/user_creates_project_spec.rb
+++ b/spec/features/projects/user_creates_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User creates a project', :js do
@@ -26,7 +28,7 @@ describe 'User creates a project', :js do
expect(page).to have_content(project.url_to_repo)
end
- context 'in a subgroup they do not own', :nested_groups do
+ context 'in a subgroup they do not own' do
let(:parent) { create(:group) }
let!(:subgroup) { create(:group, parent: parent) }
diff --git a/spec/features/projects/user_sees_sidebar_spec.rb b/spec/features/projects/user_sees_sidebar_spec.rb
index 383e8824b7b..4226cdcc759 100644
--- a/spec/features/projects/user_sees_sidebar_spec.rb
+++ b/spec/features/projects/user_sees_sidebar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > User sees sidebar' do
diff --git a/spec/features/projects/user_uses_shortcuts_spec.rb b/spec/features/projects/user_uses_shortcuts_spec.rb
index 64f9a4fcd39..ff24730acef 100644
--- a/spec/features/projects/user_uses_shortcuts_spec.rb
+++ b/spec/features/projects/user_uses_shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User uses shortcuts', :js do
diff --git a/spec/features/projects/user_views_empty_project_spec.rb b/spec/features/projects/user_views_empty_project_spec.rb
index b7c0834d33a..cb6b63d4dd5 100644
--- a/spec/features/projects/user_views_empty_project_spec.rb
+++ b/spec/features/projects/user_views_empty_project_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views an empty project' do
diff --git a/spec/features/projects/view_on_env_spec.rb b/spec/features/projects/view_on_env_spec.rb
index 7bfcd46713e..beb32104809 100644
--- a/spec/features/projects/view_on_env_spec.rb
+++ b/spec/features/projects/view_on_env_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'View on environment', :js do
diff --git a/spec/features/projects/wiki/markdown_preview_spec.rb b/spec/features/projects/wiki/markdown_preview_spec.rb
index 49058d1372a..5c6b04a7141 100644
--- a/spec/features/projects/wiki/markdown_preview_spec.rb
+++ b/spec/features/projects/wiki/markdown_preview_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
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
@@ -18,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")
@@ -48,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")
@@ -71,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")
@@ -94,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
@@ -128,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
@@ -146,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
@@ -164,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"
@@ -178,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/shortcuts_spec.rb b/spec/features/projects/wiki/shortcuts_spec.rb
index c01be1f14ed..806d2f28bb9 100644
--- a/spec/features/projects/wiki/shortcuts_spec.rb
+++ b/spec/features/projects/wiki/shortcuts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Wiki shortcuts', :js do
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 aac095bfa6b..56d0518015d 100644
--- a/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_creates_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe "User creates wiki page" do
@@ -40,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
@@ -56,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
@@ -79,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")
@@ -91,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")
@@ -103,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
@@ -148,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
@@ -179,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}")
@@ -202,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}")
@@ -225,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.
@@ -249,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("")
@@ -272,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_deletes_wiki_page_spec.rb b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
index 18ccd31f3d0..38e5e292064 100644
--- a/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_deletes_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User deletes wiki page', :js do
diff --git a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
index db97d59e918..ab3d912dd15 100644
--- a/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_git_access_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Wiki > User views Git access wiki page' do
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 dbf8af3e5bb..3f3711f9eb8 100644
--- a/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_updates_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User updates wiki page' do
@@ -68,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')
@@ -80,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')
@@ -127,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_empty_spec.rb b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
index e94b3a9432b..ab0f9b750d2 100644
--- a/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_empty_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views empty wiki' do
diff --git a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
index fb0ebe22bf7..471e80b27dc 100644
--- a/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_in_project_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Projects > Wiki > User views wiki in project page' do
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 6e28ec0d7b2..77e725e7f11 100644
--- a/spec/features/projects/wiki/user_views_wiki_page_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User views a wiki page' do
@@ -99,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
@@ -129,7 +130,7 @@ describe 'User views a wiki page' do
end
context 'when page has invalid content encoding' do
- let(:content) { 'whatever'.force_encoding('ISO-8859-1') }
+ let(:content) { (+'whatever').force_encoding('ISO-8859-1') }
before do
allow(Gitlab::EncodingHelper).to receive(:encode!).and_return(content)
@@ -154,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/projects/wiki/user_views_wiki_pages_spec.rb b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
index 5c16d7783f0..6740df1d4ed 100644
--- a/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
+++ b/spec/features/projects/wiki/user_views_wiki_pages_spec.rb
@@ -42,7 +42,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
@@ -75,7 +75,7 @@ describe 'User views wiki pages' do
context 'desc' do
before do
page.within('.wiki-sort-dropdown') do
- page.find('.qa-reverse-sort').click
+ page.find('.rspec-reverse-sort').click
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index b5112758475..67ae26d8d1e 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -314,7 +314,7 @@ describe 'Project' do
end
end
- context 'for subgroups', :js, :nested_groups do
+ context 'for subgroups', :js do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
let(:project) { create(:project, :repository, group: subgroup) }
@@ -387,7 +387,7 @@ describe 'Project' do
end
it_behaves_like 'dirty submit form', [{ form: '.js-general-settings-form', input: 'input[name="project[name]"]' },
- { form: '.qa-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
+ { form: '.rspec-merge-request-settings', input: '#project_printing_merge_request_link_enabled' }]
end
def remove_with_confirm(button_text, confirm_with)
diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb
index 0dbff5a2701..80937223016 100644
--- a/spec/features/protected_branches_spec.rb
+++ b/spec/features/protected_branches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Protected Branches', :js do
diff --git a/spec/features/protected_tags_spec.rb b/spec/features/protected_tags_spec.rb
index 652542b1719..3322a747cf5 100644
--- a/spec/features/protected_tags_spec.rb
+++ b/spec/features/protected_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Protected Tags', :js do
@@ -14,6 +16,7 @@ describe 'Protected Tags', :js do
it "allows creating explicit protected tags" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('some-tag') }
@@ -27,6 +30,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content(commit.id[0..7]) }
@@ -35,6 +39,7 @@ describe 'Protected Tags', :js do
it "displays an error message if the named tag does not exist" do
visit project_protected_tags_path(project)
set_protected_tag_name('some-tag')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('tag was removed') }
@@ -45,6 +50,7 @@ describe 'Protected Tags', :js do
it "allows creating protected tags with a wildcard" do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") { expect(page).to have_content('*-stable') }
@@ -58,6 +64,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
within(".protected-tags-list") do
@@ -73,6 +80,7 @@ describe 'Protected Tags', :js do
visit project_protected_tags_path(project)
set_protected_tag_name('*-stable')
+ set_allowed_to('create') if Gitlab.ee?
click_on "Protect"
visit project_protected_tags_path(project)
diff --git a/spec/features/raven_js_spec.rb b/spec/features/raven_js_spec.rb
index a4dd79b3179..38699f0cc1b 100644
--- a/spec/features/raven_js_spec.rb
+++ b/spec/features/raven_js_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'RavenJS' do
diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb
index 8bfaf558466..3af4b51b9b1 100644
--- a/spec/features/read_only_spec.rb
+++ b/spec/features/read_only_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'read-only message' do
diff --git a/spec/features/reportable_note/commit_spec.rb b/spec/features/reportable_note/commit_spec.rb
index 54ebda9dcab..3502401095e 100644
--- a/spec/features/reportable_note/commit_spec.rb
+++ b/spec/features/reportable_note/commit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on commit', :js do
diff --git a/spec/features/reportable_note/issue_spec.rb b/spec/features/reportable_note/issue_spec.rb
index bce1f7a3780..c45ef77df55 100644
--- a/spec/features/reportable_note/issue_spec.rb
+++ b/spec/features/reportable_note/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on issue', :js do
diff --git a/spec/features/reportable_note/merge_request_spec.rb b/spec/features/reportable_note/merge_request_spec.rb
index d00324156c4..2e4d032754b 100644
--- a/spec/features/reportable_note/merge_request_spec.rb
+++ b/spec/features/reportable_note/merge_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on merge request', :js do
diff --git a/spec/features/reportable_note/snippets_spec.rb b/spec/features/reportable_note/snippets_spec.rb
index 06218d9b286..c2c853cdb05 100644
--- a/spec/features/reportable_note/snippets_spec.rb
+++ b/spec/features/reportable_note/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Reportable note on snippets', :js do
diff --git a/spec/features/runners_spec.rb b/spec/features/runners_spec.rb
index 6eae0be4b9f..63d21d94b5f 100644
--- a/spec/features/runners_spec.rb
+++ b/spec/features/runners_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Runners' do
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index ecec2f3e043..4ca79ccaea8 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -1,9 +1,26 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for code' do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) }
+ def submit_search(search, with_send_keys: false)
+ page.within('.search') do
+ field = find_field('search')
+ field.fill_in(with: search)
+
+ if with_send_keys
+ field.send_keys(:enter)
+ else
+ click_button("Go")
+ end
+ end
+
+ click_link('Code')
+ end
+
context 'when signed in' do
before do
project.add_maintainer(user)
@@ -13,12 +30,7 @@ describe 'User searches for code' do
it 'finds a file' do
visit(project_path(project))
- page.within('.search') do
- fill_in('search', with: 'application.js')
- click_button('Go')
- end
-
- click_link('Code')
+ submit_search('application.js')
expect(page).to have_selector('.file-content .code')
expect(page).to have_selector("span.line[lang='javascript']")
@@ -27,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
@@ -45,6 +56,71 @@ 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
+ let(:ref_name) { 'v1.0.0' }
+
+ before do
+ visit(project_tree_path(project, ref_name))
+ submit_search('gitlab-grack', with_send_keys: true)
+ end
+
+ it 'shows ref switcher in code result summary' do
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+ it 'persists branch name across search' do
+ find('.btn-search').click
+ expect(find('.js-project-refs-dropdown')).to have_text(ref_name)
+ end
+
+ # this example is use to test the desgine that the refs is not
+ # only repersent the branch as well as the tags.
+ it 'ref swither list all the branchs and tags' do
+ find('.js-project-refs-dropdown').click
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('sha-starting-with-large-number')
+ expect(find('.dropdown-page-one .dropdown-content')).to have_link('v1.0.0')
+ end
+
+ it 'search result changes when refs switched' do
+ expect(find('.search-results')).not_to have_content('path = gitlab-grack')
+ find('.js-project-refs-dropdown').click
+ find('.dropdown-page-one .dropdown-content').click_link('master')
+ expect(find('.search-results')).to have_content('path = gitlab-grack')
+ end
+ end
+
+ it 'no ref switcher shown in issue result summary', :js do
+ issue = create(:issue, title: 'test', project: project)
+ visit(project_tree_path(project))
+ submit_search('test', with_send_keys: true)
+ expect(page).to have_selector('.js-project-refs-dropdown')
+ page.within('.search-filter') do
+ click_link('Issues')
+ end
+ expect(find(:css, '.search-results')).to have_link(issue.title)
+ expect(page).not_to have_selector('.js-project-refs-dropdown')
end
end
@@ -56,8 +132,7 @@ describe 'User searches for code' do
end
it 'finds code' do
- fill_in('search', with: 'rspec')
- click_button('Go')
+ submit_search('rspec')
page.within('.results') do
expect(find(:css, '.search-results')).to have_content('Update capybara, rspec-rails, poltergeist to recent versions')
diff --git a/spec/features/search/user_searches_for_comments_spec.rb b/spec/features/search/user_searches_for_comments_spec.rb
index c7c469a262c..2ce3fa4735f 100644
--- a/spec/features/search/user_searches_for_comments_spec.rb
+++ b/spec/features/search/user_searches_for_comments_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for comments' do
diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb
index 998f8521384..81c299752ea 100644
--- a/spec/features/search/user_searches_for_commits_spec.rb
+++ b/spec/features/search/user_searches_for_commits_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for commits' do
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index 4bff269f89e..f0fcf6df70c 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for issues', :js do
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 75d44e413cb..d005b87cdfe 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for merge requests', :js do
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 7d52c4c8bcc..00964ab4f1d 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for milestones', :js do
diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb
index 242e437e41c..082c1ae8e4a 100644
--- a/spec/features/search/user_searches_for_projects_spec.rb
+++ b/spec/features/search/user_searches_for_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for projects' do
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
index 3725143291d..e10c1afc0b8 100644
--- a/spec/features/search/user_searches_for_users_spec.rb
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for users' do
context 'when on the dashboard' do
- it 'finds the user' do
+ it 'finds the user', :js do
create(:user, username: 'gob_bluth', name: 'Gob Bluth')
sign_in(create(:user))
@@ -10,7 +12,7 @@ describe 'User searches for users' do
visit dashboard_projects_path
fill_in 'search', with: 'gob'
- click_button 'Go'
+ find('#search').send_keys(:enter)
expect(page).to have_content('Users 1')
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index ee43755262e..0a5abfbf46a 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User searches for wiki pages', :js do
diff --git a/spec/features/search/user_uses_header_search_field_spec.rb b/spec/features/search/user_uses_header_search_field_spec.rb
index 7ddd5c12cdf..5006631cc14 100644
--- a/spec/features/search/user_uses_header_search_field_spec.rb
+++ b/spec/features/search/user_uses_header_search_field_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User uses header search field', :js do
@@ -20,7 +22,7 @@ describe 'User uses header search field', :js do
fill_in('search', with: 'gitlab')
find('#search').native.send_keys(:enter)
- page.within('.breadcrumbs-sub-title') do
+ page.within('.page-title') do
expect(page).to have_content('Search')
end
end
@@ -94,6 +96,23 @@ describe 'User uses header search field', :js do
let(:url) { root_path }
let(:scope_name) { 'All GitLab' }
end
+
+ context 'when searching through the search field' do
+ before do
+ create(:issue, project: project, title: 'project issue')
+
+ fill_in('search', with: 'project')
+ find('#search').send_keys(:enter)
+ end
+
+ it 'displays result counts for all categories' do
+ expect(page).to have_content('Projects 1')
+ expect(page).to have_content('Issues 1')
+ expect(page).to have_content('Merge requests 0')
+ expect(page).to have_content('Milestones 0')
+ expect(page).to have_content('Users 0')
+ end
+ end
end
context 'when user is in a project scope' do
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index 0725ff178ac..fbd7da3c643 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User uses search filters', :js do
@@ -20,7 +22,7 @@ describe 'User uses search filters', :js do
wait_for_requests
- page.within('.search-holder') do
+ page.within('.search-page-form') do
click_link(group.name)
end
diff --git a/spec/features/security/admin_access_spec.rb b/spec/features/security/admin_access_spec.rb
index ff679034a36..9b2c873b2aa 100644
--- a/spec/features/security/admin_access_spec.rb
+++ b/spec/features/security/admin_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Admin::Projects" do
diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb
index 07cddc92ac4..13fafd88a4c 100644
--- a/spec/features/security/dashboard_access_spec.rb
+++ b/spec/features/security/dashboard_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Dashboard access" do
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index 51b32ba6c03..f4f3872aa09 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Internal Group access' do
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index a776169a8e5..9cef8ef777c 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Private Group access' do
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index 3a53c3c2bc7..bbe74f0dab0 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Public Group access' do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index e23000fa676..42c747c674f 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Internal Project Access" do
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index f380bc122a7..a86d240b7d6 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Private Project Access" do
@@ -126,7 +128,7 @@ describe "Private Project Access" do
describe "GET /:project_path/blob" do
let(:commit) { project.repository.commit }
- subject { project_blob_path(project, File.join(commit.id, '.gitignore'))}
+ subject { project_blob_path(project, File.join(commit.id, '.gitignore')) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 57d56371719..8d7f8c84358 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Public Project Access" do
diff --git a/spec/features/security/project/snippet/internal_access_spec.rb b/spec/features/security/project/snippet/internal_access_spec.rb
index 0c58fdf2f12..4b6c7d2c8fb 100644
--- a/spec/features/security/project/snippet/internal_access_spec.rb
+++ b/spec/features/security/project/snippet/internal_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Internal Project Snippets Access" do
diff --git a/spec/features/security/project/snippet/private_access_spec.rb b/spec/features/security/project/snippet/private_access_spec.rb
index 420f1938763..3135d25cc10 100644
--- a/spec/features/security/project/snippet/private_access_spec.rb
+++ b/spec/features/security/project/snippet/private_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Private Project Snippets Access" do
diff --git a/spec/features/security/project/snippet/public_access_spec.rb b/spec/features/security/project/snippet/public_access_spec.rb
index 6c75902c6e9..81689a7bcb5 100644
--- a/spec/features/security/project/snippet/public_access_spec.rb
+++ b/spec/features/security/project/snippet/public_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe "Public Project Snippets Access" do
diff --git a/spec/features/signed_commits_spec.rb b/spec/features/signed_commits_spec.rb
index e2b3444272e..70e6978a7b6 100644
--- a/spec/features/signed_commits_spec.rb
+++ b/spec/features/signed_commits_spec.rb
@@ -15,8 +15,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user changes his email which makes the gpg key verified
perform_enqueued_jobs do
@@ -26,8 +26,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
it 'changes from unverified to verified when the user adds the missing gpg key' do
@@ -36,8 +36,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).to have_link 'Unverified'
- expect(page).not_to have_link 'Verified'
+ expect(page).to have_button 'Unverified'
+ expect(page).not_to have_button 'Verified'
# user adds the gpg key which makes the signature valid
perform_enqueued_jobs do
@@ -46,8 +46,8 @@ describe 'GPG signed commits' do
visit project_commit_path(project, ref)
- expect(page).not_to have_link 'Unverified'
- expect(page).to have_link 'Verified'
+ expect(page).not_to have_button 'Unverified'
+ expect(page).to have_button 'Verified'
end
context 'shows popover badges', :js do
@@ -136,7 +136,7 @@ describe 'GPG signed commits' do
visit project_commit_path(project, GpgHelpers::SIGNED_AND_AUTHORED_SHA)
# wait for the signature to get generated
- expect(page).to have_link 'Verified'
+ expect(page).to have_button 'Verified'
user_1.destroy!
diff --git a/spec/features/snippets/embedded_snippet_spec.rb b/spec/features/snippets/embedded_snippet_spec.rb
index ab661f6fc69..d6275b5a265 100644
--- a/spec/features/snippets/embedded_snippet_spec.rb
+++ b/spec/features/snippets/embedded_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Embedded Snippets' do
diff --git a/spec/features/snippets/explore_spec.rb b/spec/features/snippets/explore_spec.rb
index c08f25875f8..b48a5691e96 100644
--- a/spec/features/snippets/explore_spec.rb
+++ b/spec/features/snippets/explore_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Explore Snippets' do
diff --git a/spec/features/snippets/internal_snippet_spec.rb b/spec/features/snippets/internal_snippet_spec.rb
index f6215b481dc..8454a347382 100644
--- a/spec/features/snippets/internal_snippet_spec.rb
+++ b/spec/features/snippets/internal_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Internal Snippets', :js do
diff --git a/spec/features/snippets/notes_on_personal_snippets_spec.rb b/spec/features/snippets/notes_on_personal_snippets_spec.rb
index 78e0a43ce6d..2bd01be25e9 100644
--- a/spec/features/snippets/notes_on_personal_snippets_spec.rb
+++ b/spec/features/snippets/notes_on_personal_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Comments on personal snippets', :js do
diff --git a/spec/features/snippets/public_snippets_spec.rb b/spec/features/snippets/public_snippets_spec.rb
index 71c72b98fad..e32a9292e22 100644
--- a/spec/features/snippets/public_snippets_spec.rb
+++ b/spec/features/snippets/public_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Public Snippets', :js do
diff --git a/spec/features/snippets/search_snippets_spec.rb b/spec/features/snippets/search_snippets_spec.rb
index c137b0bcd96..4a8c5f9b1fe 100644
--- a/spec/features/snippets/search_snippets_spec.rb
+++ b/spec/features/snippets/search_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Search Snippets' do
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index 74fdfcf492e..edf7d37fd6d 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippet', :js do
diff --git a/spec/features/snippets/user_creates_snippet_spec.rb b/spec/features/snippets/user_creates_snippet_spec.rb
index 93d77d5b5ce..a4a5407d1f7 100644
--- a/spec/features/snippets/user_creates_snippet_spec.rb
+++ b/spec/features/snippets/user_creates_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User creates snippet', :js do
diff --git a/spec/features/snippets/user_deletes_snippet_spec.rb b/spec/features/snippets/user_deletes_snippet_spec.rb
index 7bdccacb0fa..9773aca849a 100644
--- a/spec/features/snippets/user_deletes_snippet_spec.rb
+++ b/spec/features/snippets/user_deletes_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User deletes snippet' do
diff --git a/spec/features/snippets/user_edits_snippet_spec.rb b/spec/features/snippets/user_edits_snippet_spec.rb
index 77f62990158..5ff12c37aff 100644
--- a/spec/features/snippets/user_edits_snippet_spec.rb
+++ b/spec/features/snippets/user_edits_snippet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User edits snippet', :js do
@@ -32,7 +34,7 @@ describe 'User edits snippet', :js do
click_button('Save changes')
wait_for_requests
- link = find('a.no-attachment-icon img[alt="banana_sample"]')['src']
+ link = find('a.no-attachment-icon img:not(.lazy)[alt="banana_sample"]')['src']
expect(link).to match(%r{/uploads/-/system/personal_snippet/#{snippet.id}/\h{32}/banana_sample\.gif\z})
end
diff --git a/spec/features/snippets/user_sees_breadcrumb_links.rb b/spec/features/snippets/user_sees_breadcrumb_links.rb
deleted file mode 100644
index 696f2b93390..00000000000
--- a/spec/features/snippets/user_sees_breadcrumb_links.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'rails_helper'
-
-describe 'New user snippet breadcrumbs' do
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
- visit new_snippet_path
- end
-
- it 'display a link to user snippets and new user snippet pages' do
- page.within '.breadcrumbs' do
- expect(find_link('Snippets')[:href]).to end_with(dashboard_snippets_path)
- expect(find_link('New')[:href]).to end_with(new_snippet_path)
- end
- end
-end
diff --git a/spec/features/snippets/user_snippets_spec.rb b/spec/features/snippets/user_snippets_spec.rb
index e3065a899cc..4e9215db945 100644
--- a/spec/features/snippets/user_snippets_spec.rb
+++ b/spec/features/snippets/user_snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User Snippets' do
diff --git a/spec/features/snippets_spec.rb b/spec/features/snippets_spec.rb
index 96c50f6c804..9df6fe7d16b 100644
--- a/spec/features/snippets_spec.rb
+++ b/spec/features/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippets' do
diff --git a/spec/features/tags/developer_creates_tag_spec.rb b/spec/features/tags/developer_creates_tag_spec.rb
index b2ad7ed8f3f..a2d53b04c9d 100644
--- a/spec/features/tags/developer_creates_tag_spec.rb
+++ b/spec/features/tags/developer_creates_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer creates tag' do
diff --git a/spec/features/tags/developer_deletes_tag_spec.rb b/spec/features/tags/developer_deletes_tag_spec.rb
index dc4c7a4fb0a..82b416c3a7f 100644
--- a/spec/features/tags/developer_deletes_tag_spec.rb
+++ b/spec/features/tags/developer_deletes_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer deletes tag' do
diff --git a/spec/features/tags/developer_updates_tag_spec.rb b/spec/features/tags/developer_updates_tag_spec.rb
index 1e11fc9e5d5..0cdd953b9ae 100644
--- a/spec/features/tags/developer_updates_tag_spec.rb
+++ b/spec/features/tags/developer_updates_tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer updates tag' do
diff --git a/spec/features/tags/developer_views_tags_spec.rb b/spec/features/tags/developer_views_tags_spec.rb
index 09e644c6b97..b892b2b4d12 100644
--- a/spec/features/tags/developer_views_tags_spec.rb
+++ b/spec/features/tags/developer_views_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Developer views tags' do
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 33d9c10f5e8..acffc4ce580 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Task Lists' do
diff --git a/spec/features/triggers_spec.rb b/spec/features/triggers_spec.rb
index 7c44680e9f7..19cd21e4161 100644
--- a/spec/features/triggers_spec.rb
+++ b/spec/features/triggers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Triggers', :js do
@@ -81,29 +83,6 @@ describe 'Triggers', :js do
end
end
- describe 'trigger "Take ownership" workflow' do
- before do
- create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
- visit project_settings_ci_cd_path(@project)
- end
-
- it 'button "Take ownership" has correct alert' do
- expected_alert = 'By taking ownership you will bind this trigger to your user account. With this the trigger will have access to all your projects as if it was you. Are you sure?'
- expect(page.find('a.btn-trigger-take-ownership')['data-confirm']).to eq expected_alert
- end
-
- it 'take trigger ownership' do
- # See if "Take ownership" on trigger works post trigger creation
- page.accept_confirm do
- first(:link, "Take ownership").send_keys(:return)
- end
-
- expect(page.find('.flash-notice')).to have_content 'Trigger was re-assigned.'
- expect(page.find('.triggers-list')).to have_content trigger_title
- expect(page.find('.triggers-list .trigger-owner')).to have_content user.name
- end
- end
-
describe 'trigger "Revoke" workflow' do
before do
create(:ci_trigger, owner: user2, project: @project, description: trigger_title)
diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb
index ea02f36d9d0..bb18703f90e 100644
--- a/spec/features/u2f_spec.rb
+++ b/spec/features/u2f_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do
diff --git a/spec/features/unsubscribe_links_spec.rb b/spec/features/unsubscribe_links_spec.rb
index 392d8e3e1c1..2f8b715289c 100644
--- a/spec/features/unsubscribe_links_spec.rb
+++ b/spec/features/unsubscribe_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Unsubscribe links' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
index 72b53bae46a..40d864e0002 100644
--- a/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_group_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads avatar to group' do
diff --git a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
index 48f8b8bf77e..4c694365e3b 100644
--- a/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
+++ b/spec/features/uploads/user_uploads_avatar_to_profile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads avatar to profile' do
diff --git a/spec/features/uploads/user_uploads_file_to_note_spec.rb b/spec/features/uploads/user_uploads_file_to_note_spec.rb
index 24a00c86b0a..6bf8e8ea74f 100644
--- a/spec/features/uploads/user_uploads_file_to_note_spec.rb
+++ b/spec/features/uploads/user_uploads_file_to_note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User uploads file to note' do
diff --git a/spec/features/usage_stats_consent_spec.rb b/spec/features/usage_stats_consent_spec.rb
index dd8f3179895..14232b1b370 100644
--- a/spec/features/usage_stats_consent_spec.rb
+++ b/spec/features/usage_stats_consent_spec.rb
@@ -8,7 +8,15 @@ describe 'Usage stats consent' do
let(:message) { 'To help improve GitLab, we would like to periodically collect usage information.' }
before do
- allow(user).to receive(:has_current_license?).and_return false
+ if Gitlab.ee?
+ allow_any_instance_of(EE::User)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ else
+ allow(user)
+ .to receive(:has_current_license?)
+ .and_return(false)
+ end
gitlab_sign_in(user)
end
diff --git a/spec/features/user_can_display_performance_bar_spec.rb b/spec/features/user_can_display_performance_bar_spec.rb
index e069c2fddd1..b2036108d42 100644
--- a/spec/features/user_can_display_performance_bar_spec.rb
+++ b/spec/features/user_can_display_performance_bar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'User can display performance bar', :js do
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment.rb
deleted file mode 100644
index f1e07e55799..00000000000
--- a/spec/features/user_opens_link_to_comment.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe 'User opens link to comment', :js do
- let(:project) { create(:project, :public) }
- let(:note) { create(:note_on_issue, project: project) }
-
- context 'authenticated user' do
- let(:user) { create(:user) }
-
- before do
- sign_in(user)
- end
-
- it 'switches to all activity and does not show error message' do
- create(:user_preference, user: user, issue_notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
-
- visit Gitlab::UrlBuilder.build(note)
-
- expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
- expect(page).not_to have_content('Something went wrong while fetching comments')
- end
- end
-
- context 'anonymous user' do
- it 'does not show error message' do
- visit Gitlab::UrlBuilder.build(note)
-
- expect(page).not_to have_content('Something went wrong while fetching comments')
- end
- end
-end
diff --git a/spec/features/user_opens_link_to_comment_spec.rb b/spec/features/user_opens_link_to_comment_spec.rb
new file mode 100644
index 00000000000..9533a4fe40d
--- /dev/null
+++ b/spec/features/user_opens_link_to_comment_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User opens link to comment', :js do
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ context 'authenticated user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'switches to all activity and does not show error message' do
+ create(:user_preference, user: user, issue_notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+
+ visit Gitlab::UrlBuilder.build(note)
+
+ wait_for_requests
+
+ expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+
+ # Auto-switching to show all notes shouldn't be persisted
+ expect(user.reload.notes_filter_for(note.noteable)).to eq(UserPreference::NOTES_FILTERS[:only_activity])
+ end
+ end
+
+ context 'anonymous user' do
+ it 'does not show error message' do
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+end
diff --git a/spec/features/user_sees_revert_modal_spec.rb b/spec/features/user_sees_revert_modal_spec.rb
index d2cdade88d1..35828b5f086 100644
--- a/spec/features/user_sees_revert_modal_spec.rb
+++ b/spec/features/user_sees_revert_modal_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe 'Merge request > User sees revert modal', :js do
diff --git a/spec/features/user_sorts_things_spec.rb b/spec/features/user_sorts_things_spec.rb
index 0295f588326..41f8f3761e8 100644
--- a/spec/features/user_sorts_things_spec.rb
+++ b/spec/features/user_sorts_things_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
# The main goal of this spec is not to check whether the sorting UI works, but
diff --git a/spec/features/users/active_sessions_spec.rb b/spec/features/users/active_sessions_spec.rb
index 25349b5d036..c717e89eafb 100644
--- a/spec/features/users/active_sessions_spec.rb
+++ b/spec/features/users/active_sessions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Active user sessions', :clean_gitlab_redis_shared_state do
diff --git a/spec/features/users/add_email_to_existing_account.rb b/spec/features/users/add_email_to_existing_account.rb
deleted file mode 100644
index 4355f769429..00000000000
--- a/spec/features/users/add_email_to_existing_account.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-require 'spec_helper'
-
-describe 'AdditionalEmailToExistingAccount' do
- describe 'add secondary email associated with account' do
- let(:user) { create(:user) }
-
- it 'verifies confirmation of additional email' do
- sign_in(user)
-
- email = create(:email, user: user)
- visit email_confirmation_path(confirmation_token: email.confirmation_token)
- expect(page).to have_content 'Your email address has been successfully confirmed.'
- end
- end
-end
diff --git a/spec/features/users/add_email_to_existing_account_spec.rb b/spec/features/users/add_email_to_existing_account_spec.rb
new file mode 100644
index 00000000000..42e352399a8
--- /dev/null
+++ b/spec/features/users/add_email_to_existing_account_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'AdditionalEmailToExistingAccount' do
+ describe 'add secondary email associated with account' do
+ let(:user) { create(:user) }
+
+ it 'verifies confirmation of additional email' do
+ sign_in(user)
+
+ email = create(:email, user: user)
+ visit email_confirmation_path(confirmation_token: email.confirmation_token)
+ expect(page).to have_content 'Your email address has been successfully confirmed.'
+ end
+ end
+end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index efba303033b..8e4db2ca840 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Login' do
@@ -93,6 +95,42 @@ describe 'Login' do
end
end
+ describe 'with an unconfirmed email address' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ context 'within the grace period' do
+ it 'allows to login' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(page).not_to have_content('You have to confirm your email address before continuing.')
+ expect(page).not_to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+
+ context 'when the confirmation grace period is expired' do
+ it 'prevents the user from logging in and renders a resend confirmation email link' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+ end
+ end
+
describe 'with the ghost user' do
it 'disallows login' do
expect(authentication_metrics)
@@ -132,7 +170,6 @@ describe 'Login' do
it 'does not show a "You are already signed in." error message' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -144,7 +181,6 @@ describe 'Login' do
it 'allows login with valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(user.current_otp)
@@ -170,7 +206,6 @@ describe 'Login' do
it 'allows login with invalid code, then valid code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code('foo')
@@ -179,6 +214,15 @@ describe 'Login' do
enter_code(user.current_otp)
expect(current_path).to eq root_path
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(user.current_otp)
+ end
end
context 'using backup code' do
@@ -195,7 +239,6 @@ describe 'Login' do
it 'allows login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
enter_code(codes.sample)
@@ -206,7 +249,6 @@ describe 'Login' do
it 'invalidates the used code' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
expect { enter_code(codes.sample) }
@@ -216,7 +258,6 @@ describe 'Login' do
it 'invalidates backup codes twice in a row' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter).twice
- .and increment(:user_session_override_counter).twice
.and increment(:user_two_factor_authenticated_counter).twice
.and increment(:user_session_destroyed_counter)
@@ -230,6 +271,15 @@ describe 'Login' do
expect { enter_code(codes.sample) }
.to change { user.reload.otp_backup_codes.size }.by(-1)
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ .and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ enter_code(codes.sample)
+ end
end
context 'with invalid code' do
@@ -274,7 +324,7 @@ describe 'Login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -287,8 +337,8 @@ describe 'Login' do
it 'shows 2FA prompt after OAuth login' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
sign_in_using_saml!
@@ -329,6 +379,14 @@ describe 'Login' do
expect(page).not_to have_content(I18n.t('devise.failure.already_authenticated'))
end
+
+ it 'triggers ActiveSession.cleanup for the user' do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+ expect(ActiveSession).to receive(:cleanup).with(user).once.and_call_original
+
+ gitlab_sign_in(user)
+ end
end
context 'with invalid username and password' do
@@ -649,7 +707,6 @@ describe 'Login' do
it 'asks the user to accept the terms' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
.and increment(:user_two_factor_authenticated_counter)
visit new_user_session_path
@@ -708,7 +765,6 @@ describe 'Login' do
it 'asks the user to accept the terms before setting an email' do
expect(authentication_metrics)
.to increment(:user_authenticated_counter)
- .and increment(:user_session_override_counter)
gitlab_sign_in_via('saml', user, 'my-uid')
@@ -725,4 +781,39 @@ describe 'Login' do
end
end
end
+
+ context 'when sending confirmation email and not yet confirmed' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ stub_feature_flags(soft_email_confirmation: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ it 'allows login and shows a flash warning to confirm the email address' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq root_path
+ expect(page).to have_content("Please check your email (#{user.email}) to verify that you own this address.")
+ end
+
+ context "when not having confirmed within Devise's allow_unconfirmed_access_for time" do
+ it 'does not allow login and shows a flash alert to confirm the email address' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq new_user_session_path
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/users/logout_spec.rb b/spec/features/users/logout_spec.rb
index 635729efa53..64202776b9b 100644
--- a/spec/features/users/logout_spec.rb
+++ b/spec/features/users/logout_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Logout/Sign out', :js do
diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb
index bfa85696e19..b3531d040e4 100644
--- a/spec/features/users/overview_spec.rb
+++ b/spec/features/users/overview_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Overview tab on a user profile', :js do
diff --git a/spec/features/users/rss_spec.rb b/spec/features/users/rss_spec.rb
index 9af4447056e..ecdbf032623 100644
--- a/spec/features/users/rss_spec.rb
+++ b/spec/features/users/rss_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User RSS' do
diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb
index 351750c0179..932c1d8d4bd 100644
--- a/spec/features/users/show_spec.rb
+++ b/spec/features/users/show_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'User page' do
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index 50befa7028d..fb927a9ca3b 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -1,8 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Signup' do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
let(:new_user) { build_stubbed(:user) }
describe 'username validation', :js do
@@ -160,24 +166,51 @@ describe 'Signup' do
end
context 'with no errors' do
- context "when sending confirmation email" do
+ context 'when sending confirmation email' do
before do
stub_application_setting(send_user_confirmation_email: true)
end
- it 'creates the user account and sends a confirmation email' do
- visit root_path
+ context 'when soft email confirmation is not enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: false)
+ end
- fill_in 'new_user_name', with: new_user.name
- fill_in 'new_user_username', with: new_user.username
- fill_in 'new_user_email', with: new_user.email
- fill_in 'new_user_email_confirmation', with: new_user.email
- fill_in 'new_user_password', with: new_user.password
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
+
+ expect { click_button 'Register' }.to change { User.count }.by(1)
+
+ expect(current_path).to eq users_almost_there_path
+ expect(page).to have_content('Please check your email to confirm your account')
+ end
+ end
+
+ context 'when soft email confirmation is enabled' do
+ before do
+ stub_feature_flags(soft_email_confirmation: true)
+ end
+
+ it 'creates the user account and sends a confirmation email' do
+ visit root_path
+
+ fill_in 'new_user_name', with: new_user.name
+ fill_in 'new_user_username', with: new_user.username
+ fill_in 'new_user_email', with: new_user.email
+ fill_in 'new_user_email_confirmation', with: new_user.email
+ fill_in 'new_user_password', with: new_user.password
- expect { click_button 'Register' }.to change { User.count }.by(1)
+ expect { click_button 'Register' }.to change { User.count }.by(1)
- expect(current_path).to eq users_almost_there_path
- expect(page).to have_content("Please check your email to confirm your account")
+ expect(current_path).to eq dashboard_projects_path
+ expect(page).to have_content("Please check your email (#{new_user.email}) to verify that you own this address.")
+ end
end
end
diff --git a/spec/features/users/snippets_spec.rb b/spec/features/users/snippets_spec.rb
index 8c697e33436..8b6bf54b642 100644
--- a/spec/features/users/snippets_spec.rb
+++ b/spec/features/users/snippets_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Snippets tab on a user profile', :js do
diff --git a/spec/features/users/terms_spec.rb b/spec/features/users/terms_spec.rb
index a770309e6b0..d44e3622a56 100644
--- a/spec/features/users/terms_spec.rb
+++ b/spec/features/users/terms_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Users > Terms' do
diff --git a/spec/features/users/user_browses_projects_on_user_page_spec.rb b/spec/features/users/user_browses_projects_on_user_page_spec.rb
index 6a9b281fb4c..9a1ee7715d6 100644
--- a/spec/features/users/user_browses_projects_on_user_page_spec.rb
+++ b/spec/features/users/user_browses_projects_on_user_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'Users > User browses projects on user page', :js do
@@ -53,6 +55,19 @@ describe 'Users > User browses projects on user page', :js do
expect(page).to have_content(project2.name)
end
+ it 'does not have incorrectly interpolated message', :js do
+ project = create(:project, namespace: user.namespace, updated_at: 2.minutes.since)
+
+ sign_in(user)
+ visit user_path(user)
+ click_nav_link('Personal projects')
+
+ wait_for_requests
+
+ expect(page).to have_content(project.name)
+ expect(page).not_to have_content("_('Updated')")
+ end
+
context 'when not signed in' do
it 'renders user public project' do
visit user_path(user)
diff --git a/spec/finders/autocomplete/move_to_project_finder_spec.rb b/spec/finders/autocomplete/move_to_project_finder_spec.rb
index c3bc410a7f6..4a87b47bd08 100644
--- a/spec/finders/autocomplete/move_to_project_finder_spec.rb
+++ b/spec/finders/autocomplete/move_to_project_finder_spec.rb
@@ -6,9 +6,9 @@ describe Autocomplete::MoveToProjectFinder do
let(:no_access_project) { create(:project) }
let(:guest_project) { create(:project) }
- let(:reporter_project) { create(:project) }
- let(:developer_project) { create(:project) }
- let(:maintainer_project) { create(:project) }
+ let(:reporter_project) { create(:project, name: 'name') }
+ let(:developer_project) { create(:project, name: 'name2') }
+ let(:maintainer_project) { create(:project, name: 'name3') }
describe '#execute' do
context 'filter' do
@@ -20,14 +20,14 @@ describe Autocomplete::MoveToProjectFinder do
expect(finder.execute).to be_empty
end
- it 'returns projects equal or above Gitlab::Access::REPORTER ordered by id in descending order' do
+ it 'returns projects equal or above Gitlab::Access::REPORTER ordered by name' do
reporter_project.add_reporter(user)
developer_project.add_developer(user)
maintainer_project.add_maintainer(user)
finder = described_class.new(user, project_id: project.id)
- expect(finder.execute.to_a).to eq([maintainer_project, developer_project, reporter_project])
+ expect(finder.execute.to_a).to eq([reporter_project, developer_project, maintainer_project])
end
it 'does not include the source project' do
@@ -60,46 +60,32 @@ describe Autocomplete::MoveToProjectFinder do
expect(finder.execute.to_a).to eq([other_reporter_project])
end
- it 'returns a page of projects ordered by id in descending order' do
- allow(Kaminari.config).to receive(:default_per_page).and_return(2)
+ it 'returns a page of projects ordered by name' do
+ stub_const('Autocomplete::MoveToProjectFinder::LIMIT', 2)
- projects = create_list(:project, 2) do |project|
+ projects = create_list(:project, 3) do |project|
project.add_developer(user)
end
finder = described_class.new(user, project_id: project.id)
page = finder.execute.to_a
- expect(page.length).to eq(Kaminari.config.default_per_page)
- expect(page[0]).to eq(projects.last)
- end
-
- it 'returns projects after the given offset id' do
- reporter_project.add_reporter(user)
- developer_project.add_developer(user)
- maintainer_project.add_maintainer(user)
-
- expect(described_class.new(user, project_id: project.id, offset_id: maintainer_project.id).execute.to_a)
- .to eq([developer_project, reporter_project])
-
- expect(described_class.new(user, project_id: project.id, offset_id: developer_project.id).execute.to_a)
- .to eq([reporter_project])
-
- expect(described_class.new(user, project_id: project.id, offset_id: reporter_project.id).execute.to_a)
- .to be_empty
+ expected_projects = projects.sort_by(&:name).first(2)
+ expect(page.length).to eq(2)
+ expect(page).to eq(expected_projects)
end
end
context 'search' do
it 'returns projects matching a search query' do
- foo_project = create(:project)
+ foo_project = create(:project, name: 'foo')
foo_project.add_maintainer(user)
wadus_project = create(:project, name: 'wadus')
wadus_project.add_maintainer(user)
expect(described_class.new(user, project_id: project.id).execute.to_a)
- .to eq([wadus_project, foo_project])
+ .to eq([foo_project, wadus_project])
expect(described_class.new(user, project_id: project.id, search: 'wadus').execute.to_a)
.to eq([wadus_project])
diff --git a/spec/finders/autocomplete/users_finder_spec.rb b/spec/finders/autocomplete/users_finder_spec.rb
index bcde115b1a6..f3b54ca0461 100644
--- a/spec/finders/autocomplete/users_finder_spec.rb
+++ b/spec/finders/autocomplete/users_finder_spec.rb
@@ -50,7 +50,7 @@ describe Autocomplete::UsersFinder do
it { is_expected.to match_array([user1]) }
end
- context 'when passed a subgroup', :nested_groups do
+ context 'when passed a subgroup' do
let(:grandparent) { create(:group, :public) }
let(:parent) { create(:group, :public, parent: grandparent) }
let(:child) { create(:group, :public, parent: parent) }
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/finders/awarded_emoji_finder_spec.rb b/spec/finders/awarded_emoji_finder_spec.rb
deleted file mode 100644
index d4479df7418..00000000000
--- a/spec/finders/awarded_emoji_finder_spec.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe AwardedEmojiFinder do
- describe '#execute' do
- it 'returns an Array containing the awarded emoji names' do
- user = create(:user)
-
- create(:award_emoji, user: user, name: 'thumbsup')
- create(:award_emoji, user: user, name: 'thumbsup')
- create(:award_emoji, user: user, name: 'thumbsdown')
-
- awarded = described_class.new(user).execute
-
- expect(awarded).to eq([{ name: 'thumbsup' }, { name: 'thumbsdown' }])
- end
-
- it 'returns an empty Array when no user is given' do
- awarded = described_class.new.execute
-
- expect(awarded).to be_empty
- end
- end
-end
diff --git a/spec/finders/cluster_ancestors_finder_spec.rb b/spec/finders/cluster_ancestors_finder_spec.rb
index 750042b6b54..4aedb41d446 100644
--- a/spec/finders/cluster_ancestors_finder_spec.rb
+++ b/spec/finders/cluster_ancestors_finder_spec.rb
@@ -32,7 +32,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([project_cluster, group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
@@ -65,7 +65,7 @@ describe ClusterAncestorsFinder, '#execute' do
is_expected.to eq([group_cluster, instance_cluster])
end
- context 'nested groups', :nested_groups do
+ context 'nested groups' do
let(:group) { create(:group, parent: parent_group) }
let(:parent_group) { create(:group) }
diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb
index b731c2bd6bf..159724b3c1f 100644
--- a/spec/finders/clusters/knative_services_finder_spec.rb
+++ b/spec/finders/clusters/knative_services_finder_spec.rb
@@ -7,15 +7,19 @@ describe Clusters::KnativeServicesFinder do
include ReactiveCachingHelpers
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:service) { cluster.platform_kubernetes }
+ let(:service) { environment.deployment_platform }
let(:project) { cluster.cluster_project.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: project)
+ project: project,
+ environment: environment)
end
+ let(:finder) { described_class.new(cluster, environment) }
+
before do
stub_kubeclient_knative_services(namespace: namespace.namespace)
stub_kubeclient_service_pods(
@@ -35,7 +39,7 @@ describe Clusters::KnativeServicesFinder do
context 'when using synchronous reactive cache' do
before do
- synchronous_reactive_cache(cluster.knative_services_finder(project))
+ synchronous_reactive_cache(finder)
end
context 'when there are functions for cluster namespace' do
@@ -60,21 +64,21 @@ describe Clusters::KnativeServicesFinder do
end
describe '#service_pod_details' do
- subject { cluster.knative_services_finder(project).service_pod_details(project.name) }
+ subject { finder.service_pod_details(project.name) }
it_behaves_like 'a cached data'
end
describe '#services' do
- subject { cluster.knative_services_finder(project).services }
+ subject { finder.services }
it_behaves_like 'a cached data'
end
describe '#knative_detected' do
- subject { cluster.knative_services_finder(project).knative_detected }
+ subject { finder.knative_detected }
before do
- synchronous_reactive_cache(cluster.knative_services_finder(project))
+ synchronous_reactive_cache(finder)
end
context 'when knative is installed' do
@@ -85,7 +89,7 @@ describe Clusters::KnativeServicesFinder do
it { is_expected.to be_truthy }
it "discovers knative installation" do
expect { subject }
- .to change { cluster.kubeclient.knative_client.discovered }
+ .to change { finder.cluster.kubeclient.knative_client.discovered }
.from(false)
.to(true)
end
diff --git a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
new file mode 100644
index 00000000000..8beba0b99a4
--- /dev/null
+++ b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
@@ -0,0 +1,110 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Clusters::KubernetesNamespaceFinder do
+ let(:finder) do
+ described_class.new(
+ cluster,
+ project: project,
+ environment_slug: 'production',
+ allow_blank_token: allow_blank_token
+ )
+ end
+
+ def create_namespace(environment, with_token: true)
+ create(:cluster_kubernetes_namespace,
+ (with_token ? :with_token : :without_token),
+ cluster: cluster,
+ project: project,
+ environment: environment
+ )
+ end
+
+ describe '#execute' do
+ let(:production) { create(:environment, project: project, slug: 'production') }
+ let(:staging) { create(:environment, project: project, slug: 'staging') }
+
+ let(:cluster) { create(:cluster, :group, :provided_by_user) }
+ let(:project) { create(:project) }
+ let(:allow_blank_token) { false }
+
+ subject { finder.execute }
+
+ before do
+ allow(cluster).to receive(:namespace_per_environment?).and_return(namespace_per_environment)
+ end
+
+ context 'cluster supports separate namespaces per environment' do
+ let(:namespace_per_environment) { true }
+
+ context 'no persisted namespace is present' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'a namespace with an environment is present' do
+ context 'environment matches' do
+ let!(:namespace_with_environment) { create_namespace(production) }
+
+ it { is_expected.to eq namespace_with_environment }
+
+ context 'project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) }
+
+ it { is_expected.to eq namespace_with_environment }
+ end
+
+ context 'service account token is blank' do
+ let!(:namespace_with_environment) { create_namespace(production, with_token: false) }
+
+ it { is_expected.to be_nil }
+
+ context 'allow_blank_token is true' do
+ let(:allow_blank_token) { true }
+
+ it { is_expected.to eq namespace_with_environment }
+ end
+ end
+ end
+
+ context 'environment does not match' do
+ let!(:namespace_with_environment) { create_namespace(staging) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+
+ context 'cluster does not support separate namespaces per environment' do
+ let(:namespace_per_environment) { false }
+
+ context 'no persisted namespace is present' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'a legacy namespace with no environment is present' do
+ let!(:legacy_namespace) { create_namespace(nil) }
+
+ it { is_expected.to eq legacy_namespace }
+
+ context 'project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_user, projects: [project]) }
+
+ it { is_expected.to eq legacy_namespace }
+ end
+
+ context 'service account token is blank' do
+ let!(:legacy_namespace) { create_namespace(nil, with_token: false) }
+
+ it { is_expected.to be_nil }
+
+ context 'allow_blank_token is true' do
+ let(:allow_blank_token) { true }
+
+ it { is_expected.to eq legacy_namespace }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/finders/container_repositories_finder_spec.rb b/spec/finders/container_repositories_finder_spec.rb
new file mode 100644
index 00000000000..deec62d6598
--- /dev/null
+++ b/spec/finders/container_repositories_finder_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ContainerRepositoriesFinder do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:project_repository) { create(:container_repository, project: project) }
+
+ describe '#execute' do
+ let(:id) { nil }
+
+ subject { described_class.new(id: id, container_type: container_type).execute }
+
+ context 'when container_type is group' do
+ let(:other_project) { create(:project, group: group) }
+
+ let(:other_repository) do
+ create(:container_repository, name: 'test_repository2', project: other_project)
+ end
+
+ let(:container_type) { :group }
+ let(:id) { group.id }
+
+ it { is_expected.to match_array([project_repository, other_repository]) }
+ end
+
+ context 'when container_type is project' do
+ let(:container_type) { :project }
+ let(:id) { project.id }
+
+ it { is_expected.to match_array([project_repository]) }
+ end
+
+ context 'with invalid id' do
+ let(:container_type) { :project }
+ let(:id) { 123456789 }
+
+ it 'raises an error' do
+ expect { subject.execute }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+ end
+end
diff --git a/spec/finders/group_descendants_finder_spec.rb b/spec/finders/group_descendants_finder_spec.rb
index c28fd7cad11..5fb6739d6e2 100644
--- a/spec/finders/group_descendants_finder_spec.rb
+++ b/spec/finders/group_descendants_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupDescendantsFinder do
expect(finder.has_children?).to be_truthy
end
- context 'when there are subgroups', :nested_groups do
+ context 'when there are subgroups' do
it 'is true when there are projects' do
create(:group, parent: group)
@@ -99,7 +99,7 @@ describe GroupDescendantsFinder do
)
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:subgroup1) { create(:group, parent: group, name: 'a', path: 'sub-a') }
let!(:subgroup2) { create(:group, parent: group, name: 'z', path: 'sub-z') }
@@ -126,7 +126,7 @@ describe GroupDescendantsFinder do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let!(:project) { create(:project, namespace: group) }
let!(:subgroup) { create(:group, :private, parent: group) }
diff --git a/spec/finders/group_members_finder_spec.rb b/spec/finders/group_members_finder_spec.rb
index 8975ea0f063..49b0e14241e 100644
--- a/spec/finders/group_members_finder_spec.rb
+++ b/spec/finders/group_members_finder_spec.rb
@@ -18,7 +18,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member3, member2, member1])
end
- it 'returns members for nested group', :nested_groups do
+ it 'returns members for nested group' do
group.add_developer(user2)
nested_group.request_access(user4)
member1 = group.add_maintainer(user1)
@@ -30,7 +30,7 @@ describe GroupMembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member3, member4])
end
- it 'returns members for descendant groups if requested', :nested_groups do
+ it 'returns members for descendant groups if requested' do
member1 = group.add_maintainer(user2)
member2 = group.add_maintainer(user1)
nested_group.add_maintainer(user2)
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index f8fcc2d0e40..f4bd8a3f6ba 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -19,7 +19,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -33,7 +33,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -78,7 +78,7 @@ describe GroupProjectsFinder do
subgroup_private_project.add_maintainer(current_user)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -96,7 +96,7 @@ describe GroupProjectsFinder do
current_user.update(external: true)
end
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -111,7 +111,7 @@ describe GroupProjectsFinder do
end
context "all" do
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
@@ -153,7 +153,7 @@ describe GroupProjectsFinder do
context "only owned" do
let(:options) { { only_owned: true } }
- context 'with subgroups projects', :nested_groups do
+ context 'with subgroups projects' do
before do
options[:include_subgroups] = true
end
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
index 367ca43bdfe..c8875d1f92d 100644
--- a/spec/finders/groups_finder_spec.rb
+++ b/spec/finders/groups_finder_spec.rb
@@ -65,7 +65,7 @@ describe GroupsFinder do
end
end
- context 'subgroups', :nested_groups do
+ context 'subgroups' do
let(:user) { create(:user) }
let!(:parent_group) { create(:group, :public) }
let!(:public_subgroup) { create(:group, :public, parent: parent_group) }
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index bf38d083ca6..879ff01f294 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -51,7 +51,7 @@ describe IssuesFinder do
end
end
- context 'when include_subgroup param is true', :nested_groups do
+ context 'when include_subgroup param is true' do
before do
params[:include_subgroups] = true
end
@@ -690,7 +690,6 @@ describe IssuesFinder do
let(:finder) { described_class.new(nil, params) }
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
stub_feature_flags(attempt_group_search_optimizations: true)
end
@@ -702,18 +701,6 @@ describe IssuesFinder do
end
end
- context 'when the database is not Postgres' do
- let(:params) { { search: 'foo', attempt_group_search_optimizations: true } }
-
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns false' do
- expect(finder.use_cte_for_search?).to be_falsey
- end
- end
-
context 'when the force_cte param is falsey' do
let(:params) { { search: 'foo' } }
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 98b4933fef6..ba41ded112a 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -89,7 +89,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group ancestors', :nested_groups do
+ context 'when including labels from group ancestors' do
it 'returns labels from group and its ancestors' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -108,7 +108,7 @@ describe LabelsFinder do
end
end
- context 'when including labels from group descendants', :nested_groups do
+ context 'when including labels from group descendants' do
it 'returns labels from group and its descendants' do
private_group_1.add_developer(user)
private_subgroup_1.add_developer(user)
@@ -128,7 +128,7 @@ describe LabelsFinder do
end
end
- context 'filtering by project_id', :nested_groups do
+ context 'filtering by project_id' do
context 'when include_ancestor_groups is true' do
let!(:sub_project) { create(:project, namespace: private_subgroup_1 ) }
let!(:project_label) { create(:label, project: sub_project, title: 'Label 5') }
diff --git a/spec/finders/members_finder_spec.rb b/spec/finders/members_finder_spec.rb
index 83348457caa..4203f58fe81 100644
--- a/spec/finders/members_finder_spec.rb
+++ b/spec/finders/members_finder_spec.rb
@@ -9,7 +9,7 @@ describe MembersFinder, '#execute' do
set(:user3) { create(:user) }
set(:user4) { create(:user) }
- it 'returns members for project and parent groups', :nested_groups do
+ it 'returns members for project and parent groups' do
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
member2 = nested_group.add_maintainer(user3)
@@ -20,7 +20,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- it 'includes nested group members if asked', :nested_groups do
+ it 'includes nested group members if asked' do
project = create(:project, namespace: group)
nested_group.request_access(user1)
member1 = group.add_maintainer(user2)
@@ -32,7 +32,7 @@ describe MembersFinder, '#execute' do
expect(result.to_a).to match_array([member1, member2, member3])
end
- context 'when include_invited_groups_members == true', :nested_groups do
+ context 'when include_invited_groups_members == true' do
subject { described_class.new(project, user2).execute(include_invited_groups_members: true) }
set(:linked_group) { create(:group, :public, :access_requestable) }
@@ -40,7 +40,7 @@ describe MembersFinder, '#execute' do
set(:linked_group_member) { linked_group.add_developer(user1) }
set(:nested_linked_group_member) { nested_linked_group.add_developer(user2) }
- it 'includes all the invited_groups members including members inherited from ancestor groups', :nested_groups do
+ it 'includes all the invited_groups members including members inherited from ancestor groups' do
create(:project_group_link, project: project, group: nested_linked_group)
expect(subject).to contain_exactly(linked_group_member, nested_linked_group_member)
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index da5e9dab058..78224f0b9da 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -42,7 +42,7 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
end
- it 'filters by group including subgroups', :nested_groups do
+ it 'filters by group including subgroups' do
params = { group_id: group.id, include_subgroups: true }
merge_requests = described_class.new(user, params).execute
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 87bde4ca2f6..88906adfeeb 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -14,7 +14,7 @@ describe NotesFinder do
let!(:system_note) { create(:note_on_issue, project: project, system: true) }
it 'returns only user notes when using only_comments filter' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:only_comments])
notes = finder.execute
@@ -22,7 +22,7 @@ describe NotesFinder do
end
it 'returns only system notes when using only_activity filters' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
notes = finder.execute
@@ -30,7 +30,7 @@ describe NotesFinder do
end
it 'gets all notes' do
- finder = described_class.new(project, user, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
+ finder = described_class.new(user, project: project, notes_filter: UserPreference::NOTES_FILTERS[:all_activity])
notes = finder.execute
@@ -41,7 +41,7 @@ describe NotesFinder do
it 'finds notes on merge requests' do
create(:note_on_merge_request, project: project)
- notes = described_class.new(project, user).execute
+ notes = described_class.new(user, project: project).execute
expect(notes.count).to eq(1)
end
@@ -49,7 +49,7 @@ describe NotesFinder do
it 'finds notes on snippets' do
create(:note_on_project_snippet, project: project)
- notes = described_class.new(project, user).execute
+ notes = described_class.new(user, project: project).execute
expect(notes.count).to eq(1)
end
@@ -59,13 +59,13 @@ describe NotesFinder do
note = create(:note_on_commit, project: project)
params = { target_type: 'commit', target_id: note.noteable.id }
- notes = described_class.new(project, create(:user), params).execute
+ notes = described_class.new(create(:user), params).execute
expect(notes.count).to eq(0)
end
it 'succeeds when no notes found' do
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -82,7 +82,7 @@ describe NotesFinder do
it 'publicly excludes notes on merge requests' do
create(:note_on_merge_request, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -90,7 +90,7 @@ describe NotesFinder do
it 'publicly excludes notes on issues' do
create(:note_on_issue, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -98,7 +98,7 @@ describe NotesFinder do
it 'publicly excludes notes on snippets' do
create(:note_on_project_snippet, project: project)
- notes = described_class.new(project, create(:user)).execute
+ notes = described_class.new(create(:user), project: project).execute
expect(notes.count).to eq(0)
end
@@ -110,7 +110,7 @@ describe NotesFinder do
let!(:note2) { create :note_on_commit, project: project }
it 'finds only notes for the selected type' do
- notes = described_class.new(project, user, target_type: 'issue').execute
+ notes = described_class.new(user, project: project, target_type: 'issue').execute
expect(notes).to eq([note1])
end
@@ -118,56 +118,51 @@ describe NotesFinder do
context 'for target' do
let(:project) { create(:project, :repository) }
- let(:note1) { create :note_on_commit, project: project }
- let(:note2) { create :note_on_commit, project: project }
+ let!(:note1) { create :note_on_commit, project: project }
+ let!(:note2) { create :note_on_commit, project: project }
let(:commit) { note1.noteable }
- let(:params) { { target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
-
- before do
- note1
- note2
- end
+ let(:params) { { project: project, target_id: commit.id, target_type: 'commit', last_fetched_at: 1.hour.ago.to_i } }
it 'finds all notes' do
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.size).to eq(2)
end
it 'finds notes on merge requests' do
note = create(:note_on_merge_request, project: project)
- params = { target_type: 'merge_request', target_id: note.noteable.id }
+ params = { project: project, target_type: 'merge_request', target_id: note.noteable.id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes).to include(note)
end
it 'finds notes on snippets' do
note = create(:note_on_project_snippet, project: project)
- params = { target_type: 'snippet', target_id: note.noteable.id }
+ params = { project: project, target_type: 'snippet', target_id: note.noteable.id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.count).to eq(1)
end
it 'finds notes on personal snippets' do
note = create(:note_on_personal_snippet)
- params = { target_type: 'personal_snippet', target_id: note.noteable_id }
+ params = { project: project, target_type: 'personal_snippet', target_id: note.noteable_id }
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes.count).to eq(1)
end
it 'raises an exception for an invalid target_type' do
params[:target_type] = 'invalid'
- expect { described_class.new(project, user, params).execute }.to raise_error("invalid target_type '#{params[:target_type]}'")
+ expect { described_class.new(user, params).execute }.to raise_error("invalid target_type '#{params[:target_type]}'")
end
it 'filters out old notes' do
note2.update_attribute(:updated_at, 2.hours.ago)
- notes = described_class.new(project, user, params).execute
+ notes = described_class.new(user, params).execute
expect(notes).to eq([note1])
end
@@ -175,25 +170,47 @@ describe NotesFinder do
let(:confidential_issue) { create(:issue, :confidential, project: project, author: user) }
let!(:confidential_note) { create(:note, noteable: confidential_issue, project: confidential_issue.project) }
- let(:params) { { target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
+ let(:params) { { project: confidential_issue.project, target_id: confidential_issue.id, target_type: 'issue', last_fetched_at: 1.hour.ago.to_i } }
it 'returns notes if user can see the issue' do
- expect(described_class.new(project, user, params).execute).to eq([confidential_note])
+ expect(described_class.new(user, params).execute).to eq([confidential_note])
end
it 'raises an error if user can not see the issue' do
user = create(:user)
- expect { described_class.new(project, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
it 'raises an error for project members with guest role' do
user = create(:user)
project.add_guest(user)
- expect { described_class.new(project, user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { described_class.new(user, params).execute }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
+
+ context 'for explicit target' do
+ let(:project) { create(:project, :repository) }
+ let!(:note1) { create :note_on_commit, project: project, created_at: 1.day.ago, updated_at: 2.hours.ago }
+ let!(:note2) { create :note_on_commit, project: project }
+ let(:commit) { note1.noteable }
+ let(:params) { { project: project, target: commit } }
+
+ it 'returns the expected notes' do
+ expect(described_class.new(user, params).execute).to eq([note1, note2])
+ end
+
+ it 'returns the expected notes when last_fetched_at is given' do
+ params = { project: project, target: commit, last_fetched_at: 1.hour.ago.to_i }
+ expect(described_class.new(user, params).execute).to eq([note2])
+ end
+
+ it 'fails when nil is provided' do
+ params = { project: project, target: nil }
+ expect { described_class.new(user, params).execute }.to raise_error(RuntimeError)
+ end
+ end
end
describe '.search' do
@@ -201,17 +218,17 @@ describe NotesFinder do
let(:note) { create(:note_on_issue, note: 'WoW', project: project) }
it 'returns notes with matching content' do
- expect(described_class.new(note.project, nil, search: note.note).execute).to eq([note])
+ expect(described_class.new(nil, project: note.project, search: note.note).execute).to eq([note])
end
it 'returns notes with matching content regardless of the casing' do
- expect(described_class.new(note.project, nil, search: 'WOW').execute).to eq([note])
+ expect(described_class.new(nil, project: note.project, search: 'WOW').execute).to eq([note])
end
it 'returns commit notes user can access' do
note = create(:note_on_commit, project: project)
- expect(described_class.new(note.project, create(:user), search: note.note).execute).to eq([note])
+ expect(described_class.new(create(:user), project: note.project, search: note.note).execute).to eq([note])
end
context "confidential issues" do
@@ -220,27 +237,27 @@ describe NotesFinder do
let(:confidential_note) { create(:note, note: "Random", noteable: confidential_issue, project: confidential_issue.project) }
it "returns notes with matching content if user can see the issue" do
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to eq([confidential_note])
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to eq([confidential_note])
end
it "does not return notes with matching content if user can not see the issue" do
user = create(:user)
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
it "does not return notes with matching content for project members with guest role" do
user = create(:user)
project.add_guest(user)
- expect(described_class.new(confidential_note.project, user, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(user, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
it "does not return notes with matching content for unauthenticated users" do
- expect(described_class.new(confidential_note.project, nil, search: confidential_note.note).execute).to be_empty
+ expect(described_class.new(nil, project: confidential_note.project, search: confidential_note.note).execute).to be_empty
end
end
context 'inlines SQL filters on subqueries for performance' do
- let(:sql) { described_class.new(note.project, nil, search: note.note).execute.to_sql }
+ let(:sql) { described_class.new(nil, project: note.project, search: note.note).execute.to_sql }
let(:number_of_noteable_types) { 4 }
specify 'project_id check' do
@@ -254,11 +271,11 @@ describe NotesFinder do
end
describe '#target' do
- subject { described_class.new(project, user, params) }
+ subject { described_class.new(user, params) }
context 'for a issue target' do
let(:issue) { create(:issue, project: project) }
- let(:params) { { target_type: 'issue', target_id: issue.id } }
+ let(:params) { { project: project, target_type: 'issue', target_id: issue.id } }
it 'returns the issue' do
expect(subject.target).to eq(issue)
@@ -267,7 +284,7 @@ describe NotesFinder do
context 'for a merge request target' do
let(:merge_request) { create(:merge_request, source_project: project) }
- let(:params) { { target_type: 'merge_request', target_id: merge_request.id } }
+ let(:params) { { project: project, target_type: 'merge_request', target_id: merge_request.id } }
it 'returns the merge_request' do
expect(subject.target).to eq(merge_request)
@@ -276,7 +293,7 @@ describe NotesFinder do
context 'for a snippet target' do
let(:snippet) { create(:project_snippet, project: project) }
- let(:params) { { target_type: 'snippet', target_id: snippet.id } }
+ let(:params) { { project: project, target_type: 'snippet', target_id: snippet.id } }
it 'returns the snippet' do
expect(subject.target).to eq(snippet)
@@ -286,7 +303,7 @@ describe NotesFinder do
context 'for a commit target' do
let(:project) { create(:project, :repository) }
let(:commit) { project.commit }
- let(:params) { { target_type: 'commit', target_id: commit.id } }
+ let(:params) { { project: project, target_type: 'commit', target_id: commit.id } }
it 'returns the commit' do
expect(subject.target).to eq(commit)
@@ -298,24 +315,24 @@ describe NotesFinder do
let(:merge_request) { create(:merge_request, source_project: project, target_project: project) }
it 'finds issues by iid' do
- iid_params = { target_type: 'issue', target_iid: issue.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(issue)
+ iid_params = { project: project, target_type: 'issue', target_iid: issue.iid }
+ expect(described_class.new(user, iid_params).target).to eq(issue)
end
it 'finds merge requests by iid' do
- iid_params = { target_type: 'merge_request', target_iid: merge_request.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(merge_request)
+ iid_params = { project: project, target_type: 'merge_request', target_iid: merge_request.iid }
+ expect(described_class.new(user, iid_params).target).to eq(merge_request)
end
it 'returns nil if both target_id and target_iid are not given' do
- params_without_any_id = { target_type: 'issue' }
- expect(described_class.new(project, user, params_without_any_id).target).to be_nil
+ params_without_any_id = { project: project, target_type: 'issue' }
+ expect(described_class.new(user, params_without_any_id).target).to be_nil
end
it 'prioritizes target_id over target_iid' do
issue2 = create(:issue, project: project)
- iid_params = { target_type: 'issue', target_id: issue2.id, target_iid: issue.iid }
- expect(described_class.new(project, user, iid_params).target).to eq(issue2)
+ iid_params = { project: project, target_type: 'issue', target_id: issue2.id, target_iid: issue.iid }
+ expect(described_class.new(user, iid_params).target).to eq(issue2)
end
end
end
diff --git a/spec/finders/projects/serverless/functions_finder_spec.rb b/spec/finders/projects/serverless/functions_finder_spec.rb
index 8aea45b457c..589e4000d46 100644
--- a/spec/finders/projects/serverless/functions_finder_spec.rb
+++ b/spec/finders/projects/serverless/functions_finder_spec.rb
@@ -11,12 +11,15 @@ describe Projects::Serverless::FunctionsFinder do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:service) { cluster.platform_kubernetes }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+ let(:knative_services_finder) { environment.knative_services_finder }
let(:namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
- cluster_project: cluster.cluster_project,
- project: cluster.cluster_project.project)
+ project: project,
+ environment: environment)
end
before do
@@ -29,11 +32,9 @@ describe Projects::Serverless::FunctionsFinder do
end
context 'when reactive_caching has finished' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
-
before do
- allow_any_instance_of(Clusters::Cluster)
- .to receive(:knative_services_finder)
+ allow(Clusters::KnativeServicesFinder)
+ .to receive(:new)
.and_return(knative_services_finder)
synchronous_reactive_cache(knative_services_finder)
end
@@ -47,8 +48,6 @@ describe Projects::Serverless::FunctionsFinder do
end
context 'reactive_caching is finished and knative is installed' do
- let(:knative_services_finder) { project.clusters.first.knative_services_finder(project) }
-
it 'returns true' do
stub_kubeclient_knative_services(namespace: namespace.namespace)
stub_kubeclient_service_pods(nil, namespace: namespace.namespace)
@@ -74,24 +73,24 @@ describe Projects::Serverless::FunctionsFinder do
it 'there are functions', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
expect(finder.execute).not_to be_empty
end
it 'has a function', :use_clean_rails_memory_store_caching do
stub_kubeclient_service_pods
- stub_reactive_cache(cluster.knative_services_finder(project),
+ stub_reactive_cache(knative_services_finder,
{
services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
},
- *cluster.knative_services_finder(project).cache_args)
+ *knative_services_finder.cache_args)
result = finder.service(cluster.environment_scope, cluster.project.name)
expect(result).not_to be_empty
@@ -109,7 +108,7 @@ describe Projects::Serverless::FunctionsFinder do
let(:finder) { described_class.new(project) }
before do
- allow(finder).to receive(:prometheus_adapter).and_return(prometheus_adapter)
+ allow(Prometheus::AdapterService).to receive(:new).and_return(double(prometheus_adapter: prometheus_adapter))
allow(prometheus_adapter).to receive(:query).and_return(prometheus_empty_body('matrix'))
end
diff --git a/spec/finders/starred_projects_finder_spec.rb b/spec/finders/starred_projects_finder_spec.rb
new file mode 100644
index 00000000000..7aa8251c3ab
--- /dev/null
+++ b/spec/finders/starred_projects_finder_spec.rb
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe StarredProjectsFinder do
+ let(:project1) { create(:project, :public, :empty_repo) }
+ let(:project2) { create(:project, :public, :empty_repo) }
+ let(:other_project) { create(:project, :public, :empty_repo) }
+
+ let(:user) { create(:user) }
+ let(:other_user) { create(:user) }
+
+ before do
+ user.toggle_star(project1)
+ user.toggle_star(project2)
+ end
+
+ describe '#execute' do
+ let(:finder) { described_class.new(user, params: {}, current_user: current_user) }
+
+ subject { finder.execute }
+
+ describe 'as same user' do
+ let(:current_user) { user }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+
+ describe 'as other user' do
+ let(:current_user) { other_user }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+
+ describe 'as no user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to contain_exactly(project1, project2) }
+ end
+ end
+end
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 22318a9946a..f7b35e76925 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -36,7 +36,7 @@ describe TodosFinder do
expect(todos).to match_array([todo1])
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:subgroup) { create(:group, parent: group) }
let!(:todo3) { create(:todo, user: user, group: subgroup, target: issue) }
diff --git a/spec/finders/users_star_projects_finder_spec.rb b/spec/finders/users_star_projects_finder_spec.rb
new file mode 100644
index 00000000000..fb1d8088f44
--- /dev/null
+++ b/spec/finders/users_star_projects_finder_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UsersStarProjectsFinder do
+ let(:project) { create(:project, :public, :empty_repo) }
+
+ let(:user) { create(:user) }
+ let(:private_user) { create(:user, private_profile: true) }
+ let(:other_user) { create(:user) }
+
+ before do
+ user.toggle_star(project)
+ private_user.toggle_star(project)
+ end
+
+ describe '#execute' do
+ let(:finder) { described_class.new(project, {}, current_user: current_user) }
+ let(:public_stars) { user.users_star_projects }
+ let(:private_stars) { private_user.users_star_projects }
+
+ subject { finder.execute }
+
+ describe 'as same user' do
+ let(:current_user) { private_user }
+
+ it { is_expected.to match_array(private_stars + public_stars) }
+ end
+
+ describe 'as other user' do
+ let(:current_user) { other_user }
+
+ it { is_expected.to match_array(public_stars) }
+ end
+
+ describe 'as no user' do
+ let(:current_user) { nil }
+
+ it { is_expected.to match_array(public_stars) }
+ end
+ end
+end
diff --git a/spec/fixtures/api/schemas/deployment.json b/spec/fixtures/api/schemas/deployment.json
index 0828f113495..fe725b97c21 100644
--- a/spec/fixtures/api/schemas/deployment.json
+++ b/spec/fixtures/api/schemas/deployment.json
@@ -3,6 +3,7 @@
"required": [
"sha",
"created_at",
+ "deployed_at",
"iid",
"tag",
"last?",
@@ -11,6 +12,7 @@
],
"properties": {
"created_at": { "type": "string" },
+ "deployed_at": { "type": ["string", "null"] },
"id": { "type": "integer" },
"iid": { "type": "integer" },
"last?": { "type": "boolean" },
diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json
new file mode 100644
index 00000000000..bcc1db79e83
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/discussion.json
@@ -0,0 +1,67 @@
+{
+ "type": "object",
+ "required" : [
+ "id",
+ "notes",
+ "individual_note"
+ ],
+ "properties" : {
+ "id": { "type": "string" },
+ "individual_note": { "type": "boolean" },
+ "notes": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id": { "type": "string" },
+ "type": { "type": ["string", "null"] },
+ "body": { "type": "string" },
+ "attachment": { "type": ["string", "null"]},
+ "award_emoji": { "type": "array" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" },
+ "status_tooltip_html": { "type": ["string", "null"] },
+ "path": { "type": "string" }
+ },
+ "additionalProperties": false
+ },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "system": { "type": "boolean" },
+ "noteable_id": { "type": "integer" },
+ "noteable_iid": { "type": "integer" },
+ "noteable_type": { "type": "string" },
+ "resolved": { "type": "boolean" },
+ "resolvable": { "type": "boolean" },
+ "resolved_by": { "type": ["string", "null"] },
+ "note": { "type": "string" },
+ "note_html": { "type": "string" },
+ "current_user": { "type": "object" },
+ "suggestions": { "type": "array" },
+ "discussion_id": { "type": "string" },
+ "emoji_awardable": { "type": "boolean" },
+ "report_abuse_path": { "type": "string" },
+ "noteable_note_url": { "type": "string" },
+ "resolve_path": { "type": "string" },
+ "resolve_with_issue_path": { "type": "string" },
+ "cached_markdown_version": { "type": "integer" },
+ "human_access": { "type": ["string", "null"] },
+ "toggle_award_path": { "type": "string" },
+ "path": { "type": "string" }
+ },
+ "required": [
+ "id", "attachment", "author", "created_at", "updated_at",
+ "system", "noteable_id", "noteable_type"
+ ],
+ "additionalProperties": false
+ }
+ }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/discussions.json b/spec/fixtures/api/schemas/entities/discussions.json
new file mode 100644
index 00000000000..5a837429776
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/discussions.json
@@ -0,0 +1,4 @@
+{
+ "type": "array",
+ "items": { "$ref": "discussion.json" }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
new file mode 100644
index 00000000000..e8959307767
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
@@ -0,0 +1,46 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "description": { "type": ["string", "null"] },
+ "title": { "type": "string" },
+ "auto_merge_enabled": { "type": "boolean" },
+ "state": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "short_merge_commit_sha": { "type": ["string", "null"] },
+ "merge_error": { "type": ["string", "null"] },
+ "merge_status": { "type": "string" },
+ "merge_user_id": { "type": ["integer", "null"] },
+ "source_branch": { "type": "string" },
+ "source_project_id": { "type": "integer" },
+ "target_branch": { "type": "string" },
+ "target_branch_sha": { "type": "string" },
+ "target_project_id": { "type": "integer" },
+ "squash": { "type": "boolean" },
+ "rebase_in_progress": { "type": "boolean" },
+ "default_squash_commit_message": { "type": "string" },
+ "commits_count": { "type": ["integer", "null"] },
+ "merge_ongoing": { "type": "boolean" },
+ "work_in_progress": { "type": "boolean" },
+ "has_conflicts": { "type": "boolean" },
+ "can_be_merged": { "type": "boolean" },
+ "remove_source_branch": { "type": ["boolean", "null"] },
+ "source_branch_exists": { "type": "boolean" },
+ "branch_missing": { "type": "boolean" },
+ "commits_without_merge_commits": { "type": "array" },
+ "diff_head_sha": { "type": ["string", "null"] },
+ "metrics": {
+ "oneOf": [
+ { "type": "null" },
+ { "$ref": "merge_request_metrics.json" }
+ ]
+ },
+ "diverged_commits_count": { "type": "integer" },
+ "target_branch_commits_path": { "type": "string" },
+ "target_branch_tree_path": { "type": "string" },
+ "merge_commit_path": { "type": ["string", "null"] },
+ "source_branch_with_namespace_link": { "type": "string" },
+ "source_branch_path": { "type": "string" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
new file mode 100644
index 00000000000..2052892dfa3
--- /dev/null
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_widget.json
@@ -0,0 +1,55 @@
+{
+ "type": "object",
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "description": { "type": ["string", "null"] },
+ "title": { "type": "string" },
+ "auto_merge_strategy": { "type": ["string", "null"] },
+ "available_auto_merge_strategies": { "type": "array" },
+ "source_branch_protected": { "type": "boolean" },
+ "allow_collaboration": { "type": "boolean"},
+ "should_be_rebased": { "type": "boolean" },
+ "ff_only_enabled": { "type": ["boolean", false] },
+ "merge_user": { "type": ["object", "null"] },
+ "pipeline": { "type": ["object", "null"] },
+ "merge_pipeline": { "type": ["object", "null"] },
+ "default_merge_commit_message": { "type": ["string", "null"] },
+ "mergeable": { "type": "boolean" },
+ "default_merge_commit_message_with_description": { "type": "string" },
+ "mergeable_discussions_state": { "type": "boolean" },
+ "project_archived": { "type": "boolean" },
+ "only_allow_merge_if_pipeline_succeeds": { "type": "boolean" },
+ "has_ci": { "type": "boolean" },
+ "ci_status": { "type": ["string", "null"] },
+ "cancel_auto_merge_path": { "type": ["string", "null"] },
+ "test_reports_path": { "type": ["string", "null"] },
+ "can_receive_suggestion": { "type": "boolean" },
+ "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
+ "current_user": {
+ "type": "object",
+ "required": [
+ "can_remove_source_branch",
+ "can_revert_on_current_merge_request",
+ "can_cherry_pick_on_current_merge_request"
+ ],
+ "properties": {
+ "can_remove_source_branch": { "type": "boolean" },
+ "can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
+ "can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
+ "can_create_note": { "type": "boolean" },
+ "can_create_issue": { "type": "boolean" },
+ "can_update": { "type": "boolean" }
+ },
+ "additionalProperties": false
+ },
+ "can_push_to_source_branch": { "type": "boolean" },
+ "new_blob_path": { "type": ["string", "null"] },
+ "rebase_path": { "type": ["string", "null"] },
+ "conflict_resolution_path": { "type": ["string", "null"] },
+ "remove_wip_path": { "type": ["string", "null"] },
+ "merge_path": { "type": ["string", "null"] },
+ "cherry_pick_in_fork_path": { "type": ["string", "null"] },
+ "revert_in_fork_path": { "type": ["string", "null"] }
+ }
+}
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/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index eac1dbc6474..779a47222b7 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -1,132 +1,35 @@
{
"type": "object",
- "properties" : {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "author_id": { "type": "integer" },
- "description": { "type": ["string", "null"] },
- "lock_version": { "type": ["string", "null"] },
- "milestone_id": { "type": ["string", "null"] },
- "position": { "type": "integer" },
- "state": { "type": "string" },
- "title": { "type": "string" },
- "updated_by_id": { "type": ["string", "null"] },
- "created_at": { "type": "string" },
- "updated_at": { "type": "string" },
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["integer", "null"] },
- "human_total_time_spent": { "type": ["integer", "null"] },
- "milestone": { "type": ["object", "null"] },
- "labels": { "type": ["array", "null"] },
- "in_progress_merge_commit_sha": { "type": ["string", "null"] },
- "merge_error": { "type": ["string", "null"] },
- "merge_commit_sha": { "type": ["string", "null"] },
- "short_merge_commit_sha": { "type": ["string", "null"] },
- "merge_params": { "type": ["object", "null"] },
- "merge_status": { "type": "string" },
- "merge_user_id": { "type": ["integer", "null"] },
- "merge_when_pipeline_succeeds": { "type": "boolean" },
- "source_branch": { "type": "string" },
- "source_project_id": { "type": "integer" },
- "source_project_full_path": { "type": ["string", "null"]},
- "target_branch": { "type": "string" },
- "target_project_id": { "type": "integer" },
- "target_project_full_path": { "type": ["string", "null"]},
- "allow_collaboration": { "type": "boolean"},
- "metrics": {
- "oneOf": [
- { "type": "null" },
- { "$ref": "merge_request_metrics.json" }
- ]
- },
- "author": { "type": ["object", "null"] },
- "merge_user": { "type": ["object", "null"] },
- "diff_head_sha": { "type": ["string", "null"] },
- "diff_head_commit_short_id": { "type": ["string", "null"] },
- "default_merge_commit_message": { "type": ["string", "null"] },
- "pipeline": { "type": ["object", "null"] },
- "merge_pipeline": { "type": ["object", "null"] },
- "work_in_progress": { "type": "boolean" },
- "source_branch_exists": { "type": "boolean" },
- "mergeable_discussions_state": { "type": "boolean" },
- "conflicts_can_be_resolved_in_ui": { "type": "boolean" },
- "branch_missing": { "type": "boolean" },
- "commits_count": { "type": ["integer", "null"] },
- "has_conflicts": { "type": "boolean" },
- "can_be_merged": { "type": "boolean" },
- "mergeable": { "type": "boolean" },
- "project_archived": { "type": "boolean" },
- "only_allow_merge_if_pipeline_succeeds": { "type": "boolean" },
- "has_ci": { "type": "boolean" },
- "ci_status": { "type": ["string", "null"] },
- "issues_links": {
- "type": "object",
- "required": ["closing", "mentioned_but_not_closing", "assign_to_closing"],
+ "allOf": [
+ { "$ref": "merge_request_poll_cached_widget.json" },
+ { "$ref": "merge_request_poll_widget.json" },
+ {
"properties" : {
- "closing": { "type": "string" },
- "mentioned_but_not_closing": { "type": "string" },
- "assign_to_closing": { "type": ["string", "null"] }
- },
- "additionalProperties": false
- },
- "source_branch_with_namespace_link": { "type": "string" },
- "current_user": {
- "type": "object",
- "required": [
- "can_remove_source_branch",
- "can_revert_on_current_merge_request",
- "can_cherry_pick_on_current_merge_request"
- ],
- "properties": {
- "can_remove_source_branch": { "type": "boolean" },
- "can_revert_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_cherry_pick_on_current_merge_request": { "type": ["boolean", "null"] },
- "can_create_note": { "type": "boolean" },
- "can_create_issue": { "type": "boolean" },
- "can_update": { "type": "boolean" }
- },
- "additionalProperties": false
- },
- "target_branch_commits_path": { "type": "string" },
- "target_branch_tree_path": { "type": "string" },
- "source_branch_path": { "type": "string" },
- "conflict_resolution_path": { "type": ["string", "null"] },
- "cancel_merge_when_pipeline_succeeds_path": { "type": ["string", "null"] },
- "create_issue_to_resolve_discussions_path": { "type": ["string", "null"] },
- "merge_path": { "type": ["string", "null"] },
- "cherry_pick_in_fork_path": { "type": ["string", "null"] },
- "revert_in_fork_path": { "type": ["string", "null"] },
- "email_patches_path": { "type": "string" },
- "plain_diff_path": { "type": "string" },
- "merge_request_basic_path": { "type": "string" },
- "merge_request_widget_path": { "type": "string" },
- "new_blob_path": { "type": ["string", "null"] },
- "merge_check_path": { "type": "string" },
- "ci_environments_status_path": { "type": "string" },
- "default_merge_commit_message_with_description": { "type": "string" },
- "default_squash_commit_message": { "type": "string" },
- "commits_without_merge_commits": { "type": "array" },
- "diverged_commits_count": { "type": "integer" },
- "commit_change_content_path": { "type": "string" },
- "merge_commit_path": { "type": ["string", "null"] },
- "remove_wip_path": { "type": ["string", "null"] },
- "commits_count": { "type": "integer" },
- "remove_source_branch": { "type": ["boolean", "null"] },
- "merge_ongoing": { "type": "boolean" },
- "ff_only_enabled": { "type": ["boolean", false] },
- "should_be_rebased": { "type": "boolean" },
- "create_note_path": { "type": ["string", "null"] },
- "preview_note_path": { "type": ["string", "null"] },
- "rebase_commit_sha": { "type": ["string", "null"] },
- "rebase_in_progress": { "type": "boolean" },
- "can_push_to_source_branch": { "type": "boolean" },
- "rebase_path": { "type": ["string", "null"] },
- "squash": { "type": "boolean" },
- "test_reports_path": { "type": ["string", "null"] },
- "can_receive_suggestion": { "type": "boolean" },
- "source_branch_protected": { "type": "boolean" },
- "conflicts_docs_path": { "type": ["string", "null"] },
- "merge_request_pipelines_docs_path": { "type": ["string", "null"] }
- }
+ "merge_params": { "type": ["object", "null"] },
+ "source_project_full_path": { "type": ["string", "null"]},
+ "target_project_full_path": { "type": ["string", "null"]},
+ "email_patches_path": { "type": "string" },
+ "plain_diff_path": { "type": "string" },
+ "merge_request_basic_path": { "type": "string" },
+ "merge_request_widget_path": { "type": "string" },
+ "merge_request_cached_widget_path": { "type": "string" },
+ "create_note_path": { "type": ["string", "null"] },
+ "commit_change_content_path": { "type": "string" },
+ "preview_note_path": { "type": ["string", "null"] },
+ "conflicts_docs_path": { "type": ["string", "null"] },
+ "merge_request_pipelines_docs_path": { "type": ["string", "null"] },
+ "ci_environments_status_path": { "type": "string" },
+ "issues_links": {
+ "type": "object",
+ "required": ["closing", "mentioned_but_not_closing", "assign_to_closing"],
+ "properties" : {
+ "closing": { "type": "string" },
+ "mentioned_but_not_closing": { "type": "string" },
+ "assign_to_closing": { "type": ["string", "null"] }
+ },
+ "additionalProperties": false
+ }
+ }
+ }
+ ]
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
deleted file mode 100644
index fbde45f2904..00000000000
--- a/spec/fixtures/api/schemas/public_api/v4/group_labels.json
+++ /dev/null
@@ -1,19 +0,0 @@
-{
- "type": "array",
- "items": {
- "type": "object",
- "properties" : {
- "id" : { "type": "integer" },
- "name" : { "type": "string "},
- "color" : { "type": "string "},
- "text_color" : { "type": "string "},
- "description" : { "type": "string "},
- "open_issues_count" : { "type": "integer "},
- "closed_issues_count" : { "type": "integer "},
- "open_merge_requests_count" : { "type": "integer "},
- "subscribed" : { "type": "boolean" },
- "priority" : { "type": "null" }
- },
- "additionalProperties": false
- }
-}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label.json b/spec/fixtures/api/schemas/public_api/v4/labels/label.json
new file mode 100644
index 00000000000..a116b33572d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/label.json
@@ -0,0 +1,11 @@
+{
+ "type": "object",
+ "properties": {
+ "id": { "type": "integer" },
+ "name": { "type": "string" },
+ "color": { "type": "string" },
+ "text_color": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "subscribed": { "type": "boolean" }
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
new file mode 100644
index 00000000000..2331932e07d
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/label_with_counts.json
@@ -0,0 +1,16 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "label.json" },
+ {
+ "type": "object",
+ "properties": {
+ "open_issues_count": { "type": "integer" },
+ "closed_issues_count": { "type": "integer" },
+ "open_merge_requests_count": { "type": "integer" }
+ }
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
new file mode 100644
index 00000000000..a9a065ee71f
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label.json
@@ -0,0 +1,15 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "label.json" },
+ {
+ "type": "object",
+ "properties": {
+ "priority": { "type": ["integer", "null"] },
+ "is_project_label": { "type": "boolean" }
+ }
+ }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
new file mode 100644
index 00000000000..87b90b2b3b5
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/labels/project_label_with_counts.json
@@ -0,0 +1,9 @@
+{
+ "type": "object",
+ "properties": {
+ "allOf": [
+ { "$ref": "project_label.json" },
+ { "$ref": "label_with_counts.json" }
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/registry/repository.json b/spec/fixtures/api/schemas/registry/repository.json
index e0fd4620c43..d0a068b65a7 100644
--- a/spec/fixtures/api/schemas/registry/repository.json
+++ b/spec/fixtures/api/schemas/registry/repository.json
@@ -17,6 +17,9 @@
"path": {
"type": "string"
},
+ "project_id": {
+ "type": "integer"
+ },
"location": {
"type": "string"
},
@@ -28,7 +31,8 @@
},
"destroy_path": {
"type": "string"
- }
+ },
+ "tags": { "$ref": "tags.json" }
},
"additionalProperties": false
}
diff --git a/spec/fixtures/clusters/sample_key.key b/spec/fixtures/clusters/sample_key.key
new file mode 100644
index 00000000000..4ddb20b0922
--- /dev/null
+++ b/spec/fixtures/clusters/sample_key.key
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAMA5sXIBE0HwgIB40iNidN4PGWzOyLQK0bsdOBNgpEXkDlZBvnak
+OUgAPF+rME4PB0Yl415DabUI40T5UNmlwxcCAwEAAQJAZtY2pSwIFm3JAXIh0cZZ
+iXcAfiJ+YzuqinUOS+eW2sBCAEzjcARlU/o6sFQgtsOi4FOMczAd1Yx8UDMXMmrw
+2QIhAPBgVhJiTF09pdmeFWutCvTJDlFFAQNbrbo2X2x/9WF9AiEAzLgqMKeStSRu
+H9N16TuDrUoO8R+DPqriCwkKrSHaWyMCIFzMhE4inuKcSywBaLmiG4m3GQzs++Al
+A6PRG/PSTpQtAiBxtBg6zdf+JC3GH3zt/dA0/10tL4OF2wORfYQghRzyYQIhAL2l
+0ZQW+yLIZAGrdBFWYEAa52GZosncmzBNlsoTgwE4
+-----END RSA PRIVATE KEY----- \ No newline at end of file
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
index 1ee1205e29a..5d779a323c2 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/dashboard.json
@@ -1,6 +1,10 @@
{
"type": "object",
- "required": ["dashboard", "priority", "panel_groups"],
+ "required": [
+ "dashboard",
+ "priority",
+ "panel_groups"
+ ],
"properties": {
"dashboard": { "type": "string" },
"priority": { "type": "number" },
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
index 33393805464..9c1be32645a 100644
--- a/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/schemas/metrics.json
@@ -16,7 +16,8 @@
"unit": { "type": "string" },
"label": { "type": "string" },
"track": { "type": "string" },
- "prometheus_endpoint_path": { "type": "string" }
+ "prometheus_endpoint_path": { "type": "string" },
+ "metric_id": { "type": "number" }
},
"additionalProperties": false
}
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 1e62d020026..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": "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"
- }
- ]
- },
- {
- "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": "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-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 a85b9be8b5f..00000000000
--- a/spec/fixtures/security-reports/deprecated/gl-sast-report.json
+++ /dev/null
@@ -1,944 +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": "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/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 4bef3d22f70..00000000000
--- a/spec/fixtures/security-reports/master/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/remediations/gl-dependency-scanning-report.json b/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
deleted file mode 100644
index c96e831b027..00000000000
--- a/spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json
+++ /dev/null
@@ -1,104 +0,0 @@
-{
- "version": "2.0",
- "vulnerabilities": [
- {
- "category": "dependency_scanning",
- "name": "Regular Expression Denial of Service",
- "message": "Regular Expression Denial of Service in debug",
- "description": "The debug module is vulnerable to regular expression denial of service when untrusted user input is passed into the `o` formatter. It takes around 50k characters to block for 2 seconds making this a low severity issue.",
- "cve": "yarn.lock:debug:gemnasium:37283ed4-0380-40d7-ada7-2d994afcc62a",
- "severity": "Unknown",
- "solution": "Upgrade to latest versions.",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "debug"
- },
- "version": "1.0.5"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-37283ed4-0380-40d7-ada7-2d994afcc62a",
- "value": "37283ed4-0380-40d7-ada7-2d994afcc62a",
- "url": "https://deps.sec.gitlab.com/packages/npm/debug/versions/1.0.5/advisories"
- }
- ],
- "links": [
- {
- "url": "https://nodesecurity.io/advisories/534"
- },
- {
- "url": "https://github.com/visionmedia/debug/issues/501"
- },
- {
- "url": "https://github.com/visionmedia/debug/pull/504"
- }
- ]
- },
- {
- "category": "dependency_scanning",
- "name": "Authentication bypass via incorrect DOM traversal and canonicalization",
- "message": "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
- "description": "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\r\n\r\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
- "severity": "Unknown",
- "solution": "Upgrade to fixed version.\r\n",
- "scanner": {
- "id": "gemnasium",
- "name": "Gemnasium"
- },
- "location": {
- "file": "yarn.lock",
- "dependency": {
- "package": {
- "name": "saml2-js"
- },
- "version": "1.5.0"
- }
- },
- "identifiers": [
- {
- "type": "gemnasium",
- "name": "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
- "value": "9952e574-7b5b-46fa-a270-aeb694198a98",
- "url": "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
- },
- {
- "type": "cve",
- "name": "CVE-2017-11429",
- "value": "CVE-2017-11429",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
- }
- ],
- "links": [
- {
- "url": "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
- },
- {
- "url": "https://github.com/Clever/saml2/issues/127"
- },
- {
- "url": "https://www.kb.cert.org/vuls/id/475445"
- }
- ]
- }
- ],
- "remediations": [
- {
- "fixes": [
- {
- "cve": "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"
- }
- ],
- "summary": "Upgrade saml2-js",
- "diff": "ZGlmZiAtLWdpdCBhL3lhcm4ubG9jayBiL3lhcm4ubG9jawppbmRleCAwZWNjOTJmLi43ZmE0NTU0IDEwMDY0NAotLS0gYS95YXJuLmxvY2sKKysrIGIveWFybi5sb2NrCkBAIC0yLDEwMyArMiwxMjQgQEAKICMgeWFybiBsb2NrZmlsZSB2MQogCiAKLWFzeW5jQH4wLjIuNzoKLSAgdmVyc2lvbiAiMC4yLjEwIgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy9hc3luYy8tL2FzeW5jLTAuMi4xMC50Z3ojYjZiYmUwYjA2NzRiOWQ3MTk3MDhjYTM4ZGU4YzIzN2NiNTI2YzNkMSIKLQotYXN5bmNAfjEuNS4yOgotICB2ZXJzaW9uICIxLjUuMiIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcvYXN5bmMvLS9hc3luYy0xLjUuMi50Z3ojZWM2YTYxYWU1NjQ4MGMwYzNjYjI0MWM5NTYxOGUyMDg5MmY5NjcyYSIKK2FzeW5jQF4yLjEuNSwgYXN5bmNAXjIuNS4wOgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vYXN5bmMvLS9hc3luYy0yLjYuMS50Z3ojYjI0NWEyM2NhNzE5MzAwNDRlYzUzZmE0NmFhMDBhM2U4N2M2YTYxMCIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mTkVpTDIrQVp0NkFsQXcvMjlDcjBVRGU0c1JBSENwRUhoNTRXTXorQmI3UWZOY0Z3NGgzbG9vZnlKcExlUXM0WXg3eXVxdS8yZExnTTVoS09zNkhsUT09CisgIGRlcGVuZGVuY2llczoKKyAgICBsb2Rhc2ggIl40LjE3LjEwIgogCi1kZWJ1Z0BeMS4wLjQ6Ci0gIHZlcnNpb24gIjEuMC41IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9kZWJ1Zy8tL2RlYnVnLTEuMC41LnRneiNmNzI0MTIxNzQzMGY5OWRlYzRjMmI0NzNlYWI5MjIyOGU4NzRjMmFjIgorZGVidWdAXjIuNi4wOgorICB2ZXJzaW9uICIyLjYuOSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZGVidWcvLS9kZWJ1Zy0yLjYuOS50Z3ojNWQxMjg1MTVkZjEzNGZmMzI3ZTkwYTRjOTNmNGUwNzdhNTM2MzQxZiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1iQzdFbHJkSmFKblBiQVArMUVvdFl2cVpzYjNlY2w1d2k2QmZpNkJKVFVjTm93cDZjdnNwZzBqWHpuUlRLRGptL0U3QWRnRkJWZUFQVk1OY0tHc0hNQT09CiAgIGRlcGVuZGVuY2llczoKICAgICBtcyAiMi4wLjAiCiAKLWVqc0B+MC44LjM6Ci0gIHZlcnNpb24gIjAuOC44IgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9lanMvLS9lanMtMC44LjgudGd6I2ZmZGM1NmRjYzM1ZDAyOTI2ZGQ1MGFkMTM0MzliYmM1NDA2MWQ1OTgiCitlanNAXjIuNS42OgorICB2ZXJzaW9uICIyLjYuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vZWpzLy0vZWpzLTIuNi4xLnRneiM0OThlYzBkNDk1NjU1YWJjNmYyM2NkNjE4NjhkOTI2NDY0MDcxYWEwIgorICBpbnRlZ3JpdHkgc2hhNTEyLTB4eTRBL3R3ZnJSQ25raGZrOEVyRGk1RHFkQXNBcWVHeGh0NHhrQ1Vyc3ZoaGJRTnM3RSs0alYwQ043K05LSVkwYUhFNzIrWHZxdEJJWHpEMzFaYlhRPT0KKworbG9kYXNoLW5vZGVAfjIuNC4xOgorICB2ZXJzaW9uICIyLjQuMSIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbG9kYXNoLW5vZGUvLS9sb2Rhc2gtbm9kZS0yLjQuMS50Z3ojZWE4MmY3YjEwMGM3MzNkMWE0MmFmNzY4MDFlNTA2MTA1ZTJhODBlYyIKKyAgaW50ZWdyaXR5IHNoYTEtNm9MM3NRREhNOUdrS3Zkb0FlVUdFRjRxZ093PQorCitsb2Rhc2hAXjQuMTcuMTA6CisgIHZlcnNpb24gIjQuMTcuMTEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL2xvZGFzaC8tL2xvZGFzaC00LjE3LjExLnRneiNiMzllYTYyMjllZjYwN2VjZDg5ZTJjOGRmMTI1MzY4OTFjYWM5YjhkIgorICBpbnRlZ3JpdHkgc2hhNTEyLWNRS2g4aWdvNVFVaFo3bGczOERZV0F4TXZqU0FLRzBBOHdHU1ZpbVAwN1NJVUVLMlVPK2FyU1JLYlJaV3RlbE10TjVWMEhrd2g1cnlPdG8vU3NoWUlnPT0KIAogbXNAMi4wLjA6CiAgIHZlcnNpb24gIjIuMC4wIgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9tcy8tL21zLTIuMC4wLnRneiM1NjA4YWVhZGZjMDBiZTZjMjkwMWRmNWY5ODYxNzg4ZGUwZDU5N2M4IgorICBpbnRlZ3JpdHkgc2hhMS1WZ2l1cmZ3QXZtd3BBZDlmbUdGNGplRFZsOGc9CiAKLW5vZGUtZm9yZ2VAMC4yLjI0OgotICB2ZXJzaW9uICIwLjIuMjQiCi0gIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL25vZGUtZm9yZ2UvLS9ub2RlLWZvcmdlLTAuMi4yNC50Z3ojZmE2Zjg0NmY0MmZhOTNmNjNhMGEzMGM5ZmJmZjdiNGUxMzBlMDg1OCIKK25vZGUtZm9yZ2VAXjAuNy4wOgorICB2ZXJzaW9uICIwLjcuNiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20vbm9kZS1mb3JnZS8tL25vZGUtZm9yZ2UtMC43LjYudGd6I2ZkZjNiNDE4YWVlMWY5NGYwZWY2NDJjZDYzNDg2Yzc3Y2E5NzI0YWMiCisgIGludGVncml0eSBzaGE1MTItc29sMzBMVXB6MWpRRkJqT0t3Ymp4aWppRTNiNnBqZDc0WXdmRDBmSk9LUGpGK2ZPTktiMllnOHJZZ1M2K2JLNlZEbCsvd2ZyNElZcEM3akR6TFVJZnc9PQogCiBzYW1sMi1qc0BeMS41LjA6Ci0gIHZlcnNpb24gIjEuNS4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYW1sMi1qcy8tL3NhbWwyLWpzLTEuNS4wLnRneiNjMGQyMjY4YTE3OWU3MzI5ZDI5ZWIyNWFhODJkZjU1MDM3NzRiMGQ5IgorICB2ZXJzaW9uICIxLjEyLjQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3NhbWwyLWpzLy0vc2FtbDItanMtMS4xMi40LnRneiNjMjg4ZjIwYmRhNmQyYjkxMDczYjE2Yzk0ZWE3MmYyMjM0OWFjM2IzIgorICBpbnRlZ3JpdHkgc2hhMS13b2p5QzlwdEs1RUhPeGJKVHFjdklqU2F3N009CiAgIGRlcGVuZGVuY2llczoKLSAgICBhc3luYyAifjEuNS4yIgotICAgIGRlYnVnICJeMS4wLjQiCi0gICAgdW5kZXJzY29yZSAifjEuNi4wIgotICAgIHhtbC1jcnlwdG8gIl4wLjguMSIKLSAgICB4bWwtZW5jcnlwdGlvbiAifjAuNy40IgotICAgIHhtbDJqcyAifjAuNC4xIgotICAgIHhtbGJ1aWxkZXIgIn4yLjEuMCIKLSAgICB4bWxkb20gIn4wLjEuMTkiCisgICAgYXN5bmMgIl4yLjUuMCIKKyAgICBkZWJ1ZyAiXjIuNi4wIgorICAgIHVuZGVyc2NvcmUgIl4xLjguMCIKKyAgICB4bWwtY3J5cHRvICJeMC4xMC4wIgorICAgIHhtbC1lbmNyeXB0aW9uICJeMC4xMS4wIgorICAgIHhtbDJqcyAiXjAuNC4wIgorICAgIHhtbGJ1aWxkZXIgIn4yLjIuMCIKKyAgICB4bWxkb20gIl4wLjEuMCIKIAogc2F4QD49MC42LjA6CiAgIHZlcnNpb24gIjEuMi40IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS9zYXgvLS9zYXgtMS4yLjQudGd6IzI4MTYyMzRlMjM3OGJkZGM0ZTUzNTRmYWI1Y2FhODk1ZGY3MTAwZDkiCisgIGludGVncml0eSBzaGE1MTItTnFWRHY5VHBBTlVqRm0wTjh1TTVHeEwzNlVnS2k5L2F0WncreDdZRm5ROGNrd0ZHS3JsNHhYNHlXdHJleTNVSm01blAxa1VibllnTG9wcVdOU1JoV3c9PQogCi11bmRlcnNjb3JlQD49MS41Lng6Cit1bmRlcnNjb3JlQF4xLjguMDoKICAgdmVyc2lvbiAiMS45LjEiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3VuZGVyc2NvcmUvLS91bmRlcnNjb3JlLTEuOS4xLnRneiMwNmRjZTM0YTBlNjhhN2JhYmMyOWIzNjViOGU3NGI4OTI1MjAzOTYxIgorICBpbnRlZ3JpdHkgc2hhNTEyLTUvNGV0bkNrZDljOGd3Z293aTUvb20vbVlPNWFqQ2FPZ2R6ai9vVyswZVFWOVd4S0JEWnc1K3ljbUttZWFUWGpJblMvVzBCenBHTG8yeFIyYUJ3WmRnPT0KIAotdW5kZXJzY29yZUB+MS42LjA6Ci0gIHZlcnNpb24gIjEuNi4wIgotICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS91bmRlcnNjb3JlLy0vdW5kZXJzY29yZS0xLjYuMC50Z3ojOGIzOGIxMGNhY2RlZjYzMzM3YjhiMjRlNGZmODZkNDVhZWE1MjlhOCIKLQoteG1sLWNyeXB0b0BeMC44LjE6Ci0gIHZlcnNpb24gIjAuOC41IgotICByZXNvbHZlZCAiaHR0cDovL3JlZ2lzdHJ5Lm5wbWpzLm9yZy94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjguNS50Z3ojMmJiY2ZiM2ViMzNmM2E4MmEyMThiODIyYmY2NzJiNmIxYzIwZTUzOCIKK3htbC1jcnlwdG9AXjAuMTAuMDoKKyAgdmVyc2lvbiAiMC4xMC4xIgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwtY3J5cHRvLy0veG1sLWNyeXB0by0wLjEwLjEudGd6I2Y4MzJmNzRjY2Y1NmYyNGFmY2FlMTE2M2ExZmNhYjQ0ZDk2Nzc0YTgiCisgIGludGVncml0eSBzaGExLStETDNUTTlXOGtyOHJoRmpvZnlyUk5sbmRLZz0KICAgZGVwZW5kZW5jaWVzOgogICAgIHhtbGRvbSAiPTAuMS4xOSIKICAgICB4cGF0aC5qcyAiPj0wLjAuMyIKIAoteG1sLWVuY3J5cHRpb25AfjAuNy40OgotICB2ZXJzaW9uICIwLjcuNCIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjcuNC50Z3ojNDI3OTFlYzY0ZDU1NmQyNDU1ZGNiOWRhMGE1NDEyMzY2NWFjNjVjNyIKK3htbC1lbmNyeXB0aW9uQF4wLjExLjA6CisgIHZlcnNpb24gIjAuMTEuMiIKKyAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sLWVuY3J5cHRpb24vLS94bWwtZW5jcnlwdGlvbi0wLjExLjIudGd6I2MyMTdmNTUwOTU0N2UzNGI1MDBiODI5ZjJjMGJjYTg1Y2NhNzNhMjEiCisgIGludGVncml0eSBzaGE1MTItalZ2RVM3aTVvdmRPN04rTmpnbmNBMzI2eFlLamhxZUFubnZJZ1JuWTdST0xDZkZxRURMd1AwU3hwLzMwU0hHMEFYUVYxMDQ4VDV5aW5PRnl2d0dGemc9PQogICBkZXBlbmRlbmNpZXM6Ci0gICAgYXN5bmMgIn4wLjIuNyIKLSAgICBlanMgIn4wLjguMyIKLSAgICBub2RlLWZvcmdlICIwLjIuMjQiCisgICAgYXN5bmMgIl4yLjEuNSIKKyAgICBlanMgIl4yLjUuNiIKKyAgICBub2RlLWZvcmdlICJeMC43LjAiCiAgICAgeG1sZG9tICJ+MC4xLjE1IgotICAgIHhwYXRoICIwLjAuNSIKKyAgICB4cGF0aCAiMC4wLjI3IgogCi14bWwyanNAfjAuNC4xOgoreG1sMmpzQF4wLjQuMDoKICAgdmVyc2lvbiAiMC40LjE5IgogICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94bWwyanMvLS94bWwyanMtMC40LjE5LnRneiM2ODZjMjBmMjEzMjA5ZTk0YWJmMGQxYmNmMWVmYWEyOTFjNzgyN2E3IgorICBpbnRlZ3JpdHkgc2hhNTEyLWVzWm5KWkpPaUpSOXdXS015dXZTRTF5NkRxNUxDdUphbnFoeHNsSDJieE02ZHVhaE5aK0hNcENMaEJRR1prYlg2eFJmOHgxWTJlSmxndDJxM3FvNDlRPT0KICAgZGVwZW5kZW5jaWVzOgogICAgIHNheCAiPj0wLjYuMCIKICAgICB4bWxidWlsZGVyICJ+OS4wLjEiCiAKLXhtbGJ1aWxkZXJAfjIuMS4wOgotICB2ZXJzaW9uICIyLjEuMCIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItMi4xLjAudGd6IzZkZGFlMzE2ODNiNmRmMTIxMDBiMjlmYzhhMGQ0ZjQ2MzQ5YWJiZWQiCit4bWxidWlsZGVyQH4yLjIuMDoKKyAgdmVyc2lvbiAiMi4yLjEiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTIuMi4xLnRneiM5MzI2NDMwZjEzMGQ4NzQzNWQ0YzQwODY2NDNhYTI5MjZlMTA1YTMyIgorICBpbnRlZ3JpdHkgc2hhMS1reVpERHhNTmgwTmRURUNHWkRxaWttNFFXakk9CiAgIGRlcGVuZGVuY2llczoKLSAgICB1bmRlcnNjb3JlICI+PTEuNS54IgorICAgIGxvZGFzaC1ub2RlICJ+Mi40LjEiCiAKIHhtbGJ1aWxkZXJAfjkuMC4xOgogICB2ZXJzaW9uICI5LjAuNyIKLSAgcmVzb2x2ZWQgImh0dHA6Ly9yZWdpc3RyeS5ucG1qcy5vcmcveG1sYnVpbGRlci8tL3htbGJ1aWxkZXItOS4wLjcudGd6IzEzMmVlNjNkMmVjNTU2NWM1NTdlMjBmNGMyMmRmOWFjYTY4NmIxMGQiCisgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGJ1aWxkZXIvLS94bWxidWlsZGVyLTkuMC43LnRneiMxMzJlZTYzZDJlYzU1NjVjNTU3ZTIwZjRjMjJkZjlhY2E2ODZiMTBkIgorICBpbnRlZ3JpdHkgc2hhMS1FeTdtUFM3RlZseFZmaUQwd2kzNXJLYUdzUTA9CiAKIHhtbGRvbUA9MC4xLjE5OgogICB2ZXJzaW9uICIwLjEuMTkiCiAgIHJlc29sdmVkICJodHRwczovL3JlZ2lzdHJ5Lnlhcm5wa2cuY29tL3htbGRvbS8tL3htbGRvbS0wLjEuMTkudGd6IzYzMWZjMDc3NzZlZmQ4NDExOGJmMjUxNzFiMzdlZDRkMDc1YTBhYmMiCisgIGludGVncml0eSBzaGExLVl4L0FkM2J2MkVFWXZ5VVhHemZ0VFFkYUNydz0KIAoteG1sZG9tQH4wLjEuMTUsIHhtbGRvbUB+MC4xLjE5OgoreG1sZG9tQF4wLjEuMCwgeG1sZG9tQH4wLjEuMTU6CiAgIHZlcnNpb24gIjAuMS4yNyIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veG1sZG9tLy0veG1sZG9tLTAuMS4yNy50Z3ojZDUwMWY5N2IzYmRiNDAzYWY4ZWY5ZWNjMjA1NzMxODdhYWRhYzBlOSIKKyAgaW50ZWdyaXR5IHNoYTEtMVFINWV6dmJRRHI0NzU3TUlGY3hoNnJhd09rPQogCiB4cGF0aC5qc0A+PTAuMC4zOgogICB2ZXJzaW9uICIxLjEuMCIKICAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGguanMvLS94cGF0aC5qcy0xLjEuMC50Z3ojMzgxNmE0NGVkNGJiMzUyMDkxMDgzZDAwMmEzODNkZDUxMDRhNWZmMSIKKyAgaW50ZWdyaXR5IHNoYTUxMi1qZytxa2ZTNEs4RTc5NjVzcWFVbDhtUm5nWGlLYjNXWkdmT05nRTE4cHIwM0ZVUWl1U1Y2RytFajR0UzU1QitySVFTRkVJdzNwaGRWQVE0cFBxTldmUT09CiAKLXhwYXRoQDAuMC41OgotICB2ZXJzaW9uICIwLjAuNSIKLSAgcmVzb2x2ZWQgImh0dHBzOi8vcmVnaXN0cnkueWFybnBrZy5jb20veHBhdGgvLS94cGF0aC0wLjAuNS50Z3ojNDU0MDM2ZjZlZjBmM2RmNWFmNWQ0YmE0YTExOWZiNzU2NzRiM2U2YyIKK3hwYXRoQDAuMC4yNzoKKyAgdmVyc2lvbiAiMC4wLjI3IgorICByZXNvbHZlZCAiaHR0cHM6Ly9yZWdpc3RyeS55YXJucGtnLmNvbS94cGF0aC8tL3hwYXRoLTAuMC4yNy50Z3ojZGQzNDIxZmJkY2M1NjQ2YWMzMmM0ODUzMWI0ZDdlOWQwYzJjZmE5MiIKKyAgaW50ZWdyaXR5IHNoYTUxMi1mZzAzV1J4dGtDVjZvaENsZVBOQUVDWXNtcEtLVHY1TDh5L1gzRG4xaFFyZWMzUE94MmpIWi8wUDJxUTZIdnNyVTFCbWVxWGNvZjNOR0d1ZUc2THh3UT09Cg=="
- }
- ]
-}
diff --git a/spec/fixtures/security-reports/remediations/remediation.patch b/spec/fixtures/security-reports/remediations/remediation.patch
deleted file mode 100644
index bbfb6874627..00000000000
--- a/spec/fixtures/security-reports/remediations/remediation.patch
+++ /dev/null
@@ -1,180 +0,0 @@
-diff --git a/yarn.lock b/yarn.lock
-index 0ecc92f..7fa4554 100644
---- a/yarn.lock
-+++ b/yarn.lock
-@@ -2,103 +2,124 @@
- # yarn lockfile v1
-
-
--async@~0.2.7:
-- version "0.2.10"
-- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
--
--async@~1.5.2:
-- version "1.5.2"
-- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-+async@^2.1.5, async@^2.5.0:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
-+ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
-+ dependencies:
-+ lodash "^4.17.10"
-
--debug@^1.0.4:
-- version "1.0.5"
-- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
-+debug@^2.6.0:
-+ version "2.6.9"
-+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
-+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
- dependencies:
- ms "2.0.0"
-
--ejs@~0.8.3:
-- version "0.8.8"
-- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-+ejs@^2.5.6:
-+ version "2.6.1"
-+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
-+ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-+
-+lodash-node@~2.4.1:
-+ version "2.4.1"
-+ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec"
-+ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=
-+
-+lodash@^4.17.10:
-+ version "4.17.11"
-+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
-+ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
-
- ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-
--node-forge@0.2.24:
-- version "0.2.24"
-- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-+node-forge@^0.7.0:
-+ version "0.7.6"
-+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
-+ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
-
- saml2-js@^1.5.0:
-- version "1.5.0"
-- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
-+ version "1.12.4"
-+ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3"
-+ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M=
- dependencies:
-- async "~1.5.2"
-- debug "^1.0.4"
-- underscore "~1.6.0"
-- xml-crypto "^0.8.1"
-- xml-encryption "~0.7.4"
-- xml2js "~0.4.1"
-- xmlbuilder "~2.1.0"
-- xmldom "~0.1.19"
-+ async "^2.5.0"
-+ debug "^2.6.0"
-+ underscore "^1.8.0"
-+ xml-crypto "^0.10.0"
-+ xml-encryption "^0.11.0"
-+ xml2js "^0.4.0"
-+ xmlbuilder "~2.2.0"
-+ xmldom "^0.1.0"
-
- sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-
--underscore@>=1.5.x:
-+underscore@^1.8.0:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-+ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
-
--underscore@~1.6.0:
-- version "1.6.0"
-- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
--
--xml-crypto@^0.8.1:
-- version "0.8.5"
-- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
-+xml-crypto@^0.10.0:
-+ version "0.10.1"
-+ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8"
-+ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
--xml-encryption@~0.7.4:
-- version "0.7.4"
-- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
-+xml-encryption@^0.11.0:
-+ version "0.11.2"
-+ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21"
-+ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==
- dependencies:
-- async "~0.2.7"
-- ejs "~0.8.3"
-- node-forge "0.2.24"
-+ async "^2.1.5"
-+ ejs "^2.5.6"
-+ node-forge "^0.7.0"
- xmldom "~0.1.15"
-- xpath "0.0.5"
-+ xpath "0.0.27"
-
--xml2js@~0.4.1:
-+xml2js@^0.4.0:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
-+ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
--xmlbuilder@~2.1.0:
-- version "2.1.0"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
-+xmlbuilder@~2.2.0:
-+ version "2.2.1"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32"
-+ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=
- dependencies:
-- underscore ">=1.5.x"
-+ lodash-node "~2.4.1"
-
- xmlbuilder@~9.0.1:
- version "9.0.7"
-- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-+ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
-
- xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-+ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
-
--xmldom@~0.1.15, xmldom@~0.1.19:
-+xmldom@^0.1.0, xmldom@~0.1.15:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-+ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
-
- xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-+ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==
-
--xpath@0.0.5:
-- version "0.0.5"
-- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
-+xpath@0.0.27:
-+ version "0.0.27"
-+ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"
-+ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
diff --git a/spec/fixtures/security-reports/remediations/yarn.lock b/spec/fixtures/security-reports/remediations/yarn.lock
deleted file mode 100644
index 0ecc92fb711..00000000000
--- a/spec/fixtures/security-reports/remediations/yarn.lock
+++ /dev/null
@@ -1,104 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-async@~0.2.7:
- version "0.2.10"
- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
-async@~1.5.2:
- version "1.5.2"
- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
-
-debug@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
- dependencies:
- ms "2.0.0"
-
-ejs@~0.8.3:
- version "0.8.8"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
-
-ms@2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
-
-node-forge@0.2.24:
- version "0.2.24"
- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
-
-saml2-js@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
- dependencies:
- async "~1.5.2"
- debug "^1.0.4"
- underscore "~1.6.0"
- xml-crypto "^0.8.1"
- xml-encryption "~0.7.4"
- xml2js "~0.4.1"
- xmlbuilder "~2.1.0"
- xmldom "~0.1.19"
-
-sax@>=0.6.0:
- version "1.2.4"
- resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
-
-underscore@>=1.5.x:
- version "1.9.1"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
-
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-
-xml-crypto@^0.8.1:
- version "0.8.5"
- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
- dependencies:
- xmldom "=0.1.19"
- xpath.js ">=0.0.3"
-
-xml-encryption@~0.7.4:
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
- dependencies:
- async "~0.2.7"
- ejs "~0.8.3"
- node-forge "0.2.24"
- xmldom "~0.1.15"
- xpath "0.0.5"
-
-xml2js@~0.4.1:
- version "0.4.19"
- resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
- dependencies:
- sax ">=0.6.0"
- xmlbuilder "~9.0.1"
-
-xmlbuilder@~2.1.0:
- version "2.1.0"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
- dependencies:
- underscore ">=1.5.x"
-
-xmlbuilder@~9.0.1:
- version "9.0.7"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
-
-xmldom@=0.1.19:
- version "0.1.19"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
-
-xmldom@~0.1.15, xmldom@~0.1.19:
- version "0.1.27"
- resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
-
-xpath.js@>=0.0.3:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
-
-xpath@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
diff --git a/spec/frontend/autosave_spec.js b/spec/frontend/autosave_spec.js
index 4d9c8f96d62..33d402388c9 100644
--- a/spec/frontend/autosave_spec.js
+++ b/spec/frontend/autosave_spec.js
@@ -63,12 +63,15 @@ describe('Autosave', () => {
expect(field.trigger).toHaveBeenCalled();
});
- it('triggers native event', done => {
- autosave.field.get(0).addEventListener('change', () => {
- done();
- });
+ it('triggers native event', () => {
+ const fieldElement = autosave.field.get(0);
+ const eventHandler = jest.fn();
+ fieldElement.addEventListener('change', eventHandler);
Autosave.prototype.restore.call(autosave);
+
+ expect(eventHandler).toHaveBeenCalledTimes(1);
+ fieldElement.removeEventListener('change', eventHandler);
});
});
diff --git a/spec/frontend/behaviors/markdown/render_metrics_spec.js b/spec/frontend/behaviors/markdown/render_metrics_spec.js
new file mode 100644
index 00000000000..6db0eabc16b
--- /dev/null
+++ b/spec/frontend/behaviors/markdown/render_metrics_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+import renderMetrics from '~/behaviors/markdown/render_metrics';
+import { TEST_HOST } from 'helpers/test_constants';
+
+const originalExtend = Vue.extend;
+
+describe('Render metrics for Gitlab Flavoured Markdown', () => {
+ const container = {
+ Metrics() {},
+ };
+
+ let spyExtend;
+
+ beforeEach(() => {
+ Vue.extend = () => container.Metrics;
+ spyExtend = jest.spyOn(Vue, 'extend');
+ });
+
+ afterEach(() => {
+ Vue.extend = originalExtend;
+ });
+
+ it('does nothing when no elements are found', () => {
+ renderMetrics([]);
+
+ expect(spyExtend).not.toHaveBeenCalled();
+ });
+
+ it('renders a vue component when elements are found', () => {
+ const element = document.createElement('div');
+ element.setAttribute('data-dashboard-url', TEST_HOST);
+
+ renderMetrics([element]);
+
+ expect(spyExtend).toHaveBeenCalled();
+ });
+});
diff --git a/spec/frontend/clusters/clusters_bundle_spec.js b/spec/frontend/clusters/clusters_bundle_spec.js
index 6de06a9e2d5..80816faa5fc 100644
--- a/spec/frontend/clusters/clusters_bundle_spec.js
+++ b/spec/frontend/clusters/clusters_bundle_spec.js
@@ -147,47 +147,80 @@ describe('Clusters', () => {
});
describe('updateContainer', () => {
+ const { location } = window;
+
+ beforeEach(() => {
+ delete window.location;
+ window.location = {
+ reload: jest.fn(),
+ hash: location.hash,
+ };
+ });
+
+ afterEach(() => {
+ window.location = location;
+ });
+
describe('when creating cluster', () => {
it('should show the creating container', () => {
cluster.updateContainer(null, 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
});
it('should continue to show `creating` banner with subsequent updates of the same status', () => {
+ cluster.updateContainer(null, 'creating');
cluster.updateContainer('creating', 'creating');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeFalsy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
});
});
describe('when cluster is created', () => {
- it('should show the success container and fresh the page', () => {
- cluster.updateContainer(null, 'created');
+ it('should hide the "creating" banner and refresh the page', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ cluster.updateContainer(null, 'creating');
+ cluster.updateContainer('creating', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(true);
+ });
- expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
+ it('when the page is refreshed, it should show the "success" banner', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(true);
+
+ cluster.updateContainer(null, 'created');
+ cluster.updateContainer('created', 'created');
+ expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
+ expect(cluster.successContainer.classList.contains('hidden')).toBeFalsy();
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).toHaveBeenCalledWith(false);
});
it('should not show a banner when status is already `created`', () => {
+ jest.spyOn(cluster, 'setClusterNewlyCreated');
+ jest.spyOn(cluster, 'isClusterNewlyCreated').mockReturnValue(false);
+
+ cluster.updateContainer(null, 'created');
cluster.updateContainer('created', 'created');
expect(cluster.creatingContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.successContainer.classList.contains('hidden')).toBeTruthy();
-
expect(cluster.errorContainer.classList.contains('hidden')).toBeTruthy();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ expect(cluster.setClusterNewlyCreated).not.toHaveBeenCalled();
});
});
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/environment.js b/spec/frontend/environment.js
index a8c8688441d..290c0e797cb 100644
--- a/spec/frontend/environment.js
+++ b/spec/frontend/environment.js
@@ -1,8 +1,11 @@
/* eslint-disable import/no-commonjs */
+const path = require('path');
const { ErrorWithStack } = require('jest-util');
const JSDOMEnvironment = require('jest-environment-jsdom');
+const ROOT_PATH = path.resolve(__dirname, '../..');
+
class CustomEnvironment extends JSDOMEnvironment {
constructor(config, context) {
super(config, context);
@@ -35,9 +38,8 @@ class CustomEnvironment extends JSDOMEnvironment {
this.rejectedPromises.push(error);
};
- this.global.fixturesBasePath = `${process.cwd()}/${
- IS_EE ? 'ee/' : ''
- }spec/javascripts/fixtures`;
+ this.global.fixturesBasePath = `${ROOT_PATH}/tmp/tests/frontend/fixtures${IS_EE ? '-ee' : ''}`;
+ this.global.staticFixturesBasePath = `${ROOT_PATH}/spec/frontend/fixtures`;
// Not yet supported by JSDOM: https://github.com/jsdom/jsdom/issues/317
this.global.document.createRange = () => ({
diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js
new file mode 100644
index 00000000000..a1a22274e8f
--- /dev/null
+++ b/spec/frontend/environments/confirm_rollback_modal_spec.js
@@ -0,0 +1,70 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlModal } from '@gitlab/ui';
+import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
+import eventHub from '~/environments/event_hub';
+
+describe('Confirm Rollback Modal Component', () => {
+ let environment;
+
+ beforeEach(() => {
+ environment = {
+ name: 'test',
+ last_deployment: {
+ commit: {
+ short_id: 'abc0123',
+ },
+ },
+ modalId: 'test',
+ };
+ });
+
+ it('should show "Rollback" when isLastDeployment is false', () => {
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment: {
+ ...environment,
+ isLastDeployment: false,
+ },
+ },
+ });
+ const modal = component.find(GlModal);
+
+ expect(modal.attributes('title')).toContain('Rollback');
+ expect(modal.attributes('title')).toContain('test');
+ expect(modal.attributes('ok-title')).toBe('Rollback');
+ expect(modal.text()).toContain('commit abc0123');
+ expect(modal.text()).toContain('Are you sure you want to continue?');
+ });
+
+ it('should show "Re-deploy" when isLastDeployment is true', () => {
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment: {
+ ...environment,
+ isLastDeployment: true,
+ },
+ },
+ });
+ const modal = component.find(GlModal);
+
+ expect(modal.attributes('title')).toContain('Re-deploy');
+ expect(modal.attributes('title')).toContain('test');
+ expect(modal.attributes('ok-title')).toBe('Re-deploy');
+ expect(modal.text()).toContain('commit abc0123');
+ expect(modal.text()).toContain('Are you sure you want to continue?');
+ });
+
+ it('should emit the "rollback" event when "ok" is clicked', () => {
+ environment = { ...environment, isLastDeployment: true };
+ const component = shallowMount(ConfirmRollbackModal, {
+ propsData: {
+ environment,
+ },
+ });
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
+ const modal = component.find(GlModal);
+ modal.vm.$emit('ok');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment);
+ });
+});
diff --git a/spec/frontend/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js
new file mode 100644
index 00000000000..fb62a096c3d
--- /dev/null
+++ b/spec/frontend/environments/environment_rollback_spec.js
@@ -0,0 +1,53 @@
+import { shallowMount, mount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import eventHub from '~/environments/event_hub';
+import RollbackComponent from '~/environments/components/environment_rollback.vue';
+
+describe('Rollback Component', () => {
+ const retryUrl = 'https://gitlab.com/retry';
+
+ it('Should render Re-deploy label when isLastDeployment is true', () => {
+ const wrapper = mount(RollbackComponent, {
+ propsData: {
+ retryUrl,
+ isLastDeployment: true,
+ environment: {},
+ },
+ });
+
+ expect(wrapper.element).toHaveSpriteIcon('repeat');
+ });
+
+ it('Should render Rollback label when isLastDeployment is false', () => {
+ const wrapper = mount(RollbackComponent, {
+ propsData: {
+ retryUrl,
+ isLastDeployment: false,
+ environment: {},
+ },
+ });
+
+ expect(wrapper.element).toHaveSpriteIcon('redo');
+ });
+
+ it('should emit a "rollback" event on button click', () => {
+ const eventHubSpy = jest.spyOn(eventHub, '$emit');
+ const wrapper = shallowMount(RollbackComponent, {
+ propsData: {
+ retryUrl,
+ environment: {
+ name: 'test',
+ },
+ },
+ });
+ const button = wrapper.find(GlButton);
+
+ button.vm.$emit('click');
+
+ expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
+ retryUrl,
+ isLastDeployment: true,
+ name: 'test',
+ });
+ });
+});
diff --git a/spec/frontend/fixtures/abuse_reports.rb b/spec/frontend/fixtures/abuse_reports.rb
new file mode 100644
index 00000000000..21356390cae
--- /dev/null
+++ b/spec/frontend/fixtures/abuse_reports.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let!(:abuse_report) { create(:abuse_report) }
+ let!(:abuse_report_with_short_message) { create(:abuse_report, message: 'SHORT MESSAGE') }
+ let!(:abuse_report_with_long_message) { create(:abuse_report, message: "LONG MESSAGE\n" * 50) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('abuse_reports/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'abuse_reports/abuse_reports_list.html' do
+ get :index
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/admin_users.rb b/spec/frontend/fixtures/admin_users.rb
new file mode 100644
index 00000000000..0209594dadc
--- /dev/null
+++ b/spec/frontend/fixtures/admin_users.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('admin/users')
+ end
+
+ it 'admin/users/new_with_internal_user_regex.html' do
+ stub_application_setting(user_default_external: true)
+ stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
+
+ get :new
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/application_settings.rb b/spec/frontend/fixtures/application_settings.rb
new file mode 100644
index 00000000000..38a060580c1
--- /dev/null
+++ b/spec/frontend/fixtures/application_settings.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
+ include StubENV
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
+
+ before do
+ stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
+ sign_in(admin)
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('application_settings/')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'application_settings/accounts_and_limit.html' do
+ stub_application_setting(user_default_external: false)
+
+ get :show
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/autocomplete_sources.rb b/spec/frontend/fixtures/autocomplete_sources.rb
new file mode 100644
index 00000000000..9e04328e2b9
--- /dev/null
+++ b/spec/frontend/fixtures/autocomplete_sources.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:admin) { create(:admin) }
+ set(:group) { create(:group, name: 'frontend-fixtures') }
+ set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
+ set(:issue) { create(:issue, project: project) }
+
+ before(:all) do
+ clean_frontend_fixtures('autocomplete_sources/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'autocomplete_sources/labels.json' do
+ issue.labels << create(:label, project: project, title: 'bug')
+ issue.labels << create(:label, project: project, title: 'critical')
+
+ create(:label, project: project, title: 'feature')
+ create(:label, project: project, title: 'documentation')
+
+ get :labels,
+ format: :json,
+ params: {
+ namespace_id: group.path,
+ project_id: project.path,
+ type: issue.class.name,
+ type_id: issue.id
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/blob.rb b/spec/frontend/fixtures/blob.rb
new file mode 100644
index 00000000000..ce5030efbf8
--- /dev/null
+++ b/spec/frontend/fixtures/blob.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('blob/')
+ end
+
+ before do
+ sign_in(admin)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'blob/show.html' do
+ get(:show, params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: 'add-ipython-files/files/ipython/basic.ipynb'
+ })
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/boards.rb b/spec/frontend/fixtures/boards.rb
new file mode 100644
index 00000000000..f257d80390f
--- /dev/null
+++ b/spec/frontend/fixtures/boards.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('boards/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'boards/show.html' do
+ get(:index, params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/branches.rb b/spec/frontend/fixtures/branches.rb
new file mode 100644
index 00000000000..197fe42c52a
--- /dev/null
+++ b/spec/frontend/fixtures/branches.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('branches/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'branches/new_branch.html' do
+ get :new, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/clusters.rb b/spec/frontend/fixtures/clusters.rb
new file mode 100644
index 00000000000..f15ef010807
--- /dev/null
+++ b/spec/frontend/fixtures/clusters.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('clusters/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'clusters/show_cluster.html' do
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: cluster
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/commit.rb b/spec/frontend/fixtures/commit.rb
new file mode 100644
index 00000000000..a328c455356
--- /dev/null
+++ b/spec/frontend/fixtures/commit.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:project) { create(:project, :repository) }
+ set(:user) { create(:user) }
+ let(:commit) { project.commit("master") }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('commit/')
+ end
+
+ before do
+ project.add_maintainer(user)
+ sign_in(user)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
+ end
+
+ it 'commit/show.html' do
+ params = {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: commit.id
+ }
+
+ get :show, params: params
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/deploy_keys.rb b/spec/frontend/fixtures/deploy_keys.rb
new file mode 100644
index 00000000000..fca233c6f59
--- /dev/null
+++ b/spec/frontend/fixtures/deploy_keys.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
+ let(:project2) { create(:project, :internal)}
+ let(:project3) { create(:project, :internal)}
+ let(:project4) { create(:project, :internal)}
+
+ before(:all) do
+ clean_frontend_fixtures('deploy_keys/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ render_views
+
+ it 'deploy_keys/keys.json' do
+ create(:rsa_deploy_key_2048, public: true)
+ project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
+ internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
+ create(:deploy_keys_project, project: project, deploy_key: project_key)
+ create(:deploy_keys_project, project: project2, deploy_key: internal_key)
+ create(:deploy_keys_project, project: project3, deploy_key: project_key)
+ create(:deploy_keys_project, project: project4, deploy_key: project_key)
+
+ get :index, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }, format: :json
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/groups.rb b/spec/frontend/fixtures/groups.rb
new file mode 100644
index 00000000000..c1bb2d43332
--- /dev/null
+++ b/spec/frontend/fixtures/groups.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe 'Groups (JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('groups/')
+ end
+
+ before do
+ group.add_maintainer(admin)
+ sign_in(admin)
+ end
+
+ describe GroupsController, '(JavaScript fixtures)', type: :controller do
+ it 'groups/edit.html' do
+ get :edit, params: { id: group }
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
+ it 'groups/ci_cd_settings.html' do
+ get :show, params: { group_id: group }
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/issues.rb b/spec/frontend/fixtures/issues.rb
new file mode 100644
index 00000000000..b5eb38e0023
--- /dev/null
+++ b/spec/frontend/fixtures/issues.rb
@@ -0,0 +1,122 @@
+require 'spec_helper'
+
+describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('issues/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'issues/open-issue.html' do
+ render_issue(create(:issue, project: project))
+ end
+
+ it 'issues/closed-issue.html' do
+ render_issue(create(:closed_issue, project: project))
+ end
+
+ it 'issues/issue-with-task-list.html' do
+ issue = create(:issue, project: project, description: '- [ ] Task List Item')
+ render_issue(issue)
+ end
+
+ it 'issues/issue_with_comment.html' do
+ issue = create(:issue, project: project)
+ create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
+ render_issue(issue)
+ end
+
+ it 'issues/issue_list.html' do
+ create(:issue, project: project)
+
+ get :index, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
+
+ private
+
+ def render_issue(issue)
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: issue.to_param
+ }
+
+ expect(response).to be_successful
+ end
+end
+
+describe API::Issues, '(JavaScript fixtures)', type: :request do
+ include ApiHelpers
+ include JavaScriptFixturesHelpers
+
+ def get_related_merge_requests(project_id, issue_iid, user = nil)
+ get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
+ end
+
+ def create_referencing_mr(user, project, issue)
+ attributes = {
+ author: user,
+ source_project: project,
+ target_project: project,
+ source_branch: "master",
+ target_branch: "test",
+ assignee: user,
+ description: "See #{issue.to_reference}"
+ }
+ create(:merge_request, attributes).tap do |merge_request|
+ create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
+ end
+ end
+
+ it 'issues/related_merge_requests.json' do
+ user = create(:user)
+ project = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ issue_title = 'foo'
+ issue_description = 'closed'
+ milestone = create(:milestone, title: '1.0.0', project: project)
+ issue = create :issue,
+ author: user,
+ assignees: [user],
+ project: project,
+ milestone: milestone,
+ created_at: generate(:past_time),
+ updated_at: 1.hour.ago,
+ title: issue_title,
+ description: issue_description
+
+ project.add_reporter(user)
+ create_referencing_mr(user, project, issue)
+
+ create(:merge_request,
+ :simple,
+ author: user,
+ source_project: project,
+ target_project: project,
+ description: "Some description")
+ project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
+ create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline))
+
+ get_related_merge_requests(project.id, issue.iid, user)
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/jobs.rb b/spec/frontend/fixtures/jobs.rb
new file mode 100644
index 00000000000..a3a7759c85b
--- /dev/null
+++ b/spec/frontend/fixtures/jobs.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
+ let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
+ let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
+ let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
+ let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
+ let!(:delayed_job) do
+ create(:ci_build, :scheduled,
+ pipeline: pipeline,
+ name: 'delayed job',
+ stage: 'test')
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('builds/')
+ clean_frontend_fixtures('jobs/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'builds/build-with-artifacts.html' do
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: build_with_artifacts.to_param
+ }
+
+ expect(response).to be_successful
+ end
+
+ it 'jobs/delayed.json' do
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: delayed_job.to_param
+ }, format: :json
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/labels.rb b/spec/frontend/fixtures/labels.rb
new file mode 100644
index 00000000000..a312287970f
--- /dev/null
+++ b/spec/frontend/fixtures/labels.rb
@@ -0,0 +1,58 @@
+require 'spec_helper'
+
+describe 'Labels (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:group) { create(:group, name: 'frontend-fixtures-group' )}
+ let(:project) { create(:project_empty_repo, namespace: group, path: 'labels-project') }
+
+ let!(:project_label_bug) { create(:label, project: project, title: 'bug', color: '#FF0000') }
+ let!(:project_label_enhancement) { create(:label, project: project, title: 'enhancement', color: '#00FF00') }
+ let!(:project_label_feature) { create(:label, project: project, title: 'feature', color: '#0000FF') }
+
+ let!(:group_label_roses) { create(:group_label, group: group, title: 'roses', color: '#FF0000') }
+ let!(:groub_label_space) { create(:group_label, group: group, title: 'some space', color: '#FFFFFF') }
+ let!(:groub_label_violets) { create(:group_label, group: group, title: 'violets', color: '#0000FF') }
+
+ before(:all) do
+ clean_frontend_fixtures('labels/')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'labels/group_labels.json' do
+ get :index, params: {
+ group_id: group
+ }, format: 'json'
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'labels/project_labels.json' do
+ get :index, params: {
+ namespace_id: group,
+ project_id: project
+ }, format: 'json'
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/merge_requests.rb b/spec/frontend/fixtures/merge_requests.rb
new file mode 100644
index 00000000000..88706e96676
--- /dev/null
+++ b/spec/frontend/fixtures/merge_requests.rb
@@ -0,0 +1,134 @@
+require 'spec_helper'
+
+describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
+ let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
+ let(:pipeline) do
+ create(
+ :ci_pipeline,
+ project: merge_request.source_project,
+ ref: merge_request.source_branch,
+ sha: merge_request.diff_head_sha
+ )
+ end
+ let(:path) { "files/ruby/popen.rb" }
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ )
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_requests/')
+ end
+
+ before do
+ sign_in(admin)
+ allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'merge_requests/merge_request_of_current_user.html' do
+ merge_request.update(author: admin)
+
+ render_merge_request(merge_request)
+ end
+
+ it 'merge_requests/merge_request_with_task_list.html' do
+ create(:ci_build, :pending, pipeline: pipeline)
+
+ render_merge_request(merge_request)
+ end
+
+ it 'merge_requests/merged_merge_request.html' do
+ expect_next_instance_of(MergeRequest) do |merge_request|
+ allow(merge_request).to receive(:source_branch_exists?).and_return(true)
+ allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
+ end
+ render_merge_request(merged_merge_request)
+ end
+
+ it 'merge_requests/diff_comment.html' do
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(merge_request)
+ end
+
+ it 'merge_requests/merge_request_with_comment.html' do
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
+ render_merge_request(merge_request)
+ end
+
+ it 'merge_requests/discussions.json' do
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ render_discussions_json(merge_request)
+ end
+
+ it 'merge_requests/diff_discussion.json' do
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ render_discussions_json(merge_request)
+ end
+
+ it 'merge_requests/resolved_diff_discussion.json' do
+ note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
+ create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
+
+ render_discussions_json(merge_request)
+ end
+
+ context 'with image diff' do
+ let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") }
+ let(:image_path) { "files/images/ee_repo_logo.png" }
+ let(:image_position) do
+ Gitlab::Diff::Position.new(
+ old_path: image_path,
+ new_path: image_path,
+ width: 100,
+ height: 100,
+ x: 1,
+ y: 1,
+ position_type: "image",
+ diff_refs: merge_request2.diff_refs
+ )
+ end
+
+ it 'merge_requests/image_diff_discussion.json' do
+ create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position)
+ render_discussions_json(merge_request2)
+ end
+ end
+
+ private
+
+ def render_discussions_json(merge_request)
+ get :discussions, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.to_param
+ }, format: :json
+ end
+
+ def render_merge_request(merge_request)
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.to_param
+ }, format: :html
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/merge_requests_diffs.rb b/spec/frontend/fixtures/merge_requests_diffs.rb
new file mode 100644
index 00000000000..b633a0495a6
--- /dev/null
+++ b/spec/frontend/fixtures/merge_requests_diffs.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
+ let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
+ let(:path) { "files/ruby/popen.rb" }
+ let(:selected_commit) { merge_request.all_commits[0] }
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: path,
+ new_path: path,
+ old_line: nil,
+ new_line: 14,
+ diff_refs: merge_request.diff_refs
+ )
+ end
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('merge_request_diffs/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'merge_request_diffs/with_commit.json' do
+ # Create a user that matches the selected commit author
+ # This is so that the "author" information will be populated
+ create(:user, email: selected_commit.author_email, name: selected_commit.author_name)
+
+ render_merge_request(merge_request, commit_id: selected_commit.sha)
+ end
+
+ it 'merge_request_diffs/inline_changes_tab_with_comments.json' do
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(merge_request)
+ end
+
+ it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do
+ create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
+ create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
+ render_merge_request(merge_request, view: 'parallel')
+ end
+
+ private
+
+ def render_merge_request(merge_request, view: 'inline', **extra_params)
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: merge_request.to_param,
+ view: view,
+ **extra_params
+ }, format: :json
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/pipeline_schedules.rb b/spec/frontend/fixtures/pipeline_schedules.rb
new file mode 100644
index 00000000000..a70091a3919
--- /dev/null
+++ b/spec/frontend/fixtures/pipeline_schedules.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :public, :repository) }
+ let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
+ let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
+ let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('pipeline_schedules/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'pipeline_schedules/edit.html' do
+ get :edit, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule.id
+ }
+
+ expect(response).to be_successful
+ end
+
+ it 'pipeline_schedules/edit_with_variables.html' do
+ get :edit, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project,
+ id: pipeline_schedule_populated.id
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/pipelines.rb b/spec/frontend/fixtures/pipelines.rb
new file mode 100644
index 00000000000..ed57eb0aa80
--- /dev/null
+++ b/spec/frontend/fixtures/pipelines.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
+ let(:commit) { create(:commit, project: project) }
+ let(:commit_without_author) { RepoHelpers.another_sample_commit }
+ let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
+ let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
+ let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('pipelines/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'pipelines/pipelines.json' do
+ get :index, params: {
+ namespace_id: namespace,
+ project_id: project
+ }, format: :json
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/projects.rb b/spec/frontend/fixtures/projects.rb
new file mode 100644
index 00000000000..91e3b65215a
--- /dev/null
+++ b/spec/frontend/fixtures/projects.rb
@@ -0,0 +1,81 @@
+require 'spec_helper'
+
+describe 'Projects (JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ runners_token = 'runnerstoken:intabulasreferre'
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) }
+ let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
+ let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('projects/')
+ end
+
+ before do
+ project.add_maintainer(admin)
+ sign_in(admin)
+ allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ describe ProjectsController, '(JavaScript fixtures)', type: :controller do
+ it 'projects/dashboard.html' do
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ id: project
+ }
+
+ expect(response).to be_successful
+ end
+
+ it 'projects/overview.html' do
+ get :show, params: {
+ namespace_id: project_with_repo.namespace.to_param,
+ id: project_with_repo
+ }
+
+ expect(response).to be_successful
+ end
+
+ it 'projects/edit.html' do
+ get :edit, params: {
+ namespace_id: project.namespace.to_param,
+ id: project
+ }
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
+ it 'projects/ci_cd_settings.html' do
+ get :show, params: {
+ namespace_id: project.namespace.to_param,
+ project_id: project
+ }
+
+ expect(response).to be_successful
+ end
+
+ it 'projects/ci_cd_settings_with_variables.html' do
+ create(:ci_variable, project: project_variable_populated)
+ create(:ci_variable, project: project_variable_populated)
+
+ get :show, params: {
+ namespace_id: project_variable_populated.namespace.to_param,
+ project_id: project_variable_populated
+ }
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/prometheus_service.rb b/spec/frontend/fixtures/prometheus_service.rb
new file mode 100644
index 00000000000..93ee81120d7
--- /dev/null
+++ b/spec/frontend/fixtures/prometheus_service.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
+ let!(:service) { create(:prometheus_service, project: project) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('services/prometheus')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'services/prometheus/prometheus_service.html' do
+ get :edit, params: {
+ namespace_id: namespace,
+ project_id: project,
+ id: service.to_param
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/javascripts/fixtures/raw.rb b/spec/frontend/fixtures/raw.rb
index 801c80a0112..801c80a0112 100644
--- a/spec/javascripts/fixtures/raw.rb
+++ b/spec/frontend/fixtures/raw.rb
diff --git a/spec/frontend/fixtures/search.rb b/spec/frontend/fixtures/search.rb
new file mode 100644
index 00000000000..c26c6998ae9
--- /dev/null
+++ b/spec/frontend/fixtures/search.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe SearchController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('search/')
+ end
+
+ it 'search/show.html' do
+ get :show
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/services.rb b/spec/frontend/fixtures/services.rb
new file mode 100644
index 00000000000..ee1e088f158
--- /dev/null
+++ b/spec/frontend/fixtures/services.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
+ let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('services/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'services/edit_service.html' do
+ get :edit, params: {
+ namespace_id: namespace,
+ project_id: project,
+ id: service.to_param
+ }
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/sessions.rb b/spec/frontend/fixtures/sessions.rb
new file mode 100644
index 00000000000..18574ea06b5
--- /dev/null
+++ b/spec/frontend/fixtures/sessions.rb
@@ -0,0 +1,25 @@
+require 'spec_helper'
+
+describe 'Sessions (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ before(:all) do
+ clean_frontend_fixtures('sessions/')
+ end
+
+ describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ include DeviseHelpers
+
+ render_views
+
+ before do
+ set_devise_mapping(context: @request)
+ end
+
+ it 'sessions/new.html' do
+ get :new
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/snippet.rb b/spec/frontend/fixtures/snippet.rb
new file mode 100644
index 00000000000..23bcdb47ac6
--- /dev/null
+++ b/spec/frontend/fixtures/snippet.rb
@@ -0,0 +1,33 @@
+require 'spec_helper'
+
+describe SnippetsController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
+ let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
+
+ render_views
+
+ before(:all) do
+ clean_frontend_fixtures('snippets/')
+ end
+
+ before do
+ sign_in(admin)
+ allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ it 'snippets/show.html' do
+ create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
+
+ get(:show, params: { id: snippet.to_param })
+
+ expect(response).to be_successful
+ end
+end
diff --git a/spec/frontend/fixtures/static/README.md b/spec/frontend/fixtures/static/README.md
new file mode 100644
index 00000000000..011601d0df8
--- /dev/null
+++ b/spec/frontend/fixtures/static/README.md
@@ -0,0 +1,3 @@
+# Please do not add new files here!
+
+Instead use a Ruby file in the fixtures root directory (`spec/frontend/fixtures/`).
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html b/spec/frontend/fixtures/static/ajax_loading_spinner.html
index 0e1ebb32b1c..0e1ebb32b1c 100644
--- a/spec/javascripts/fixtures/static/ajax_loading_spinner.html
+++ b/spec/frontend/fixtures/static/ajax_loading_spinner.html
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html b/spec/frontend/fixtures/static/balsamiq_viewer.html
index cdd723d1a84..cdd723d1a84 100644
--- a/spec/javascripts/fixtures/static/balsamiq_viewer.html
+++ b/spec/frontend/fixtures/static/balsamiq_viewer.html
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html b/spec/frontend/fixtures/static/create_item_dropdown.html
index d2d38370092..d2d38370092 100644
--- a/spec/javascripts/fixtures/static/create_item_dropdown.html
+++ b/spec/frontend/fixtures/static/create_item_dropdown.html
diff --git a/spec/javascripts/fixtures/static/environments/table.html b/spec/frontend/fixtures/static/environments/table.html
index 417af564ff1..417af564ff1 100644
--- a/spec/javascripts/fixtures/static/environments/table.html
+++ b/spec/frontend/fixtures/static/environments/table.html
diff --git a/spec/frontend/fixtures/static/environments_logs.html b/spec/frontend/fixtures/static/environments_logs.html
new file mode 100644
index 00000000000..6179d3dbc23
--- /dev/null
+++ b/spec/frontend/fixtures/static/environments_logs.html
@@ -0,0 +1,29 @@
+<div class="js-kubernetes-logs" data-logs-path="/root/kubernetes-app/environments/1/logs">
+ <div class="build-page">
+ <div class="build-trace-container prepend-top-default">
+ <div class="top-bar js-top-bar">
+ <div class="truncated-info hidden-xs pull-left"></div>
+ <div class="dropdown prepend-left-10 js-pod-dropdown">
+ <button aria-expanded="false" class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
+ <i class="fa fa-chevron-down"></i>
+ </button>
+ <div class="dropdown-menu dropdown-menu-selectable dropdown-menu-drop-up"></div>
+ </div>
+ <div class="controllers pull-right">
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to top">
+ <button class="js-scroll-up btn-scroll btn-transparent btn-blank" disabled type="button"></button>
+ </div>
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Scroll to bottom">
+ <button class="js-scroll-down btn-scroll btn-transparent btn-blank" disabled type="button"></button>
+ </div>
+ <div class="refresh-control pull-right">
+ <div class="has-tooltip controllers-buttons" data-container="body" data-placement="top" title="Refresh">
+ <button class="js-refresh-log btn-default btn-refresh" disabled type="button"></button>
+ </div>
+ </div>
+ </div>
+ </div>
+ <pre class="build-trace" id="build-trace"><code class="bash js-build-output"><div class="build-loader-animation js-build-refresh"></div></code></pre>
+ </div>
+ </div>
+</div>
diff --git a/spec/javascripts/fixtures/static/event_filter.html b/spec/frontend/fixtures/static/event_filter.html
index 8e9b6fb1b5c..8e9b6fb1b5c 100644
--- a/spec/javascripts/fixtures/static/event_filter.html
+++ b/spec/frontend/fixtures/static/event_filter.html
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html b/spec/frontend/fixtures/static/gl_dropdown.html
index 08f6738414e..08f6738414e 100644
--- a/spec/javascripts/fixtures/static/gl_dropdown.html
+++ b/spec/frontend/fixtures/static/gl_dropdown.html
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html b/spec/frontend/fixtures/static/gl_field_errors.html
index f8470e02b7c..f8470e02b7c 100644
--- a/spec/javascripts/fixtures/static/gl_field_errors.html
+++ b/spec/frontend/fixtures/static/gl_field_errors.html
diff --git a/spec/javascripts/fixtures/static/images/green_box.png b/spec/frontend/fixtures/static/images/green_box.png
index cd1ff9f9ade..cd1ff9f9ade 100644
--- a/spec/javascripts/fixtures/static/images/green_box.png
+++ b/spec/frontend/fixtures/static/images/green_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/one_white_pixel.png b/spec/frontend/fixtures/static/images/one_white_pixel.png
index 073fcf40a18..073fcf40a18 100644
--- a/spec/javascripts/fixtures/static/images/one_white_pixel.png
+++ b/spec/frontend/fixtures/static/images/one_white_pixel.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/images/red_box.png b/spec/frontend/fixtures/static/images/red_box.png
index 73b2927da0f..73b2927da0f 100644
--- a/spec/javascripts/fixtures/static/images/red_box.png
+++ b/spec/frontend/fixtures/static/images/red_box.png
Binary files differ
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html b/spec/frontend/fixtures/static/issuable_filter.html
index 06b70fb43f1..06b70fb43f1 100644
--- a/spec/javascripts/fixtures/static/issuable_filter.html
+++ b/spec/frontend/fixtures/static/issuable_filter.html
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html b/spec/frontend/fixtures/static/issue_sidebar_label.html
index ec8fb30f219..ec8fb30f219 100644
--- a/spec/javascripts/fixtures/static/issue_sidebar_label.html
+++ b/spec/frontend/fixtures/static/issue_sidebar_label.html
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html b/spec/frontend/fixtures/static/line_highlighter.html
index 897a25d6760..897a25d6760 100644
--- a/spec/javascripts/fixtures/static/line_highlighter.html
+++ b/spec/frontend/fixtures/static/line_highlighter.html
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html b/spec/frontend/fixtures/static/linked_tabs.html
index c25463bf1db..c25463bf1db 100644
--- a/spec/javascripts/fixtures/static/linked_tabs.html
+++ b/spec/frontend/fixtures/static/linked_tabs.html
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html b/spec/frontend/fixtures/static/merge_requests_show.html
index 87e36c9f315..87e36c9f315 100644
--- a/spec/javascripts/fixtures/static/merge_requests_show.html
+++ b/spec/frontend/fixtures/static/merge_requests_show.html
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html b/spec/frontend/fixtures/static/mini_dropdown_graph.html
index cd0b8dec3fc..cd0b8dec3fc 100644
--- a/spec/javascripts/fixtures/static/mini_dropdown_graph.html
+++ b/spec/frontend/fixtures/static/mini_dropdown_graph.html
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html b/spec/frontend/fixtures/static/notebook_viewer.html
index 4bbb7bf1094..4bbb7bf1094 100644
--- a/spec/javascripts/fixtures/static/notebook_viewer.html
+++ b/spec/frontend/fixtures/static/notebook_viewer.html
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html b/spec/frontend/fixtures/static/oauth_remember_me.html
index 9ba1ffc72fe..9ba1ffc72fe 100644
--- a/spec/javascripts/fixtures/static/oauth_remember_me.html
+++ b/spec/frontend/fixtures/static/oauth_remember_me.html
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html b/spec/frontend/fixtures/static/pdf_viewer.html
index 350d35a262f..350d35a262f 100644
--- a/spec/javascripts/fixtures/static/pdf_viewer.html
+++ b/spec/frontend/fixtures/static/pdf_viewer.html
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html b/spec/frontend/fixtures/static/pipeline_graph.html
index 422372bb7d5..422372bb7d5 100644
--- a/spec/javascripts/fixtures/static/pipeline_graph.html
+++ b/spec/frontend/fixtures/static/pipeline_graph.html
diff --git a/spec/javascripts/fixtures/static/pipelines.html b/spec/frontend/fixtures/static/pipelines.html
index 42333f94f2f..42333f94f2f 100644
--- a/spec/javascripts/fixtures/static/pipelines.html
+++ b/spec/frontend/fixtures/static/pipelines.html
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html b/spec/frontend/fixtures/static/project_select_combo_button.html
index 50c826051c0..50c826051c0 100644
--- a/spec/javascripts/fixtures/static/project_select_combo_button.html
+++ b/spec/frontend/fixtures/static/project_select_combo_button.html
diff --git a/spec/javascripts/fixtures/static/projects.json b/spec/frontend/fixtures/static/projects.json
index 68a150f602a..68a150f602a 100644
--- a/spec/javascripts/fixtures/static/projects.json
+++ b/spec/frontend/fixtures/static/projects.json
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html b/spec/frontend/fixtures/static/search_autocomplete.html
index 29db9020424..29db9020424 100644
--- a/spec/javascripts/fixtures/static/search_autocomplete.html
+++ b/spec/frontend/fixtures/static/search_autocomplete.html
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html b/spec/frontend/fixtures/static/signin_tabs.html
index 7e66ab9394b..7e66ab9394b 100644
--- a/spec/javascripts/fixtures/static/signin_tabs.html
+++ b/spec/frontend/fixtures/static/signin_tabs.html
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html b/spec/frontend/fixtures/static/sketch_viewer.html
index e25e554e568..e25e554e568 100644
--- a/spec/javascripts/fixtures/static/sketch_viewer.html
+++ b/spec/frontend/fixtures/static/sketch_viewer.html
diff --git a/spec/frontend/fixtures/todos.rb b/spec/frontend/fixtures/todos.rb
new file mode 100644
index 00000000000..a7c183d2414
--- /dev/null
+++ b/spec/frontend/fixtures/todos.rb
@@ -0,0 +1,54 @@
+require 'spec_helper'
+
+describe 'Todos (JavaScript fixtures)' do
+ include JavaScriptFixturesHelpers
+
+ let(:admin) { create(:admin) }
+ let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
+ let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
+ let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
+ let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
+ let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
+ let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
+
+ before(:all) do
+ clean_frontend_fixtures('todos/')
+ end
+
+ after do
+ remove_repository(project)
+ end
+
+ describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.html' do
+ get :index
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'todos/todos.json' do
+ post :create, params: {
+ namespace_id: namespace,
+ project_id: project,
+ issuable_type: 'issue',
+ issuable_id: issue_2.id
+ }, format: 'json'
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/fixtures/u2f.rb b/spec/frontend/fixtures/u2f.rb
new file mode 100644
index 00000000000..8ecbc0390cd
--- /dev/null
+++ b/spec/frontend/fixtures/u2f.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+context 'U2F' do
+ include JavaScriptFixturesHelpers
+
+ let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') }
+
+ before(:all) do
+ clean_frontend_fixtures('u2f/')
+ end
+
+ describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ include DeviseHelpers
+
+ render_views
+
+ before do
+ set_devise_mapping(context: @request)
+ end
+
+ it 'u2f/authenticate.html' do
+ allow(controller).to receive(:find_user).and_return(user)
+
+ post :create, params: { user: { login: user.username, password: user.password } }
+
+ expect(response).to be_successful
+ end
+ end
+
+ describe Profiles::TwoFactorAuthsController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(user)
+ allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
+ end
+
+ it 'u2f/register.html' do
+ get :show
+
+ expect(response).to be_successful
+ end
+ end
+end
diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js
index b77bcd6266e..778196843db 100644
--- a/spec/frontend/helpers/fixtures.js
+++ b/spec/frontend/helpers/fixtures.js
@@ -4,12 +4,15 @@ import path from 'path';
import { ErrorWithStack } from 'jest-util';
export function getFixture(relativePath) {
- const absolutePath = path.join(global.fixturesBasePath, relativePath);
+ const basePath = relativePath.startsWith('static/')
+ ? global.staticFixturesBasePath
+ : global.fixturesBasePath;
+ const absolutePath = path.join(basePath, relativePath);
if (!fs.existsSync(absolutePath)) {
throw new ErrorWithStack(
`Fixture file ${relativePath} does not exist.
-Did you run bin/rake karma:fixtures?`,
+Did you run bin/rake frontend:fixtures?`,
getFixture,
);
}
diff --git a/spec/frontend/helpers/vue_test_utils_helper.js b/spec/frontend/helpers/vue_test_utils_helper.js
index 121e99c9783..68326e37ae7 100644
--- a/spec/frontend/helpers/vue_test_utils_helper.js
+++ b/spec/frontend/helpers/vue_test_utils_helper.js
@@ -1,5 +1,3 @@
-/* eslint-disable import/prefer-default-export */
-
const vNodeContainsText = (vnode, text) =>
(vnode.text && vnode.text.includes(text)) ||
(vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
@@ -19,3 +17,19 @@ export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =
Boolean(
shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
);
+
+/**
+ * Returns a promise that waits for a mutation to be fired before resolving
+ * NOTE: There's no reject action here so it will hang if it waits for a mutation that won't happen.
+ * @param {Object} store - The Vue store that contains the mutations
+ * @param {String} expectedMutationType - The Mutation to wait for
+ */
+export const waitForMutation = (store, expectedMutationType) =>
+ new Promise(resolve => {
+ const unsubscribe = store.subscribe(mutation => {
+ if (mutation.type === expectedMutationType) {
+ unsubscribe();
+ resolve();
+ }
+ });
+ });
diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/frontend/helpers/vue_test_utils_helper_spec.js
index 41714066da5..41714066da5 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper_spec.js
+++ b/spec/frontend/helpers/vue_test_utils_helper_spec.js
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 499fa8fc012..3d5ed4b5c0c 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -16,40 +16,16 @@ describe('IDE services', () => {
branch: TEST_BRANCH,
commit_message: 'Hello world',
actions: [],
- start_sha: undefined,
+ start_sha: TEST_COMMIT_SHA,
};
- Api.createBranch.mockReturnValue(Promise.resolve());
Api.commitMultiple.mockReturnValue(Promise.resolve());
});
- describe.each`
- startSha | shouldCreateBranch
- ${undefined} | ${false}
- ${TEST_COMMIT_SHA} | ${true}
- `('when start_sha is $startSha', ({ startSha, shouldCreateBranch }) => {
- beforeEach(() => {
- payload.start_sha = startSha;
+ it('should commit', () => {
+ services.commit(TEST_PROJECT_ID, payload);
- return services.commit(TEST_PROJECT_ID, payload);
- });
-
- if (shouldCreateBranch) {
- it('should create branch', () => {
- expect(Api.createBranch).toHaveBeenCalledWith(TEST_PROJECT_ID, {
- ref: TEST_COMMIT_SHA,
- branch: TEST_BRANCH,
- });
- });
- } else {
- it('should not create branch', () => {
- expect(Api.createBranch).not.toHaveBeenCalled();
- });
- }
-
- it('should commit', () => {
- expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
- });
+ expect(Api.commitMultiple).toHaveBeenCalledWith(TEST_PROJECT_ID, payload);
});
});
});
diff --git a/spec/frontend/jobs/components/empty_state_spec.js b/spec/frontend/jobs/components/empty_state_spec.js
deleted file mode 100644
index a2df79bdda0..00000000000
--- a/spec/frontend/jobs/components/empty_state_spec.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import Vue from 'vue';
-import component from '~/jobs/components/empty_state.vue';
-import mountComponent from '../../helpers/vue_mount_component_helper';
-
-describe('Empty State', () => {
- const Component = Vue.extend(component);
- let vm;
-
- const props = {
- illustrationPath: 'illustrations/pending_job_empty.svg',
- illustrationSizeClass: 'svg-430',
- title: 'This job has not started yet',
- };
-
- const content = 'This job is in pending state and is waiting to be picked by a runner';
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('renders image and title', () => {
- beforeEach(() => {
- vm = mountComponent(Component, {
- ...props,
- content,
- });
- });
-
- it('renders img with provided path and size', () => {
- expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath);
- expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass);
- });
-
- it('renders provided title', () => {
- expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual(
- props.title,
- );
- });
- });
-
- describe('with content', () => {
- it('renders content', () => {
- vm = mountComponent(Component, {
- ...props,
- content,
- });
-
- expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual(
- content,
- );
- });
- });
-
- describe('without content', () => {
- it('does not render content', () => {
- vm = mountComponent(Component, {
- ...props,
- });
-
- expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull();
- });
- });
-
- describe('with action', () => {
- it('renders action', () => {
- vm = mountComponent(Component, {
- ...props,
- content,
- action: {
- path: 'runner',
- button_title: 'Check runner',
- method: 'post',
- },
- });
-
- expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual(
- 'runner',
- );
- });
- });
-
- describe('without action', () => {
- it('does not render action', () => {
- vm = mountComponent(Component, {
- ...props,
- content,
- action: null,
- });
-
- expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
- });
- });
-});
diff --git a/spec/frontend/lib/utils/color_utils_spec.js b/spec/frontend/lib/utils/color_utils_spec.js
new file mode 100644
index 00000000000..433e9d5a85e
--- /dev/null
+++ b/spec/frontend/lib/utils/color_utils_spec.js
@@ -0,0 +1,35 @@
+import { textColorForBackground, hexToRgb } from '~/lib/utils/color_utils';
+
+describe('Color utils', () => {
+ describe('Converting hex code to rgb', () => {
+ it('convert hex code to rgb', () => {
+ expect(hexToRgb('#000000')).toEqual([0, 0, 0]);
+ expect(hexToRgb('#ffffff')).toEqual([255, 255, 255]);
+ });
+
+ it('convert short hex code to rgb', () => {
+ expect(hexToRgb('#000')).toEqual([0, 0, 0]);
+ expect(hexToRgb('#fff')).toEqual([255, 255, 255]);
+ });
+
+ it('handle conversion regardless of the characters case', () => {
+ expect(hexToRgb('#f0F')).toEqual([255, 0, 255]);
+ });
+ });
+
+ describe('Getting text color for given background', () => {
+ // following tests are being ported from `text_color_for_bg` section in labels_helper_spec.rb
+ it('uses light text on dark backgrounds', () => {
+ expect(textColorForBackground('#222E2E')).toEqual('#FFFFFF');
+ });
+
+ it('uses dark text on light backgrounds', () => {
+ expect(textColorForBackground('#EEEEEE')).toEqual('#333333');
+ });
+
+ it('supports RGB triplets', () => {
+ expect(textColorForBackground('#FFF')).toEqual('#333333');
+ expect(textColorForBackground('#000')).toEqual('#FFFFFF');
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/forms_spec.js b/spec/frontend/lib/utils/forms_spec.js
new file mode 100644
index 00000000000..cac17235f0d
--- /dev/null
+++ b/spec/frontend/lib/utils/forms_spec.js
@@ -0,0 +1,74 @@
+import { serializeForm } from '~/lib/utils/forms';
+
+describe('lib/utils/forms', () => {
+ const createDummyForm = inputs => {
+ const form = document.createElement('form');
+
+ form.innerHTML = inputs
+ .map(({ type, name, value }) => {
+ let str = ``;
+ if (type === 'select') {
+ str = `<select name="${name}">`;
+ value.forEach(v => {
+ if (v.length > 0) {
+ str += `<option value="${v}"></option> `;
+ }
+ });
+ str += `</select>`;
+ } else {
+ str = `<input type="${type}" name="${name}" value="${value}" checked/>`;
+ }
+ return str;
+ })
+ .join('');
+
+ return form;
+ };
+
+ describe('serializeForm', () => {
+ it('returns an object of key values from inputs', () => {
+ const form = createDummyForm([
+ { type: 'text', name: 'foo', value: 'foo-value' },
+ { type: 'text', name: 'bar', value: 'bar-value' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value',
+ bar: 'bar-value',
+ });
+ });
+
+ it('works with select', () => {
+ const form = createDummyForm([
+ { type: 'select', name: 'foo', value: ['foo-value1', 'foo-value2'] },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: 'foo-value1',
+ bar: 'bar-value1',
+ });
+ });
+
+ it('works with multiple inputs of the same name', () => {
+ const form = createDummyForm([
+ { type: 'checkbox', name: 'foo', value: 'foo-value3' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value2' },
+ { type: 'checkbox', name: 'foo', value: 'foo-value1' },
+ { type: 'text', name: 'bar', value: 'bar-value2' },
+ { type: 'text', name: 'bar', value: 'bar-value1' },
+ ]);
+
+ const data = serializeForm(form);
+
+ expect(data).toEqual({
+ foo: ['foo-value3', 'foo-value2', 'foo-value1'],
+ bar: ['bar-value2', 'bar-value1'],
+ });
+ });
+ });
+});
diff --git a/spec/frontend/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index dc886d0db3b..b6f1aef9ce4 100644
--- a/spec/frontend/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
@@ -29,20 +29,6 @@ describe('text_utility', () => {
});
});
- describe('pluralize', () => {
- it('should pluralize given string', () => {
- expect(textUtils.pluralize('test', 2)).toBe('tests');
- });
-
- it('should pluralize when count is 0', () => {
- expect(textUtils.pluralize('test', 0)).toBe('tests');
- });
-
- it('should not pluralize when count is 1', () => {
- expect(textUtils.pluralize('test', 1)).toBe('test');
- });
- });
-
describe('dasherize', () => {
it('should replace underscores with dashes', () => {
expect(textUtils.dasherize('foo_bar_foo')).toEqual('foo-bar-foo');
diff --git a/spec/frontend/lib/utils/url_utility_spec.js b/spec/frontend/lib/utils/url_utility_spec.js
index c771984a137..b0bdd924921 100644
--- a/spec/frontend/lib/utils/url_utility_spec.js
+++ b/spec/frontend/lib/utils/url_utility_spec.js
@@ -34,6 +34,41 @@ describe('URL utility', () => {
});
});
+ describe('getParameterValues', () => {
+ beforeEach(() => {
+ setWindowLocation({
+ href: 'https://gitlab.com?test=passing&multiple=1&multiple=2',
+ // make our fake location act like real window.location.toString
+ // URL() (used in getParameterValues) does this if passed an object
+ toString() {
+ return this.href;
+ },
+ });
+ });
+
+ it('returns empty array for no params', () => {
+ expect(urlUtils.getParameterValues()).toEqual([]);
+ });
+
+ it('returns empty array for non-matching params', () => {
+ expect(urlUtils.getParameterValues('notFound')).toEqual([]);
+ });
+
+ it('returns single match', () => {
+ expect(urlUtils.getParameterValues('test')).toEqual(['passing']);
+ });
+
+ it('returns multiple matches', () => {
+ expect(urlUtils.getParameterValues('multiple')).toEqual(['1', '2']);
+ });
+
+ it('accepts url as second arg', () => {
+ const url = 'https://gitlab.com?everything=works';
+ expect(urlUtils.getParameterValues('everything', url)).toEqual(['works']);
+ expect(urlUtils.getParameterValues('test', url)).toEqual([]);
+ });
+ });
+
describe('mergeUrlParams', () => {
it('adds w', () => {
expect(urlUtils.mergeUrlParams({ w: 1 }, '#frag')).toBe('?w=1#frag');
@@ -59,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/matchers.js b/spec/frontend/matchers.js
new file mode 100644
index 00000000000..35c362d0bf5
--- /dev/null
+++ b/spec/frontend/matchers.js
@@ -0,0 +1,38 @@
+export default {
+ toHaveSpriteIcon: (element, iconName) => {
+ if (!iconName) {
+ throw new Error('toHaveSpriteIcon is missing iconName argument!');
+ }
+
+ if (!(element instanceof HTMLElement)) {
+ throw new Error(`${element} is not a DOM element!`);
+ }
+
+ const iconReferences = [].slice.apply(element.querySelectorAll('svg use'));
+ const matchingIcon = iconReferences.find(reference =>
+ reference.getAttribute('xlink:href').endsWith(`#${iconName}`),
+ );
+
+ const pass = Boolean(matchingIcon);
+
+ let message;
+ if (pass) {
+ message = `${element.outerHTML} contains the sprite icon "${iconName}"!`;
+ } else {
+ message = `${element.outerHTML} does not contain the sprite icon "${iconName}"!`;
+
+ const existingIcons = iconReferences.map(reference => {
+ const iconUrl = reference.getAttribute('xlink:href');
+ return `"${iconUrl.replace(/^.+#/, '')}"`;
+ });
+ if (existingIcons.length > 0) {
+ message += ` (only found ${existingIcons.join(',')})`;
+ }
+ }
+
+ return {
+ pass,
+ message: () => message,
+ };
+ },
+};
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
new file mode 100644
index 00000000000..3b18a0f77c7
--- /dev/null
+++ b/spec/frontend/monitoring/embed/embed_spec.js
@@ -0,0 +1,78 @@
+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 { TEST_HOST } from 'helpers/test_constants';
+import { groups, initialState, metricsData, metricsWithData } from './mock_data';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('Embed', () => {
+ let wrapper;
+ let store;
+ let actions;
+
+ function mountComponent() {
+ wrapper = shallowMount(Embed, {
+ localVue,
+ store,
+ propsData: {
+ dashboardUrl: TEST_HOST,
+ },
+ });
+ }
+
+ beforeEach(() => {
+ actions = {
+ setFeatureFlags: () => {},
+ setShowErrorBanner: () => {},
+ setEndpoints: () => {},
+ fetchMetricsData: () => {},
+ };
+
+ store = new Vuex.Store({
+ modules: {
+ monitoringDashboard: {
+ namespaced: true,
+ actions,
+ state: initialState,
+ },
+ },
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('no metrics are available yet', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ 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);
+ });
+ });
+
+ describe('metrics are available', () => {
+ beforeEach(() => {
+ store.state.monitoringDashboard.groups = groups;
+ store.state.monitoringDashboard.groups[0].metrics = metricsData;
+ store.state.monitoringDashboard.metricsWithData = metricsWithData;
+
+ mountComponent();
+ });
+
+ 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);
+ });
+ });
+});
diff --git a/spec/frontend/monitoring/embed/mock_data.js b/spec/frontend/monitoring/embed/mock_data.js
new file mode 100644
index 00000000000..df4acb82e95
--- /dev/null
+++ b/spec/frontend/monitoring/embed/mock_data.js
@@ -0,0 +1,87 @@
+export const metricsWithData = [15, 16];
+
+export const groups = [
+ {
+ panels: [
+ {
+ title: 'Memory Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Memory Used',
+ weight: 4,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_memory_total',
+ metric_id: 15,
+ },
+ ],
+ },
+ {
+ title: 'Core Usage (Total)',
+ type: 'area-chart',
+ y_label: 'Total Cores',
+ weight: 3,
+ metrics: [
+ {
+ id: 'system_metrics_kubernetes_container_cores_total',
+ metric_id: 16,
+ },
+ ],
+ },
+ ],
+ },
+];
+
+export const metrics = [
+ {
+ id: 'system_metrics_kubernetes_container_memory_total',
+ metric_id: 15,
+ },
+ {
+ id: 'system_metrics_kubernetes_container_cores_total',
+ metric_id: 16,
+ },
+];
+
+const queries = [
+ {
+ result: [
+ {
+ values: [
+ ['Mon', 1220],
+ ['Tue', 932],
+ ['Wed', 901],
+ ['Thu', 934],
+ ['Fri', 1290],
+ ['Sat', 1330],
+ ['Sun', 1320],
+ ],
+ },
+ ],
+ },
+];
+
+export const metricsData = [
+ {
+ queries,
+ metrics: [
+ {
+ metric_id: 15,
+ },
+ ],
+ },
+ {
+ queries,
+ metrics: [
+ {
+ metric_id: 16,
+ },
+ ],
+ },
+];
+
+export const initialState = {
+ monitoringDashboard: {},
+ groups: [],
+ metricsWithData: [],
+ useDashboardEndpoint: true,
+};
diff --git a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
index 989b0458481..fd439ba46bd 100644
--- a/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
+++ b/spec/frontend/notes/components/discussion_jump_to_next_button_spec.js
@@ -23,8 +23,7 @@ describe('JumpToNextDiscussionButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- onClick: [[]],
- });
+ expect(wrapper.emitted().onClick).toBeTruthy();
+ expect(wrapper.emitted().onClick.length).toBe(1);
});
});
diff --git a/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
new file mode 100644
index 00000000000..8881bedf3cc
--- /dev/null
+++ b/spec/frontend/notes/components/discussion_keyboard_navigator_spec.js
@@ -0,0 +1,104 @@
+/* global Mousetrap */
+import 'mousetrap';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import Vuex from 'vuex';
+import DiscussionKeyboardNavigator from '~/notes/components/discussion_keyboard_navigator.vue';
+import notesModule from '~/notes/stores/modules';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+const NEXT_ID = 'abc123';
+const PREV_ID = 'def456';
+const NEXT_DIFF_ID = 'abc123_diff';
+const PREV_DIFF_ID = 'def456_diff';
+
+describe('notes/components/discussion_keyboard_navigator', () => {
+ let storeOptions;
+ let wrapper;
+ let store;
+
+ const createComponent = (options = {}) => {
+ store = new Vuex.Store(storeOptions);
+
+ wrapper = shallowMount(DiscussionKeyboardNavigator, {
+ localVue,
+ store,
+ ...options,
+ });
+
+ wrapper.vm.jumpToDiscussion = jest.fn();
+ };
+
+ beforeEach(() => {
+ const notes = notesModule();
+
+ notes.getters.nextUnresolvedDiscussionId = () => (currId, isDiff) =>
+ isDiff ? NEXT_DIFF_ID : NEXT_ID;
+ notes.getters.previousUnresolvedDiscussionId = () => (currId, isDiff) =>
+ isDiff ? PREV_DIFF_ID : PREV_ID;
+
+ storeOptions = {
+ modules: {
+ notes,
+ },
+ };
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ storeOptions = null;
+ store = null;
+ });
+
+ describe.each`
+ isDiffView | expectedNextId | expectedPrevId
+ ${true} | ${NEXT_DIFF_ID} | ${PREV_DIFF_ID}
+ ${false} | ${NEXT_ID} | ${PREV_ID}
+ `('when isDiffView is $isDiffView', ({ isDiffView, expectedNextId, expectedPrevId }) => {
+ beforeEach(() => {
+ createComponent({ propsData: { isDiffView } });
+ });
+
+ it('calls jumpToNextDiscussion when pressing `n`', () => {
+ Mousetrap.trigger('n');
+
+ expect(wrapper.vm.jumpToDiscussion).toHaveBeenCalledWith(expectedNextId);
+ expect(wrapper.vm.currentDiscussionId).toEqual(expectedNextId);
+ });
+
+ it('calls jumpToPreviousDiscussion when pressing `p`', () => {
+ Mousetrap.trigger('p');
+
+ expect(wrapper.vm.jumpToDiscussion).toHaveBeenCalledWith(expectedPrevId);
+ expect(wrapper.vm.currentDiscussionId).toEqual(expectedPrevId);
+ });
+ });
+
+ describe('on destroy', () => {
+ beforeEach(() => {
+ jest.spyOn(Mousetrap, 'unbind');
+
+ createComponent();
+
+ wrapper.destroy();
+ });
+
+ it('unbinds keys', () => {
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('n');
+ expect(Mousetrap.unbind).toHaveBeenCalledWith('p');
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `n`', () => {
+ Mousetrap.trigger('n');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+
+ it('does not call jumpToNextDiscussion when pressing `p`', () => {
+ Mousetrap.trigger('p');
+
+ expect(wrapper.vm.jumpToDiscussion).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap b/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
new file mode 100644
index 00000000000..ce456d6c899
--- /dev/null
+++ b/spec/frontend/pages/search/show/__snapshots__/refresh_counts_spec.js.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`pages/search/show/refresh_counts fetches and displays search counts 1`] = `
+"<div class=\\"badge\\">22</div>
+<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&amp;project_id=3&amp;scope=issues\\">4</div>
+<div class=\\"badge js-search-count\\" data-url=\\"http://test.host/search/count?search=lorem+ipsum&amp;project_id=3&amp;scope=merge_requests\\">5</div>"
+`;
diff --git a/spec/frontend/pages/search/show/refresh_counts_spec.js b/spec/frontend/pages/search/show/refresh_counts_spec.js
new file mode 100644
index 00000000000..ead268b3971
--- /dev/null
+++ b/spec/frontend/pages/search/show/refresh_counts_spec.js
@@ -0,0 +1,35 @@
+import MockAdapter from 'axios-mock-adapter';
+import { TEST_HOST } from 'helpers/test_constants';
+import axios from '~/lib/utils/axios_utils';
+import refreshCounts from '~/pages/search/show/refresh_counts';
+
+const URL = `${TEST_HOST}/search/count?search=lorem+ipsum&project_id=3`;
+const urlWithScope = scope => `${URL}&scope=${scope}`;
+const counts = [{ scope: 'issues', count: 4 }, { scope: 'merge_requests', count: 5 }];
+const fixture = `<div class="badge">22</div>
+<div class="badge js-search-count hidden" data-url="${urlWithScope('issues')}"></div>
+<div class="badge js-search-count hidden" data-url="${urlWithScope('merge_requests')}"></div>`;
+
+describe('pages/search/show/refresh_counts', () => {
+ let mock;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ setFixtures(fixture);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ it('fetches and displays search counts', () => {
+ counts.forEach(({ scope, count }) => {
+ mock.onGet(urlWithScope(scope)).reply(200, { count });
+ });
+
+ // assert before act behavior
+ return refreshCounts().then(() => {
+ expect(document.body.innerHTML).toMatchSnapshot();
+ });
+ });
+});
diff --git a/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
new file mode 100644
index 00000000000..7b8df03d3c3
--- /dev/null
+++ b/spec/frontend/projects/gke_cluster_namespace/gke_cluster_namespace_spec.js
@@ -0,0 +1,61 @@
+import initGkeNamespace from '~/projects/gke_cluster_namespace';
+
+describe('GKE cluster namespace', () => {
+ const changeEvent = new Event('change');
+ const isHidden = el => el.classList.contains('hidden');
+ const hasDisabledInput = el => el.querySelector('input').disabled;
+
+ let glManagedCheckbox;
+ let selfManaged;
+ let glManaged;
+
+ beforeEach(() => {
+ setFixtures(`
+ <input class="js-gl-managed" type="checkbox" value="1" checked />
+ <div class="js-namespace">
+ <input type="text" />
+ </div>
+ <div class="js-namespace-prefixed">
+ <input type="text" />
+ </div>
+ `);
+
+ glManagedCheckbox = document.querySelector('.js-gl-managed');
+ selfManaged = document.querySelector('.js-namespace');
+ glManaged = document.querySelector('.js-namespace-prefixed');
+
+ initGkeNamespace();
+ });
+
+ describe('GKE cluster namespace toggles', () => {
+ it('initially displays the GitLab-managed label and input', () => {
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+
+ it('displays the self-managed label and input when the Gitlab-managed checkbox is unchecked', () => {
+ glManagedCheckbox.checked = false;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(true);
+ expect(hasDisabledInput(glManaged)).toEqual(true);
+
+ expect(isHidden(selfManaged)).toEqual(false);
+ expect(hasDisabledInput(selfManaged)).toEqual(false);
+ });
+
+ it('displays the GitLab-managed label and input when the Gitlab-managed checkbox is checked', () => {
+ glManagedCheckbox.checked = true;
+ glManagedCheckbox.dispatchEvent(changeEvent);
+
+ expect(isHidden(glManaged)).toEqual(false);
+ expect(hasDisabledInput(glManaged)).toEqual(false);
+
+ expect(isHidden(selfManaged)).toEqual(true);
+ expect(hasDisabledInput(selfManaged)).toEqual(true);
+ });
+ });
+});
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 634c78ec029..df8a625319b 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -6,6 +6,7 @@ import { config as testUtilsConfig } from '@vue/test-utils';
import { initializeTestTimeout } from './helpers/timeout';
import { loadHTMLFixture, setHTMLFixture } from './helpers/fixtures';
import { setupManualMocks } from './mocks/mocks_helper';
+import customMatchers from './matchers';
// Expose jQuery so specs using jQuery plugins can be imported nicely.
// Here is an issue to explore better alternatives:
@@ -67,5 +68,28 @@ Object.entries(jqueryMatchers).forEach(([matcherName, matcherFactory]) => {
});
});
+expect.extend(customMatchers);
+
// Tech debt issue TBD
testUtilsConfig.logModifiedComponents = false;
+
+// Basic stub for MutationObserver
+global.MutationObserver = () => ({
+ disconnect: () => {},
+ observe: () => {},
+});
+
+Object.assign(global, {
+ requestIdleCallback(cb) {
+ const start = Date.now();
+ return setTimeout(() => {
+ cb({
+ didTimeout: false,
+ timeRemaining: () => Math.max(0, 50 - (Date.now() - start)),
+ });
+ });
+ },
+ cancelIdleCallback(id) {
+ clearTimeout(id);
+ },
+});
diff --git a/spec/frontend/tracking_spec.js b/spec/frontend/tracking_spec.js
new file mode 100644
index 00000000000..cd0bf50f8e9
--- /dev/null
+++ b/spec/frontend/tracking_spec.js
@@ -0,0 +1,116 @@
+import $ from 'jquery';
+import { setHTMLFixture } from './helpers/fixtures';
+
+import Tracking from '~/tracking';
+
+describe('Tracking', () => {
+ beforeEach(() => {
+ window.snowplow = window.snowplow || (() => {});
+ });
+
+ describe('.event', () => {
+ let snowplowSpy = null;
+
+ beforeEach(() => {
+ snowplowSpy = jest.spyOn(window, 'snowplow');
+ });
+
+ it('tracks to snowplow (our current tracking system)', () => {
+ Tracking.event('_category_', '_eventName_', { label: '_label_' });
+
+ expect(snowplowSpy).toHaveBeenCalledWith('trackStructEvent', '_category_', '_eventName_', {
+ label: '_label_',
+ property: '',
+ value: '',
+ });
+ });
+
+ it('skips tracking if snowplow is unavailable', () => {
+ window.snowplow = false;
+ Tracking.event('_category_', '_eventName_');
+
+ expect(snowplowSpy).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('tracking interface events', () => {
+ let eventSpy = null;
+ let subject = null;
+
+ beforeEach(() => {
+ eventSpy = jest.spyOn(Tracking, 'event');
+ subject = new Tracking('_category_');
+ setHTMLFixture(`
+ <input data-track-event="click_input1" data-track-label="_label_" value="_value_"/>
+ <input data-track-event="click_input2" data-track-value="_value_override_" value="_value_"/>
+ <input type="checkbox" data-track-event="toggle_checkbox" value="_value_" checked/>
+ <input class="dropdown" data-track-event="toggle_dropdown"/>
+ <div class="js-projects-list-holder"></div>
+ `);
+ });
+
+ it('binds to clicks on elements matching [data-track-event]', () => {
+ subject.bind(document);
+ $('[data-track-event="click_input1"]').click();
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input1', {
+ label: '_label_',
+ value: '_value_',
+ property: '',
+ });
+ });
+
+ it('allows value override with the data-track-value attribute', () => {
+ subject.bind(document);
+ $('[data-track-event="click_input2"]').click();
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'click_input2', {
+ label: '',
+ value: '_value_override_',
+ property: '',
+ });
+ });
+
+ it('handles checkbox values correctly', () => {
+ subject.bind(document);
+ const $checkbox = $('[data-track-event="toggle_checkbox"]');
+
+ $checkbox.click(); // unchecking
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
+ label: '',
+ property: '',
+ value: false,
+ });
+
+ $checkbox.click(); // checking
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_checkbox', {
+ label: '',
+ property: '',
+ value: '_value_',
+ });
+ });
+
+ it('handles bootstrap dropdowns', () => {
+ new Tracking('_category_').bind(document);
+ const $dropdown = $('[data-track-event="toggle_dropdown"]');
+
+ $dropdown.trigger('show.bs.dropdown'); // showing
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_show', {
+ label: '',
+ property: '',
+ value: '',
+ });
+
+ $dropdown.trigger('hide.bs.dropdown'); // hiding
+
+ expect(eventSpy).toHaveBeenCalledWith('_category_', 'toggle_dropdown_hide', {
+ label: '',
+ property: '',
+ value: '',
+ });
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
new file mode 100644
index 00000000000..806602877ef
--- /dev/null
+++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js
@@ -0,0 +1,123 @@
+import { shallowMount } from '@vue/test-utils';
+import ChangedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const changedFile = () => ({ changed: true });
+const stagedFile = () => ({ changed: false, staged: true });
+const changedAndStagedFile = () => ({ changed: true, staged: true });
+const newFile = () => ({ changed: true, tempFile: true });
+const unchangedFile = () => ({ changed: false, tempFile: false, staged: false, deleted: false });
+
+describe('Changed file icon', () => {
+ let wrapper;
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(ChangedFileIcon, {
+ propsData: {
+ file: changedFile(),
+ showTooltip: true,
+ ...props,
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findIcon = () => wrapper.find(Icon);
+ const findIconName = () => findIcon().props('name');
+ const findIconClasses = () =>
+ findIcon()
+ .props('cssClasses')
+ .split(' ');
+ const findTooltipText = () => wrapper.attributes('data-original-title');
+
+ it('with isCentered true, adds center class', () => {
+ factory({
+ isCentered: true,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(true);
+ });
+
+ it('with isCentered false, does not center', () => {
+ factory({
+ isCentered: false,
+ });
+
+ expect(wrapper.classes('ml-auto')).toBe(false);
+ });
+
+ it('with showTooltip false, does not show tooltip', () => {
+ factory({
+ showTooltip: false,
+ });
+
+ expect(findTooltipText()).toBeFalsy();
+ });
+
+ describe.each`
+ file | iconName | tooltipText | desc
+ ${changedFile()} | ${'file-modified'} | ${'Unstaged modification'} | ${'with file changed'}
+ ${stagedFile()} | ${'file-modified-solid'} | ${'Staged modification'} | ${'with file staged'}
+ ${changedAndStagedFile()} | ${'file-modified'} | ${'Unstaged and staged modification'} | ${'with file changed and staged'}
+ ${newFile()} | ${'file-addition'} | ${'Unstaged addition'} | ${'with file new'}
+ `('$desc', ({ file, iconName, tooltipText }) => {
+ beforeEach(() => {
+ factory({ file });
+ });
+
+ it('renders icon', () => {
+ expect(findIconName()).toBe(iconName);
+ expect(findIconClasses()).toContain(iconName);
+ });
+
+ it('renders tooltip text', () => {
+ expect(findTooltipText()).toBe(tooltipText);
+ });
+ });
+
+ describe('with file unchanged', () => {
+ beforeEach(() => {
+ factory({
+ file: unchangedFile(),
+ });
+ });
+
+ it('does not show icon', () => {
+ expect(findIcon().exists()).toBe(false);
+ });
+
+ it('does not have tooltip text', () => {
+ expect(findTooltipText()).toBe('');
+ });
+ });
+
+ it('with size set, sets icon size', () => {
+ const size = 8;
+
+ factory({
+ file: changedFile(),
+ size,
+ });
+
+ expect(findIcon().props('size')).toBe(size);
+ });
+
+ // NOTE: It looks like 'showStagedIcon' behavior is backwards to what the name suggests
+ // https://gitlab.com/gitlab-org/gitlab-ce/issues/66071
+ it.each`
+ showStagedIcon | iconName | desc
+ ${false} | ${'file-modified-solid'} | ${'with showStagedIcon false, renders staged icon'}
+ ${true} | ${'file-modified'} | ${'with showStagedIcon true, renders regular icon'}
+ `('$desc', ({ showStagedIcon, iconName }) => {
+ factory({
+ file: stagedFile(),
+ showStagedIcon,
+ });
+
+ expect(findIconName()).toEqual(iconName);
+ });
+});
diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js
index aa0b544f948..48f2ee86619 100644
--- a/spec/frontend/vue_shared/components/markdown/header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/header_spec.js
@@ -101,7 +101,7 @@ describe('Markdown field header component', () => {
vm.canSuggest = false;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.qa-suggestion-btn')).toBe(null);
+ expect(vm.$el.querySelector('.js-suggestion-btn')).toBe(null);
});
});
});
diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 3b6f67457ad..6716e5cd794 100644
--- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -28,8 +28,8 @@ describe('Suggestion Diff component', () => {
wrapper.destroy();
});
- const findApplyButton = () => wrapper.find('.qa-apply-btn');
- const findHeader = () => wrapper.find('.qa-suggestion-diff-header');
+ const findApplyButton = () => wrapper.find('.js-apply-btn');
+ const findHeader = () => wrapper.find('.js-suggestion-diff-header');
const findHelpButton = () => wrapper.find('.js-help-btn');
const findLoading = () => wrapper.find(GlLoadingIcon);
@@ -73,7 +73,10 @@ describe('Suggestion Diff component', () => {
});
it('emits apply', () => {
- expect(wrapper.emittedByOrder()).toEqual([{ name: 'apply', args: [expect.any(Function)] }]);
+ expect(wrapper.emittedByOrder()).toContainEqual({
+ name: 'apply',
+ args: [expect.any(Function)],
+ });
});
it('hides apply button', () => {
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/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
index 20e197e9f73..47591445fc0 100644
--- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
+++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Resolvers::NamespaceProjectsResolver, :nested_groups do
+describe Resolvers::NamespaceProjectsResolver do
include GraphqlHelpers
let(:current_user) { create(:user) }
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index d2540696b17..e80388f9ea7 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -118,7 +118,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
before do
receiver.update(parent: parent)
end
@@ -170,7 +170,7 @@ describe AutoDevopsHelper do
it { is_expected.to eq('instance enabled') }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
before do
@@ -203,7 +203,7 @@ describe AutoDevopsHelper do
it { is_expected.to be_nil }
end
- context 'with groups', :nested_groups do
+ context 'with groups' do
let(:receiver) { create(:project, :repository, namespace: group) }
context 'when auto devops is disabled on group level' do
diff --git a/spec/helpers/dashboard_helper_spec.rb b/spec/helpers/dashboard_helper_spec.rb
index 023238ee0ae..059ae128d93 100644
--- a/spec/helpers/dashboard_helper_spec.rb
+++ b/spec/helpers/dashboard_helper_spec.rb
@@ -12,7 +12,7 @@ describe DashboardHelper do
it 'has all the expected links by default' do
menu_items = [:projects, :groups, :activity, :milestones, :snippets]
- expect(helper.dashboard_nav_links).to contain_exactly(*menu_items)
+ expect(helper.dashboard_nav_links).to include(*menu_items)
end
it 'does not contain cross project elements when the user cannot read cross project' do
@@ -22,6 +22,43 @@ describe DashboardHelper do
end
end
+ describe '#feature_entry' do
+ context 'when implicitly enabled' do
+ it 'considers feature enabled by default' do
+ entry = feature_entry('Demo', href: 'demo.link')
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).to include('<a href="demo.link">Demo</a>')
+ end
+ end
+
+ context 'when explicitly enabled' do
+ it 'returns a link' do
+ entry = feature_entry('Demo', href: 'demo.link', enabled: true)
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).to include('<a href="demo.link">Demo</a>')
+ end
+
+ it 'returns text if href is not provided' do
+ entry = feature_entry('Demo', enabled: true)
+
+ expect(entry).to include('<p aria-label="Demo: status on">')
+ expect(entry).not_to match(/<a[^>]+>/)
+ end
+ end
+
+ context 'when disabled' do
+ it 'returns text without link' do
+ entry = feature_entry('Demo', href: 'demo.link', enabled: false)
+
+ expect(entry).to include('<p aria-label="Demo: status off">')
+ expect(entry).not_to match(/<a[^>]+>/)
+ expect(entry).to include('Demo')
+ end
+ end
+ end
+
describe '.has_start_trial?' do
subject { helper.has_start_trial? }
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index 1763c46389a..98719697cea 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -86,7 +86,7 @@ describe GroupsHelper do
end
end
- describe 'group_title', :nested_groups do
+ describe 'group_title' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -99,7 +99,7 @@ describe GroupsHelper do
end
# rubocop:disable Layout/SpaceBeforeComma
- describe '#share_with_group_lock_help_text', :nested_groups do
+ describe '#share_with_group_lock_help_text' do
let!(:root_group) { create(:group) }
let!(:subgroup) { create(:group, parent: root_group) }
let!(:sub_subgroup) { create(:group, parent: subgroup) }
@@ -230,7 +230,7 @@ describe GroupsHelper do
end
end
- describe 'parent_group_options', :nested_groups do
+ describe 'parent_group_options' do
let(:current_user) { create(:user) }
let(:group) { create(:group, name: 'group') }
let(:group2) { create(:group, name: 'group2') }
@@ -262,4 +262,44 @@ describe GroupsHelper do
expect(parent_group_options(group2)).to eq([{ id: group.id, text: group.human_name }].to_json)
end
end
+
+ describe '#can_disable_group_emails?' do
+ let(:current_user) { create(:user) }
+ let(:group) { create(:group, name: 'group') }
+ let(:subgroup) { create(:group, name: 'subgroup', parent: group) }
+
+ before do
+ allow(helper).to receive(:current_user) { current_user }
+ end
+
+ it 'returns true for the group owner' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { true }
+
+ expect(helper.can_disable_group_emails?(group)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, group) { false }
+
+ expect(helper.can_disable_group_emails?(group)).to be_falsey
+ end
+
+ context 'when subgroups' do
+ before do
+ allow(helper).to receive(:can?).with(current_user, :set_emails_disabled, subgroup) { true }
+ end
+
+ it 'returns false if parent group is disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_falsey
+ end
+
+ it 'returns true if parent group is not disabling emails' do
+ allow(group).to receive(:emails_disabled?).and_return(false)
+
+ expect(helper.can_disable_group_emails?(subgroup)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 314305d7a8e..4f1cab38f34 100644
--- a/spec/helpers/labels_helper_spec.rb
+++ b/spec/helpers/labels_helper_spec.rb
@@ -3,10 +3,8 @@ require 'spec_helper'
describe LabelsHelper do
describe '#show_label_issuables_link?' do
shared_examples 'a valid response to show_label_issuables_link?' do |issuables_type, when_enabled = true, when_disabled = false|
- let(:context_project) { project }
-
context "when asking for a #{issuables_type} link" do
- subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type, project: context_project) }
+ subject { show_label_issuables_link?(label.present(issuable_subject: nil), issuables_type) }
context "when #{issuables_type} are enabled for the project" do
let(:project) { create(:project, "#{issuables_type}_access_level": ProjectFeature::ENABLED) }
@@ -39,27 +37,11 @@ describe LabelsHelper do
let(:label) { create(:group_label, group: group, title: 'bug') }
context 'when asking for an issue link' do
- context 'in the context of a project' do
- it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
- end
-
- context 'in the context of a group' do
- let(:context_project) { nil }
-
- it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
- end
+ it_behaves_like 'a valid response to show_label_issuables_link?', :issues, true, true
end
context 'when asking for a merge requests link' do
- context 'in the context of a project' do
- it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
- end
-
- context 'in the context of a group' do
- let(:context_project) { nil }
-
- it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
- end
+ it_behaves_like 'a valid response to show_label_issuables_link?', :merge_requests, true, true
end
end
end
diff --git a/spec/helpers/namespaces_helper_spec.rb b/spec/helpers/namespaces_helper_spec.rb
index 601f864ef36..e38513f6d94 100644
--- a/spec/helpers/namespaces_helper_spec.rb
+++ b/spec/helpers/namespaces_helper_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe NamespacesHelper, :postgresql do
+describe NamespacesHelper do
let!(:admin) { create(:admin) }
let!(:admin_project_creation_level) { nil }
let!(:admin_group) do
@@ -109,7 +109,7 @@ describe NamespacesHelper, :postgresql do
expect(options).to include(user_group.name)
end
- context 'when nested groups are available', :nested_groups do
+ context 'when nested groups are available' do
it 'includes groups nested in groups the user can administer' do
allow(helper).to receive(:current_user).and_return(user)
child_group = create(:group, :private, parent: user_group)
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 9ecaabc04ed..5717b15d656 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe NotificationsHelper do
describe 'notification_icon' do
it { expect(notification_icon(:disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
+ it { expect(notification_icon(:owner_disabled)).to match('class="fa fa-microphone-slash fa-fw"') }
it { expect(notification_icon(:participating)).to match('class="fa fa-volume-up fa-fw"') }
it { expect(notification_icon(:mention)).to match('class="fa fa-at fa-fw"') }
it { expect(notification_icon(:global)).to match('class="fa fa-globe fa-fw"') }
@@ -19,4 +20,14 @@ describe NotificationsHelper do
it { expect(notification_event_name(:success_pipeline)).to match('Successful pipeline') }
it { expect(notification_event_name(:failed_pipeline)).to match('Failed pipeline') }
end
+
+ describe '#notification_icon_level' do
+ let(:user) { create(:user) }
+ let(:global_setting) { user.global_notification_setting }
+ let(:notification_setting) { create(:notification_setting, level: :watch) }
+
+ it { expect(notification_icon_level(notification_setting, true)).to eq 'owner_disabled' }
+ it { expect(notification_icon_level(notification_setting)).to eq 'watch' }
+ it { expect(notification_icon_level(global_setting)).to eq 'participating' }
+ end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 3716879c458..a70bfc2adc7 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -107,6 +107,30 @@ describe ProjectsHelper do
end
end
+ describe '#can_disable_emails?' do
+ let(:project) { create(:project) }
+ let(:user) { create(:project_member, :maintainer, user: create(:user), project: project).user }
+
+ it 'returns true for the project owner' do
+ allow(helper).to receive(:can?).with(project.owner, :set_emails_disabled, project) { true }
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_truthy
+ end
+
+ it 'returns false for anyone else' do
+ allow(helper).to receive(:can?).with(user, :set_emails_disabled, project) { false }
+
+ expect(helper.can_disable_emails?(project, user)).to be_falsey
+ end
+
+ it 'returns false if group emails disabled' do
+ project = create(:project, group: create(:group))
+ allow(project.group).to receive(:emails_disabled?).and_return(true)
+
+ expect(helper.can_disable_emails?(project, project.owner)).to be_falsey
+ end
+ end
+
describe "readme_cache_key" do
let(:project) { create(:project, :repository) }
@@ -477,6 +501,7 @@ describe ProjectsHelper do
it 'returns the command to push to create project over SSH' do
allow(Gitlab::CurrentSettings.current_application_settings).to receive(:enabled_git_access_protocol) { 'ssh' }
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return('git@localhost:')
expect(helper.push_to_create_project_command(user)).to eq('git push --set-upstream git@localhost:john/$(git rev-parse --show-toplevel | xargs basename).git $(git rev-parse --abbrev-ref HEAD)')
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index c69493b579f..2ab72679ee7 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -177,4 +177,48 @@ describe SearchHelper do
end
end
end
+
+ describe 'search_filter_link' do
+ it 'renders a search filter link for the current scope' do
+ @scope = 'projects'
+ @search_results = double
+
+ expect(@search_results).to receive(:formatted_count).with('projects').and_return('23')
+
+ link = search_filter_link('projects', 'Projects')
+
+ expect(link).to have_css('li.active')
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
+ expect(link).to have_css('span.badge.badge-pill:not(.js-search-count):not(.hidden):not([data-url])', text: '23')
+ end
+
+ it 'renders a search filter link for another scope' do
+ link = search_filter_link('projects', 'Projects')
+ count_path = search_count_path(scope: 'projects')
+
+ expect(link).to have_css('li:not([class="active"])')
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects'))
+ expect(link).to have_css("span.badge.badge-pill.js-search-count.hidden[data-url='#{count_path}']", text: '')
+ end
+
+ it 'merges in the current search params and given params' do
+ expect(self).to receive(:params).and_return(
+ ActionController::Parameters.new(
+ search: 'hello',
+ scope: 'ignored',
+ other_param: 'ignored'
+ )
+ )
+
+ link = search_filter_link('projects', 'Projects', search: { project_id: 23 })
+
+ expect(link).to have_link('Projects', href: search_path(scope: 'projects', search: 'hello', project_id: 23))
+ end
+
+ it 'assigns given data attributes on the list container' do
+ link = search_filter_link('projects', 'Projects', data: { foo: 'bar' })
+
+ expect(link).to have_css('li[data-foo="bar"]')
+ end
+ end
end
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
new file mode 100644
index 00000000000..647771ace92
--- /dev/null
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SessionsHelper do
+ describe '#unconfirmed_email?' do
+ it 'returns true when the flash alert contains a devise failure unconfirmed message' do
+ flash[:alert] = t(:unconfirmed, scope: [:devise, :failure])
+ expect(helper.unconfirmed_email?).to be_truthy
+ end
+
+ it 'returns false when the flash alert does not contain a devise failure unconfirmed message' do
+ flash[:alert] = 'something else'
+ expect(helper.unconfirmed_email?).to be_falsey
+ end
+ end
+end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index ea48c69e0ae..ab4ef899119 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -3,15 +3,11 @@ require 'spec_helper'
describe SubmoduleHelper do
include RepoHelpers
- describe 'submodule links' do
- let(:submodule_item) { double(id: 'hash', path: 'rack') }
- let(:config) { Gitlab.config.gitlab }
- let(:repo) { double }
-
- before do
- self.instance_variable_set(:@repository, repo)
- end
+ let(:submodule_item) { double(id: 'hash', path: 'rack') }
+ let(:config) { Gitlab.config.gitlab }
+ let(:repo) { double }
+ shared_examples 'submodule_links' do
context 'submodule on self' do
before do
allow(Gitlab.config.gitlab).to receive(:protocol).and_return('http') # set this just to be sure
@@ -21,28 +17,28 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([config.user, '@', config.host, ':gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects ssh on non-standard port' do
allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url(['ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'detects http on non-standard port' do
allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, ':3000/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'works with relative_url_root' do
@@ -50,7 +46,7 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash')])
end
it 'works with subgroups' do
@@ -58,34 +54,34 @@ describe SubmoduleHelper do
allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url(['http://', config.host, '/gitlab/root/gitlab-org/sub/gitlab-ce.git'].join(''))
- expect(submodule_links(submodule_item)).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')])
+ expect(subject).to eq([namespace_project_path('gitlab-org/sub', 'gitlab-ce'), namespace_project_tree_path('gitlab-org/sub', 'gitlab-ce', 'hash')])
end
end
context 'submodule on github.com' do
it 'detects ssh' do
stub_url('git@github.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://github.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with no .git on the end' do
stub_url('http://github.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://github.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -97,39 +93,39 @@ describe SubmoduleHelper do
allow(repo).to receive(:project).and_return(project)
stub_url('./')
- expect(submodule_links(submodule_item)).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
+ expect(subject).to eq(["/master-project/#{project.path}", "/master-project/#{project.path}/tree/hash"])
end
end
context 'submodule on gitlab.com' do
it 'detects ssh' do
stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects http' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'detects https' do
stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with no .git on the end' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'handles urls with trailing whitespace' do
stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git ')
- expect(submodule_links(submodule_item)).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
end
it 'returns original with non-standard url' do
stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -137,27 +133,25 @@ describe SubmoduleHelper do
it 'sanitizes unsupported protocols' do
stub_url('javascript:alert("XSS");')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'sanitizes unsupported protocols disguised as a repository URL' do
stub_url('javascript:alert("XSS");foo/bar.git')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'sanitizes invalid URL with extended ASCII' do
stub_url('é')
- expect(helper.submodule_links(submodule_item)).to eq([nil, nil])
+ expect(subject).to eq([nil, nil])
end
it 'returns original' do
stub_url('http://mygitserver.com/gitlab-org/gitlab-ce')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
- stub_url('http://mygitserver.com/gitlab-org/gitlab-ce.git')
- expect(submodule_links(submodule_item)).to eq([repo.submodule_url_for, nil])
+ expect(subject).to eq([repo.submodule_url_for, nil])
end
end
@@ -168,8 +162,7 @@ describe SubmoduleHelper do
def expect_relative_link_to_resolve_to(relative_path, expected_path)
allow(repo).to receive(:submodule_url_for).and_return(relative_path)
-
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([expected_path, "#{expected_path}/tree/#{submodule_item.id}"])
end
@@ -190,7 +183,7 @@ describe SubmoduleHelper do
it 'returns nil' do
allow(repo).to receive(:submodule_url_for).and_return('../../test.git')
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([nil, nil])
end
@@ -200,7 +193,7 @@ describe SubmoduleHelper do
it 'returns nil because it is not possible to have repo nested under another repo' do
allow(repo).to receive(:submodule_url_for).and_return('./test.git')
- result = submodule_links(submodule_item)
+ result = subject
expect(result).to eq([nil, nil])
end
@@ -236,6 +229,35 @@ describe SubmoduleHelper do
end
end
end
+
+ context 'unknown submodule' do
+ before do
+ # When there is no `.gitmodules` file, or if `.gitmodules` does not
+ # know the submodule at the specified path,
+ # `Repository#submodule_url_for` returns `nil`
+ stub_url(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+ end
+
+ context 'as view helpers in view context' do
+ subject { helper.submodule_links(submodule_item) }
+
+ before do
+ self.instance_variable_set(:@repository, repo)
+ end
+
+ it_behaves_like 'submodule_links'
+ end
+
+ context 'as stand-alone module' do
+ subject { described_class.submodule_links(submodule_item, nil, repo) }
+
+ it_behaves_like 'submodule_links'
end
def stub_url(url)
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
index 71505e8ea69..b0c98be4130 100644
--- a/spec/helpers/tracking_helper_spec.rb
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -4,8 +4,32 @@ require 'spec_helper'
describe TrackingHelper do
describe '#tracking_attrs' do
- it 'returns an empty hash' do
- expect(helper.tracking_attrs('a', 'b', 'c')).to eq({})
+ using RSpec::Parameterized::TableSyntax
+
+ let(:input) { %w(a b c) }
+ let(:results) do
+ {
+ no_data: {},
+ with_data: { data: { track_label: 'a', track_event: 'b', track_property: 'c' } }
+ }
+ end
+
+ where(:snowplow_enabled, :environment, :result) do
+ true | 'production' | :with_data
+ false | 'production' | :no_data
+ true | 'development' | :no_data
+ false | 'development' | :no_data
+ true | 'test' | :no_data
+ false | 'test' | :no_data
+ end
+
+ with_them do
+ it 'returns a hash' do
+ stub_application_setting(snowplow_enabled: snowplow_enabled)
+ allow(Rails).to receive(:env).and_return(environment.inquiry)
+
+ expect(helper.tracking_attrs(*input)).to eq(results[result])
+ end
end
end
end
diff --git a/spec/helpers/users_helper_spec.rb b/spec/helpers/users_helper_spec.rb
index f3649495493..a6623bc7941 100644
--- a/spec/helpers/users_helper_spec.rb
+++ b/spec/helpers/users_helper_spec.rb
@@ -27,7 +27,7 @@ describe UsersHelper do
context 'with public profile' do
it 'includes all the expected tabs' do
- expect(tabs).to include(:activity, :groups, :contributed, :projects, :snippets)
+ expect(tabs).to include(:activity, :groups, :contributed, :projects, :starred, :snippets)
end
end
diff --git a/spec/helpers/wiki_helper_spec.rb b/spec/helpers/wiki_helper_spec.rb
index 8eab40aeaf3..ee977e37ec1 100644
--- a/spec/helpers/wiki_helper_spec.rb
+++ b/spec/helpers/wiki_helper_spec.rb
@@ -22,7 +22,7 @@ describe WikiHelper do
describe '#wiki_sort_controls' do
let(:project) { create(:project) }
let(:wiki_link) { helper.wiki_sort_controls(project, sort, direction) }
- let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort" }
+ let(:classes) { "btn btn-default has-tooltip reverse-sort-btn qa-reverse-sort rspec-reverse-sort" }
def expected_link(sort, direction, icon_class)
path = "/#{project.full_path}/wikis/pages?direction=#{direction}&sort=#{sort}"
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 2f72c9ed89d..2fa807657de 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.spinner');
+ const loadingIcon = vm.$el.querySelector('.gl-spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 4e4d1ae2e99..c82a03a628a 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.spinner'),
+ loadingIcon: vm.$el.querySelector('.gl-spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/boards/board_blank_state_spec.js b/spec/javascripts/boards/board_blank_state_spec.js
index 8e2a947b0dd..b64859ec32a 100644
--- a/spec/javascripts/boards/board_blank_state_spec.js
+++ b/spec/javascripts/boards/board_blank_state_spec.js
@@ -1,7 +1,6 @@
import Vue from 'vue';
import boardsStore from '~/boards/stores/boards_store';
import BoardBlankState from '~/boards/components/board_blank_state.vue';
-import { mockBoardService } from './mock_data';
describe('Boards blank state', () => {
let vm;
@@ -11,9 +10,10 @@ describe('Boards blank state', () => {
const Comp = Vue.extend(BoardBlankState);
boardsStore.create();
- gl.boardService = mockBoardService();
- spyOn(gl.boardService, 'generateDefaultLists').and.callFake(
+ spyOn(boardsStore, 'addList').and.stub();
+ spyOn(boardsStore, 'removeList').and.stub();
+ spyOn(boardsStore, 'generateDefaultLists').and.callFake(
() =>
new Promise((resolve, reject) => {
if (fail) {
@@ -71,9 +71,14 @@ describe('Boards blank state', () => {
vm.$el.querySelector('.btn-success').click();
setTimeout(() => {
- expect(boardsStore.state.lists.length).toBe(2);
- expect(boardsStore.state.lists[0].title).toEqual('To Do');
- expect(boardsStore.state.lists[1].title).toEqual('Doing');
+ expect(boardsStore.addList).toHaveBeenCalledTimes(2);
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'To Do' }),
+ );
+
+ expect(boardsStore.addList).toHaveBeenCalledWith(
+ jasmine.objectContaining({ title: 'Doing' }),
+ );
done();
});
@@ -86,7 +91,7 @@ describe('Boards blank state', () => {
setTimeout(() => {
expect(boardsStore.welcomeIsHidden()).toBeFalsy();
- expect(boardsStore.state.lists.length).toBe(1);
+ expect(boardsStore.removeList).toHaveBeenCalledWith(undefined, 'label');
done();
});
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 9c9b435d7fd..6774a46ed58 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -148,7 +148,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .gl-spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/boards/components/board_form_spec.js b/spec/javascripts/boards/components/board_form_spec.js
new file mode 100644
index 00000000000..e9014156a98
--- /dev/null
+++ b/spec/javascripts/boards/components/board_form_spec.js
@@ -0,0 +1,56 @@
+import $ from 'jquery';
+import Vue from 'vue';
+import boardsStore from '~/boards/stores/boards_store';
+import boardForm from '~/boards/components/board_form.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+
+describe('board_form.vue', () => {
+ const props = {
+ canAdminBoard: false,
+ labelsPath: `${gl.TEST_HOST}/labels/path`,
+ milestonePath: `${gl.TEST_HOST}/milestone/path`,
+ };
+ let vm;
+
+ beforeEach(() => {
+ spyOn($, 'ajax');
+ boardsStore.state.currentPage = 'edit';
+ const Component = Vue.extend(boardForm);
+ vm = mountComponent(Component, props);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('methods', () => {
+ describe('cancel', () => {
+ it('resets currentPage', done => {
+ vm.cancel();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(boardsStore.state.currentPage).toBe('');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+ });
+
+ describe('buttons', () => {
+ it('cancel button triggers cancel()', done => {
+ spyOn(vm, 'cancel');
+
+ Vue.nextTick()
+ .then(() => {
+ const cancelButton = vm.$el.querySelector('button[data-dismiss="modal"]');
+ cancelButton.click();
+
+ expect(vm.cancel).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/boards/components/boards_selector_spec.js b/spec/javascripts/boards/components/boards_selector_spec.js
new file mode 100644
index 00000000000..473cc0612ea
--- /dev/null
+++ b/spec/javascripts/boards/components/boards_selector_spec.js
@@ -0,0 +1,206 @@
+import Vue from 'vue';
+import BoardService from '~/boards/services/board_service';
+import BoardsSelector from '~/boards/components/boards_selector.vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { TEST_HOST } from 'spec/test_constants';
+import boardsStore from '~/boards/stores/boards_store';
+
+const throttleDuration = 1;
+
+function boardGenerator(n) {
+ return new Array(n).fill().map((board, id) => {
+ const name = `board${id}`;
+
+ return {
+ id,
+ name,
+ };
+ });
+}
+
+describe('BoardsSelector', () => {
+ let vm;
+ let allBoardsResponse;
+ let recentBoardsResponse;
+ let fillSearchBox;
+ const boards = boardGenerator(20);
+ const recentBoards = boardGenerator(5);
+
+ beforeEach(done => {
+ setFixtures('<div class="js-boards-selector"></div>');
+ window.gl = window.gl || {};
+
+ boardsStore.setEndpoints({
+ boardsEndpoint: '',
+ recentBoardsEndpoint: '',
+ listsEndpoint: '',
+ bulkUpdatePath: '',
+ boardId: '',
+ });
+ window.gl.boardService = new BoardService();
+
+ allBoardsResponse = Promise.resolve({
+ data: boards,
+ });
+ recentBoardsResponse = Promise.resolve({
+ data: recentBoards,
+ });
+
+ spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse);
+ spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse);
+
+ const Component = Vue.extend(BoardsSelector);
+ vm = mountComponent(
+ Component,
+ {
+ throttleDuration,
+ currentBoard: {
+ id: 1,
+ name: 'Development',
+ milestone_id: null,
+ weight: null,
+ assignee_id: null,
+ labels: [],
+ },
+ milestonePath: `${TEST_HOST}/milestone/path`,
+ boardBaseUrl: `${TEST_HOST}/board/base/url`,
+ hasMissingBoards: false,
+ canAdminBoard: true,
+ multipleIssueBoardsAvailable: true,
+ labelsPath: `${TEST_HOST}/labels/path`,
+ projectId: 42,
+ groupId: 19,
+ scopedIssueBoardFeatureEnabled: true,
+ weights: [],
+ },
+ document.querySelector('.js-boards-selector'),
+ );
+
+ // Emits gl-dropdown show event to simulate the dropdown is opened at initialization time
+ vm.$children[0].$emit('show');
+
+ Promise.all([allBoardsResponse, recentBoardsResponse])
+ .then(() => vm.$nextTick())
+ .then(done)
+ .catch(done.fail);
+
+ fillSearchBox = filterTerm => {
+ const { searchBox } = vm.$refs;
+ const searchBoxInput = searchBox.$el.querySelector('input');
+ searchBoxInput.value = filterTerm;
+ searchBoxInput.dispatchEvent(new Event('input'));
+ };
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ window.gl.boardService = undefined;
+ });
+
+ describe('filtering', () => {
+ it('shows all boards without filtering', done => {
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItem = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItem.length).toBe(boards.length + recentBoards.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows only matching boards when filtering', done => {
+ const filterTerm = 'board1';
+ const expectedCount = boards.filter(board => board.name.includes(filterTerm)).length;
+
+ fillSearchBox(filterTerm);
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(expectedCount);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('shows message if there are no matching boards', done => {
+ fillSearchBox('does not exist');
+
+ vm.$nextTick()
+ .then(() => {
+ const dropdownItems = vm.$el.querySelectorAll('.js-dropdown-item');
+
+ expect(dropdownItems.length).toBe(0);
+ expect(vm.$el).toContainText('No matching boards found');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('recent boards section', () => {
+ it('shows only when boards are greater than 10', done => {
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+
+ const expectedCount = 2; // Recent + All
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when boards are less than 10', done => {
+ spyOn(vm, 'initScrollFade');
+ spyOn(vm, 'setScrollFade');
+
+ vm.$nextTick()
+ .then(() => {
+ vm.boards = vm.boards.slice(0, 5);
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when recentBoards api returns empty array', done => {
+ vm.$nextTick()
+ .then(() => {
+ vm.recentBoards = [];
+ })
+ .then(vm.$nextTick)
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not show when search is active', done => {
+ fillSearchBox('Random string');
+
+ vm.$nextTick()
+ .then(() => {
+ const headerEls = vm.$el.querySelectorAll('.dropdown-bold-header');
+ const expectedCount = 0;
+
+ expect(expectedCount).toBe(headerEls.length);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index e6a969bd855..b2fe315f6c6 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -224,7 +224,7 @@ describe('AjaxFormVariableList', () => {
describe('maskableRegex', () => {
it('takes in the regex provided by the data attribute', () => {
- expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect(container.dataset.maskableRegex).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
expect(ajaxVariableList.maskableRegex).toBe(container.dataset.maskableRegex);
});
});
diff --git a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
index 064113e879a..c8d6f789ed0 100644
--- a/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ci_variable_list_spec.js
@@ -162,7 +162,7 @@ describe('VariableList', () => {
});
it('has a regex provided via a data attribute', () => {
- expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/-]{8,}$');
+ expect($wrapper.attr('data-maskable-regex')).toBe('^[a-zA-Z0-9_+=/@:-]{8,}$');
});
it('allows values that are 8 characters long', done => {
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 77f8352047c..ef4bb470734 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import CompareVersionsComponent from '~/diffs/components/compare_versions.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffsMockData from '../mock_data/merge_request_diffs';
import getDiffWithCommit from '../mock_data/diff_with_commit';
@@ -10,6 +10,8 @@ describe('CompareVersions', () => {
const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
beforeEach(() => {
+ const store = createStore();
+
store.state.diffs.addedLines = 10;
store.state.diffs.removedLines = 20;
store.state.diffs.diffFiles.push('test');
diff --git a/spec/javascripts/diffs/components/diff_expansion_cell_spec.js b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
new file mode 100644
index 00000000000..63c50c09fce
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_expansion_cell_spec.js
@@ -0,0 +1,64 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import DiffExpansionCell from '~/diffs/components/diff_expansion_cell.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+const EXPAND_UP_CLASS = '.js-unfold';
+const EXPAND_DOWN_CLASS = '.js-unfold-down';
+const EXPAND_ALL_CLASS = '.js-unfold-all';
+
+describe('DiffExpansionCell', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(DiffExpansionCell);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('top row', () => {
+ it('should have "expand up" and "show all" option', () => {
+ const vm = createComponent({
+ isTop: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('middle row', () => {
+ it('should have "expand down", "show all", "expand up" option', () => {
+ const vm = createComponent();
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+
+ describe('bottom row', () => {
+ it('should have "expand down" and "show all" option', () => {
+ const vm = createComponent({
+ isBottom: true,
+ });
+ const el = vm.$el;
+
+ expect(el.querySelector(EXPAND_UP_CLASS)).toBe(null);
+ expect(el.querySelector(EXPAND_DOWN_CLASS)).not.toBe(null);
+ expect(el.querySelector(EXPAND_ALL_CLASS)).not.toBe(null);
+ });
+ });
+});
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/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index ef4589ada48..3ca2d1dc934 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import DiffFileComponent from '~/diffs/components/diff_file.vue';
import { diffViewerModes, diffViewerErrors } from '~/ide/constants';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,14 +9,18 @@ describe('DiffFile', () => {
let vm;
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, {
+ vm = createComponentWithStore(Vue.extend(DiffFileComponent), createStore(), {
file: JSON.parse(JSON.stringify(diffFileMockData)),
canCurrentUserFork: false,
}).$mount();
});
+ afterEach(() => {
+ vm.$destroy();
+ });
+
describe('template', () => {
- it('should render component with file header, file content components', () => {
+ it('should render component with file header, file content components', done => {
const el = vm.$el;
const { file_hash, file_path } = vm.file;
@@ -30,9 +34,13 @@ describe('DiffFile', () => {
vm.file.renderIt = true;
- vm.$nextTick(() => {
- expect(el.querySelectorAll('.line_content').length).toBeGreaterThan(5);
- });
+ vm.$nextTick()
+ .then(() => {
+ expect(el.querySelectorAll('.line_content').length).toBe(5);
+ expect(el.querySelectorAll('.js-line-expansion-content').length).toBe(1);
+ })
+ .then(done)
+ .catch(done.fail);
});
describe('collapsed', () => {
diff --git a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
index 038db8eaa7c..6bb704658fb 100644
--- a/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_gutter_content_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineGutterContent from '~/diffs/components/diff_line_gutter_content.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import discussionsMockData from '../mock_data/diff_discussions';
import diffFileMockData from '../mock_data/diff_file';
@@ -23,7 +23,7 @@ describe('DiffLineGutterContent', () => {
props.fileHash = getDiffFileMock().file_hash;
props.contextLinesPath = '/context/lines/path';
- return createComponentWithStore(cmp, store, props).$mount();
+ return createComponentWithStore(cmp, createStore(), props).$mount();
};
describe('computed', () => {
@@ -61,7 +61,7 @@ describe('DiffLineGutterContent', () => {
contextLinesPath: '/context/lines/path',
};
props.line.discussions = [Object.assign({}, discussionsMockData)];
- const component = createComponentWithStore(cmp, store, props).$mount();
+ const component = createComponentWithStore(cmp, createStore(), props).$mount();
expect(component.hasDiscussions).toEqual(true);
expect(component.shouldShowAvatarsOnGutter).toEqual(true);
@@ -70,15 +70,6 @@ describe('DiffLineGutterContent', () => {
});
describe('template', () => {
- it('should render three dots for context lines', () => {
- const component = createComponent({
- isMatchLine: true,
- });
-
- expect(component.$el.querySelector('span').classList.contains('context-cell')).toEqual(true);
- expect(component.$el.innerText).toEqual('...');
- });
-
it('should render comment button', () => {
const component = createComponent({
showCommentButton: true,
diff --git a/spec/javascripts/diffs/components/diff_line_note_form_spec.js b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
index b983dc35a57..237cfccfa29 100644
--- a/spec/javascripts/diffs/components/diff_line_note_form_spec.js
+++ b/spec/javascripts/diffs/components/diff_line_note_form_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import DiffLineNoteForm from '~/diffs/components/diff_line_note_form.vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import { noteableDataMock } from '../../notes/mock_data';
@@ -15,7 +15,7 @@ describe('DiffLineNoteForm', () => {
diffFile = getDiffFileMock();
diffLines = diffFile.highlighted_diff_lines;
- component = createComponentWithStore(Vue.extend(DiffLineNoteForm), store, {
+ component = createComponentWithStore(Vue.extend(DiffLineNoteForm), createStore(), {
diffFileHash: diffFile.file_hash,
diffLines,
line: diffLines[0],
diff --git a/spec/javascripts/diffs/components/diff_table_cell_spec.js b/spec/javascripts/diffs/components/diff_table_cell_spec.js
index 170e661beea..a5a042c577c 100644
--- a/spec/javascripts/diffs/components/diff_table_cell_spec.js
+++ b/spec/javascripts/diffs/components/diff_table_cell_spec.js
@@ -1,12 +1,12 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import DiffTableCell from '~/diffs/components/diff_table_cell.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
describe('DiffTableCell', () => {
const createComponent = options =>
- createComponentWithStore(Vue.extend(DiffTableCell), store, {
+ createComponentWithStore(Vue.extend(DiffTableCell), createStore(), {
line: diffFileMockData.highlighted_diff_lines[0],
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..290b3d7c803
--- /dev/null
+++ b/spec/javascripts/diffs/components/inline_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import InlineDiffExpansionRow from '~/diffs/components/inline_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('InlineDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(InlineDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
index 97926f6625e..0ddffe926d9 100644
--- a/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_table_row_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import store from '~/mr_notes/stores';
+import { createStore } from '~/mr_notes/stores';
import InlineDiffTableRow from '~/diffs/components/inline_diff_table_row.vue';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -9,7 +9,7 @@ describe('InlineDiffTableRow', () => {
const thisLine = diffFileMockData.highlighted_diff_lines[0];
beforeEach(() => {
- vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), store, {
+ vm = createComponentWithStore(Vue.extend(InlineDiffTableRow), createStore(), {
line: thisLine,
fileHash: diffFileMockData.file_hash,
contextLinesPath: 'contextLinesPath',
diff --git a/spec/javascripts/diffs/components/inline_diff_view_spec.js b/spec/javascripts/diffs/components/inline_diff_view_spec.js
index 9b61dbe7975..486d9629e26 100644
--- a/spec/javascripts/diffs/components/inline_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/inline_diff_view_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import '~/behaviors/markdown/render_gfm';
import InlineDiffView from '~/diffs/components/inline_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
import discussionsMockData from '../mock_data/diff_discussions';
@@ -15,6 +15,8 @@ describe('InlineDiffView', () => {
beforeEach(done => {
const diffFile = getDiffFileMock();
+ const store = createStore();
+
store.dispatch('diffs/setInlineDiffViewType');
component = createComponentWithStore(Vue.extend(InlineDiffView), store, {
diffFile,
@@ -28,9 +30,9 @@ describe('InlineDiffView', () => {
it('should have rendered diff lines', () => {
const el = component.$el;
- expect(el.querySelectorAll('tr.line_holder').length).toEqual(6);
+ expect(el.querySelectorAll('tr.line_holder').length).toEqual(5);
expect(el.querySelectorAll('tr.line_holder.new').length).toEqual(2);
- expect(el.querySelectorAll('tr.line_holder.match').length).toEqual(1);
+ expect(el.querySelectorAll('tr.line_expansion.match').length).toEqual(1);
expect(el.textContent.indexOf('Bad dates')).toBeGreaterThan(-1);
});
diff --git a/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
new file mode 100644
index 00000000000..a766ebb5efb
--- /dev/null
+++ b/spec/javascripts/diffs/components/parallel_diff_expansion_row_spec.js
@@ -0,0 +1,31 @@
+import Vue from 'vue';
+import { createStore } from '~/mr_notes/stores';
+import ParallelDiffExpansionRow from '~/diffs/components/parallel_diff_expansion_row.vue';
+import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import diffFileMockData from '../mock_data/diff_file';
+
+describe('ParallelDiffExpansionRow', () => {
+ const matchLine = diffFileMockData.highlighted_diff_lines[5];
+
+ const createComponent = (options = {}) => {
+ const cmp = Vue.extend(ParallelDiffExpansionRow);
+ const defaults = {
+ fileHash: diffFileMockData.file_hash,
+ contextLinesPath: 'contextLinesPath',
+ line: matchLine,
+ isTop: false,
+ isBottom: false,
+ };
+ const props = Object.assign({}, defaults, options);
+
+ return createComponentWithStore(cmp, createStore(), props).$mount();
+ };
+
+ describe('template', () => {
+ it('should render expansion row for match lines', () => {
+ const vm = createComponent();
+
+ expect(vm.$el.classList.contains('line_expansion')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/diffs/components/parallel_diff_view_spec.js b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
index 236bda96145..191313bf487 100644
--- a/spec/javascripts/diffs/components/parallel_diff_view_spec.js
+++ b/spec/javascripts/diffs/components/parallel_diff_view_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
import ParallelDiffView from '~/diffs/components/parallel_diff_view.vue';
-import store from 'ee_else_ce/mr_notes/stores';
+import { createStore } from 'ee_else_ce/mr_notes/stores';
import * as constants from '~/diffs/constants';
import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import diffFileMockData from '../mock_data/diff_file';
@@ -12,7 +12,7 @@ describe('ParallelDiffView', () => {
beforeEach(() => {
const diffFile = getDiffFileMock();
- component = createComponentWithStore(Vue.extend(ParallelDiffView), store, {
+ component = createComponentWithStore(Vue.extend(ParallelDiffView), createStore(), {
diffFile,
diffLines: diffFile.parallel_diff_lines,
}).$mount();
diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js
index 27428197c1c..531686efff1 100644
--- a/spec/javascripts/diffs/mock_data/diff_file.js
+++ b/spec/javascripts/diffs/mock_data/diff_file.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/diffs/mock_data/diff_file.js
+
export default {
submodule: false,
submodule_link: null,
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index f8872a3eb13..5806cb47034 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -380,7 +380,9 @@ describe('DiffsStoreActions', () => {
const params = { since: 6, to: 26 };
const lineNumbers = { oldLineNumber: 3, newLineNumber: 5 };
const fileHash = 'ff9200';
- const options = { endpoint, params, lineNumbers, fileHash };
+ const isExpandDown = false;
+ const nextLineNumbers = {};
+ const options = { endpoint, params, lineNumbers, fileHash, isExpandDown, nextLineNumbers };
const mock = new MockAdapter(axios);
const contextLines = { contextLines: [{ lineCode: 6 }] };
mock.onGet(endpoint).reply(200, contextLines);
@@ -392,7 +394,7 @@ describe('DiffsStoreActions', () => {
[
{
type: types.ADD_CONTEXT_LINES,
- payload: { lineNumbers, contextLines, params, fileHash },
+ payload: { lineNumbers, contextLines, params, fileHash, isExpandDown, nextLineNumbers },
},
],
[],
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 9c13c7ceb7a..3e033b6c9dc 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -81,6 +81,8 @@ describe('DiffsStoreMutations', () => {
params: {
bottom: true,
},
+ isExpandDown: false,
+ nextLineNumbers: {},
};
const diffFile = {
file_hash: options.fileHash,
@@ -108,6 +110,8 @@ describe('DiffsStoreMutations', () => {
options.contextLines,
options.lineNumbers,
options.params.bottom,
+ options.isExpandDown,
+ options.nextLineNumbers,
);
expect(addContextLinesSpy).toHaveBeenCalledWith({
@@ -116,6 +120,7 @@ describe('DiffsStoreMutations', () => {
contextLines: options.contextLines,
bottom: options.params.bottom,
lineNumbers: options.lineNumbers,
+ isExpandDown: false,
});
});
});
diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js
index 1f877910125..65eb4c9d2a3 100644
--- a/spec/javascripts/diffs/store/utils_spec.js
+++ b/spec/javascripts/diffs/store/utils_spec.js
@@ -260,6 +260,17 @@ describe('DiffsStoreUtils', () => {
expect(linesWithReferences[1].meta_data.old_pos).toEqual(2);
expect(linesWithReferences[1].meta_data.new_pos).toEqual(3);
});
+
+ it('should add correct line references when isExpandDown is true', () => {
+ const lines = [{ type: null }, { type: MATCH_LINE_TYPE }];
+ const linesWithReferences = utils.addLineReferences(lines, lineNumbers, false, true, {
+ old_line: 10,
+ new_line: 11,
+ });
+
+ expect(linesWithReferences[1].meta_data.old_pos).toEqual(10);
+ expect(linesWithReferences[1].meta_data.new_pos).toEqual(11);
+ });
});
describe('trimFirstCharOfLineContent', () => {
diff --git a/spec/javascripts/environments/confirm_rollback_modal_spec.js b/spec/javascripts/environments/confirm_rollback_modal_spec.js
deleted file mode 100644
index 05715bce38f..00000000000
--- a/spec/javascripts/environments/confirm_rollback_modal_spec.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import { shallowMount } from '@vue/test-utils';
-import { GlModal } from '@gitlab/ui';
-import ConfirmRollbackModal from '~/environments/components/confirm_rollback_modal.vue';
-import eventHub from '~/environments/event_hub';
-
-describe('Confirm Rollback Modal Component', () => {
- let environment;
-
- beforeEach(() => {
- environment = {
- name: 'test',
- last_deployment: {
- commit: {
- short_id: 'abc0123',
- },
- },
- modalId: 'test',
- };
- });
-
- it('should show "Rollback" when isLastDeployment is false', () => {
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment: {
- ...environment,
- isLastDeployment: false,
- },
- },
- });
- const modal = component.find(GlModal);
-
- expect(modal.attributes('title')).toContain('Rollback');
- expect(modal.attributes('title')).toContain('test');
- expect(modal.attributes('ok-title')).toBe('Rollback');
- expect(modal.text()).toContain('commit abc0123');
- expect(modal.text()).toContain('Are you sure you want to continue?');
- });
-
- it('should show "Re-deploy" when isLastDeployment is true', () => {
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment: {
- ...environment,
- isLastDeployment: true,
- },
- },
- });
- const modal = component.find(GlModal);
-
- expect(modal.attributes('title')).toContain('Re-deploy');
- expect(modal.attributes('title')).toContain('test');
- expect(modal.attributes('ok-title')).toBe('Re-deploy');
- expect(modal.text()).toContain('commit abc0123');
- expect(modal.text()).toContain('Are you sure you want to continue?');
- });
-
- it('should emit the "rollback" event when "ok" is clicked', () => {
- environment = { ...environment, isLastDeployment: true };
- const component = shallowMount(ConfirmRollbackModal, {
- propsData: {
- environment,
- },
- });
- const eventHubSpy = spyOn(eventHub, '$emit');
- const modal = component.find(GlModal);
- modal.vm.$emit('ok');
-
- expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', environment);
- });
-});
diff --git a/spec/javascripts/environments/environment_rollback_spec.js b/spec/javascripts/environments/environment_rollback_spec.js
deleted file mode 100644
index 8c47f6a12c0..00000000000
--- a/spec/javascripts/environments/environment_rollback_spec.js
+++ /dev/null
@@ -1,61 +0,0 @@
-import Vue from 'vue';
-import { shallowMount } from '@vue/test-utils';
-import { GlButton } from '@gitlab/ui';
-import eventHub from '~/environments/event_hub';
-import rollbackComp from '~/environments/components/environment_rollback.vue';
-
-describe('Rollback Component', () => {
- const retryUrl = 'https://gitlab.com/retry';
- let RollbackComponent;
-
- beforeEach(() => {
- RollbackComponent = Vue.extend(rollbackComp);
- });
-
- it('Should render Re-deploy label when isLastDeployment is true', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
- propsData: {
- retryUrl,
- isLastDeployment: true,
- environment: {},
- },
- }).$mount();
-
- expect(component.$el).toHaveSpriteIcon('repeat');
- });
-
- it('Should render Rollback label when isLastDeployment is false', () => {
- const component = new RollbackComponent({
- el: document.querySelector('.test-dom-element'),
- propsData: {
- retryUrl,
- isLastDeployment: false,
- environment: {},
- },
- }).$mount();
-
- expect(component.$el).toHaveSpriteIcon('redo');
- });
-
- it('should emit a "rollback" event on button click', () => {
- const eventHubSpy = spyOn(eventHub, '$emit');
- const component = shallowMount(RollbackComponent, {
- propsData: {
- retryUrl,
- environment: {
- name: 'test',
- },
- },
- });
- const button = component.find(GlButton);
-
- button.vm.$emit('click');
-
- expect(eventHubSpy).toHaveBeenCalledWith('requestRollbackEnvironment', {
- retryUrl,
- isLastDeployment: true,
- name: 'test',
- });
- });
-});
diff --git a/spec/javascripts/fixtures/.gitignore b/spec/javascripts/fixtures/.gitignore
index bed020f5b0a..d6b7ef32c84 100644
--- a/spec/javascripts/fixtures/.gitignore
+++ b/spec/javascripts/fixtures/.gitignore
@@ -1,5 +1,2 @@
-*.html.raw
-*.html
-*.json
-*.pdf
-*.bmpr
+*
+!.gitignore
diff --git a/spec/javascripts/fixtures/abuse_reports.rb b/spec/javascripts/fixtures/abuse_reports.rb
deleted file mode 100644
index e0aaecf626a..00000000000
--- a/spec/javascripts/fixtures/abuse_reports.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-require 'spec_helper'
-
-describe Admin::AbuseReportsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let!(:abuse_report) { create(:abuse_report) }
- let!(:abuse_report_with_short_message) { create(:abuse_report, message: 'SHORT MESSAGE') }
- let!(:abuse_report_with_long_message) { create(:abuse_report, message: "LONG MESSAGE\n" * 50) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('abuse_reports/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'abuse_reports/abuse_reports_list.html' do
- get :index
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/admin_users.rb b/spec/javascripts/fixtures/admin_users.rb
deleted file mode 100644
index 22a5de66577..00000000000
--- a/spec/javascripts/fixtures/admin_users.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Admin::UsersController, '(JavaScript fixtures)', type: :controller do
- include StubENV
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
-
- before do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(admin)
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('admin/users')
- end
-
- it 'admin/users/new_with_internal_user_regex.html' do
- stub_application_setting(user_default_external: true)
- stub_application_setting(user_default_internal_regex: '^(?:(?!\.ext@).)*$\r?')
-
- get :new
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/application_settings.rb b/spec/javascripts/fixtures/application_settings.rb
deleted file mode 100644
index d4651fa6ece..00000000000
--- a/spec/javascripts/fixtures/application_settings.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ApplicationSettingsController, '(JavaScript fixtures)', type: :controller do
- include StubENV
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'application-settings') }
-
- before do
- stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
- sign_in(admin)
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('application_settings/')
- end
-
- after do
- remove_repository(project)
- end
-
- it 'application_settings/accounts_and_limit.html' do
- stub_application_setting(user_default_external: false)
-
- get :show
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb
deleted file mode 100644
index b20a0159d7d..00000000000
--- a/spec/javascripts/fixtures/autocomplete_sources.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- set(:admin) { create(:admin) }
- set(:group) { create(:group, name: 'frontend-fixtures') }
- set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
- set(:issue) { create(:issue, project: project) }
-
- before(:all) do
- clean_frontend_fixtures('autocomplete_sources/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'autocomplete_sources/labels.json' do
- issue.labels << create(:label, project: project, title: 'bug')
- issue.labels << create(:label, project: project, title: 'critical')
-
- create(:label, project: project, title: 'feature')
- create(:label, project: project, title: 'documentation')
-
- get :labels,
- format: :json,
- params: {
- namespace_id: group.path,
- project_id: project.path,
- type: issue.class.name,
- type_id: issue.id
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/blob.rb b/spec/javascripts/fixtures/blob.rb
deleted file mode 100644
index 07670552cd5..00000000000
--- a/spec/javascripts/fixtures/blob.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BlobController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('blob/')
- end
-
- before do
- sign_in(admin)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- after do
- remove_repository(project)
- end
-
- it 'blob/show.html' do
- get(:show, params: {
- namespace_id: project.namespace,
- project_id: project,
- id: 'add-ipython-files/files/ipython/basic.ipynb'
- })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/boards.rb b/spec/javascripts/fixtures/boards.rb
deleted file mode 100644
index 5835721d3d5..00000000000
--- a/spec/javascripts/fixtures/boards.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BoardsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'boards-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('boards/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'boards/show.html' do
- get(:index, params: {
- namespace_id: project.namespace,
- project_id: project
- })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/branches.rb b/spec/javascripts/fixtures/branches.rb
deleted file mode 100644
index 204aa9b7c7a..00000000000
--- a/spec/javascripts/fixtures/branches.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe Projects::BranchesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('branches/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'branches/new_branch.html' do
- get :new, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/clusters.rb b/spec/javascripts/fixtures/clusters.rb
deleted file mode 100644
index 1076404e0e3..00000000000
--- a/spec/javascripts/fixtures/clusters.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ClustersController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace) }
- let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('clusters/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'clusters/show_cluster.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: cluster
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/commit.rb b/spec/javascripts/fixtures/commit.rb
deleted file mode 100644
index ff9a4bc1adc..00000000000
--- a/spec/javascripts/fixtures/commit.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe Projects::CommitController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- set(:project) { create(:project, :repository) }
- set(:user) { create(:user) }
- let(:commit) { project.commit("master") }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('commit/')
- end
-
- before do
- project.add_maintainer(user)
- sign_in(user)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- it 'commit/show.html' do
- params = {
- namespace_id: project.namespace,
- project_id: project,
- id: commit.id
- }
-
- get :show, params: params
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/deploy_keys.rb b/spec/javascripts/fixtures/deploy_keys.rb
deleted file mode 100644
index 38eab853da2..00000000000
--- a/spec/javascripts/fixtures/deploy_keys.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Projects::DeployKeysController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:project2) { create(:project, :internal)}
- let(:project3) { create(:project, :internal)}
- let(:project4) { create(:project, :internal)}
-
- before(:all) do
- clean_frontend_fixtures('deploy_keys/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- render_views
-
- it 'deploy_keys/keys.json' do
- create(:rsa_deploy_key_2048, public: true)
- project_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCdMHEHyhRjbhEZVddFn6lTWdgEy5Q6Bz4nwGB76xWZI5YT/1WJOMEW+sL5zYd31kk7sd3FJ5L9ft8zWMWrr/iWXQikC2cqZK24H1xy+ZUmrRuJD4qGAaIVoyyzBL+avL+lF8J5lg6YSw8gwJY/lX64/vnJHUlWw2n5BF8IFOWhiw== dummy@gitlab.com')
- internal_key = create(:deploy_key, key: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQDNd/UJWhPrpb+b/G5oL109y57yKuCxE+WUGJGYaj7WQKsYRJmLYh1mgjrl+KVyfsWpq4ylOxIfFSnN9xBBFN8mlb0Fma5DC7YsSsibJr3MZ19ZNBprwNcdogET7aW9I0In7Wu5f2KqI6e5W/spJHCy4JVxzVMUvk6Myab0LnJ2iQ== dummy@gitlab.com')
- create(:deploy_keys_project, project: project, deploy_key: project_key)
- create(:deploy_keys_project, project: project2, deploy_key: internal_key)
- create(:deploy_keys_project, project: project3, deploy_key: project_key)
- create(:deploy_keys_project, project: project4, deploy_key: project_key)
-
- get :index, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/groups.rb b/spec/javascripts/fixtures/groups.rb
deleted file mode 100644
index 4d0afc3ce1a..00000000000
--- a/spec/javascripts/fixtures/groups.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe 'Groups (JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:group) { create(:group, name: 'frontend-fixtures-group', runners_token: 'runnerstoken:intabulasreferre')}
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('groups/')
- end
-
- before do
- group.add_maintainer(admin)
- sign_in(admin)
- end
-
- describe GroupsController, '(JavaScript fixtures)', type: :controller do
- it 'groups/edit.html' do
- get :edit, params: { id: group }
-
- expect(response).to be_success
- end
- end
-
- describe Groups::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'groups/ci_cd_settings.html' do
- get :show, params: { group_id: group }
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/issues.rb b/spec/javascripts/fixtures/issues.rb
deleted file mode 100644
index d8d77f767de..00000000000
--- a/spec/javascripts/fixtures/issues.rb
+++ /dev/null
@@ -1,122 +0,0 @@
-require 'spec_helper'
-
-describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin, feed_token: 'feedtoken:coldfeed') }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'issues-project') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('issues/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'issues/open-issue.html' do
- render_issue(create(:issue, project: project))
- end
-
- it 'issues/closed-issue.html' do
- render_issue(create(:closed_issue, project: project))
- end
-
- it 'issues/issue-with-task-list.html' do
- issue = create(:issue, project: project, description: '- [ ] Task List Item')
- render_issue(issue)
- end
-
- it 'issues/issue_with_comment.html' do
- issue = create(:issue, project: project)
- create(:note, project: project, noteable: issue, note: '- [ ] Task List Item').save
- render_issue(issue)
- end
-
- it 'issues/issue_list.html' do
- create(:issue, project: project)
-
- get :index, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-
- private
-
- def render_issue(issue)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: issue.to_param
- }
-
- expect(response).to be_success
- end
-end
-
-describe API::Issues, '(JavaScript fixtures)', type: :request do
- include ApiHelpers
- include JavaScriptFixturesHelpers
-
- def get_related_merge_requests(project_id, issue_iid, user = nil)
- get api("/projects/#{project_id}/issues/#{issue_iid}/related_merge_requests", user)
- end
-
- def create_referencing_mr(user, project, issue)
- attributes = {
- author: user,
- source_project: project,
- target_project: project,
- source_branch: "master",
- target_branch: "test",
- assignee: user,
- description: "See #{issue.to_reference}"
- }
- create(:merge_request, attributes).tap do |merge_request|
- create(:note, :system, project: issue.project, noteable: issue, author: user, note: merge_request.to_reference(full: true))
- end
- end
-
- it 'issues/related_merge_requests.json' do
- user = create(:user)
- project = create(:project, :public, creator_id: user.id, namespace: user.namespace)
- issue_title = 'foo'
- issue_description = 'closed'
- milestone = create(:milestone, title: '1.0.0', project: project)
- issue = create :issue,
- author: user,
- assignees: [user],
- project: project,
- milestone: milestone,
- created_at: generate(:past_time),
- updated_at: 1.hour.ago,
- title: issue_title,
- description: issue_description
-
- project.add_reporter(user)
- create_referencing_mr(user, project, issue)
-
- create(:merge_request,
- :simple,
- author: user,
- source_project: project,
- target_project: project,
- description: "Some description")
- project2 = create(:project, :public, creator_id: user.id, namespace: user.namespace)
- create_referencing_mr(user, project2, issue).update!(head_pipeline: create(:ci_pipeline))
-
- get_related_merge_requests(project.id, issue.iid, user)
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/jobs.rb b/spec/javascripts/fixtures/jobs.rb
deleted file mode 100644
index 46ccd6f8c8a..00000000000
--- a/spec/javascripts/fixtures/jobs.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe Projects::JobsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'builds-project') }
- let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.id) }
- let!(:build_with_artifacts) { create(:ci_build, :success, :artifacts, :trace_artifact, pipeline: pipeline, stage: 'test', artifacts_expire_at: Time.now + 18.months) }
- let!(:failed_build) { create(:ci_build, :failed, pipeline: pipeline, stage: 'build') }
- let!(:pending_build) { create(:ci_build, :pending, pipeline: pipeline, stage: 'deploy') }
- let!(:delayed_job) do
- create(:ci_build, :scheduled,
- pipeline: pipeline,
- name: 'delayed job',
- stage: 'test')
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('builds/')
- clean_frontend_fixtures('jobs/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'builds/build-with-artifacts.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: build_with_artifacts.to_param
- }
-
- expect(response).to be_success
- end
-
- it 'jobs/delayed.json' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: delayed_job.to_param
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/labels.rb b/spec/javascripts/fixtures/labels.rb
deleted file mode 100644
index 4d1b7317274..00000000000
--- a/spec/javascripts/fixtures/labels.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-require 'spec_helper'
-
-describe 'Labels (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:group) { create(:group, name: 'frontend-fixtures-group' )}
- let(:project) { create(:project_empty_repo, namespace: group, path: 'labels-project') }
-
- let!(:project_label_bug) { create(:label, project: project, title: 'bug', color: '#FF0000') }
- let!(:project_label_enhancement) { create(:label, project: project, title: 'enhancement', color: '#00FF00') }
- let!(:project_label_feature) { create(:label, project: project, title: 'feature', color: '#0000FF') }
-
- let!(:group_label_roses) { create(:group_label, group: group, title: 'roses', color: '#FF0000') }
- let!(:groub_label_space) { create(:group_label, group: group, title: 'some space', color: '#FFFFFF') }
- let!(:groub_label_violets) { create(:group_label, group: group, title: 'violets', color: '#0000FF') }
-
- before(:all) do
- clean_frontend_fixtures('labels/')
- end
-
- after do
- remove_repository(project)
- end
-
- describe Groups::LabelsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'labels/group_labels.json' do
- get :index, params: {
- group_id: group
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-
- describe Projects::LabelsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'labels/project_labels.json' do
- get :index, params: {
- namespace_id: group,
- project_id: project
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/merge_requests.rb b/spec/javascripts/fixtures/merge_requests.rb
deleted file mode 100644
index 05860be2291..00000000000
--- a/spec/javascripts/fixtures/merge_requests.rb
+++ /dev/null
@@ -1,134 +0,0 @@
-require 'spec_helper'
-
-describe Projects::MergeRequestsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
- let(:merged_merge_request) { create(:merge_request, :merged, source_project: project, target_project: project) }
- let(:pipeline) do
- create(
- :ci_pipeline,
- project: merge_request.source_project,
- ref: merge_request.source_branch,
- sha: merge_request.diff_head_sha
- )
- end
- let(:path) { "files/ruby/popen.rb" }
- let(:position) do
- Gitlab::Diff::Position.new(
- old_path: path,
- new_path: path,
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs
- )
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('merge_requests/')
- end
-
- before do
- sign_in(admin)
- allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
- end
-
- after do
- remove_repository(project)
- end
-
- it 'merge_requests/merge_request_of_current_user.html' do
- merge_request.update(author: admin)
-
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merge_request_with_task_list.html' do
- create(:ci_build, :pending, pipeline: pipeline)
-
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merged_merge_request.html' do
- expect_next_instance_of(MergeRequest) do |merge_request|
- allow(merge_request).to receive(:source_branch_exists?).and_return(true)
- allow(merge_request).to receive(:can_remove_source_branch?).and_return(true)
- end
- render_merge_request(merged_merge_request)
- end
-
- it 'merge_requests/diff_comment.html' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/merge_request_with_comment.html' do
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request, note: '- [ ] Task List Item')
- render_merge_request(merge_request)
- end
-
- it 'merge_requests/discussions.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request)
- end
-
- it 'merge_requests/diff_discussion.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- render_discussions_json(merge_request)
- end
-
- it 'merge_requests/resolved_diff_discussion.json' do
- note = create(:discussion_note_on_merge_request, :resolved, project: project, author: admin, position: position, noteable: merge_request)
- create(:system_note, project: project, author: admin, noteable: merge_request, discussion_id: note.discussion.id)
-
- render_discussions_json(merge_request)
- end
-
- context 'with image diff' do
- let(:merge_request2) { create(:merge_request_with_diffs, :with_image_diffs, source_project: project, title: "Added images") }
- let(:image_path) { "files/images/ee_repo_logo.png" }
- let(:image_position) do
- Gitlab::Diff::Position.new(
- old_path: image_path,
- new_path: image_path,
- width: 100,
- height: 100,
- x: 1,
- y: 1,
- position_type: "image",
- diff_refs: merge_request2.diff_refs
- )
- end
-
- it 'merge_requests/image_diff_discussion.json' do
- create(:diff_note_on_merge_request, project: project, noteable: merge_request2, position: image_position)
- render_discussions_json(merge_request2)
- end
- end
-
- private
-
- def render_discussions_json(merge_request)
- get :discussions, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param
- }, format: :json
- end
-
- def render_merge_request(merge_request)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param
- }, format: :html
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/merge_requests_diffs.rb b/spec/javascripts/fixtures/merge_requests_diffs.rb
deleted file mode 100644
index 03b9b713fd8..00000000000
--- a/spec/javascripts/fixtures/merge_requests_diffs.rb
+++ /dev/null
@@ -1,70 +0,0 @@
-
-require 'spec_helper'
-
-describe Projects::MergeRequests::DiffsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'merge-requests-project') }
- let(:merge_request) { create(:merge_request, :with_diffs, source_project: project, target_project: project, description: '- [ ] Task List Item') }
- let(:path) { "files/ruby/popen.rb" }
- let(:selected_commit) { merge_request.all_commits[0] }
- let(:position) do
- Gitlab::Diff::Position.new(
- old_path: path,
- new_path: path,
- old_line: nil,
- new_line: 14,
- diff_refs: merge_request.diff_refs
- )
- end
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('merge_request_diffs/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'merge_request_diffs/with_commit.json' do
- # Create a user that matches the selected commit author
- # This is so that the "author" information will be populated
- create(:user, email: selected_commit.author_email, name: selected_commit.author_name)
-
- render_merge_request(merge_request, commit_id: selected_commit.sha)
- end
-
- it 'merge_request_diffs/inline_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request)
- end
-
- it 'merge_request_diffs/parallel_changes_tab_with_comments.json' do
- create(:diff_note_on_merge_request, project: project, author: admin, position: position, noteable: merge_request)
- create(:note_on_merge_request, author: admin, project: project, noteable: merge_request)
- render_merge_request(merge_request, view: 'parallel')
- end
-
- private
-
- def render_merge_request(merge_request, view: 'inline', **extra_params)
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: merge_request.to_param,
- view: view,
- **extra_params
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/pipeline_schedules.rb b/spec/javascripts/fixtures/pipeline_schedules.rb
deleted file mode 100644
index aecd56e6198..00000000000
--- a/spec/javascripts/fixtures/pipeline_schedules.rb
+++ /dev/null
@@ -1,43 +0,0 @@
-require 'spec_helper'
-
-describe Projects::PipelineSchedulesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :public, :repository) }
- let!(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: admin) }
- let!(:pipeline_schedule_populated) { create(:ci_pipeline_schedule, project: project, owner: admin) }
- let!(:pipeline_schedule_variable1) { create(:ci_pipeline_schedule_variable, key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule_populated) }
- let!(:pipeline_schedule_variable2) { create(:ci_pipeline_schedule_variable, key: 'bar', value: 'barvalue', pipeline_schedule: pipeline_schedule_populated) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('pipeline_schedules/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'pipeline_schedules/edit.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule.id
- }
-
- expect(response).to be_success
- end
-
- it 'pipeline_schedules/edit_with_variables.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- project_id: project,
- id: pipeline_schedule_populated.id
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb
deleted file mode 100644
index 6b6b0eefab9..00000000000
--- a/spec/javascripts/fixtures/pipelines.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') }
- let(:commit) { create(:commit, project: project) }
- let(:commit_without_author) { RepoHelpers.another_sample_commit }
- let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) }
- let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) }
- let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) }
- let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('pipelines/')
- end
-
- before do
- sign_in(admin)
- end
-
- it 'pipelines/pipelines.json' do
- get :index, params: {
- namespace_id: namespace,
- project_id: project
- }, format: :json
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/projects.rb b/spec/javascripts/fixtures/projects.rb
deleted file mode 100644
index 94c59207898..00000000000
--- a/spec/javascripts/fixtures/projects.rb
+++ /dev/null
@@ -1,81 +0,0 @@
-require 'spec_helper'
-
-describe 'Projects (JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- runners_token = 'runnerstoken:intabulasreferre'
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, namespace: namespace, path: 'builds-project', runners_token: runners_token) }
- let(:project_with_repo) { create(:project, :repository, description: 'Code and stuff') }
- let(:project_variable_populated) { create(:project, namespace: namespace, path: 'builds-project2', runners_token: runners_token) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('projects/')
- end
-
- before do
- project.add_maintainer(admin)
- sign_in(admin)
- allow(SecureRandom).to receive(:hex).and_return('securerandomhex:thereisnospoon')
- end
-
- after do
- remove_repository(project)
- end
-
- describe ProjectsController, '(JavaScript fixtures)', type: :controller do
- it 'projects/dashboard.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- id: project
- }
-
- expect(response).to be_success
- end
-
- it 'projects/overview.html' do
- get :show, params: {
- namespace_id: project_with_repo.namespace.to_param,
- id: project_with_repo
- }
-
- expect(response).to be_success
- end
-
- it 'projects/edit.html' do
- get :edit, params: {
- namespace_id: project.namespace.to_param,
- id: project
- }
-
- expect(response).to be_success
- end
- end
-
- describe Projects::Settings::CiCdController, '(JavaScript fixtures)', type: :controller do
- it 'projects/ci_cd_settings.html' do
- get :show, params: {
- namespace_id: project.namespace.to_param,
- project_id: project
- }
-
- expect(response).to be_success
- end
-
- it 'projects/ci_cd_settings_with_variables.html' do
- create(:ci_variable, project: project_variable_populated)
- create(:ci_variable, project: project_variable_populated)
-
- get :show, params: {
- namespace_id: project_variable_populated.namespace.to_param,
- project_id: project_variable_populated
- }
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/prometheus_service.rb b/spec/javascripts/fixtures/prometheus_service.rb
deleted file mode 100644
index f3171fdd97b..00000000000
--- a/spec/javascripts/fixtures/prometheus_service.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:prometheus_service, project: project) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('services/prometheus')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'services/prometheus/prometheus_service.html' do
- get :edit, params: {
- namespace_id: namespace,
- project_id: project,
- id: service.to_param
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/search.rb b/spec/javascripts/fixtures/search.rb
deleted file mode 100644
index 22fc546d761..00000000000
--- a/spec/javascripts/fixtures/search.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe SearchController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('search/')
- end
-
- it 'search/show.html' do
- get :show
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/services.rb b/spec/javascripts/fixtures/services.rb
deleted file mode 100644
index 2237702ccca..00000000000
--- a/spec/javascripts/fixtures/services.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-require 'spec_helper'
-
-describe Projects::ServicesController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'services-project') }
- let!(:service) { create(:custom_issue_tracker_service, project: project, title: 'Custom Issue Tracker') }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('services/')
- end
-
- before do
- sign_in(admin)
- end
-
- after do
- remove_repository(project)
- end
-
- it 'services/edit_service.html' do
- get :edit, params: {
- namespace_id: namespace,
- project_id: project,
- id: service.to_param
- }
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/sessions.rb b/spec/javascripts/fixtures/sessions.rb
deleted file mode 100644
index 92b74c01c89..00000000000
--- a/spec/javascripts/fixtures/sessions.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-require 'spec_helper'
-
-describe 'Sessions (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- before(:all) do
- clean_frontend_fixtures('sessions/')
- end
-
- describe SessionsController, '(JavaScript fixtures)', type: :controller do
- include DeviseHelpers
-
- render_views
-
- before do
- set_devise_mapping(context: @request)
- end
-
- it 'sessions/new.html' do
- get :new
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/snippet.rb b/spec/javascripts/fixtures/snippet.rb
deleted file mode 100644
index ace84b14eb7..00000000000
--- a/spec/javascripts/fixtures/snippet.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-require 'spec_helper'
-
-describe SnippetsController, '(JavaScript fixtures)', type: :controller do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project, :repository, namespace: namespace, path: 'branches-project') }
- let(:snippet) { create(:personal_snippet, title: 'snippet.md', content: '# snippet', file_name: 'snippet.md', author: admin) }
-
- render_views
-
- before(:all) do
- clean_frontend_fixtures('snippets/')
- end
-
- before do
- sign_in(admin)
- allow(Discussion).to receive(:build_discussion_id).and_return(['discussionid:ceterumcenseo'])
- end
-
- after do
- remove_repository(project)
- end
-
- it 'snippets/show.html' do
- create(:discussion_note_on_snippet, noteable: snippet, project: project, author: admin, note: '- [ ] Task List Item')
-
- get(:show, params: { id: snippet.to_param })
-
- expect(response).to be_success
- end
-end
diff --git a/spec/javascripts/fixtures/static/README.md b/spec/javascripts/fixtures/static/README.md
deleted file mode 100644
index b5c2f8233bf..00000000000
--- a/spec/javascripts/fixtures/static/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Please do not add new files here!
-
-Instead use a Ruby file in the fixtures root directory (`spec/javascripts/fixtures/`).
diff --git a/spec/javascripts/fixtures/todos.rb b/spec/javascripts/fixtures/todos.rb
deleted file mode 100644
index d0c8a6eca01..00000000000
--- a/spec/javascripts/fixtures/todos.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-require 'spec_helper'
-
-describe 'Todos (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- let(:admin) { create(:admin) }
- let(:namespace) { create(:namespace, name: 'frontend-fixtures' )}
- let(:project) { create(:project_empty_repo, namespace: namespace, path: 'todos-project') }
- let(:issue_1) { create(:issue, title: 'issue_1', project: project) }
- let!(:todo_1) { create(:todo, user: admin, project: project, target: issue_1, created_at: 5.hours.ago) }
- let(:issue_2) { create(:issue, title: 'issue_2', project: project) }
- let!(:todo_2) { create(:todo, :done, user: admin, project: project, target: issue_2, created_at: 50.hours.ago) }
-
- before(:all) do
- clean_frontend_fixtures('todos/')
- end
-
- after do
- remove_repository(project)
- end
-
- describe Dashboard::TodosController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'todos/todos.html' do
- get :index
-
- expect(response).to be_success
- end
- end
-
- describe Projects::TodosController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(admin)
- end
-
- it 'todos/todos.json' do
- post :create, params: {
- namespace_id: namespace,
- project_id: project,
- issuable_type: 'issue',
- issuable_id: issue_2.id
- }, format: 'json'
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
deleted file mode 100644
index f52832b6efb..00000000000
--- a/spec/javascripts/fixtures/u2f.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-require 'spec_helper'
-
-context 'U2F' do
- include JavaScriptFixturesHelpers
-
- let(:user) { create(:user, :two_factor_via_u2f, otp_secret: 'otpsecret:coolkids') }
-
- before(:all) do
- clean_frontend_fixtures('u2f/')
- end
-
- describe SessionsController, '(JavaScript fixtures)', type: :controller do
- include DeviseHelpers
-
- render_views
-
- before do
- set_devise_mapping(context: @request)
- end
-
- it 'u2f/authenticate.html' do
- allow(controller).to receive(:find_user).and_return(user)
-
- post :create, params: { user: { login: user.username, password: user.password } }
-
- expect(response).to be_success
- end
- end
-
- describe Profiles::TwoFactorAuthsController, '(JavaScript fixtures)', type: :controller do
- render_views
-
- before do
- sign_in(user)
- allow_any_instance_of(Profiles::TwoFactorAuthsController).to receive(:build_qr_code).and_return('qrcode:blackandwhitesquares')
- end
-
- it 'u2f/register.html' do
- get :show
-
- expect(response).to be_success
- end
- end
-end
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
index 121e99c9783..5b749b11246 100644
--- a/spec/javascripts/helpers/vue_test_utils_helper.js
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -1,21 +1,5 @@
-/* eslint-disable import/prefer-default-export */
+// No new code should be added to this file. Instead, modify the
+// file this one re-exports from. For more detail about why, see:
+// https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/31349
-const vNodeContainsText = (vnode, text) =>
- (vnode.text && vnode.text.includes(text)) ||
- (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
-
-/**
- * Determines whether a `shallowMount` Wrapper contains text
- * within one of it's slots. This will also work on Wrappers
- * acquired with `find()`, but only if it's parent Wrapper
- * was shallowMounted.
- * NOTE: Prefer checking the rendered output of a component
- * wherever possible using something like `text()` instead.
- * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
- * @param {String} slotName
- * @param {String} text
- */
-export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
- Boolean(
- shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length,
- );
+export * from '../../frontend/helpers/vue_test_utils_helper';
diff --git a/spec/javascripts/ide/components/repo_editor_spec.js b/spec/javascripts/ide/components/repo_editor_spec.js
index 7dc5cb24981..0701b773e17 100644
--- a/spec/javascripts/ide/components/repo_editor_spec.js
+++ b/spec/javascripts/ide/components/repo_editor_spec.js
@@ -5,7 +5,7 @@ import axios from '~/lib/utils/axios_utils';
import store from '~/ide/stores';
import repoEditor from '~/ide/components/repo_editor.vue';
import Editor from '~/ide/lib/editor';
-import { activityBarViews } from '~/ide/constants';
+import { activityBarViews, FILE_VIEW_MODE_EDITOR, FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { createComponentWithStore } from '../../helpers/vue_mount_component_helper';
import setTimeoutPromise from '../../helpers/set_timeout_promise_helper';
import { file, resetStore } from '../helpers';
@@ -16,7 +16,7 @@ describe('RepoEditor', () => {
beforeEach(done => {
const f = {
...file(),
- viewMode: 'editor',
+ viewMode: FILE_VIEW_MODE_EDITOR,
};
const RepoEditor = Vue.extend(repoEditor);
@@ -370,7 +370,7 @@ describe('RepoEditor', () => {
describe('when files view mode is preview', () => {
beforeEach(done => {
spyOn(vm.editor, 'updateDimensions');
- vm.file.viewMode = 'preview';
+ vm.file.viewMode = FILE_VIEW_MODE_PREVIEW;
vm.$nextTick(done);
});
@@ -392,7 +392,7 @@ describe('RepoEditor', () => {
describe('when file view mode changes to editor', () => {
beforeEach(done => {
- vm.file.viewMode = 'editor';
+ vm.file.viewMode = FILE_VIEW_MODE_EDITOR;
// one tick to trigger watch
vm.$nextTick()
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 8a3c132972e..14d861f21d2 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -245,7 +245,7 @@ describe('IDE commit module actions', () => {
master: {
workingReference: '1',
commit: {
- short_id: TEST_COMMIT_SHA,
+ id: TEST_COMMIT_SHA,
},
},
},
@@ -411,7 +411,7 @@ describe('IDE commit module actions', () => {
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
store.getters['commit/placeholderBranchName']
- }&merge_request[target_branch]=master`,
+ }&merge_request[target_branch]=master&nav_source=webide`,
);
done();
diff --git a/spec/javascripts/ide/stores/mutations/file_spec.js b/spec/javascripts/ide/stores/mutations/file_spec.js
index 7714f66c9a4..064e66cef64 100644
--- a/spec/javascripts/ide/stores/mutations/file_spec.js
+++ b/spec/javascripts/ide/stores/mutations/file_spec.js
@@ -1,5 +1,6 @@
import mutations from '~/ide/stores/mutations/file';
import state from '~/ide/stores/state';
+import { FILE_VIEW_MODE_PREVIEW } from '~/ide/constants';
import { file } from '../../helpers';
describe('IDE store file mutations', () => {
@@ -103,6 +104,43 @@ describe('IDE store file mutations', () => {
expect(localState.openFiles[0].rawPath).toEqual(rawPath);
expect(localFile.rawPath).toEqual(rawPath);
});
+
+ it('does not mutate certain props on the file', () => {
+ const path = 'New Path';
+ const name = 'New Name';
+ localFile.path = path;
+ localFile.name = name;
+
+ localState.stagedFiles = [localFile];
+ localState.changedFiles = [localFile];
+ localState.openFiles = [localFile];
+
+ mutations.SET_FILE_DATA(localState, {
+ data: {
+ path: 'Old Path',
+ name: 'Old Name',
+ raw: 'Old Raw',
+ base_raw: 'Old Base Raw',
+ },
+ file: localFile,
+ });
+
+ [
+ localState.stagedFiles[0],
+ localState.changedFiles[0],
+ localState.openFiles[0],
+ localFile,
+ ].forEach(f => {
+ expect(f).toEqual(
+ jasmine.objectContaining({
+ path,
+ name,
+ raw: null,
+ baseRaw: null,
+ }),
+ );
+ });
+ });
});
describe('SET_FILE_RAW_DATA', () => {
@@ -388,10 +426,10 @@ describe('IDE store file mutations', () => {
it('updates file view mode', () => {
mutations.SET_FILE_VIEWMODE(localState, {
file: localFile,
- viewMode: 'preview',
+ viewMode: FILE_VIEW_MODE_PREVIEW,
});
- expect(localFile.viewMode).toBe('preview');
+ expect(localFile.viewMode).toBe(FILE_VIEW_MODE_PREVIEW);
});
});
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/issue_show/components/form_spec.js b/spec/javascripts/issue_show/components/form_spec.js
index b0f4ab2b12d..a111333ac80 100644
--- a/spec/javascripts/issue_show/components/form_spec.js
+++ b/spec/javascripts/issue_show/components/form_spec.js
@@ -1,81 +1,98 @@
import Vue from 'vue';
+import mountComponent from 'spec/helpers/vue_mount_component_helper';
import formComponent from '~/issue_show/components/form.vue';
import eventHub from '~/issue_show/event_hub';
describe('Inline edit form component', () => {
let vm;
- let autosave;
- let autosaveObj;
-
- beforeEach(done => {
- autosaveObj = { reset: jasmine.createSpy() };
-
- autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ const defaultProps = {
+ canDestroy: true,
+ formState: {
+ title: 'b',
+ description: 'a',
+ lockedWarningVisible: false,
+ },
+ issuableType: 'issue',
+ markdownPreviewPath: '/',
+ markdownDocsPath: '/',
+ projectPath: '/',
+ projectNamespace: '/',
+ };
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+ const createComponent = props => {
const Component = Vue.extend(formComponent);
- vm = new Component({
- propsData: {
- canDestroy: true,
- formState: {
- title: 'b',
- description: 'a',
- lockedWarningVisible: false,
- },
- issuableType: 'issue',
- markdownPreviewPath: '/',
- markdownDocsPath: '/',
- projectPath: '/',
- projectNamespace: '/',
- },
- }).$mount();
-
- Vue.nextTick(done);
- });
+ vm = mountComponent(Component, {
+ ...defaultProps,
+ ...props,
+ });
+ };
it('does not render template selector if no templates exist', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.js-issuable-selector-wrap')).toBeNull();
});
- it('renders template selector when templates exists', done => {
- vm.issuableTemplates = ['test'];
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
+ it('renders template selector when templates exists', () => {
+ createComponent({ issuableTemplates: ['test'] });
- done();
- });
+ expect(vm.$el.querySelector('.js-issuable-selector-wrap')).not.toBeNull();
});
it('hides locked warning by default', () => {
+ createComponent();
+
expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('shows locked warning if formState is different', done => {
- vm.formState.lockedWarningVisible = true;
+ it('shows locked warning if formState is different', () => {
+ createComponent({ formState: { ...defaultProps.formState, lockedWarningVisible: true } });
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ expect(vm.$el.querySelector('.alert')).not.toBeNull();
+ });
- done();
+ it('hides locked warning when currently saving', () => {
+ createComponent({
+ formState: { ...defaultProps.formState, updateLoading: true, lockedWarningVisible: true },
});
- });
- it('initialized Autosave on mount', () => {
- expect(autosave).toHaveBeenCalledTimes(2);
+ expect(vm.$el.querySelector('.alert')).toBeNull();
});
- it('calls reset on autosave when eventHub emits appropriate events', () => {
- eventHub.$emit('close.form');
+ describe('autosave', () => {
+ let autosaveObj;
+ let autosave;
+
+ beforeEach(() => {
+ autosaveObj = { reset: jasmine.createSpy() };
+ autosave = spyOnDependency(formComponent, 'Autosave').and.returnValue(autosaveObj);
+ });
+
+ it('initialized Autosave on mount', () => {
+ createComponent();
- expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
+ expect(autosave).toHaveBeenCalledTimes(2);
+ });
+
+ it('calls reset on autosave when eventHub emits appropriate events', () => {
+ createComponent();
+
+ eventHub.$emit('close.form');
- eventHub.$emit('delete.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(2);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
+ eventHub.$emit('delete.issuable');
- eventHub.$emit('update.issuable');
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(4);
- expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ eventHub.$emit('update.issuable');
+
+ expect(autosaveObj.reset).toHaveBeenCalledTimes(6);
+ });
});
});
diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js
new file mode 100644
index 00000000000..c6eac4e27b3
--- /dev/null
+++ b/spec/javascripts/jobs/components/empty_state_spec.js
@@ -0,0 +1,141 @@
+import Vue from 'vue';
+import component from '~/jobs/components/empty_state.vue';
+import mountComponent from '../../helpers/vue_mount_component_helper';
+
+describe('Empty State', () => {
+ const Component = Vue.extend(component);
+ let vm;
+
+ const props = {
+ illustrationPath: 'illustrations/pending_job_empty.svg',
+ illustrationSizeClass: 'svg-430',
+ title: 'This job has not started yet',
+ playable: false,
+ variablesSettingsUrl: '',
+ };
+
+ const content = 'This job is in pending state and is waiting to be picked by a runner';
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('renders image and title', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+ });
+
+ it('renders img with provided path and size', () => {
+ expect(vm.$el.querySelector('img').getAttribute('src')).toEqual(props.illustrationPath);
+ expect(vm.$el.querySelector('.svg-content').classList).toContain(props.illustrationSizeClass);
+ });
+
+ it('renders provided title', () => {
+ expect(vm.$el.querySelector('.js-job-empty-state-title').textContent.trim()).toEqual(
+ props.title,
+ );
+ });
+ });
+
+ describe('with content', () => {
+ it('renders content', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-content').textContent.trim()).toEqual(
+ content,
+ );
+ });
+ });
+
+ describe('without content', () => {
+ it('does not render content', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-content')).toBeNull();
+ });
+ });
+
+ describe('with action', () => {
+ it('renders action', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ action: {
+ path: 'runner',
+ button_title: 'Check runner',
+ method: 'post',
+ },
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-action').getAttribute('href')).toEqual(
+ 'runner',
+ );
+ });
+ });
+
+ describe('without action', () => {
+ it('does not render action', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ action: null,
+ });
+
+ expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+ });
+ });
+
+ describe('without playbale action', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and not scheduled job', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ playable: true,
+ scheduled: false,
+ action: {
+ path: 'runner',
+ button_title: 'Check runner',
+ method: 'post',
+ },
+ });
+ });
+
+ it('renders manual variables form', () => {
+ expect(vm.$el.querySelector('.js-manual-vars-form')).not.toBeNull();
+ });
+
+ it('does not render the empty state action', () => {
+ expect(vm.$el.querySelector('.js-job-empty-state-action')).toBeNull();
+ });
+ });
+
+ describe('with playbale action and scheduled job', () => {
+ it('does not render manual variables form', () => {
+ vm = mountComponent(Component, {
+ ...props,
+ content,
+ });
+
+ expect(vm.$el.querySelector('.js-manual-vars-form')).toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js
index f28d2c2a882..d3c1cf831bb 100644
--- a/spec/javascripts/jobs/components/job_app_spec.js
+++ b/spec/javascripts/jobs/components/job_app_spec.js
@@ -3,7 +3,9 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import jobApp from '~/jobs/components/job_app.vue';
import createStore from '~/jobs/store';
+import * as types from '~/jobs/store/mutation_types';
import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
+import { waitForMutation } from 'spec/helpers/vue_test_utils_helper';
import { resetStore } from '../store/helpers';
import job from '../mock_data';
@@ -19,12 +21,24 @@ describe('Job App ', () => {
runnerHelpUrl: 'help/runner',
deploymentHelpUrl: 'help/deployment',
runnerSettingsUrl: 'settings/ci-cd/runners',
+ variablesSettingsUrl: 'settings/ci-cd/variables',
terminalPath: 'jobs/123/terminal',
pagePath: `${gl.TEST_HOST}jobs/123`,
+ projectPath: 'user-name/project-name',
logState:
'eyJvZmZzZXQiOjE3NDUxLCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowfQ%3D%3D',
};
+ const waitForJobReceived = () => waitForMutation(store, types.RECEIVE_JOB_SUCCESS);
+ const setupAndMount = ({ jobData = {}, traceData = {} } = {}) => {
+ mock.onGet(props.endpoint).replyOnce(200, { ...job, ...jobData });
+ mock.onGet(`${props.pagePath}/trace.json`).reply(200, traceData);
+
+ vm = mountComponentWithStore(Component, { props, store });
+
+ return waitForJobReceived();
+ };
+
beforeEach(() => {
mock = new MockAdapter(axios);
store = createStore();
@@ -38,103 +52,81 @@ describe('Job App ', () => {
describe('while loading', () => {
beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {});
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount();
});
- it('renders loading icon', done => {
+ it('renders loading icon', () => {
expect(vm.$el.querySelector('.js-job-loading')).not.toBeNull();
expect(vm.$el.querySelector('.js-job-sidebar')).toBeNull();
expect(vm.$el.querySelector('.js-job-content')).toBeNull();
-
- setTimeout(() => {
- done();
- }, 0);
});
});
describe('with successful request', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).replyOnce(200, {});
- });
-
describe('Header section', () => {
describe('job callout message', () => {
it('should not render the reason when reason is absent', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(false);
-
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(false);
+ })
+ .then(done)
+ .catch(done.fail);
});
it('should render the reason when reason is present', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
- callout_message: 'There is an unknown failure, please try again',
- }),
- );
-
- vm = mountComponentWithStore(Component, { props, store });
- setTimeout(() => {
- expect(vm.shouldRenderCalloutMessage).toBe(true);
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ callout_message: 'There is an unkown failure, please try again',
+ },
+ })
+ .then(() => {
+ expect(vm.shouldRenderCalloutMessage).toBe(true);
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('triggered job', () => {
- beforeEach(() => {
+ beforeEach(done => {
const aYearAgo = new Date();
aYearAgo.setFullYear(aYearAgo.getFullYear() - 1);
- mock
- .onGet(props.endpoint)
- .replyOnce(200, Object.assign({}, job, { started: aYearAgo.toISOString() }));
- vm = mountComponentWithStore(Component, { props, store });
+ setupAndMount({ jobData: { started: aYearAgo.toISOString() } })
+ .then(done)
+ .catch(done.fail);
});
- it('should render provided job information', done => {
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 triggered 1 year ago by Root');
- done();
- }, 0);
+ it('should render provided job information', () => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 triggered 1 year ago by Root');
});
- it('should render new issue link', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
- job.new_issue_path,
- );
- done();
- }, 0);
+ it('should render new issue link', () => {
+ expect(vm.$el.querySelector('.js-new-issue').getAttribute('href')).toEqual(
+ job.new_issue_path,
+ );
});
});
describe('created job', () => {
it('should render created key', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(
- vm.$el
- .querySelector('.header-main-content')
- .textContent.replace(/\s+/g, ' ')
- .trim(),
- ).toContain('passed Job #4757 created 3 weeks ago by Root');
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(
+ vm.$el
+ .querySelector('.header-main-content')
+ .textContent.replace(/\s+/g, ' ')
+ .trim(),
+ ).toContain('passed Job #4757 created 3 weeks ago by Root');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
@@ -142,9 +134,8 @@ describe('Job App ', () => {
describe('stuck block', () => {
describe('without active runners availabl', () => {
it('renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -158,23 +149,23 @@ describe('Job App ', () => {
online: false,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).not.toBeNull();
+ expect(
+ vm.$el.querySelector('.js-job-stuck .js-stuck-no-active-runner'),
+ ).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when available runners can not run specified tag', () => {
it('renders tags in stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -187,27 +178,21 @@ describe('Job App ', () => {
available: false,
online: false,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when runners are offline and build has tags', () => {
it('renders message about job being stuck because of no runners with the specified tags', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'pending',
icon: 'status_pending',
@@ -220,48 +205,35 @@ describe('Job App ', () => {
available: true,
online: true,
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
- expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck').textContent).toContain(job.tags[0]);
+ expect(vm.$el.querySelector('.js-job-stuck .js-stuck-with-tags')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
it('does not renders stuck block when there are no runners', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
runners: { available: true },
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-stuck')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('unmet prerequisites block', () => {
it('renders unmet prerequisites block when there is an unmet prerequisites failure', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
status: {
group: 'failed',
icon: 'status_failed',
@@ -281,104 +253,81 @@ describe('Job App ', () => {
available: true,
},
tags: [],
- }),
- );
- vm = mountComponentWithStore(Component, { props, store });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-failed')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('environments block', () => {
it('renders environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
deployment_status: {
environment: {
environment_path: '/path',
name: 'foo',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render environment block when job has environment', done => {
- mock.onGet(props.endpoint).replyOnce(200, job);
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
- done();
- }, 0);
+ setupAndMount()
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-environment')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('erased block', () => {
it('renders erased block when `erased` is true', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
erased_by: {
username: 'root',
web_url: 'gitlab.com/root',
},
erased_at: '2016-11-07T11:11:16.525Z',
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render erased block when `erased` is false', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { erased_at: null }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({
+ jobData: {
+ erased_at: null,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-erased-block')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('empty states block', () => {
it('renders empty state when job does not have trace and is not running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'pending',
@@ -398,25 +347,18 @@ describe('Job App ', () => {
path: '/path',
},
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job does not have trace but it is running', done => {
- mock.onGet(props.endpoint).replyOnce(
- 200,
- Object.assign({}, job, {
+ setupAndMount({
+ jobData: {
has_trace: false,
status: {
group: 'running',
@@ -425,34 +367,23 @@ describe('Job App ', () => {
text: 'running',
details_path: 'path',
},
- }),
- );
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
});
it('does not render empty state when job has trace but it is not running', done => {
- mock.onGet(props.endpoint).replyOnce(200, Object.assign({}, job, { has_trace: true }));
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
-
- done();
- }, 0);
+ setupAndMount({ jobData: { has_trace: true } })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull();
+ })
+ .then(done)
+ .catch(done.fail);
+ done();
});
it('displays remaining time for a delayed job', done => {
@@ -460,120 +391,114 @@ describe('Job App ', () => {
spyOn(Date, 'now').and.callFake(
() => new Date(delayedJobFixture.scheduled_at).getTime() - oneHourInMilliseconds,
);
- mock.onGet(props.endpoint).replyOnce(200, { ...delayedJobFixture });
+ setupAndMount({ jobData: delayedJobFixture })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- store.subscribeAction(action => {
- if (action.type !== 'receiveJobSuccess') {
- return;
- }
+ const title = vm.$el.querySelector('.js-job-empty-state-title');
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull();
-
- const title = vm.$el.querySelector('.js-job-empty-state-title');
+ expect(title).toContainText('01:00:00');
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
- expect(title).toContainText('01:00:00');
- done();
- })
- .catch(done.fail);
- });
+ describe('sidebar', () => {
+ it('has no blank blocks', done => {
+ setupAndMount({
+ jobData: {
+ duration: null,
+ finished_at: null,
+ erased_at: null,
+ queued: null,
+ runner: null,
+ coverage: null,
+ tags: [],
+ cancel_path: null,
+ },
+ })
+ .then(() => {
+ vm.$el.querySelectorAll('.blocks-container > *').forEach(block => {
+ expect(block.textContent.trim()).not.toBe('');
+ });
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, Object.assign({}, job, { archived: true }), {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({ jobData: { archived: true } })
+ .then(done)
+ .catch(done.fail);
});
- it('renders warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
- done();
- }, 0);
+ it('renders warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).not.toBeNull();
});
});
describe('non-archived job', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount()
+ .then(done)
+ .catch(done.fail);
});
- it('does not warning about job being archived', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
- done();
- }, 0);
+ it('does not warning about job being archived', () => {
+ expect(vm.$el.querySelector('.js-archived-job ')).toBeNull();
});
});
describe('trace output', () => {
- beforeEach(() => {
- mock.onGet(props.endpoint).reply(200, job, {});
- });
-
describe('with append flag', () => {
it('appends the log content to the existing one', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>More<span>',
- status: 'running',
- state: 'newstate',
- append: true,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
-
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>More<span>',
+ status: 'running',
+ state: 'newstate',
+ append: true,
+ complete: true,
+ },
+ })
+ .then(() => {
+ vm.$store.state.trace = 'Update';
+
+ return vm.$nextTick();
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Update');
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('without append flag', () => {
it('replaces the trace', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Different<span>',
- status: 'running',
- append: false,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
- vm.$store.state.trace = 'Update';
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
- 'Update',
- );
+ setupAndMount({
+ traceData: {
+ html: '<span>Different<span>',
+ status: 'running',
+ append: false,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).not.toContain(
+ 'Update',
+ );
- expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain('Different');
- done();
- }, 0);
+ expect(vm.$el.querySelector('.js-build-trace').textContent.trim()).toContain(
+ 'Different',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
@@ -589,83 +514,76 @@ describe('Job App ', () => {
complete: true,
});
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
describe('when size is equal than total', () => {
it('does not show the truncated information', done => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 100,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
- '50 bytes',
- );
- done();
- }, 0);
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 100,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(() => {
+ expect(vm.$el.querySelector('.js-truncated-info').textContent.trim()).not.toContain(
+ '50 bytes',
+ );
+ })
+ .then(done)
+ .catch(done.fail);
});
});
});
describe('trace controls', () => {
- beforeEach(() => {
- mock.onGet(`${props.pagePath}/trace.json`).reply(200, {
- html: '<span>Update</span>',
- status: 'success',
- append: false,
- size: 50,
- total: 100,
- complete: true,
- });
-
- vm = mountComponentWithStore(Component, {
- props,
- store,
- });
+ beforeEach(done => {
+ setupAndMount({
+ traceData: {
+ html: '<span>Update</span>',
+ status: 'success',
+ append: false,
+ size: 50,
+ total: 100,
+ complete: true,
+ },
+ })
+ .then(done)
+ .catch(done.fail);
});
- it('should render scroll buttons', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
- expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
- done();
- }, 0);
+ it('should render scroll buttons', () => {
+ expect(vm.$el.querySelector('.js-scroll-top')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-scroll-bottom')).not.toBeNull();
});
- it('should render link to raw ouput', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to raw ouput', () => {
+ expect(vm.$el.querySelector('.js-raw-link-controller')).not.toBeNull();
});
- it('should render link to erase job', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
- done();
- }, 0);
+ it('should render link to erase job', () => {
+ expect(vm.$el.querySelector('.js-erase-link')).not.toBeNull();
});
});
});
diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js
index 7e2ec2ec3f7..24bb6b9a48b 100644
--- a/spec/javascripts/jobs/components/job_log_spec.js
+++ b/spec/javascripts/jobs/components/job_log_spec.js
@@ -11,7 +11,7 @@ describe('Job Log', () => {
let vm;
const trace =
- 'Running with gitlab-runner 11.1.0 (081978aa)<br> on docker-auto-scale-com d5ae8d25<br>Using Docker executor with image dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.4.4-golang-1.9-git-2.18-chrome-67.0-node-8.x-yarn-1.2-postgresql-9.6-graphicsmagick-1.3.29 ...<br>';
+ '<span>Running with gitlab-runner 12.1.0 (de7731dd)<br/></span><span> on docker-auto-scale-com d5ae8d25<br/></span><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1565502765" data-section="prepare-executor" role="button"></div><span class="section js-section-header section-header js-s-prepare-executor">Using Docker executor with image ruby:2.6 ...<br/></span>';
beforeEach(() => {
store = createStore();
@@ -32,7 +32,7 @@ describe('Job Log', () => {
});
expect(vm.$el.querySelector('code').textContent).toContain(
- 'Running with gitlab-runner 11.1.0 (081978aa)',
+ 'Running with gitlab-runner 12.1.0 (de7731dd)',
);
});
@@ -98,5 +98,25 @@ describe('Job Log', () => {
.then(done)
.catch(done.fail);
});
+
+ it('toggles hidden class to the sibilings rows when header section is clicked', done => {
+ vm.$nextTick()
+ .then(() => {
+ const { section } = vm.$el.querySelector('.js-section-header').dataset;
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(true);
+ });
+
+ vm.$el.querySelector('.js-section-header').click();
+
+ vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => {
+ expect(el.classList.contains('hidden')).toEqual(false);
+ });
+ })
+ .then(done)
+ .catch(done.fail);
+ });
});
});
diff --git a/spec/javascripts/jobs/components/manual_variables_form_spec.js b/spec/javascripts/jobs/components/manual_variables_form_spec.js
new file mode 100644
index 00000000000..093aa905185
--- /dev/null
+++ b/spec/javascripts/jobs/components/manual_variables_form_spec.js
@@ -0,0 +1,88 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlButton } from '@gitlab/ui';
+import Form from '~/jobs/components/manual_variables_form.vue';
+
+describe('Manual Variables Form', () => {
+ let wrapper;
+ const requiredProps = {
+ action: {
+ path: '/play',
+ method: 'post',
+ button_title: 'Trigger this manual action',
+ },
+ variablesSettingsUrl: '/settings',
+ };
+
+ const factory = (props = {}) => {
+ wrapper = shallowMount(Form, {
+ propsData: props,
+ });
+ };
+
+ beforeEach(() => {
+ factory(requiredProps);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders empty form with correct placeholders', () => {
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('placeholder')).toBe('Input variable key');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('placeholder')).toBe(
+ 'Input variable value',
+ );
+ });
+
+ it('renders help text with provided link', () => {
+ expect(wrapper.find('p').text()).toBe(
+ 'Specify variable values to be used in this run. The values specified in CI/CD settings will be used as default',
+ );
+
+ expect(wrapper.find('a').attributes('href')).toBe(requiredProps.variablesSettingsUrl);
+ });
+
+ describe('when adding a new variable', () => {
+ it('creates a new variable when user types a new key and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputKey' }).setValue('new key'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].key).toBe('new key');
+ expect(wrapper.find({ ref: 'inputKey' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('creates a new variable when user types a new value and resets the form', done => {
+ wrapper.vm
+ .$nextTick()
+ .then(() => wrapper.find({ ref: 'inputSecretValue' }).setValue('new value'))
+ .then(() => {
+ expect(wrapper.vm.variables.length).toBe(1);
+ expect(wrapper.vm.variables[0].secret_value).toBe('new value');
+ expect(wrapper.find({ ref: 'inputSecretValue' }).attributes('value')).toBe(undefined);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
+ describe('when deleting a variable', () => {
+ it('removes the variable row', () => {
+ wrapper.vm.variables = [
+ {
+ key: 'new key',
+ secret_value: 'value',
+ id: '1',
+ },
+ ];
+
+ wrapper.find(GlButton).vm.$emit('click');
+
+ expect(wrapper.vm.variables.length).toBe(0);
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index d3a76f33679..57f99a09002 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -1,9 +1,9 @@
import { shallowMount } from '@vue/test-utils';
+import { createStore } from '~/monitoring/stores';
import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
import Area from '~/monitoring/components/charts/area.vue';
-import { createStore } from '~/monitoring/stores';
import * as types from '~/monitoring/stores/mutation_types';
import { TEST_HOST } from 'spec/test_constants';
import MonitoringMock, { deploymentData } from '../mock_data';
@@ -17,10 +17,10 @@ describe('Area component', () => {
let mockGraphData;
let areaChart;
let spriteSpy;
+ let store;
beforeEach(() => {
- const store = createStore();
-
+ store = createStore();
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
@@ -36,6 +36,7 @@ describe('Area component', () => {
slots: {
default: mockWidgets,
},
+ store,
});
spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
diff --git a/spec/javascripts/monitoring/charts/empty_chart_spec.js b/spec/javascripts/monitoring/charts/empty_chart_spec.js
new file mode 100644
index 00000000000..bbfca27dc5a
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/empty_chart_spec.js
@@ -0,0 +1,29 @@
+import { shallowMount } from '@vue/test-utils';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+
+describe('Empty Chart component', () => {
+ let emptyChart;
+ const graphTitle = 'Memory Usage';
+
+ beforeEach(() => {
+ emptyChart = shallowMount(EmptyChart, {
+ propsData: {
+ graphTitle,
+ },
+ });
+ });
+
+ afterEach(() => {
+ emptyChart.destroy();
+ });
+
+ it('render the chart title', () => {
+ expect(emptyChart.find({ ref: 'graphTitle' }).text()).toBe(graphTitle);
+ });
+
+ describe('Computed props', () => {
+ it('sets the height for the svg container', () => {
+ expect(emptyChart.vm.svgContainerStyle.height).toBe('300px');
+ });
+ });
+});
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/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
new file mode 100644
index 00000000000..f3ec7520c6f
--- /dev/null
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -0,0 +1,588 @@
+import Vue from 'vue';
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import { GlToast } from '@gitlab/ui';
+import MockAdapter from 'axios-mock-adapter';
+import Dashboard from '~/monitoring/components/dashboard.vue';
+import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
+import * as types from '~/monitoring/stores/mutation_types';
+import { createStore } from '~/monitoring/stores';
+import axios from '~/lib/utils/axios_utils';
+import MonitoringMock, {
+ metricsGroupsAPIResponse,
+ mockApiEndpoint,
+ environmentData,
+ singleGroupResponse,
+ dashboardGitResponse,
+} from '../mock_data';
+
+const localVue = createLocalVue();
+const propsData = {
+ hasMetrics: false,
+ documentationPath: '/path/to/docs',
+ settingsPath: '/path/to/settings',
+ clustersPath: '/path/to/clusters',
+ tagsPath: '/path/to/tags',
+ projectPath: '/path/to/project',
+ metricsEndpoint: mockApiEndpoint,
+ deploymentsEndpoint: null,
+ emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
+ emptyLoadingSvgPath: '/path/to/loading.svg',
+ emptyNoDataSvgPath: '/path/to/no-data.svg',
+ emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
+ environmentsEndpoint: '/root/hello-prometheus/environments/35',
+ currentEnvironmentName: 'production',
+ customMetricsAvailable: false,
+ customMetricsPath: '',
+ validateQueryPath: '',
+};
+
+export default propsData;
+
+describe('Dashboard', () => {
+ let DashboardComponent;
+ let mock;
+ let store;
+ let component;
+ let mockGraphData;
+
+ beforeEach(() => {
+ setFixtures(`
+ <div class="prometheus-graphs"></div>
+ <div class="layout-page"></div>
+ `);
+
+ window.gon = {
+ ...window.gon,
+ ee: false,
+ };
+
+ store = createStore();
+ mock = new MockAdapter(axios);
+ DashboardComponent = Vue.extend(Dashboard);
+ });
+
+ afterEach(() => {
+ if (component) {
+ component.$destroy();
+ }
+ mock.restore();
+ });
+
+ describe('no metrics are available yet', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData },
+ store,
+ });
+ });
+
+ it('shows a getting started empty state when no metrics are present', () => {
+ expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
+ expect(component.emptyState).toEqual('gettingStarted');
+ });
+
+ it('shows the environment selector', () => {
+ expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
+ });
+
+ describe('no data found', () => {
+ it('shows the environment selector dropdown', () => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, showEmptyState: true },
+ store,
+ });
+
+ expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
+ });
+ });
+
+ describe('requests information to the server', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+ });
+
+ it('shows up a loading state', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
+
+ Vue.nextTick(() => {
+ expect(component.emptyState).toEqual('loading');
+ done();
+ });
+ });
+
+ it('hides the legend when showLegend is false', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showLegend: false,
+ },
+ store,
+ });
+
+ setTimeout(() => {
+ expect(component.showEmptyState).toEqual(false);
+ expect(component.$el.querySelector('.legend-group')).toEqual(null);
+ expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
+ done();
+ });
+ });
+
+ it('hides the group panels when showPanels is false', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ setTimeout(() => {
+ expect(component.showEmptyState).toEqual(false);
+ expect(component.$el.querySelector('.prometheus-panel')).toEqual(null);
+ expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
+ done();
+ });
+ });
+
+ it('renders the environments dropdown with a number of environments', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
+
+ expect(component.environments.length).toEqual(environmentData.length);
+ expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
+
+ Array.from(dropdownMenuEnvironments).forEach((value, index) => {
+ if (environmentData[index].metrics_path) {
+ expect(value).toHaveAttr('href', environmentData[index].metrics_path);
+ }
+ });
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('hides the environments dropdown list when there is no environments', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []);
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ const dropdownMenuEnvironments = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item',
+ );
+
+ expect(dropdownMenuEnvironments.length).toEqual(0);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('renders the environments dropdown with a single active element', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ Vue.nextTick()
+ .then(() => {
+ const dropdownItems = component.$el.querySelectorAll(
+ '.js-environments-dropdown .dropdown-item.active',
+ );
+
+ expect(dropdownItems.length).toEqual(1);
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('hides the dropdown', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ environmentsEndpoint: '',
+ },
+ store,
+ });
+
+ Vue.nextTick(() => {
+ const dropdownIsActiveElement = component.$el.querySelectorAll('.environments');
+
+ expect(dropdownIsActiveElement.length).toEqual(0);
+ done();
+ });
+ });
+
+ it('renders the time window dropdown with a set of options', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+ const numberOfTimeWindows = Object.keys(timeWindows).length;
+
+ setTimeout(() => {
+ const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
+ const timeWindowDropdownEls = component.$el.querySelectorAll(
+ '.js-time-window-dropdown .dropdown-item',
+ );
+
+ expect(timeWindowDropdown).not.toBeNull();
+ expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
+
+ done();
+ });
+ });
+
+ it('fetches the metrics data with proper time window', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ spyOn(component.$store, 'dispatch').and.stub();
+ const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff').and.callThrough();
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+
+ component.$mount();
+
+ Vue.nextTick()
+ .then(() => {
+ expect(component.$store.dispatch).toHaveBeenCalled();
+ expect(getTimeDiffSpy).toHaveBeenCalled();
+
+ done();
+ })
+ .catch(done.fail);
+ });
+
+ it('shows a specific time window selected from the url params', done => {
+ const start = 1564439536;
+ const end = 1564441336;
+ spyOnDependency(Dashboard, 'getTimeDiff').and.returnValue({
+ start,
+ end,
+ });
+ spyOnDependency(Dashboard, 'getParameterValues').and.callFake(param => {
+ if (param === 'start') return [start];
+ if (param === 'end') return [end];
+ return [];
+ });
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
+
+ setTimeout(() => {
+ const selectedTimeWindow = component.$el.querySelector('.js-time-window-dropdown .active');
+
+ expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes');
+ done();
+ });
+ });
+
+ it('defaults to the eight hours time window for non valid url parameters', done => {
+ spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
+ '<script>alert("XSS")</script>',
+ ]);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: { ...propsData, hasMetrics: true },
+ store,
+ });
+
+ Vue.nextTick(() => {
+ expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours);
+
+ done();
+ });
+ });
+ });
+
+ describe('link to chart', () => {
+ let wrapper;
+ const currentDashboard = 'TEST_DASHBOARD';
+ localVue.use(GlToast);
+ const link = () => wrapper.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ beforeEach(done => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ wrapper = shallowMount(DashboardComponent, {
+ localVue,
+ sync: false,
+ attachToDocument: true,
+ propsData: { ...propsData, hasMetrics: true, currentDashboard },
+ store,
+ });
+
+ setTimeout(done);
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('adds a copy button to the dropdown', () => {
+ expect(link().text()).toContain('Generate link to chart');
+ });
+
+ it('contains a link to the dashboard', () => {
+ expect(clipboardText()).toContain(`dashboard=${currentDashboard}`);
+ expect(clipboardText()).toContain(`group=`);
+ expect(clipboardText()).toContain(`title=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ });
+
+ it('undefined parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: undefined });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('null parameter is stripped', done => {
+ wrapper.setProps({ currentDashboard: null });
+
+ wrapper.vm.$nextTick(() => {
+ expect(clipboardText()).not.toContain(`dashboard=`);
+ expect(clipboardText()).toContain(`y_label=`);
+ done();
+ });
+ });
+
+ it('creates a toast when clicked', () => {
+ spyOn(wrapper.vm.$toast, 'show').and.stub();
+
+ link().vm.$emit('click');
+
+ expect(wrapper.vm.$toast.show).toHaveBeenCalled();
+ });
+ });
+
+ describe('when the window resizes', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
+ it('sets elWidth to page width when the sidebar is resized', done => {
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ expect(component.elWidth).toEqual(0);
+
+ const pageLayoutEl = document.querySelector('.layout-page');
+ pageLayoutEl.classList.add('page-with-icon-sidebar');
+
+ Vue.nextTick()
+ .then(() => {
+ jasmine.clock().tick(1000);
+ return Vue.nextTick();
+ })
+ .then(() => {
+ expect(component.elWidth).toEqual(pageLayoutEl.clientWidth);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('external dashboard link', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ showTimeWindowDropdown: false,
+ externalDashboardUrl: '/mockUrl',
+ },
+ store,
+ });
+ });
+
+ it('shows the link', done => {
+ setTimeout(() => {
+ expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain(
+ 'View full dashboard',
+ );
+ done();
+ });
+ });
+ });
+
+ describe('Dashboard dropdown', () => {
+ beforeEach(() => {
+ mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
+
+ component = new DashboardComponent({
+ el: document.querySelector('.prometheus-graphs'),
+ propsData: {
+ ...propsData,
+ hasMetrics: true,
+ showPanels: false,
+ },
+ store,
+ });
+
+ component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
+ prometheusEndpoint: false,
+ multipleDashboardsEnabled: true,
+ });
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
+ environmentData,
+ );
+
+ component.$store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ singleGroupResponse,
+ );
+
+ component.$store.commit(
+ `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
+ dashboardGitResponse,
+ );
+ });
+
+ it('shows the dashboard dropdown', done => {
+ setTimeout(() => {
+ const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown');
+
+ expect(dashboardDropdown).not.toEqual(null);
+ done();
+ });
+ });
+ });
+
+ describe('when downloading metrics data as CSV', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ propsData: {
+ ...propsData,
+ },
+ store,
+ });
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ MonitoringMock.data,
+ );
+ [mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
+ });
+
+ describe('csvText', () => {
+ it('converts metrics 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(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadCsv', () => {
+ it('produces a link with a Blob', () => {
+ expect(component.downloadCsv(mockGraphData)).toContain(`blob:`);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
deleted file mode 100644
index d3e10194d92..00000000000
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ /dev/null
@@ -1,475 +0,0 @@
-import Vue from 'vue';
-import MockAdapter from 'axios-mock-adapter';
-import Dashboard from '~/monitoring/components/dashboard.vue';
-import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
-import * as types from '~/monitoring/stores/mutation_types';
-import { createStore } from '~/monitoring/stores';
-import axios from '~/lib/utils/axios_utils';
-import {
- metricsGroupsAPIResponse,
- mockApiEndpoint,
- environmentData,
- singleGroupResponse,
- dashboardGitResponse,
-} from './mock_data';
-
-const propsData = {
- hasMetrics: false,
- documentationPath: '/path/to/docs',
- settingsPath: '/path/to/settings',
- clustersPath: '/path/to/clusters',
- tagsPath: '/path/to/tags',
- projectPath: '/path/to/project',
- metricsEndpoint: mockApiEndpoint,
- deploymentsEndpoint: null,
- emptyGettingStartedSvgPath: '/path/to/getting-started.svg',
- emptyLoadingSvgPath: '/path/to/loading.svg',
- emptyNoDataSvgPath: '/path/to/no-data.svg',
- emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg',
- environmentsEndpoint: '/root/hello-prometheus/environments/35',
- currentEnvironmentName: 'production',
- customMetricsAvailable: false,
- customMetricsPath: '',
- validateQueryPath: '',
-};
-
-export default propsData;
-
-describe('Dashboard', () => {
- let DashboardComponent;
- let mock;
- let store;
- let component;
-
- beforeEach(() => {
- setFixtures(`
- <div class="prometheus-graphs"></div>
- <div class="layout-page"></div>
- `);
-
- window.gon = {
- ...window.gon,
- ee: false,
- };
-
- store = createStore();
- mock = new MockAdapter(axios);
- DashboardComponent = Vue.extend(Dashboard);
- });
-
- afterEach(() => {
- component.$destroy();
- mock.restore();
- });
-
- describe('no metrics are available yet', () => {
- beforeEach(() => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData },
- store,
- });
- });
-
- it('shows a getting started empty state when no metrics are present', () => {
- expect(component.$el.querySelector('.prometheus-graphs')).toBe(null);
- expect(component.emptyState).toEqual('gettingStarted');
- });
-
- it('shows the environment selector', () => {
- expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
- });
- });
-
- describe('no data found', () => {
- it('shows the environment selector dropdown', () => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, showEmptyState: true },
- store,
- });
-
- expect(component.$el.querySelector('.js-environments-dropdown')).toBeTruthy();
- });
- });
-
- describe('requests information to the server', () => {
- beforeEach(() => {
- mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
- });
-
- it('shows up a loading state', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true },
- store,
- });
-
- Vue.nextTick(() => {
- expect(component.emptyState).toEqual('loading');
- done();
- });
- });
-
- it('hides the legend when showLegend is false', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showLegend: false,
- },
- store,
- });
-
- setTimeout(() => {
- expect(component.showEmptyState).toEqual(false);
- expect(component.$el.querySelector('.legend-group')).toEqual(null);
- expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
- done();
- });
- });
-
- it('hides the group panels when showPanels is false', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- setTimeout(() => {
- expect(component.showEmptyState).toEqual(false);
- expect(component.$el.querySelector('.prometheus-panel')).toEqual(null);
- expect(component.$el.querySelector('.prometheus-graph-group')).toBeTruthy();
- done();
- });
- });
-
- it('renders the environments dropdown with a number of environments', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
- environmentData,
- );
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
- singleGroupResponse,
- );
-
- Vue.nextTick()
- .then(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item',
- );
-
- expect(component.environments.length).toEqual(environmentData.length);
- expect(dropdownMenuEnvironments.length).toEqual(component.environments.length);
-
- Array.from(dropdownMenuEnvironments).forEach((value, index) => {
- if (environmentData[index].metrics_path) {
- expect(value).toHaveAttr('href', environmentData[index].metrics_path);
- }
- });
-
- done();
- })
- .catch(done.fail);
- });
-
- it('hides the environments dropdown list when there is no environments', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- component.$store.commit(`monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`, []);
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
- singleGroupResponse,
- );
-
- Vue.nextTick()
- .then(() => {
- const dropdownMenuEnvironments = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item',
- );
-
- expect(dropdownMenuEnvironments.length).toEqual(0);
- done();
- })
- .catch(done.fail);
- });
-
- it('renders the environments dropdown with a single active element', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
- environmentData,
- );
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
- singleGroupResponse,
- );
-
- Vue.nextTick()
- .then(() => {
- const dropdownItems = component.$el.querySelectorAll(
- '.js-environments-dropdown .dropdown-item.active',
- );
-
- expect(dropdownItems.length).toEqual(1);
- done();
- })
- .catch(done.fail);
- });
-
- it('hides the dropdown', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- environmentsEndpoint: '',
- },
- store,
- });
-
- Vue.nextTick(() => {
- const dropdownIsActiveElement = component.$el.querySelectorAll('.environments');
-
- expect(dropdownIsActiveElement.length).toEqual(0);
- done();
- });
- });
-
- it('renders the time window dropdown with a set of options', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
- const numberOfTimeWindows = Object.keys(timeWindows).length;
-
- setTimeout(() => {
- const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
- const timeWindowDropdownEls = component.$el.querySelectorAll(
- '.js-time-window-dropdown .dropdown-item',
- );
-
- expect(timeWindowDropdown).not.toBeNull();
- expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
-
- done();
- });
- });
-
- it('fetches the metrics data with proper time window', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- spyOn(component.$store, 'dispatch').and.stub();
- const getTimeDiffSpy = spyOnDependency(Dashboard, 'getTimeDiff');
-
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
- environmentData,
- );
-
- component.$mount();
-
- Vue.nextTick()
- .then(() => {
- expect(component.$store.dispatch).toHaveBeenCalled();
- expect(getTimeDiffSpy).toHaveBeenCalledWith(component.selectedTimeWindow);
-
- done();
- })
- .catch(done.fail);
- });
-
- it('shows a specific time window selected from the url params', done => {
- spyOnDependency(Dashboard, 'getParameterValues').and.returnValue(['thirtyMinutes']);
-
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true },
- store,
- });
-
- setTimeout(() => {
- const selectedTimeWindow = component.$el.querySelector('.js-time-window-dropdown .active');
-
- expect(selectedTimeWindow.textContent.trim()).toEqual('30 minutes');
- done();
- });
- });
-
- it('defaults to the eight hours time window for non valid url parameters', done => {
- spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
- '<script>alert("XSS")</script>',
- ]);
-
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: { ...propsData, hasMetrics: true },
- store,
- });
-
- Vue.nextTick(() => {
- expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours);
-
- done();
- });
- });
- });
-
- describe('when the window resizes', () => {
- beforeEach(() => {
- mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
- jasmine.clock().install();
- });
-
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('sets elWidth to page width when the sidebar is resized', done => {
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- expect(component.elWidth).toEqual(0);
-
- const pageLayoutEl = document.querySelector('.layout-page');
- pageLayoutEl.classList.add('page-with-icon-sidebar');
-
- Vue.nextTick()
- .then(() => {
- jasmine.clock().tick(1000);
- return Vue.nextTick();
- })
- .then(() => {
- expect(component.elWidth).toEqual(pageLayoutEl.clientWidth);
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('external dashboard link', () => {
- beforeEach(() => {
- mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
-
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- showTimeWindowDropdown: false,
- externalDashboardUrl: '/mockUrl',
- },
- store,
- });
- });
-
- it('shows the link', done => {
- setTimeout(() => {
- expect(component.$el.querySelector('.js-external-dashboard-link').innerText).toContain(
- 'View full dashboard',
- );
- done();
- });
- });
- });
-
- describe('Dashboard dropdown', () => {
- beforeEach(() => {
- mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
-
- component = new DashboardComponent({
- el: document.querySelector('.prometheus-graphs'),
- propsData: {
- ...propsData,
- hasMetrics: true,
- showPanels: false,
- },
- store,
- });
-
- component.$store.dispatch('monitoringDashboard/setFeatureFlags', {
- prometheusEndpoint: false,
- multipleDashboardsEnabled: true,
- });
-
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_ENVIRONMENTS_DATA_SUCCESS}`,
- environmentData,
- );
-
- component.$store.commit(
- `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
- singleGroupResponse,
- );
-
- component.$store.commit(
- `monitoringDashboard/${types.SET_ALL_DASHBOARDS}`,
- dashboardGitResponse,
- );
- });
-
- it('shows the dashboard dropdown', done => {
- setTimeout(() => {
- const dashboardDropdown = component.$el.querySelector('.js-dashboards-dropdown');
-
- expect(dashboardDropdown).not.toEqual(null);
- done();
- });
- });
- });
-});
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
new file mode 100644
index 00000000000..086be628093
--- /dev/null
+++ b/spec/javascripts/monitoring/panel_type_spec.js
@@ -0,0 +1,78 @@
+import { shallowMount } from '@vue/test-utils';
+import PanelType from '~/monitoring/components/panel_type.vue';
+import EmptyChart from '~/monitoring/components/charts/empty_chart.vue';
+import AreaChart from '~/monitoring/components/charts/area.vue';
+import { graphDataPrometheusQueryRange } from './mock_data';
+import { createStore } from '~/monitoring/stores';
+
+describe('Panel Type component', () => {
+ let store;
+ let panelType;
+ const dashboardWidth = 100;
+
+ describe('When no graphData is available', () => {
+ let glEmptyChart;
+ // Deep clone object before modifying
+ const graphDataNoResult = JSON.parse(JSON.stringify(graphDataPrometheusQueryRange));
+ graphDataNoResult.queries[0].result = [];
+
+ beforeEach(() => {
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: 'dashboard_link',
+ dashboardWidth,
+ graphData: graphDataNoResult,
+ },
+ });
+ });
+
+ afterEach(() => {
+ panelType.destroy();
+ });
+
+ describe('Empty Chart component', () => {
+ beforeEach(() => {
+ glEmptyChart = panelType.find(EmptyChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glEmptyChart.isVueInstance()).toBe(true);
+ });
+
+ it('it receives a graph title', () => {
+ const props = glEmptyChart.props();
+
+ expect(props.graphTitle).toBe(panelType.vm.graphData.title);
+ });
+ });
+ });
+
+ describe('when Graph data is available', () => {
+ const exampleText = 'example_text';
+
+ beforeEach(() => {
+ store = createStore();
+ panelType = shallowMount(PanelType, {
+ propsData: {
+ clipboardText: exampleText,
+ dashboardWidth,
+ graphData: graphDataPrometheusQueryRange,
+ },
+ store,
+ });
+ });
+
+ describe('Area Chart panel type', () => {
+ it('is rendered', () => {
+ expect(panelType.find(AreaChart).exists()).toBe(true);
+ });
+
+ it('sets clipboard text on the dropdown', () => {
+ const link = () => panelType.find('.js-chart-link');
+ const clipboardText = () => link().element.dataset.clipboardText;
+
+ expect(clipboardText()).toBe(exampleText);
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/store/actions_spec.js b/spec/javascripts/monitoring/store/actions_spec.js
index 677455275de..955a39e03a5 100644
--- a/spec/javascripts/monitoring/store/actions_spec.js
+++ b/spec/javascripts/monitoring/store/actions_spec.js
@@ -313,8 +313,8 @@ describe('Monitoring store actions', () => {
it('commits prometheus query result', done => {
const commit = jasmine.createSpy();
const params = {
- start: '1557216349.469',
- end: '1557218149.469',
+ start: '2019-08-06T12:40:02.184Z',
+ end: '2019-08-06T20:40:02.184Z',
};
const metric = metricsDashboardResponse.dashboard.panel_groups[0].panels[0].metrics[0];
const state = storeState();
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index 5570d57b8b2..e22e8cdc03d 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -3,28 +3,38 @@ import { timeWindows } from '~/monitoring/constants';
import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
describe('getTimeDiff', () => {
+ function secondsBetween({ start, end }) {
+ return (new Date(end) - new Date(start)) / 1000;
+ }
+
+ function minutesBetween(timeRange) {
+ return secondsBetween(timeRange) / 60;
+ }
+
+ function hoursBetween(timeRange) {
+ return minutesBetween(timeRange) / 60;
+ }
+
it('defaults to an 8 hour (28800s) difference', () => {
const params = getTimeDiff();
- expect(params.end - params.start).toEqual(28800);
+ expect(hoursBetween(params)).toEqual(8);
});
it('accepts time window as an argument', () => {
- const params = getTimeDiff(timeWindows.thirtyMinutes);
+ const params = getTimeDiff('thirtyMinutes');
- expect(params.end - params.start).not.toEqual(28800);
+ expect(minutesBetween(params)).toEqual(30);
});
it('returns a value for every defined time window', () => {
const nonDefaultWindows = Object.keys(timeWindows).filter(window => window !== 'eightHours');
- nonDefaultWindows.forEach(window => {
- const params = getTimeDiff(timeWindows[window]);
- const diff = params.end - params.start;
+ nonDefaultWindows.forEach(timeWindow => {
+ const params = getTimeDiff(timeWindow);
- // Ensure we're not returning the default, 28800 (the # of seconds in 8 hrs)
- expect(diff).not.toEqual(28800);
- expect(typeof diff).toEqual('number');
+ // Ensure we're not returning the default
+ expect(hoursBetween(params)).not.toEqual(8);
});
});
});
diff --git a/spec/javascripts/notes/components/note_actions/reply_button_spec.js b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
index 11fb89808d9..003773d07ea 100644
--- a/spec/javascripts/notes/components/note_actions/reply_button_spec.js
+++ b/spec/javascripts/notes/components/note_actions/reply_button_spec.js
@@ -25,8 +25,7 @@ describe('ReplyButton', () => {
button.trigger('click');
- expect(wrapper.emitted()).toEqual({
- startReplying: [[]],
- });
+ expect(wrapper.emitted().startReplying).toBeTruthy();
+ expect(wrapper.emitted().startReplying.length).toBe(1);
});
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 1df5cf9ef68..5f81a168498 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -1,3 +1,5 @@
+// Copied to ee/spec/frontend/notes/mock_data.js
+
export const notesDataMock = {
discussionsPath: '/gitlab-org/gitlab-ce/issues/26/discussions.json',
lastFetchedAt: 1501862675,
diff --git a/spec/javascripts/notes/stores/actions_spec.js b/spec/javascripts/notes/stores/actions_spec.js
index c461c28a37b..e55aa0e965a 100644
--- a/spec/javascripts/notes/stores/actions_spec.js
+++ b/spec/javascripts/notes/stores/actions_spec.js
@@ -892,4 +892,31 @@ describe('Actions Notes Store', () => {
});
});
});
+
+ describe('filterDiscussion', () => {
+ const path = 'some-discussion-path';
+ const filter = 0;
+
+ beforeEach(() => {
+ dispatch.and.returnValue(new Promise(() => {}));
+ });
+
+ it('fetches discussions with filter and persistFilter false', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: false });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: false }],
+ ]);
+ });
+
+ it('fetches discussions with filter and persistFilter true', () => {
+ actions.filterDiscussion({ dispatch }, { path, filter, persistFilter: true });
+
+ expect(dispatch.calls.allArgs()).toEqual([
+ ['setLoadingState', true],
+ ['fetchDiscussions', { path, filter, persistFilter: true }],
+ ]);
+ });
+ });
});
diff --git a/spec/javascripts/notes/stores/getters_spec.js b/spec/javascripts/notes/stores/getters_spec.js
index c3ed079e33b..71dcba114a9 100644
--- a/spec/javascripts/notes/stores/getters_spec.js
+++ b/spec/javascripts/notes/stores/getters_spec.js
@@ -256,6 +256,54 @@ describe('Getters Notes Store', () => {
});
});
+ describe('previousUnresolvedDiscussionId', () => {
+ describe('with unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123', '456', '789'],
+ };
+
+ it('with bogus returns falsey', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('456');
+ });
+
+ [
+ { 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 1 unresolved discussion', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => ['123'],
+ };
+
+ it('with bogus returns id', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('bogus')).toBe('123');
+ });
+
+ it('with match, returns value', () => {
+ expect(getters.previousUnresolvedDiscussionId(state, localGetters)('123')).toEqual('123');
+ });
+ });
+
+ describe('with 0 unresolved discussions', () => {
+ const localGetters = {
+ unresolvedDiscussionsIdsOrdered: () => [],
+ };
+
+ it('returns undefined', () => {
+ expect(
+ getters.previousUnresolvedDiscussionId(state, localGetters)('bogus'),
+ ).toBeUndefined();
+ });
+ });
+ });
+
describe('firstUnresolvedDiscussionId', () => {
const localGetters = {
unresolvedDiscussionsIdsByDate: ['123', '456'],
diff --git a/spec/javascripts/pdf/page_spec.js b/spec/javascripts/pdf/page_spec.js
index 6dea570266b..efeb65acf87 100644
--- a/spec/javascripts/pdf/page_spec.js
+++ b/spec/javascripts/pdf/page_spec.js
@@ -17,7 +17,7 @@ describe('Page component', () => {
pdfjsLib.GlobalWorkerOptions.workerSrc = workerSrc;
pdfjsLib
.getDocument(testPDF)
- .then(pdf => pdf.getPage(1))
+ .promise.then(pdf => pdf.getPage(1))
.then(page => {
testPage = page;
})
@@ -31,7 +31,8 @@ describe('Page component', () => {
it('renders the page when mounting', done => {
const promise = Promise.resolve();
- spyOn(testPage, 'render').and.callFake(() => promise);
+ spyOn(testPage, 'render').and.returnValue({ promise });
+
vm = mountComponent(Component, {
page: testPage,
number: 1,
diff --git a/spec/javascripts/performance_bar/components/detailed_metric_spec.js b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
index 8a7aa057186..0486b5fa3db 100644
--- a/spec/javascripts/performance_bar/components/detailed_metric_spec.js
+++ b/spec/javascripts/performance_bar/components/detailed_metric_spec.js
@@ -44,7 +44,6 @@ describe('detailedMetric', () => {
},
metric: 'gitaly',
header: 'Gitaly calls',
- details: 'details',
keys: ['feature', 'request'],
});
});
@@ -79,8 +78,32 @@ describe('detailedMetric', () => {
});
});
- it('displays the metric name', () => {
+ it('displays the metric title', () => {
expect(vm.$el.innerText).toContain('gitaly');
});
+
+ describe('when using a custom metric title', () => {
+ beforeEach(() => {
+ vm = mountComponent(Vue.extend(detailedMetric), {
+ currentRequest: {
+ details: {
+ gitaly: {
+ duration: '123ms',
+ calls: '456',
+ details: requestDetails,
+ },
+ },
+ },
+ metric: 'gitaly',
+ title: 'custom',
+ header: 'Gitaly calls',
+ keys: ['feature', 'request'],
+ });
+ });
+
+ it('displays the custom title', () => {
+ expect(vm.$el.innerText).toContain('custom');
+ });
+ });
});
});
diff --git a/spec/javascripts/persistent_user_callout_spec.js b/spec/javascripts/persistent_user_callout_spec.js
index 2fdfff3db03..d15758be5d2 100644
--- a/spec/javascripts/persistent_user_callout_spec.js
+++ b/spec/javascripts/persistent_user_callout_spec.js
@@ -22,6 +22,24 @@ describe('PersistentUserCallout', () => {
return fixture;
}
+ function createDeferredLinkFixture() {
+ const fixture = document.createElement('div');
+ fixture.innerHTML = `
+ <div
+ class="container"
+ data-dismiss-endpoint="${dismissEndpoint}"
+ data-feature-id="${featureName}"
+ data-defer-links="true"
+ >
+ <button type="button" class="js-close"></button>
+ <a href="/somewhere-pleasant" target="_blank" class="deferred-link">A link</a>
+ <a href="/somewhere-else" target="_blank" class="normal-link">Another link</a>
+ </div>
+ `;
+
+ return fixture;
+ }
+
describe('dismiss', () => {
let button;
let mockAxios;
@@ -74,6 +92,75 @@ describe('PersistentUserCallout', () => {
});
});
+ describe('deferred links', () => {
+ let button;
+ let deferredLink;
+ let normalLink;
+ let mockAxios;
+ let persistentUserCallout;
+ let windowSpy;
+
+ beforeEach(() => {
+ const fixture = createDeferredLinkFixture();
+ const container = fixture.querySelector('.container');
+ button = fixture.querySelector('.js-close');
+ deferredLink = fixture.querySelector('.deferred-link');
+ normalLink = fixture.querySelector('.normal-link');
+ mockAxios = new MockAdapter(axios);
+ persistentUserCallout = new PersistentUserCallout(container);
+ spyOn(persistentUserCallout.container, 'remove');
+ windowSpy = spyOn(window, 'open').and.callFake(() => {});
+ });
+
+ afterEach(() => {
+ mockAxios.restore();
+ });
+
+ it('defers loading of a link until callout is dismissed', done => {
+ const { href, target } = deferredLink;
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ deferredLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).toHaveBeenCalledWith(href, target);
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ expect(mockAxios.history.post[0].data).toBe(
+ JSON.stringify({ feature_name: featureName }),
+ );
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not dismiss callout on non-deferred links', done => {
+ normalLink.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).not.toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('does not follow link when notification is closed', done => {
+ mockAxios.onPost(dismissEndpoint).replyOnce(200);
+
+ button.click();
+
+ setTimeoutPromise()
+ .then(() => {
+ expect(windowSpy).not.toHaveBeenCalled();
+ expect(persistentUserCallout.container.remove).toHaveBeenCalled();
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+ });
+
describe('factory', () => {
it('returns an instance of PersistentUserCallout with the provided container property', () => {
const fixture = createFixture();
diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js
index a7dcd532f4f..953a42b9d15 100644
--- a/spec/javascripts/pipelines/pipelines_actions_spec.js
+++ b/spec/javascripts/pipelines/pipelines_actions_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
-import eventHub from '~/pipelines/event_hub';
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
import PipelinesActions from '~/pipelines/components/pipelines_actions.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { TEST_HOST } from 'spec/test_constants';
@@ -7,9 +8,15 @@ import { TEST_HOST } from 'spec/test_constants';
describe('Pipelines Actions dropdown', () => {
const Component = Vue.extend(PipelinesActions);
let vm;
+ let mock;
afterEach(() => {
vm.$destroy();
+ mock.restore();
+ });
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
});
describe('manual actions', () => {
@@ -40,6 +47,22 @@ describe('Pipelines Actions dropdown', () => {
expect(dropdownItem).toBeDisabled();
});
+
+ describe('on click', () => {
+ it('makes a request and toggles the loading state', done => {
+ mock.onPost(actions.path).reply(200);
+
+ vm.$el.querySelector('.dropdown-menu li button').click();
+
+ expect(vm.isLoading).toEqual(true);
+
+ setTimeout(() => {
+ expect(vm.isLoading).toEqual(false);
+
+ done();
+ });
+ });
+ });
});
describe('scheduled jobs', () => {
@@ -71,26 +94,27 @@ describe('Pipelines Actions dropdown', () => {
.catch(done.fail);
});
- it('emits postAction event after confirming', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('makes post request after confirming', done => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => true);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path);
+ setTimeout(() => {
+ expect(mock.history.post.length).toBe(1);
+ done();
+ });
});
- it('does not emit postAction event if confirmation is cancelled', () => {
- const emitSpy = jasmine.createSpy('emit');
- eventHub.$on('postAction', emitSpy);
+ it('does not make post request if confirmation is cancelled', () => {
+ mock.onPost(scheduledJobAction.path).reply(200);
spyOn(window, 'confirm').and.callFake(() => false);
findDropdownItem(scheduledJobAction).click();
expect(window.confirm).toHaveBeenCalled();
- expect(emitSpy).not.toHaveBeenCalled();
+ expect(mock.history.post.length).toBe(0);
});
it('displays the remaining time in the dropdown', () => {
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 7b9b8d2b039..e7675669f7a 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -106,7 +106,7 @@ describe('Registry List', () => {
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
done();
});
});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
index 31ac970378e..9c7439206ef 100644
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ b/spec/javascripts/registry/components/table_registry_spec.js
@@ -1,61 +1,159 @@
import Vue from 'vue';
import tableRegistry from '~/registry/components/table_registry.vue';
import store from '~/registry/stores';
+import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
import { repoPropsData } from '../mock_data';
-const [firstImage] = repoPropsData.list;
+const [firstImage, secondImage] = repoPropsData.list;
describe('table registry', () => {
let vm;
- let Component;
+ const Component = Vue.extend(tableRegistry);
+ const bulkDeletePath = 'path';
const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
+ const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
+ const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
+ const findAllRowCheckboxes = () =>
+ Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
+ const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
- beforeEach(() => {
- Component = Vue.extend(tableRegistry);
- vm = new Component({
+ const createComponent = () => {
+ vm = mountComponentWithStore(Component, {
store,
- propsData: {
+ props: {
repo: repoPropsData,
},
- }).$mount();
+ });
+ };
+
+ const selectAllCheckboxes = () => vm.selectAll();
+ const deselectAllCheckboxes = () => vm.deselectAll();
+
+ beforeEach(() => {
+ createComponent();
});
afterEach(() => {
vm.$destroy();
});
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ describe('rendering', () => {
+ it('should render a table with the registry list', () => {
+ expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
+ });
+
+ it('should render registry tag', () => {
+ const textRendered = vm.$el
+ .querySelector('.table tbody tr')
+ .textContent.trim()
+ // replace additional whitespace characters (e.g. new lines) with a single empty space
+ .replace(/\s\s+/g, ' ');
+
+ expect(textRendered).toContain(repoPropsData.list[0].tag);
+ expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
+ expect(textRendered).toContain(repoPropsData.list[0].layers);
+ expect(textRendered).toContain(repoPropsData.list[0].size);
+ });
});
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- .replace(/\s\s+/g, ' ');
+ describe('multi select', () => {
+ it('should support multiselect and selecting a row should enable delete button', done => {
+ findSelectAllCheckbox().click();
+ selectAllCheckboxes();
+
+ expect(findSelectAllCheckbox().checked).toBe(true);
+
+ Vue.nextTick(() => {
+ expect(findDeleteBtn().disabled).toBe(false);
+ done();
+ });
+ });
+
+ it('selecting all checkbox should select all rows and enable delete button', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(repoPropsData.list.length);
+ done();
+ });
+ });
+
+ it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
+ selectAllCheckboxes();
+ deselectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
+
+ expect(checkedValues.length).toBe(0);
+ done();
+ });
+ });
+
+ it('should delete multiple items when multiple items are selected', done => {
+ selectAllCheckboxes();
+
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0, 1]);
+ expect(findDeleteBtn().disabled).toBe(false);
+
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
+
+ Vue.nextTick(() => {
+ const modal = confirmationModal();
+ confirmationModal('.btn-danger').click();
+
+ expect(modal).toExist();
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag, secondImage.tag],
+ });
+ done();
+ });
+ });
+ });
+ });
});
describe('delete registry', () => {
- it('should be possible to delete a registry', () => {
- expect(findDeleteBtn()).toBeDefined();
+ beforeEach(() => {
+ vm.itemsToBeDeleted = [0];
});
- it('should call deleteItem and reset itemToBeDeleted when confirming deletion', done => {
- findDeleteBtn().click();
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
+ it('should be possible to delete a registry', done => {
+ Vue.nextTick(() => {
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn()).toBeDefined();
+ expect(findDeleteBtn().disabled).toBe(false);
+ expect(findDeleteBtnRow()).toBeDefined();
+ done();
+ });
+ });
+ it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
Vue.nextTick(() => {
- document.querySelector(`#${vm.modalId} .btn-danger`).click();
+ expect(vm.itemsToBeDeleted).toEqual([0]);
+ expect(findDeleteBtn().disabled).toBe(false);
+ findDeleteBtn().click();
+ spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
- expect(vm.deleteItem).toHaveBeenCalledWith(firstImage);
- expect(vm.itemToBeDeleted).toBeNull();
- done();
+ Vue.nextTick(() => {
+ confirmationModal('.btn-danger').click();
+
+ expect(vm.itemsToBeDeleted).toEqual([]);
+ expect(vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag],
+ });
+ done();
+ });
});
});
});
@@ -65,4 +163,27 @@ describe('table registry', () => {
expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
});
});
+
+ describe('modal content', () => {
+ it('should show the singular title and image name when deleting a single image', done => {
+ findDeleteBtnRow().click();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove image');
+ expect(vm.modalDescription).toContain(firstImage.tag);
+ done();
+ });
+ });
+
+ it('should show the plural title and image count when deleting more than one image', done => {
+ selectAllCheckboxes();
+ vm.setModalDescription();
+
+ Vue.nextTick(() => {
+ expect(vm.modalTitle).toBe('Remove images');
+ expect(vm.modalDescription).toContain('<b>2</b> images');
+ done();
+ });
+ });
+ });
});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/javascripts/registry/mock_data.js
index 22db203e77f..130ab298e89 100644
--- a/spec/javascripts/registry/mock_data.js
+++ b/spec/javascripts/registry/mock_data.js
@@ -108,6 +108,17 @@ export const repoPropsData = {
destroyPath: 'path',
canDelete: true,
},
+ {
+ tag: 'test-image',
+ revision: 'b969de599faea2b3d9b6605a8b0897261c571acaa36db1bdc7349b5775b4e0b4',
+ shortRevision: 'b969de599',
+ size: 19,
+ layers: 10,
+ location: 'location-2',
+ createdAt: 1505828744434,
+ destroyPath: 'path-2',
+ canDelete: true,
+ },
],
location: 'location',
name: 'foo',
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index a17494966a3..1f1e626ed33 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -34,7 +34,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained no changed test results out of 11 total tests',
);
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.spinner')).toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.gl-spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
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/test_constants.js b/spec/javascripts/test_constants.js
index 77c206585fe..c97d47a6406 100644
--- a/spec/javascripts/test_constants.js
+++ b/spec/javascripts/test_constants.js
@@ -1,6 +1,4 @@
-export const FIXTURES_PATH = `/base/${
- process.env.IS_GITLAB_EE ? 'ee/' : ''
-}spec/javascripts/fixtures`;
+export const FIXTURES_PATH = `/fixtures`;
export const TEST_HOST = 'http://test.host';
export const DUMMY_IMAGE_URL = `${FIXTURES_PATH}/static/images/one_white_pixel.png`;
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index f622f52a7b9..5aac37d28df 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index d93badf8cd3..55a11a72551 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -38,7 +38,9 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain(
+ 'gl-spinner',
+ );
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 96e512d222a..70c70eca746 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('gl-spinner');
});
it('renders information about merging', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index ba3ba01944d..53e1f077610 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -236,24 +236,26 @@ describe('ReadyToMerge', () => {
});
});
- describe('shouldShowMergeOptionsDropdown', () => {
- it('should return false when no auto merge strategies are available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
+ describe('shouldShowMergeImmediatelyDropdown', () => {
+ it('should return false if no pipeline is active', () => {
+ Vue.set(vm.mr, 'isPipelineActive', false);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return true when at least one auto merge strategy is available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
+ it('should return false if "Pipelines must succeed" is enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
- Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
+ it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true);
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
index d6d8eecfcb9..cb656525f06 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -21,7 +21,7 @@ describe('Squash before merge component', () => {
});
describe('checkbox', () => {
- const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+ const findCheckbox = () => wrapper.find('.js-squash-checkbox');
it('is unchecked if passed value prop is false', () => {
createComponent({
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 253413ae43e..a55d5537df7 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -233,6 +233,8 @@ export default {
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
+ merge_train_when_pipeline_succeeds_docs_path:
+ '/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
diff --git a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js b/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
deleted file mode 100644
index 634ba8403d5..00000000000
--- a/spec/javascripts/vue_shared/components/changed_file_icon_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import changedFileIcon from '~/vue_shared/components/changed_file_icon.vue';
-import createComponent from 'spec/helpers/vue_mount_component_helper';
-
-describe('Changed file icon', () => {
- let vm;
-
- function factory(props = {}) {
- const component = Vue.extend(changedFileIcon);
-
- vm = createComponent(component, {
- ...props,
- file: {
- tempFile: false,
- changed: true,
- },
- });
- }
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('centers icon', () => {
- factory({
- isCentered: true,
- });
-
- expect(vm.$el.classList).toContain('ml-auto');
- });
-
- describe('changedIcon', () => {
- it('equals file-modified when not a temp file and has changes', () => {
- factory();
-
- expect(vm.changedIcon).toBe('file-modified');
- });
-
- it('equals file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIcon).toBe('file-addition');
- });
- });
-
- describe('changedIconClass', () => {
- it('includes file-modified when not a temp file', () => {
- factory();
-
- expect(vm.changedIconClass).toContain('file-modified');
- });
-
- it('includes file-addition when a temp file', () => {
- factory();
-
- vm.file.tempFile = true;
-
- expect(vm.changedIconClass).toContain('file-addition');
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 5bea8c43da3..1f61e19fa84 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -72,7 +72,7 @@ describe('File Icon component', () => {
const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('spinner')).toEqual(true);
+ expect(classList.contains('gl-spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index a9c1a67b39b..2b059e5e9f4 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .gl-spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
index ea74cb9eb21..dc929e83eb7 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_spec.js
@@ -60,7 +60,7 @@ describe('Suggestion Diff component', () => {
describe('init', () => {
it('renders a suggestion header', () => {
- expect(vm.$el.querySelector('.qa-suggestion-diff-header')).not.toBeNull();
+ expect(vm.$el.querySelector('.js-suggestion-diff-header')).not.toBeNull();
});
it('renders a diff table with syntax highlighting', () => {
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/after_commit_queue_spec.rb b/spec/lib/after_commit_queue_spec.rb
index 6e7c2ec2363..8e9dfd90338 100644
--- a/spec/lib/after_commit_queue_spec.rb
+++ b/spec/lib/after_commit_queue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe AfterCommitQueue do
diff --git a/spec/lib/api/api_spec.rb b/spec/lib/api/api_spec.rb
index ceef0b41e59..c83d068ca50 100644
--- a/spec/lib/api/api_spec.rb
+++ b/spec/lib/api/api_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::API do
diff --git a/spec/lib/api/helpers/custom_validators_spec.rb b/spec/lib/api/helpers/custom_validators_spec.rb
index aed86b21cb7..1ebce2ab5c4 100644
--- a/spec/lib/api/helpers/custom_validators_spec.rb
+++ b/spec/lib/api/helpers/custom_validators_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::CustomValidators do
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
index b0a00392957..b57adb46385 100644
--- a/spec/lib/api/helpers/pagination_spec.rb
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::Pagination do
diff --git a/spec/lib/api/helpers/related_resources_helpers_spec.rb b/spec/lib/api/helpers/related_resources_helpers_spec.rb
index 99fe8795d91..fb26cc417e8 100644
--- a/spec/lib/api/helpers/related_resources_helpers_spec.rb
+++ b/spec/lib/api/helpers/related_resources_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::RelatedResourcesHelpers do
diff --git a/spec/lib/api/helpers/version_spec.rb b/spec/lib/api/helpers/version_spec.rb
index 34006e0930b..a9f33962537 100644
--- a/spec/lib/api/helpers/version_spec.rb
+++ b/spec/lib/api/helpers/version_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers::Version do
diff --git a/spec/lib/api/helpers_spec.rb b/spec/lib/api/helpers_spec.rb
index 00916f80784..0624c25e734 100644
--- a/spec/lib/api/helpers_spec.rb
+++ b/spec/lib/api/helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe API::Helpers do
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index 63f2298357f..e903eada62d 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Files do
diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb
index ae1c881e1f6..fee7ffc60ee 100644
--- a/spec/lib/backup/manager_spec.rb
+++ b/spec/lib/backup/manager_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Manager do
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index 5ace5c5b1a2..e1d46c25338 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Repository do
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
index 544d3754c0f..55b69f29812 100644
--- a/spec/lib/backup/uploads_spec.rb
+++ b/spec/lib/backup/uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Backup::Uploads do
diff --git a/spec/lib/banzai/color_parser_spec.rb b/spec/lib/banzai/color_parser_spec.rb
index af2a8f215c1..d9202ce77db 100644
--- a/spec/lib/banzai/color_parser_spec.rb
+++ b/spec/lib/banzai/color_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ColorParser do
diff --git a/spec/lib/banzai/commit_renderer_spec.rb b/spec/lib/banzai/commit_renderer_spec.rb
index 316dbf052c3..e5a16b167be 100644
--- a/spec/lib/banzai/commit_renderer_spec.rb
+++ b/spec/lib/banzai/commit_renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::CommitRenderer do
diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb
index ba995e16be7..cf41af7e7a1 100644
--- a/spec/lib/banzai/cross_project_reference_spec.rb
+++ b/spec/lib/banzai/cross_project_reference_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::CrossProjectReference do
diff --git a/spec/lib/banzai/filter/absolute_link_filter_spec.rb b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
index 50be551cd90..b61bd496dba 100644
--- a/spec/lib/banzai/filter/absolute_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/absolute_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AbsoluteLinkFilter do
diff --git a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
index 1e82d18d056..3e8b0ea113f 100644
--- a/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/abstract_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AbstractReferenceFilter do
diff --git a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
index 34f1657b6d3..bd06dae26ba 100644
--- a/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
+++ b/spec/lib/banzai/filter/ascii_doc_post_processing_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AsciiDocPostProcessingFilter do
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index 4972c4b4bd2..8fba72a23f6 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::AutolinkFilter do
diff --git a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
index 5b3f679084e..807f1b8bbd3 100644
--- a/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
+++ b/spec/lib/banzai/filter/blockquote_fence_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Filter::BlockquoteFenceFilter do
diff --git a/spec/lib/banzai/filter/color_filter_spec.rb b/spec/lib/banzai/filter/color_filter_spec.rb
index a098b037510..f8931d37b99 100644
--- a/spec/lib/banzai/filter/color_filter_spec.rb
+++ b/spec/lib/banzai/filter/color_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ColorFilter, lib: true do
diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
index 4daf6be1bb7..a82b890be42 100644
--- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::CommitRangeReferenceFilter do
diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
index d6c9e9e4b19..326703eea05 100644
--- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::CommitReferenceFilter do
@@ -103,6 +105,17 @@ describe Banzai::Filter::CommitReferenceFilter do
expect(doc.css('a').first[:href]).to eq(url)
end
+
+ context "a doc with many (29) strings that could be SHAs" do
+ let!(:oids) { noteable.commits.collect(&:id) }
+
+ it 'makes only a single request to Gitaly' do
+ expect(Gitlab::GitalyClient).to receive(:allow_n_plus_1_calls).exactly(0).times
+ expect(Gitlab::Git::Commit).to receive(:batch_by_oid).once.and_call_original
+
+ reference_filter("A big list of SHAs #{oids.join(", ")}", noteable: noteable)
+ end
+ end
end
end
diff --git a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
index 068cdc85a07..bcb74be1034 100644
--- a/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
+++ b/spec/lib/banzai/filter/commit_trailers_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'ffaker'
diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb
index 85a4619e33d..4e163668a28 100644
--- a/spec/lib/banzai/filter/emoji_filter_spec.rb
+++ b/spec/lib/banzai/filter/emoji_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::EmojiFilter do
diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
index 7c94cf37e32..78795a157f8 100644
--- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ExternalIssueReferenceFilter do
diff --git a/spec/lib/banzai/filter/external_link_filter_spec.rb b/spec/lib/banzai/filter/external_link_filter_spec.rb
index 2acbe05f082..59fea5766ee 100644
--- a/spec/lib/banzai/filter/external_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/external_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'an external link with rel attribute' do
diff --git a/spec/lib/banzai/filter/front_matter_filter_spec.rb b/spec/lib/banzai/filter/front_matter_filter_spec.rb
index 3071dc7cf21..90b383dbcff 100644
--- a/spec/lib/banzai/filter/front_matter_filter_spec.rb
+++ b/spec/lib/banzai/filter/front_matter_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Filter::FrontMatterFilter do
diff --git a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
index 0e178b859c4..9d179ef2a49 100644
--- a/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
+++ b/spec/lib/banzai/filter/gollum_tags_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::GollumTagsFilter do
diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb
index 1d98fc0d5db..6017380725d 100644
--- a/spec/lib/banzai/filter/html_entity_filter_spec.rb
+++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::HtmlEntityFilter do
diff --git a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
index d06c5535309..6475fd14ce4 100644
--- a/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_lazy_load_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ImageLazyLoadFilter do
diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb
index c84b98eb225..7b0cb675551 100644
--- a/spec/lib/banzai/filter/image_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/image_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ImageLinkFilter do
diff --git a/spec/lib/banzai/filter/inline_diff_filter_spec.rb b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
index 63c4ab61b86..c09065fb746 100644
--- a/spec/lib/banzai/filter/inline_diff_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_diff_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::InlineDiffFilter do
diff --git a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
index 772c94e3180..66bbcbf7292 100644
--- a/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_filter_spec.rb
@@ -12,7 +12,7 @@ describe Banzai::Filter::InlineMetricsFilter do
let(:url) { 'https://foo.com' }
it 'leaves regular non-metrics links unchanged' do
- expect(doc.to_s).to eq input
+ expect(doc.to_s).to eq(input)
end
end
@@ -21,7 +21,7 @@ describe Banzai::Filter::InlineMetricsFilter do
let(:url) { urls.metrics_namespace_project_environment_url(*params) }
it 'leaves the original link unchanged' do
- expect(doc.at_css('a').to_s).to eq input
+ expect(doc.at_css('a').to_s).to eq(input)
end
it 'appends a metrics charts placeholder with dashboard url after metrics links' do
@@ -29,7 +29,7 @@ describe Banzai::Filter::InlineMetricsFilter do
expect(node).to be_present
dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params, embedded: true)
- expect(node.attribute('data-dashboard-url').to_s).to eq dashboard_url
+ expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
end
context 'when the metrics dashboard link is part of a paragraph' do
@@ -37,18 +37,33 @@ describe Banzai::Filter::InlineMetricsFilter do
let(:input) { %(<p>#{paragraph}</p>) }
it 'appends the charts placeholder after the enclosing paragraph' do
- expect(doc.at_css('p').to_s).to include paragraph
+ expect(doc.at_css('p').to_s).to include(paragraph)
expect(doc.at_css('.js-render-metrics')).to be_present
end
+ end
+
+ context 'with dashboard params specified' do
+ let(:params) do
+ [
+ 'foo',
+ 'bar',
+ 12,
+ {
+ embedded: true,
+ dashboard: 'config/prometheus/common_metrics.yml',
+ group: 'System metrics (Kubernetes)',
+ title: 'Core Usage (Pod Average)',
+ y_label: 'Cores per Pod'
+ }
+ ]
+ end
- context 'when the feature is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
+ it 'appends a metrics charts placeholder with dashboard url after metrics links' do
+ node = doc.at_css('.js-render-metrics')
+ expect(node).to be_present
- it 'does nothing' do
- expect(doc.to_s).to eq input
- end
+ dashboard_url = urls.metrics_dashboard_namespace_project_environment_url(*params)
+ expect(node.attribute('data-dashboard-url').to_s).to eq(dashboard_url)
end
end
end
diff --git a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
index fb2186e9d12..a99cd7d6076 100644
--- a/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/inline_metrics_redactor_filter_spec.rb
@@ -11,16 +11,6 @@ describe Banzai::Filter::InlineMetricsRedactorFilter do
let(:input) { %(<a href="#{url}">example</a>) }
let(:doc) { filter(input) }
- context 'when the feature is disabled' do
- before do
- stub_feature_flags(gfm_embedded_metrics: false)
- end
-
- it 'does nothing' do
- expect(doc.to_s).to eq input
- end
- end
-
context 'without a metrics charts placeholder' do
it 'leaves regular non-metrics links unchanged' do
expect(doc.to_s).to eq input
diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
index a5373517ac8..9f6dcded56f 100644
--- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb
+++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::IssuableStateFilter do
diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
index 914c4e2d823..4a412da27a7 100644
--- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::IssueReferenceFilter do
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 108d7b43a26..213a5459118 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'html/pipeline'
diff --git a/spec/lib/banzai/filter/markdown_filter_spec.rb b/spec/lib/banzai/filter/markdown_filter_spec.rb
index 83fcda29680..06df67facf9 100644
--- a/spec/lib/banzai/filter/markdown_filter_spec.rb
+++ b/spec/lib/banzai/filter/markdown_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MarkdownFilter do
diff --git a/spec/lib/banzai/filter/math_filter_spec.rb b/spec/lib/banzai/filter/math_filter_spec.rb
index cade8cb409e..c8fd92edcdf 100644
--- a/spec/lib/banzai/filter/math_filter_spec.rb
+++ b/spec/lib/banzai/filter/math_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MathFilter do
diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
index 72dfd6ff9ea..12ee952b10e 100644
--- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MergeRequestReferenceFilter do
diff --git a/spec/lib/banzai/filter/mermaid_filter_spec.rb b/spec/lib/banzai/filter/mermaid_filter_spec.rb
index f6474c8936d..ae6725cc14c 100644
--- a/spec/lib/banzai/filter/mermaid_filter_spec.rb
+++ b/spec/lib/banzai/filter/mermaid_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MermaidFilter do
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
index f0a5dc8d0d7..3f021adc756 100644
--- a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::MilestoneReferenceFilter do
@@ -363,7 +365,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
expect(doc.css('a')).to be_empty
end
- it 'supports parent group references', :nested_groups do
+ it 'supports parent group references' do
milestone.update!(group: parent_group)
doc = reference_filter("See #{reference}")
@@ -396,7 +398,7 @@ describe Banzai::Filter::MilestoneReferenceFilter do
context 'when group milestone' do
let(:group_milestone) { create(:milestone, title: 'group_milestone', group: group) }
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
let(:sub_group) { create(:group, parent: group) }
let(:sub_group_milestone) { create(:milestone, title: 'sub_group_milestone', group: sub_group) }
diff --git a/spec/lib/banzai/filter/plantuml_filter_spec.rb b/spec/lib/banzai/filter/plantuml_filter_spec.rb
index 6f7acfe7072..713bab4527b 100644
--- a/spec/lib/banzai/filter/plantuml_filter_spec.rb
+++ b/spec/lib/banzai/filter/plantuml_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::PlantumlFilter do
diff --git a/spec/lib/banzai/filter/reference_filter_spec.rb b/spec/lib/banzai/filter/reference_filter_spec.rb
index f96b6c83b0a..d889b0b832d 100644
--- a/spec/lib/banzai/filter/reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ReferenceFilter do
diff --git a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index e87440895e0..dc888a47988 100644
--- a/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::ReferenceRedactorFilter do
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index a714fa50f5f..ecb83b6cb66 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::RelativeLinkFilter do
diff --git a/spec/lib/banzai/filter/sanitization_filter_spec.rb b/spec/lib/banzai/filter/sanitization_filter_spec.rb
index f2a5d7b2c9f..8a4b819e4d6 100644
--- a/spec/lib/banzai/filter/sanitization_filter_spec.rb
+++ b/spec/lib/banzai/filter/sanitization_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SanitizationFilter do
diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
index 21cf092428d..62ce12406a2 100644
--- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SnippetReferenceFilter do
diff --git a/spec/lib/banzai/filter/spaced_link_filter_spec.rb b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
index 76d7644d76c..98c38813144 100644
--- a/spec/lib/banzai/filter/spaced_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/spaced_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SpacedLinkFilter do
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index 80ca7a63435..f220ccecee1 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::SyntaxHighlightFilter do
diff --git a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
index 4a9880ac85a..5ca3c722e3e 100644
--- a/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
+++ b/spec/lib/banzai/filter/table_of_contents_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::TableOfContentsFilter do
@@ -112,11 +114,11 @@ describe Banzai::Filter::TableOfContentsFilter do
context 'table of contents nesting' do
let(:results) do
result(
- header(1, 'Header 1') <<
- header(2, 'Header 1-1') <<
- header(3, 'Header 1-1-1') <<
- header(2, 'Header 1-2') <<
- header(1, 'Header 2') <<
+ header(1, 'Header 1') +
+ header(2, 'Header 1-1') +
+ header(3, 'Header 1-1-1') +
+ header(2, 'Header 1-2') +
+ header(1, 'Header 2') +
header(2, 'Header 2-1')
)
end
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 1e8a44b4549..6bc87d245f5 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::UserReferenceFilter do
diff --git a/spec/lib/banzai/filter/video_link_filter_spec.rb b/spec/lib/banzai/filter/video_link_filter_spec.rb
index 81dda0687f3..483e806624c 100644
--- a/spec/lib/banzai/filter/video_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/video_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::VideoLinkFilter do
diff --git a/spec/lib/banzai/filter/wiki_link_filter_spec.rb b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
index cce1cd0b284..d2d539a62fc 100644
--- a/spec/lib/banzai/filter/wiki_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/wiki_link_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Filter::WikiLinkFilter do
@@ -70,47 +72,5 @@ describe Banzai::Filter::WikiLinkFilter do
expect(filtered_link.attribute('href').value).to eq(invalid_link)
end
end
-
- context "when the slug is deemed unsafe or invalid" do
- let(:link) { "alert(1);" }
-
- invalid_slugs = [
- "javascript:",
- "JaVaScRiPt:",
- "\u0001java\u0003script:",
- "javascript :",
- "javascript: ",
- "javascript : ",
- ":javascript:",
- "javascript&#58;",
- "javascript&#0058;",
- "javascript&#x3A;",
- "javascript&#x003A;",
- "java\0script:",
- " &#14; javascript:"
- ]
-
- invalid_slugs.each do |slug|
- context "with the slug #{slug}" do
- it "doesn't rewrite a (.) relative link" do
- filtered_link = filter(
- "<a href='.#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
-
- it "doesn't rewrite a (..) relative link" do
- filtered_link = filter(
- "<a href='..#{link}'>Link</a>",
- project_wiki: wiki,
- page_slug: slug).children[0]
-
- expect(filtered_link.attribute('href').value).not_to include(slug)
- end
- end
- end
- end
end
end
diff --git a/spec/lib/banzai/filter_array_spec.rb b/spec/lib/banzai/filter_array_spec.rb
index ea84005e7f8..bed41a80d29 100644
--- a/spec/lib/banzai/filter_array_spec.rb
+++ b/spec/lib/banzai/filter_array_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::FilterArray do
diff --git a/spec/lib/banzai/issuable_extractor_spec.rb b/spec/lib/banzai/issuable_extractor_spec.rb
index f42951d9781..7fa6048c1c6 100644
--- a/spec/lib/banzai/issuable_extractor_spec.rb
+++ b/spec/lib/banzai/issuable_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::IssuableExtractor do
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index e3e6e22568c..a523608fa50 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ObjectRenderer do
diff --git a/spec/lib/banzai/pipeline/description_pipeline_spec.rb b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
index 77cb1954ea3..d032ec71e45 100644
--- a/spec/lib/banzai/pipeline/description_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/description_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::DescriptionPipeline do
diff --git a/spec/lib/banzai/pipeline/email_pipeline_spec.rb b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
index b99161109eb..eea25320f3d 100644
--- a/spec/lib/banzai/pipeline/email_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/email_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::EmailPipeline do
diff --git a/spec/lib/banzai/pipeline/full_pipeline_spec.rb b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
index 3d3aa64d630..2b4d1b58676 100644
--- a/spec/lib/banzai/pipeline/full_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/full_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::FullPipeline do
diff --git a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
index 469692f7b5a..0a3e0962452 100644
--- a/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/gfm_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::GfmPipeline do
diff --git a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
index 64ca3ec345d..015af20f220 100644
--- a/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
+++ b/spec/lib/banzai/pipeline/wiki_pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Banzai::Pipeline::WikiPipeline do
@@ -177,6 +179,85 @@ describe Banzai::Pipeline::WikiPipeline do
end
end
end
+
+ describe "checking slug validity when assembling links" do
+ context "with a valid slug" do
+ let(:valid_slug) { "http://example.com" }
+
+ it "includes the slug in a (.) relative link" do
+ output = described_class.to_html(
+ "[Link](./alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+
+ it "includeds the slug in a (..) relative link" do
+ output = described_class.to_html(
+ "[Link](../alert(1);)",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: valid_slug
+ )
+
+ expect(output).to include(valid_slug)
+ end
+ end
+
+ context "when the slug is deemed unsafe or invalid" do
+ invalid_slugs = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ "javascript&#x3A;",
+ "javascript&#x003A;",
+ "java\0script:",
+ " &#14; javascript:"
+ ]
+
+ invalid_js_links = [
+ "alert(1);",
+ "alert(document.location);"
+ ]
+
+ invalid_slugs.each do |slug|
+ context "with the invalid slug #{slug}" do
+ invalid_js_links.each do |link|
+ it "doesn't include a prohibited slug in a (.) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](./#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+
+ it "doesn't include a prohibited slug in a (..) relative link '#{link}'" do
+ output = described_class.to_html(
+ "[Link](../#{link})",
+ project: project,
+ project_wiki: project_wiki,
+ page_slug: slug
+ )
+
+ expect(output).not_to include(slug)
+ end
+ end
+ end
+ end
+ end
+ end
end
describe 'videos' do
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
index 27da2a7439c..b7a235b0558 100644
--- a/spec/lib/banzai/querying_spec.rb
+++ b/spec/lib/banzai/querying_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Querying do
diff --git a/spec/lib/banzai/reference_parser/base_parser_spec.rb b/spec/lib/banzai/reference_parser/base_parser_spec.rb
index c6e9fc414a1..7897164d985 100644
--- a/spec/lib/banzai/reference_parser/base_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/base_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::BaseParser do
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index f558dea209f..b44ae67e430 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::CommitParser do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index ff3b82cc482..da853233018 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::CommitRangeParser do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 1cb31e57114..0f29a95bdcc 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::ExternalIssueParser do
diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
index 77c2064caba..a925d294b1b 100644
--- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::IssueParser do
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index e4df2533821..cf8adb57ffc 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::LabelParser do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 5417b1f00be..1561dabcdbf 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::MergeRequestParser do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 751d042ffde..006f8e37690 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::MilestoneParser do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index d410bd4c164..528f79ed020 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::SnippetParser do
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index 112447f098e..a5b4e59a3a1 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceParser::UserParser do
diff --git a/spec/lib/banzai/reference_redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index a3b47c4d826..c30a194a0b3 100644
--- a/spec/lib/banzai/reference_redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::ReferenceRedactor do
diff --git a/spec/lib/banzai/renderer_spec.rb b/spec/lib/banzai/renderer_spec.rb
index a099f7482c1..0d329b47aa3 100644
--- a/spec/lib/banzai/renderer_spec.rb
+++ b/spec/lib/banzai/renderer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Banzai::Renderer do
diff --git a/spec/lib/bitbucket/collection_spec.rb b/spec/lib/bitbucket/collection_spec.rb
index 9008cb3e870..5946be71565 100644
--- a/spec/lib/bitbucket/collection_spec.rb
+++ b/spec/lib/bitbucket/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Emulates paginator. It returns 2 pages with results
diff --git a/spec/lib/bitbucket/connection_spec.rb b/spec/lib/bitbucket/connection_spec.rb
index 14faeb231a9..ec8eac232cd 100644
--- a/spec/lib/bitbucket/connection_spec.rb
+++ b/spec/lib/bitbucket/connection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Connection do
diff --git a/spec/lib/bitbucket/page_spec.rb b/spec/lib/bitbucket/page_spec.rb
index 04d5a0470b1..6301dd56faf 100644
--- a/spec/lib/bitbucket/page_spec.rb
+++ b/spec/lib/bitbucket/page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Page do
diff --git a/spec/lib/bitbucket/paginator_spec.rb b/spec/lib/bitbucket/paginator_spec.rb
index bdf10a5e2a2..a1effa14000 100644
--- a/spec/lib/bitbucket/paginator_spec.rb
+++ b/spec/lib/bitbucket/paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Paginator do
diff --git a/spec/lib/bitbucket/representation/comment_spec.rb b/spec/lib/bitbucket/representation/comment_spec.rb
index fec243a9f96..1874296df8c 100644
--- a/spec/lib/bitbucket/representation/comment_spec.rb
+++ b/spec/lib/bitbucket/representation/comment_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Comment do
describe '#author' do
- it { expect(described_class.new('user' => { 'username' => 'Ben' }).author).to eq('Ben') }
+ it { expect(described_class.new('user' => { 'nickname' => 'Ben' }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/issue_spec.rb b/spec/lib/bitbucket/representation/issue_spec.rb
index 20f47224aa8..655b9b78b47 100644
--- a/spec/lib/bitbucket/representation/issue_spec.rb
+++ b/spec/lib/bitbucket/representation/issue_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Issue do
@@ -15,7 +17,7 @@ describe Bitbucket::Representation::Issue do
end
describe '#author' do
- it { expect(described_class.new({ 'reporter' => { 'username' => 'Ben' } }).author).to eq('Ben') }
+ it { expect(described_class.new({ 'reporter' => { 'nickname' => 'Ben' } }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
index 673dcf22ce8..151055f510f 100644
--- a/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::PullRequestComment do
diff --git a/spec/lib/bitbucket/representation/pull_request_spec.rb b/spec/lib/bitbucket/representation/pull_request_spec.rb
index 30453528be4..70b51b8efec 100644
--- a/spec/lib/bitbucket/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket/representation/pull_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::PullRequest do
@@ -6,7 +8,7 @@ describe Bitbucket::Representation::PullRequest do
end
describe '#author' do
- it { expect(described_class.new({ 'author' => { 'username' => 'Ben' } }).author).to eq('Ben') }
+ it { expect(described_class.new({ 'author' => { 'nickname' => 'Ben' } }).author).to eq('Ben') }
it { expect(described_class.new({}).author).to be_nil }
end
diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb
index 405265cc669..a272695e681 100644
--- a/spec/lib/bitbucket/representation/repo_spec.rb
+++ b/spec/lib/bitbucket/representation/repo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::Repo do
diff --git a/spec/lib/bitbucket/representation/user_spec.rb b/spec/lib/bitbucket/representation/user_spec.rb
index f79ff4edb7b..0169887a24c 100644
--- a/spec/lib/bitbucket/representation/user_spec.rb
+++ b/spec/lib/bitbucket/representation/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Bitbucket::Representation::User do
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index 4f0d57ca8a6..aa0217856ee 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Client do
@@ -56,6 +58,17 @@ describe BitbucketServer::Client do
subject.repos(page_offset: 10, limit: 25)
end
+
+ context 'when filter param is passed' do
+ let(:filter) { 'test' }
+ let(:expected_path) { "#{path}?name=#{filter}" }
+
+ it 'requests a collection with filter applied' do
+ expect(BitbucketServer::Paginator).to receive(:new).with(anything, expected_path, :repo, page_offset: 0, limit: nil)
+
+ subject.repos(filter: filter)
+ end
+ end
end
describe '#create_branch' do
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index ab8a5b89608..3a7fe4e7321 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Connection do
diff --git a/spec/lib/bitbucket_server/page_spec.rb b/spec/lib/bitbucket_server/page_spec.rb
index cf419a9045b..2da1d0995ca 100644
--- a/spec/lib/bitbucket_server/page_spec.rb
+++ b/spec/lib/bitbucket_server/page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Page do
diff --git a/spec/lib/bitbucket_server/paginator_spec.rb b/spec/lib/bitbucket_server/paginator_spec.rb
index eadd7f68bfb..e01cbeb4270 100644
--- a/spec/lib/bitbucket_server/paginator_spec.rb
+++ b/spec/lib/bitbucket_server/paginator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Paginator do
diff --git a/spec/lib/bitbucket_server/representation/activity_spec.rb b/spec/lib/bitbucket_server/representation/activity_spec.rb
index 15c50e40472..b548dedadfb 100644
--- a/spec/lib/bitbucket_server/representation/activity_spec.rb
+++ b/spec/lib/bitbucket_server/representation/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Activity do
diff --git a/spec/lib/bitbucket_server/representation/comment_spec.rb b/spec/lib/bitbucket_server/representation/comment_spec.rb
index 53a20a1d80a..f8c73c3da35 100644
--- a/spec/lib/bitbucket_server/representation/comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Comment do
diff --git a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
index bd7e3597486..db43e990812 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_comment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::PullRequestComment do
diff --git a/spec/lib/bitbucket_server/representation/pull_request_spec.rb b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
index 4b8afdb006b..e091890041e 100644
--- a/spec/lib/bitbucket_server/representation/pull_request_spec.rb
+++ b/spec/lib/bitbucket_server/representation/pull_request_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::PullRequest do
diff --git a/spec/lib/bitbucket_server/representation/repo_spec.rb b/spec/lib/bitbucket_server/representation/repo_spec.rb
index 3ac1030fbb0..801de247d73 100644
--- a/spec/lib/bitbucket_server/representation/repo_spec.rb
+++ b/spec/lib/bitbucket_server/representation/repo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe BitbucketServer::Representation::Repo do
diff --git a/spec/lib/constraints/feature_constrainer_spec.rb b/spec/lib/constraints/feature_constrainer_spec.rb
index 42efc164f81..0739da801a7 100644
--- a/spec/lib/constraints/feature_constrainer_spec.rb
+++ b/spec/lib/constraints/feature_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::FeatureConstrainer do
diff --git a/spec/lib/constraints/group_url_constrainer_spec.rb b/spec/lib/constraints/group_url_constrainer_spec.rb
index ff295068ba9..573de331898 100644
--- a/spec/lib/constraints/group_url_constrainer_spec.rb
+++ b/spec/lib/constraints/group_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::GroupUrlConstrainer do
diff --git a/spec/lib/constraints/project_url_constrainer_spec.rb b/spec/lib/constraints/project_url_constrainer_spec.rb
index 3496b01ebcc..ac3221ecab7 100644
--- a/spec/lib/constraints/project_url_constrainer_spec.rb
+++ b/spec/lib/constraints/project_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::ProjectUrlConstrainer do
diff --git a/spec/lib/constraints/user_url_constrainer_spec.rb b/spec/lib/constraints/user_url_constrainer_spec.rb
index e2c85bb27bb..15ef930420c 100644
--- a/spec/lib/constraints/user_url_constrainer_spec.rb
+++ b/spec/lib/constraints/user_url_constrainer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Constraints::UserUrlConstrainer do
diff --git a/spec/lib/container_registry/blob_spec.rb b/spec/lib/container_registry/blob_spec.rb
index d3fff5bad42..be7be2f3719 100644
--- a/spec/lib/container_registry/blob_spec.rb
+++ b/spec/lib/container_registry/blob_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Blob do
@@ -110,11 +112,28 @@ describe ContainerRegistry::Blob do
end
end
+ context 'for a relative address' do
+ before do
+ stub_request(:get, 'http://registry.gitlab/relative')
+ .with { |request| !request.headers.include?('Authorization') }
+ .to_return(
+ status: 200,
+ headers: { 'Content-Type' => 'application/json' },
+ body: '{"key":"value"}')
+ end
+
+ let(:location) { '/relative' }
+
+ it 'returns correct data' do
+ expect(blob.data).to eq '{"key":"value"}'
+ end
+ end
+
context 'for invalid file' do
let(:location) { 'file:///etc/passwd' }
it 'raises an error' do
- expect { blob.data }.to raise_error(ArgumentError, 'invalid address')
+ expect { blob.data }.to raise_error(ArgumentError, 'Invalid scheme for file:///etc/passwd')
end
end
end
diff --git a/spec/lib/container_registry/client_spec.rb b/spec/lib/container_registry/client_spec.rb
index 3df33f48adb..bc5fddd12ba 100644
--- a/spec/lib/container_registry/client_spec.rb
+++ b/spec/lib/container_registry/client_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Client do
@@ -6,6 +8,42 @@ describe ContainerRegistry::Client do
let(:options) { { token: token } }
let(:client) { described_class.new("http://container-registry", options) }
+ shared_examples '#repository_manifest' do |manifest_type|
+ let(:manifest) do
+ {
+ "schemaVersion" => 2,
+ "config" => {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:4a3ef0786dd241be6000311e1503869b320be433b9cba84cfafeb512d1720c95",
+ "size" => 6608
+ },
+ "layers" => [
+ {
+ "mediaType" => manifest_type,
+ "digest" =>
+ "sha256:83ef92b73cf4595aa7fe214ec6747228283d585f373d8f6bc08d66bebab531b7",
+ "size" => 2828661
+ }
+ ]
+ }
+ end
+
+ it 'GET /v2/:name/manifests/mytag' do
+ stub_request(:get, "http://container-registry/v2/group/test/manifests/mytag")
+ .with(headers: {
+ 'Accept' => described_class::ACCEPTED_TYPES.join(', '),
+ 'Authorization' => "bearer #{token}"
+ })
+ .to_return(status: 200, body: manifest.to_json, headers: { content_type: manifest_type })
+
+ expect(client.repository_manifest('group/test', 'mytag')).to eq(manifest)
+ end
+ end
+
+ it_behaves_like '#repository_manifest', described_class::DOCKER_DISTRIBUTION_MANIFEST_V2_TYPE
+ it_behaves_like '#repository_manifest', described_class::OCI_MANIFEST_V1_TYPE
+
describe '#blob' do
it 'GET /v2/:name/blobs/:digest' do
stub_request(:get, "http://container-registry/v2/group/test/blobs/sha256:0123456789012345")
diff --git a/spec/lib/container_registry/path_spec.rb b/spec/lib/container_registry/path_spec.rb
index 010deae822c..8c671b4d56d 100644
--- a/spec/lib/container_registry/path_spec.rb
+++ b/spec/lib/container_registry/path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Path do
diff --git a/spec/lib/container_registry/registry_spec.rb b/spec/lib/container_registry/registry_spec.rb
index 4d6eea94bf0..7cf70a1f562 100644
--- a/spec/lib/container_registry/registry_spec.rb
+++ b/spec/lib/container_registry/registry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Registry do
diff --git a/spec/lib/container_registry/tag_spec.rb b/spec/lib/container_registry/tag_spec.rb
index cb4ae3be525..110f006536b 100644
--- a/spec/lib/container_registry/tag_spec.rb
+++ b/spec/lib/container_registry/tag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ContainerRegistry::Tag do
@@ -9,7 +11,7 @@ describe ContainerRegistry::Tag do
end
let(:headers) do
- { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' }
+ { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') }
end
let(:tag) { described_class.new(repository, 'tag') }
diff --git a/spec/lib/event_filter_spec.rb b/spec/lib/event_filter_spec.rb
index 6648e141b7a..d6eca8d85ff 100644
--- a/spec/lib/event_filter_spec.rb
+++ b/spec/lib/event_filter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe EventFilter do
diff --git a/spec/lib/expand_variables_spec.rb b/spec/lib/expand_variables_spec.rb
index 7faa0f31b68..394efa85701 100644
--- a/spec/lib/expand_variables_spec.rb
+++ b/spec/lib/expand_variables_spec.rb
@@ -1,63 +1,134 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExpandVariables do
describe '#expand' do
- subject { described_class.expand(value, variables) }
+ context 'table tests' do
+ using RSpec::Parameterized::TableSyntax
+
+ where do
+ {
+ "no expansion": {
+ value: 'key',
+ result: 'key',
+ variables: []
+ },
+ "missing variable": {
+ value: 'key$variable',
+ result: 'key',
+ variables: []
+ },
+ "simple expansion": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "simple with hash of variables": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: {
+ 'variable' => 'value'
+ }
+ },
+ "complex expansion": {
+ value: 'key${variable}',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "simple expansions": {
+ value: 'key$variable$variable2',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "complex expansions": {
+ value: 'key${variable}${variable2}',
+ result: 'keyvalueresult',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "complex expansions with missing variable": {
+ value: 'key${variable}${variable2}',
+ result: 'keyvalue',
+ variables: [
+ { key: 'variable', value: 'value' }
+ ]
+ },
+ "out-of-order expansion": {
+ value: 'key$variable2$variable',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "out-of-order complex expansion": {
+ value: 'key${variable2}${variable}',
+ result: 'keyresultvalue',
+ variables: [
+ { key: 'variable', value: 'value' },
+ { key: 'variable2', value: 'result' }
+ ]
+ },
+ "review-apps expansion": {
+ value: 'review/$CI_COMMIT_REF_NAME',
+ result: 'review/feature/add-review-apps',
+ variables: [
+ { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
+ ]
+ },
+ "do not lazily access variables when no expansion": {
+ value: 'key',
+ result: 'key',
+ variables: -> { raise NotImplementedError }
+ },
+ "lazily access variables": {
+ value: 'key$variable',
+ result: 'keyvalue',
+ variables: -> { [{ key: 'variable', value: 'value' }] }
+ }
+ }
+ end
+
+ with_them do
+ subject { ExpandVariables.expand(value, variables) } # rubocop:disable RSpec/DescribedClass
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ context 'lazily inits variables' do
+ let(:variables) { -> { [{ key: 'variable', value: 'result' }] } }
+
+ subject { described_class.expand(value, variables) }
+
+ context 'when expanding variable' do
+ let(:value) { 'key$variable$variable2' }
+
+ it 'calls block at most once' do
+ expect(variables).to receive(:call).once.and_call_original
+
+ is_expected.to eq('keyresult')
+ end
+ end
- tests = [
- { value: 'key',
- result: 'key',
- variables: [] },
- { value: 'key$variable',
- result: 'key',
- variables: [] },
- { value: 'key$variable',
- result: 'keyvalue',
- variables: [
- { key: 'variable', value: 'value' }
- ] },
- { value: 'key${variable}',
- result: 'keyvalue',
- variables: [
- { key: 'variable', value: 'value' }
- ] },
- { value: 'key$variable$variable2',
- result: 'keyvalueresult',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key${variable}${variable2}',
- result: 'keyvalueresult',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key$variable2$variable',
- result: 'keyresultvalue',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'key${variable2}${variable}',
- result: 'keyresultvalue',
- variables: [
- { key: 'variable', value: 'value' },
- { key: 'variable2', value: 'result' }
- ] },
- { value: 'review/$CI_COMMIT_REF_NAME',
- result: 'review/feature/add-review-apps',
- variables: [
- { key: 'CI_COMMIT_REF_NAME', value: 'feature/add-review-apps' }
- ] }
- ]
+ context 'when no expansion is needed' do
+ let(:value) { 'key' }
- tests.each do |test|
- context "#{test[:value]} resolves to #{test[:result]}" do
- let(:value) { test[:value] }
- let(:variables) { test[:variables] }
+ it 'does not call block' do
+ expect(variables).not_to receive(:call)
- it { is_expected.to eq(test[:result]) }
+ is_expected.to eq('key')
+ end
end
end
end
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 21ba72953fb..ffe7584a019 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ExtractsPath do
diff --git a/spec/lib/feature/gitaly_spec.rb b/spec/lib/feature/gitaly_spec.rb
index 0e24a927d4c..4e07acf9c1a 100644
--- a/spec/lib/feature/gitaly_spec.rb
+++ b/spec/lib/feature/gitaly_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Feature::Gitaly do
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 127463a57e8..3d59b1f35a9 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Feature do
@@ -252,6 +254,22 @@ describe Feature do
end
end
+ describe '.remove' do
+ context 'for a non-persisted feature' do
+ it 'returns nil' do
+ expect(described_class.remove(:non_persisted_feature_flag)).to be_nil
+ end
+ end
+
+ context 'for a persisted feature' do
+ it 'returns true' do
+ described_class.enable(:persisted_feature_flag)
+
+ expect(described_class.remove(:persisted_feature_flag)).to be_truthy
+ end
+ end
+ end
+
describe Feature::Target do
describe '#targets' do
let(:project) { create(:project) }
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index ebd907ecb7f..87376a98c60 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe FileSizeValidator do
diff --git a/spec/lib/forever_spec.rb b/spec/lib/forever_spec.rb
index b9ffe895bf0..9f17308241b 100644
--- a/spec/lib/forever_spec.rb
+++ b/spec/lib/forever_spec.rb
@@ -1,22 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Forever do
describe '.date' do
subject { described_class.date }
- context 'when using PostgreSQL' do
- it 'returns Postgresql future date' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(subject).to eq(described_class::POSTGRESQL_DATE)
- end
- end
-
- context 'when using MySQL' do
- it 'returns MySQL future date' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(subject).to eq(described_class::MYSQL_DATE)
+ it 'returns Postgresql future date' do
+ Timecop.travel(Date.new(2999, 12, 31)) do
+ is_expected.to be > Date.today
end
end
end
diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb
index 34bd43cb3ab..12dfad6698d 100644
--- a/spec/lib/gitaly/server_spec.rb
+++ b/spec/lib/gitaly/server_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitaly::Server do
diff --git a/spec/lib/gitlab/action_rate_limiter_spec.rb b/spec/lib/gitlab/action_rate_limiter_spec.rb
index 542fc03e555..8b510a475d2 100644
--- a/spec/lib/gitlab/action_rate_limiter_spec.rb
+++ b/spec/lib/gitlab/action_rate_limiter_spec.rb
@@ -1,11 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::ActionRateLimiter do
+describe Gitlab::ActionRateLimiter, :clean_gitlab_redis_cache do
let(:redis) { double('redis') }
let(:user) { create(:user) }
let(:project) { create(:project) }
- let(:key) { [user, project] }
- let(:cache_key) { "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}" }
subject { described_class.new(action: :test_action, expiry_time: 100) }
@@ -13,17 +13,98 @@ describe Gitlab::ActionRateLimiter do
allow(Gitlab::Redis::Cache).to receive(:with).and_yield(redis)
end
- it 'increases the throttle count and sets the expire time' do
- expect(redis).to receive(:incr).with(cache_key).and_return(1)
- expect(redis).to receive(:expire).with(cache_key, 100)
+ shared_examples 'action rate limiter' do
+ it 'increases the throttle count and sets the expiration time' do
+ expect(redis).to receive(:incr).with(cache_key).and_return(1)
+ expect(redis).to receive(:expire).with(cache_key, 100)
+
+ expect(subject.throttled?(key, 1)).to be_falsy
+ end
+
+ it 'returns true if the key is throttled' do
+ expect(redis).to receive(:incr).with(cache_key).and_return(2)
+ expect(redis).not_to receive(:expire)
- expect(subject.throttled?(key, 1)).to be false
+ expect(subject.throttled?(key, 1)).to be_truthy
+ end
+
+ context 'when throttling is disabled' do
+ it 'returns false and does not set expiration time' do
+ expect(redis).not_to receive(:incr)
+ expect(redis).not_to receive(:expire)
+
+ expect(subject.throttled?(key, 0)).to be_falsy
+ end
+ end
end
- it 'returns true if the key is throttled' do
- expect(redis).to receive(:incr).with(cache_key).and_return(2)
- expect(redis).not_to receive(:expire)
+ context 'when the key is an array of only ActiveRecord models' do
+ let(:key) { [user, project] }
+
+ let(:cache_key) do
+ "action_rate_limiter:test_action:user:#{user.id}:project:#{project.id}"
+ end
+
+ it_behaves_like 'action rate limiter'
+ end
+
+ context 'when they key a combination of ActiveRecord models and strings' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:commit) { project.repository.commit }
+ let(:path) { 'app/controllers/groups_controller.rb' }
+ let(:key) { [project, commit, path] }
+
+ let(:cache_key) do
+ "action_rate_limiter:test_action:project:#{project.id}:commit:#{commit.sha}:#{path}"
+ end
+
+ it_behaves_like 'action rate limiter'
+ end
+
+ describe '#log_request' do
+ let(:file_path) { 'master/README.md' }
+ let(:type) { :raw_blob_request_limit }
+ let(:fullpath) { "/#{project.full_path}/raw/#{file_path}" }
+
+ let(:request) do
+ double('request', ip: '127.0.0.1', request_method: 'GET', fullpath: fullpath)
+ end
+
+ let(:base_attributes) do
+ {
+ message: 'Action_Rate_Limiter_Request',
+ env: type,
+ remote_ip: '127.0.0.1',
+ request_method: 'GET',
+ path: fullpath
+ }
+ end
+
+ context 'without a current user' do
+ let(:current_user) { nil }
+
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(base_attributes).once
+
+ subject.log_request(request, type, current_user)
+ end
+ end
+
+ context 'with a current_user' do
+ let(:current_user) { create(:user) }
+
+ let(:attributes) do
+ base_attributes.merge({
+ user_id: current_user.id,
+ username: current_user.username
+ })
+ end
+
+ it 'logs information to auth.log' do
+ expect(Gitlab::AuthLogger).to receive(:error).with(attributes).once
- expect(subject.throttled?(key, 1)).to be true
+ subject.log_request(request, type, current_user)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/allowable_spec.rb b/spec/lib/gitlab/allowable_spec.rb
index 9d80d480b52..4905cc4c3db 100644
--- a/spec/lib/gitlab/allowable_spec.rb
+++ b/spec/lib/gitlab/allowable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Allowable do
diff --git a/spec/lib/gitlab/app_logger_spec.rb b/spec/lib/gitlab/app_logger_spec.rb
index c86d30ce6df..3b21104b15d 100644
--- a/spec/lib/gitlab/app_logger_spec.rb
+++ b/spec/lib/gitlab/app_logger_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::AppLogger, :request_store do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index cbd4a509a55..7c65525b8dc 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'nokogiri'
diff --git a/spec/lib/gitlab/auth/activity_spec.rb b/spec/lib/gitlab/auth/activity_spec.rb
index 07854cb1eba..e03fafe3826 100644
--- a/spec/lib/gitlab/auth/activity_spec.rb
+++ b/spec/lib/gitlab/auth/activity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Auth::Activity do
diff --git a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
index f39863fdda1..52849f8c172 100644
--- a/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
+++ b/spec/lib/gitlab/auth/blocked_user_tracker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::BlockedUserTracker do
diff --git a/spec/lib/gitlab/auth/ldap/access_spec.rb b/spec/lib/gitlab/auth/ldap/access_spec.rb
index 662f899180b..ecdd5b29986 100644
--- a/spec/lib/gitlab/auth/ldap/access_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Access do
diff --git a/spec/lib/gitlab/auth/ldap/adapter_spec.rb b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
index 3eeaf3862f6..54486913b72 100644
--- a/spec/lib/gitlab/auth/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/adapter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Adapter do
diff --git a/spec/lib/gitlab/auth/ldap/authentication_spec.rb b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
index 111572d043b..e68e83e4617 100644
--- a/spec/lib/gitlab/auth/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/authentication_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Authentication do
diff --git a/spec/lib/gitlab/auth/ldap/config_spec.rb b/spec/lib/gitlab/auth/ldap/config_spec.rb
index b91a09e3137..577dfe51949 100644
--- a/spec/lib/gitlab/auth/ldap/config_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::Config do
diff --git a/spec/lib/gitlab/auth/ldap/dn_spec.rb b/spec/lib/gitlab/auth/ldap/dn_spec.rb
index f2983a02602..63656efba29 100644
--- a/spec/lib/gitlab/auth/ldap/dn_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/dn_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::DN do
diff --git a/spec/lib/gitlab/auth/ldap/user_spec.rb b/spec/lib/gitlab/auth/ldap/user_spec.rb
index 44bb9d20e47..bc09de7b525 100644
--- a/spec/lib/gitlab/auth/ldap/user_spec.rb
+++ b/spec/lib/gitlab/auth/ldap/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::LDAP::User do
diff --git a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
index 40001cea22e..a2d9e27ea5b 100644
--- a/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/auth_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::AuthHash do
@@ -13,13 +15,13 @@ describe Gitlab::Auth::OAuth::AuthHash do
end
let(:uid_raw) do
- "CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
+ +"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
end
- let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
- let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
- let(:first_name_raw) { 'Onur' }
- let(:last_name_raw) { "K\xC3\xBC\xC3\xA7\xC3\xBCk" }
- let(:name_raw) { "Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:email_raw) { +"onur.k\xC3\xBC\xC3\xA7\xC3\xBCk_ABC-123@example.net" }
+ let(:nickname_raw) { +"ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:first_name_raw) { +'Onur' }
+ let(:last_name_raw) { +"K\xC3\xBC\xC3\xA7\xC3\xBCk" }
+ let(:name_raw) { +"Onur K\xC3\xBC\xC3\xA7\xC3\xBCk" }
let(:uid_ascii) { uid_raw.force_encoding(Encoding::ASCII_8BIT) }
let(:email_ascii) { email_raw.force_encoding(Encoding::ASCII_8BIT) }
@@ -40,7 +42,11 @@ describe Gitlab::Auth::OAuth::AuthHash do
last_name: last_name_ascii,
name: name_ascii,
nickname: nickname_ascii,
- uid: uid_ascii
+ uid: uid_ascii,
+ address: {
+ locality: 'some locality',
+ country: 'some country'
+ }
}
end
@@ -51,6 +57,7 @@ describe Gitlab::Auth::OAuth::AuthHash do
it { expect(auth_hash.username).to eql nickname_utf8 }
it { expect(auth_hash.name).to eql name_utf8 }
it { expect(auth_hash.password).not_to be_empty }
+ it { expect(auth_hash.location).to eq 'some locality, some country' }
end
context 'email not provided' do
diff --git a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
index bf810d72f0e..45c1baa4089 100644
--- a/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/identity_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::IdentityLinker do
diff --git a/spec/lib/gitlab/auth/o_auth/provider_spec.rb b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
index 80d702cf9dc..f46f9d76a1e 100644
--- a/spec/lib/gitlab/auth/o_auth/provider_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/provider_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::Provider do
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index b765c265e69..a9b15c411dc 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::OAuth::User do
diff --git a/spec/lib/gitlab/auth/request_authenticator_spec.rb b/spec/lib/gitlab/auth/request_authenticator_spec.rb
index 3d979132880..f7fff389d88 100644
--- a/spec/lib/gitlab/auth/request_authenticator_spec.rb
+++ b/spec/lib/gitlab/auth/request_authenticator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::RequestAuthenticator do
diff --git a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
index 3620e1afe25..13636a495d1 100644
--- a/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
+++ b/spec/lib/gitlab/auth/saml/auth_hash_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::AuthHash do
diff --git a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
index f3305d574cc..89118ff05ba 100644
--- a/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
+++ b/spec/lib/gitlab/auth/saml/identity_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::IdentityLinker do
diff --git a/spec/lib/gitlab/auth/saml/user_spec.rb b/spec/lib/gitlab/auth/saml/user_spec.rb
index c523f5e177f..5546438b7ee 100644
--- a/spec/lib/gitlab/auth/saml/user_spec.rb
+++ b/spec/lib/gitlab/auth/saml/user_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::Saml::User do
diff --git a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
index 22708687a56..ebf7de9c701 100644
--- a/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
+++ b/spec/lib/gitlab/auth/unique_ips_limiter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UniqueIpsLimiter, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
index 002ce776be9..8ec19c454d8 100644
--- a/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
+++ b/spec/lib/gitlab/auth/user_access_denied_reason_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UserAccessDeniedReason do
diff --git a/spec/lib/gitlab/auth/user_auth_finders_spec.rb b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
index 1e2aebdc84b..41265da97a4 100644
--- a/spec/lib/gitlab/auth/user_auth_finders_spec.rb
+++ b/spec/lib/gitlab/auth/user_auth_finders_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth::UserAuthFinders do
@@ -138,6 +140,20 @@ describe Gitlab::Auth::UserAuthFinders do
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
end
+
+ context 'with OAuth headers' do
+ it 'returns user' do
+ env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}"
+
+ expect(find_user_from_access_token).to eq user
+ end
+
+ it 'returns exception if invalid personal_access_token' do
+ env['HTTP_AUTHORIZATION'] = 'Bearer invalid_20byte_token'
+
+ expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
+ end
+ end
end
describe '#find_user_from_web_access_token' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 0403830f700..edff38f05ec 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Auth do
diff --git a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
index c43ed72038e..e299e2a366f 100644
--- a/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
+++ b/spec/lib/gitlab/background_migration/add_merge_request_diff_commits_count_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::AddMergeRequestDiffCommitsCount, :migration, schema: 20180105212544 do
diff --git a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
index 877c061d11b..2a7cffb2f3e 100644
--- a/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
+++ b/spec/lib/gitlab/background_migration/archive_legacy_traces_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::ArchiveLegacyTraces, :migration, schema: 20180529152628 do
diff --git a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
index 1d9bac79dcd..3c2ed6d3a6d 100644
--- a/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_columns_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::EncryptColumns, :migration, schema: 20180910115836 do
diff --git a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
index 9d4921968b3..54af9807e7b 100644
--- a/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
+++ b/spec/lib/gitlab/background_migration/encrypt_runners_tokens_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::EncryptRunnersTokens, :migration, schema: 20181121111200 do
diff --git a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
index 20af63bc6c8..f3127cbf5df 100644
--- a/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
+++ b/spec/lib/gitlab/background_migration/fix_cross_project_label_links_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, schema: 20180702120647 do
@@ -75,7 +77,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
create_resource(target_type, 1, 2)
end
- it 'ignores label links referencing ancestor group labels', :nested_groups do
+ it 'ignores label links referencing ancestor group labels' do
labels_table.create(id: 4, title: 'bug', color: 'red', project_id: 2, type: 'ProjectLabel')
label_links_table.create(label_id: 4, target_type: target_type, target_id: 1)
link = label_links_table.create(label_id: 1, target_type: target_type, target_id: 1)
@@ -85,7 +87,7 @@ describe Gitlab::BackgroundMigration::FixCrossProjectLabelLinks, :migration, sch
expect(link.reload.label_id).to eq(1)
end
- it 'checks also issues and MRs in subgroups', :nested_groups do
+ it 'checks also issues and MRs in subgroups' do
link = label_links_table.create(label_id: 2, target_type: target_type, target_id: 1)
subject.perform(1, 100)
diff --git a/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
new file mode 100644
index 00000000000..7d67dc0251d
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_upload_mover_spec.rb
@@ -0,0 +1,296 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadMover do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+ let(:filename) { 'image.png' }
+
+ let!(:namespace) { create(:namespace) }
+ let!(:legacy_project) { create(:project, :legacy_storage, namespace: namespace) }
+ let!(:hashed_project) { create(:project, namespace: namespace) }
+ # default project
+ let(:project) { legacy_project }
+
+ let!(:issue) { create(:issue, project: project) }
+ let!(:note) { create(:note, note: 'some note', project: project, noteable: issue) }
+
+ let(:legacy_upload) { create_upload(note, filename) }
+
+ def create_remote_upload(model, filename)
+ create(:upload, :attachment_upload,
+ path: "note/attachment/#{model.id}/#{filename}", secret: nil,
+ store: ObjectStorage::Store::REMOTE, model: model)
+ end
+
+ def create_upload(model, filename, with_file = true)
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ def new_upload
+ Upload.find_by(model_id: project.id, model_type: 'Project')
+ end
+
+ def expect_error_log
+ expect_next_instance_of(Gitlab::BackgroundMigration::Logger) do |logger|
+ expect(logger).to receive(:warn)
+ end
+ end
+
+ shared_examples 'legacy upload deletion' do
+ it 'removes the upload record' do
+ described_class.new(legacy_upload).execute
+
+ expect { legacy_upload.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ shared_examples 'move error' do
+ it 'does not remove the upload file' do
+ expect_error_log
+
+ described_class.new(legacy_upload).execute
+
+ expect(legacy_upload.reload).to eq(legacy_upload)
+ end
+ end
+
+ shared_examples 'migrates the file correctly' do
+ before do
+ described_class.new(legacy_upload).execute
+ end
+
+ it 'creates a new uplaod record correctly' do
+ expect(new_upload.secret).not_to be_nil
+ expect(new_upload.path).to end_with("#{new_upload.secret}/image.png")
+ expect(new_upload.model_id).to eq(project.id)
+ expect(new_upload.model_type).to eq('Project')
+ expect(new_upload.uploader).to eq('FileUploader')
+ end
+
+ it 'updates the legacy upload note so that it references the file in the markdown' do
+ expected_path = File.join('/uploads', new_upload.secret, 'image.png')
+ expected_markdown = "some note \n ![image](#{expected_path})"
+ expect(note.reload.note).to eq(expected_markdown)
+ end
+
+ it 'removes the attachment from the note model' do
+ expect(note.reload.attachment.file).to be_nil
+ end
+ end
+
+ context 'when no model found for the upload' do
+ before do
+ legacy_upload.model = nil
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the upload move fails' do
+ before do
+ expect(FileUploader).to receive(:copy_to).and_raise('failed')
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the upload is in local storage' do
+ shared_examples 'legacy local file' do
+ it 'removes the file correctly' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+
+ described_class.new(legacy_upload).execute
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ end
+
+ it 'moves legacy uploads to the correct location' do
+ described_class.new(legacy_upload).execute
+
+ expected_path = File.join(test_dir, 'uploads', project.disk_path, new_upload.secret, filename)
+ expect(File.exist?(expected_path)).to be_truthy
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ let(:legacy_upload) { create_upload(note, filename, false) }
+
+ before do
+ expect_error_log
+ end
+
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when an upload belongs to a legacy_diff_note' do
+ let!(:merge_request) { create(:merge_request, source_project: project) }
+ let!(:note) do
+ create(:legacy_diff_note_on_merge_request,
+ note: 'some note', project: project, noteable: merge_request)
+ end
+ let(:legacy_upload) do
+ create(:upload, :with_file, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ context 'when the file does not exist for the upload' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload,
+ path: "uploads/-/system/note/attachment/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file does not exist on expected path' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system/note/attachment/some_part/#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file path does not include system/note/attachment' do
+ let(:legacy_upload) do
+ create(:upload, :attachment_upload, :with_file,
+ path: "uploads/-/system#{note.id}/#{filename}", model: note)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file move raises an error' do
+ before do
+ allow(FileUtils).to receive(:mv).and_raise(Errno::EACCES)
+ end
+
+ it_behaves_like 'move error'
+ end
+
+ context 'when the file can be handled correctly' do
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is disabled for FileUploader' do
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+
+ context 'when object storage is enabled for FileUploader' do
+ # The process of migrating to object storage is a manual one,
+ # so it would go against expectations to automatically migrate these files
+ # to object storage during this migration.
+ # After this migration, these files should be able to successfully migrate to object storage.
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ context 'when the file belongs to a legacy project' do
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy local file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+ end
+
+ context 'when legacy uploads are stored in object storage' do
+ let(:legacy_upload) { create_remote_upload(note, filename) }
+ let(:remote_file) do
+ { key: "#{legacy_upload.path}" }
+ end
+ let(:connection) { ::Fog::Storage.new(FileUploader.object_store_credentials) }
+ let(:bucket) { connection.directories.create(key: 'uploads') }
+
+ before do
+ stub_uploads_object_storage(FileUploader)
+ end
+
+ shared_examples 'legacy remote file' do
+ it 'removes the file correctly' do
+ # expect(bucket.files.get(remote_file[:key])).to be_nil
+
+ described_class.new(legacy_upload).execute
+
+ expect(bucket.files.get(remote_file[:key])).to be_nil
+ end
+
+ it 'moves legacy uploads to the correct remote location' do
+ described_class.new(legacy_upload).execute
+
+ connection = ::Fog::Storage.new(FileUploader.object_store_credentials)
+ expect(connection.get_object('uploads', new_upload.path)[:status]).to eq(200)
+ end
+ end
+
+ context 'when the upload file does not exist on the filesystem' do
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a legacy project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { legacy_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+
+ context 'when the file belongs to a hashed project' do
+ before do
+ bucket.files.create(remote_file)
+ end
+
+ let(:project) { hashed_project }
+
+ it_behaves_like 'migrates the file correctly'
+ it_behaves_like 'legacy remote file'
+ it_behaves_like 'legacy upload deletion'
+ end
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
new file mode 100644
index 00000000000..ed8cbfeb11f
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/legacy_uploads_migrator_spec.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+# rubocop: disable RSpec/FactoriesInMigrationSpecs
+describe Gitlab::BackgroundMigration::LegacyUploadsMigrator do
+ let(:test_dir) { FileUploader.options['storage_path'] }
+
+ let!(:hashed_project) { create(:project) }
+ let!(:legacy_project) { create(:project, :legacy_storage) }
+ let!(:issue) { create(:issue, project: hashed_project) }
+ let!(:issue_legacy) { create(:issue, project: legacy_project) }
+
+ let!(:note1) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note2) { create(:note, project: hashed_project, noteable: issue) }
+ let!(:note_legacy) { create(:note, project: legacy_project, noteable: issue_legacy) }
+
+ def create_upload(model, with_file = true)
+ filename = 'image.png'
+ params = {
+ path: "uploads/-/system/note/attachment/#{model.id}/#{filename}",
+ model: model,
+ store: ObjectStorage::Store::LOCAL
+ }
+
+ if with_file
+ upload = create(:upload, :with_file, :attachment_upload, params)
+ model.update(attachment: upload.build_uploader)
+ model.attachment.upload
+ else
+ create(:upload, :attachment_upload, params)
+ end
+ end
+
+ let!(:legacy_upload) { create_upload(note1) }
+ let!(:legacy_upload_no_file) { create_upload(note2, false) }
+ let!(:legacy_upload_legacy_project) { create_upload(note_legacy) }
+
+ let(:start_id) { 1 }
+ let(:end_id) { 10000 }
+
+ subject { described_class.new.perform(start_id, end_id) }
+
+ it 'removes all legacy files' do
+ expect(File.exist?(legacy_upload.absolute_path)).to be_truthy
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_truthy
+
+ subject
+
+ expect(File.exist?(legacy_upload.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_no_file.absolute_path)).to be_falsey
+ expect(File.exist?(legacy_upload_legacy_project.absolute_path)).to be_falsey
+ end
+
+ it 'removes all AttachmentUploader records' do
+ expect { subject }.to change { Upload.where(uploader: 'AttachmentUploader').count }.from(3).to(0)
+ end
+
+ it 'creates new uploads for successfully migrated records' do
+ expect { subject }.to change { Upload.where(uploader: 'FileUploader').count }.from(0).to(2)
+ end
+end
+# rubocop: enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
index 582396275ed..a496f8416bf 100644
--- a/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_build_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateBuildStage, :migration, schema: 20180212101928 do
diff --git a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
index 2d1505dacfe..268626d58fd 100644
--- a/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_legacy_artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateLegacyArtifacts, :migration, schema: 20180816161409 do
diff --git a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
index 4db829b1e7b..1a8b0355fd9 100644
--- a/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_stage_index_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::MigrateStageIndex, :migration, schema: 20180420080616 do
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
index c76adcbe2f5..ea1eaa6417d 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_dependencies/untracked_file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
diff --git a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
index 0dee683350f..44f537ca8dd 100644
--- a/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_untracked_uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
@@ -114,7 +116,7 @@ describe Gitlab::BackgroundMigration::PopulateUntrackedUploads, :sidekiq, :migra
it 'does not drop the temporary tracking table after processing the batch, if there are still untracked rows' do
subject.perform(1, untracked_files_for_uploads.last.id - 1)
- expect(ActiveRecord::Base.connection.data_source_exists?(:untracked_files_for_uploads)).to be_truthy
+ expect(ActiveRecord::Base.connection.table_exists?(:untracked_files_for_uploads)).to be_truthy
end
it 'drops the temporary tracking table after processing the batch, if there are no untracked rows left' do
diff --git a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
index 35750d89c35..591368ee98e 100644
--- a/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
+++ b/spec/lib/gitlab/background_migration/prepare_untracked_uploads_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
# Rollback DB to 10.5 (later than this was originally written for) because it still needs to work.
diff --git a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
index d494ce68c5b..f877e8cc1b8 100644
--- a/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
+++ b/spec/lib/gitlab/background_migration/schedule_calculate_wiki_sizes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20190527194900_schedule_calculate_wiki_sizes.rb')
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
index 6f3fb994f17..3600755ada7 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnServices, :migration, schema: 20180122154930 do
diff --git a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
index 82b484b7d5b..5cd9c02fd3f 100644
--- a/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
+++ b/spec/lib/gitlab/background_migration/set_confidential_note_events_on_webhooks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration::SetConfidentialNoteEventsOnWebhooks, :migration, schema: 20180104131052 do
diff --git a/spec/lib/gitlab/background_migration_spec.rb b/spec/lib/gitlab/background_migration_spec.rb
index 1d0ffb5e9df..8960ac706e6 100644
--- a/spec/lib/gitlab/background_migration_spec.rb
+++ b/spec/lib/gitlab/background_migration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BackgroundMigration do
diff --git a/spec/lib/gitlab/badge/coverage/metadata_spec.rb b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
index 74eaf7eaf8b..2b87508bdef 100644
--- a/spec/lib/gitlab/badge/coverage/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
diff --git a/spec/lib/gitlab/badge/coverage/report_spec.rb b/spec/lib/gitlab/badge/coverage/report_spec.rb
index da789bf3705..eee3f96ab85 100644
--- a/spec/lib/gitlab/badge/coverage/report_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/report_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Coverage::Report do
diff --git a/spec/lib/gitlab/badge/coverage/template_spec.rb b/spec/lib/gitlab/badge/coverage/template_spec.rb
index d9c21a22590..b51d707a61d 100644
--- a/spec/lib/gitlab/badge/coverage/template_spec.rb
+++ b/spec/lib/gitlab/badge/coverage/template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Coverage::Template do
diff --git a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
index 9032a8e9016..b096803f921 100644
--- a/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/badge/shared/metadata'
diff --git a/spec/lib/gitlab/badge/pipeline/status_spec.rb b/spec/lib/gitlab/badge/pipeline/status_spec.rb
index dc835375c66..684c6829879 100644
--- a/spec/lib/gitlab/badge/pipeline/status_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Pipeline::Status do
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index bcef0b7e120..c0aaa3d73e1 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Badge::Pipeline::Template do
diff --git a/spec/lib/gitlab/badge/shared/metadata.rb b/spec/lib/gitlab/badge/shared/metadata.rb
index 63c7ca5a915..809fa54db02 100644
--- a/spec/lib/gitlab/badge/shared/metadata.rb
+++ b/spec/lib/gitlab/badge/shared/metadata.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'badge metadata' do
describe '#to_html' do
let(:html) { Nokogiri::HTML.parse(metadata.to_html) }
diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
index f4759b69538..0c1eedad7f4 100644
--- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
@@ -103,7 +105,7 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:project_path) { 'a-group/a-sub-group/a-project' }
let(:existing_group) do
@@ -188,20 +190,6 @@ describe Gitlab::BareRepositoryImport::Importer, :seed_helper do
end
end
- context 'when subgroups are not available' do
- let(:project_path) { 'a-group/a-sub-group/a-project' }
-
- before do
- expect(Group).to receive(:supports_nested_objects?) { false }
- end
-
- describe '#create_project_if_needed' do
- it 'raises an error' do
- expect { importer.create_project_if_needed }.to raise_error('Nested groups are not supported on MySQL')
- end
- end
- end
-
def prepare_repository(project_path, source_project)
repo_path = File.join(base_dir, project_path)
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index a07c5371134..0607e2232a1 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe ::Gitlab::BareRepositoryImport::Repository do
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 35700e0b588..3d0d3f91859 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::Importer do
@@ -23,12 +25,12 @@ describe Gitlab::BitbucketImport::Importer do
let(:reporters) do
[
nil,
- { "username" => "reporter1" },
+ { "nickname" => "reporter1" },
nil,
- { "username" => "reporter2" },
- { "username" => "reporter1" },
+ { "nickname" => "reporter2" },
+ { "nickname" => "reporter1" },
nil,
- { "username" => "reporter3" }
+ { "nickname" => "reporter3" }
]
end
@@ -113,6 +115,7 @@ describe Gitlab::BitbucketImport::Importer do
created_at: Time.now,
updated_at: Time.now)
end
+ let(:author_line) { "*Created by: someuser*\n\n" }
before do
allow(subject).to receive(:import_wiki)
@@ -126,7 +129,7 @@ describe Gitlab::BitbucketImport::Importer do
old_pos: nil,
new_pos: 4,
note: 'Hello world',
- author: 'root',
+ author: 'someuser',
created_at: Time.now,
updated_at: Time.now,
inline?: true,
@@ -137,7 +140,7 @@ describe Gitlab::BitbucketImport::Importer do
iid: 3,
file_path: '.gitmodules',
note: 'Hello world',
- author: 'root',
+ author: 'someuser',
created_at: Time.now,
updated_at: Time.now,
inline?: true,
@@ -161,17 +164,39 @@ describe Gitlab::BitbucketImport::Importer do
notes = merge_request.notes.order(:id).to_a
start_note = notes.first
expect(start_note).to be_a(DiffNote)
- expect(start_note.note).to eq(@inline_note.note)
+ expect(start_note.note).to include(@inline_note.note)
+ expect(start_note.note).to include(author_line)
reply_note = notes.last
expect(reply_note).to be_a(DiffNote)
- expect(reply_note.note).to eq(@reply.note)
+ expect(reply_note.note).to include(@reply.note)
+ expect(reply_note.note).to include(author_line)
+ end
+
+ context 'when user exists in GitLab' do
+ let!(:existing_user) { create(:user, username: 'someuser') }
+ let!(:identity) { create(:identity, provider: 'bitbucket', extern_uid: existing_user.username, user: existing_user) }
+
+ it 'does not add author line to comments' do
+ expect { subject.execute }.to change { MergeRequest.count }.by(1)
+
+ merge_request = MergeRequest.first
+
+ notes = merge_request.notes.order(:id).to_a
+ start_note = notes.first
+ expect(start_note.note).to eq(@inline_note.note)
+ expect(start_note.note).not_to include(author_line)
+
+ reply_note = notes.last
+ expect(reply_note.note).to eq(@reply.note)
+ expect(reply_note.note).not_to include(author_line)
+ end
end
context 'when importing a pull request throws an exception' do
before do
allow(pull_request).to receive(:raw).and_return('hello world')
- allow(subject.client).to receive(:pull_request_comments).and_raise(HTTParty::Error)
+ allow(subject.client).to receive(:pull_request_comments).and_raise(Gitlab::HTTP::Error)
end
it 'logs an error without the backtrace' do
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index e2bee22cf1f..0dd8547a925 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::ProjectCreator do
diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
index 795fd069ab2..7b5c7847f2d 100644
--- a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketImport::WikiFormatter do
diff --git a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
index cc09804fd53..8ab7b2c5fa7 100644
--- a/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_server_import/importer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BitbucketServerImport::Importer do
diff --git a/spec/lib/gitlab/blame_spec.rb b/spec/lib/gitlab/blame_spec.rb
index 7cab04e9fc9..e1afd5b25bb 100644
--- a/spec/lib/gitlab/blame_spec.rb
+++ b/spec/lib/gitlab/blame_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Blame do
diff --git a/spec/lib/gitlab/build_access_spec.rb b/spec/lib/gitlab/build_access_spec.rb
index 08f50bf4fac..b7af8ace5b5 100644
--- a/spec/lib/gitlab/build_access_spec.rb
+++ b/spec/lib/gitlab/build_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::BuildAccess do
diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
index 972dd7e0d2b..91e7edaf704 100644
--- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
+++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
@@ -26,7 +28,6 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do
end
it 'loads 10 projects without hitting Gitaly call limit', :request_store do
- allow(Gitlab::GitalyClient).to receive(:can_access_disk?).and_return(false)
projects = Gitlab::GitalyClient.allow_n_plus_1_calls do
(1..10).map { create(:project, :repository) }
end
diff --git a/spec/lib/gitlab/cache/request_cache_spec.rb b/spec/lib/gitlab/cache/request_cache_spec.rb
index 5b82c216a13..70a7f090d0a 100644
--- a/spec/lib/gitlab/cache/request_cache_spec.rb
+++ b/spec/lib/gitlab/cache/request_cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Cache::RequestCache do
diff --git a/spec/lib/gitlab/changes_list_spec.rb b/spec/lib/gitlab/changes_list_spec.rb
index 464508fcd73..911450f3a8b 100644
--- a/spec/lib/gitlab/changes_list_spec.rb
+++ b/spec/lib/gitlab/changes_list_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::ChangesList do
diff --git a/spec/lib/gitlab/chat_name_token_spec.rb b/spec/lib/gitlab/chat_name_token_spec.rb
index 1e9fb9077fc..b2d4a466021 100644
--- a/spec/lib/gitlab/chat_name_token_spec.rb
+++ b/spec/lib/gitlab/chat_name_token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ChatNameToken do
diff --git a/spec/lib/gitlab/chat_spec.rb b/spec/lib/gitlab/chat_spec.rb
index d61c4b36668..08cc16314c5 100644
--- a/spec/lib/gitlab/chat_spec.rb
+++ b/spec/lib/gitlab/chat_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Chat, :use_clean_rails_memory_store_caching do
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 45fb33e9e4a..3a8e8f67e16 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::ChangeAccess do
diff --git a/spec/lib/gitlab/checks/force_push_spec.rb b/spec/lib/gitlab/checks/force_push_spec.rb
index 0e0788ce974..9432be083d3 100644
--- a/spec/lib/gitlab/checks/force_push_spec.rb
+++ b/spec/lib/gitlab/checks/force_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::ForcePush do
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 887ea8fc1e0..88e8f5d74d1 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Checks::LfsIntegrity do
diff --git a/spec/lib/gitlab/checks/project_created_spec.rb b/spec/lib/gitlab/checks/project_created_spec.rb
index ac02007e111..14cb5e6ec66 100644
--- a/spec/lib/gitlab/checks/project_created_spec.rb
+++ b/spec/lib/gitlab/checks/project_created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::Checks::ProjectCreated, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb
index 8e9386b1ba1..3ca977aa48d 100644
--- a/spec/lib/gitlab/checks/project_moved_spec.rb
+++ b/spec/lib/gitlab/checks/project_moved_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb
index 3d57ce431ab..c8afcbd053d 100644
--- a/spec/lib/gitlab/ci/ansi2html_spec.rb
+++ b/spec/lib/gitlab/ci/ansi2html_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Ansi2html do
subject { described_class }
it "prints non-ansi as-is" do
- expect(convert_html("Hello")).to eq('<span class="">Hello</span>')
+ expect(convert_html("Hello")).to eq('<span>Hello</span>')
end
it "strips non-color-changing control sequences" do
- expect(convert_html("Hello \e[2Kworld")).to eq('<span class="">Hello world</span>')
+ expect(convert_html("Hello \e[2Kworld")).to eq('<span>Hello world</span>')
end
it "prints simply red" do
@@ -32,7 +34,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets colors after red on blue" do
- expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span> world</span>')
end
it "performs color change from red/blue to yellow/blue" do
@@ -44,11 +46,11 @@ describe Gitlab::Ci::Ansi2html do
end
it "performs color change from red/blue to reset to yellow/green" do
- expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> </span><span class="term-fg-yellow term-bg-green">world</span>')
+ expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span> </span><span class="term-fg-yellow term-bg-green">world</span>')
end
it "ignores unsupported codes" do
- expect(convert_html("\e[51mHello\e[0m")).to eq('<span class="">Hello</span>')
+ expect(convert_html("\e[51mHello\e[0m")).to eq('<span>Hello</span>')
end
it "prints light red" do
@@ -72,8 +74,8 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets bold text" do
- expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
- expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span> world</span>')
+ expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span> world</span>')
end
it "prints italic text" do
@@ -81,7 +83,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets italic text" do
- expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span> world</span>')
end
it "prints underlined text" do
@@ -89,7 +91,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets underlined text" do
- expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span> world</span>')
end
it "prints concealed text" do
@@ -97,7 +99,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets concealed text" do
- expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span> world</span>')
end
it "prints crossed-out text" do
@@ -105,7 +107,7 @@ describe Gitlab::Ci::Ansi2html do
end
it "resets crossed-out text" do
- expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span class=""> world</span>')
+ expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span> world</span>')
end
it "can print 256 xterm fg colors" do
@@ -137,15 +139,15 @@ describe Gitlab::Ci::Ansi2html do
end
it "prints &lt;" do
- expect(convert_html("<")).to eq('<span class="">&lt;</span>')
+ expect(convert_html("<")).to eq('<span>&lt;</span>')
end
it "replaces newlines with line break tags" do
- expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>')
+ expect(convert_html("\n")).to eq('<span><br/></span>')
end
it "groups carriage returns with newlines" do
- expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>')
+ expect(convert_html("\r\n")).to eq('<span><br/></span>')
end
describe "incremental update" do
@@ -182,7 +184,7 @@ describe Gitlab::Ci::Ansi2html do
context "with partial sequence" do
let(:pre_text) { "Hello\e" }
- let(:pre_html) { "<span class=\"\">Hello</span>" }
+ let(:pre_html) { "<span>Hello</span>" }
let(:text) { "[1m World" }
let(:html) { "<span class=\"term-bold\"> World</span>" }
@@ -191,9 +193,9 @@ describe Gitlab::Ci::Ansi2html do
context 'with new line' do
let(:pre_text) { "Hello\r" }
- let(:pre_html) { "<span class=\"\">Hello\r</span>" }
+ let(:pre_html) { "<span>Hello\r</span>" }
let(:text) { "\nWorld" }
- let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" }
+ let(:html) { "<span><br/>World</span>" }
it_behaves_like 'stateable converter'
end
@@ -207,7 +209,7 @@ describe Gitlab::Ci::Ansi2html do
let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
let(:section_start_html) do
- '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"' \
+ '<div class="js-section-start section-start fa fa-caret-down pr-2 cursor-pointer"' \
" data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \
' role="button"></div>'
end
@@ -220,7 +222,7 @@ describe Gitlab::Ci::Ansi2html do
text = "#{section_start}Some text#{section_end}"
class_name_start = section_start.gsub("\033[0K", '').gsub('<', '&lt;')
class_name_end = section_end.gsub("\033[0K", '').gsub('<', '&lt;')
- html = %{<span class="">#{class_name_start}Some text#{class_name_end}</span>}
+ html = %{<span>#{class_name_start}Some text#{class_name_end}</span>}
expect(convert_html(text)).to eq(html)
end
@@ -230,12 +232,11 @@ describe Gitlab::Ci::Ansi2html do
let(:text) { "#{section_start}Some text#{section_end}" }
it 'prints light red' do
- text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
- header = %{<span class="term-fg-l-red section js-section-header section-header js-s-#{class_name(section_name)}">Hello</span>}
- line_break = %{<span class="section js-section-header section-header js-s-#{class_name(section_name)}"><br/></span>}
- line = %{<span class="section line s_#{class_name(section_name)}"></span>}
- empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>}
- html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}"
+ text = "#{section_start}\e[91mHello\e[0m\nLine 1\nLine 2\nLine 3\n#{section_end}"
+ header = %{<span class="term-fg-l-red section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}">Hello</span>}
+ line_break = %{<span class="section js-section-header section-header cursor-pointer js-s-#{class_name(section_name)}"><br/></span>}
+ output_line = %{<span class="section line js-s-#{class_name(section_name)}">Line 1<br/>Line 2<br/>Line 3<br/></span>}
+ html = "#{section_start_html}#{header}#{line_break}#{output_line}#{section_end_html}"
expect(convert_html(text)).to eq(html)
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
index 987c6b37aaa..cec3e70bb9f 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/gzip_stream_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Adapters::GzipStream do
diff --git a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
index ec2dd724b45..66a234232e1 100644
--- a/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/adapters/raw_stream_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Adapters::RawStream do
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 3b905611467..24d17eb0fb3 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index a9a4af1f455..ff189c4701e 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Metadata do
diff --git a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
index 7bd6a2ead25..7bbef0f5197 100644
--- a/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/path_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Artifacts::Path do
diff --git a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
index d53db05e5e6..9148c0d579e 100644
--- a/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Credentials::Factory do
diff --git a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
index c6054138cde..552580dcbbe 100644
--- a/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
+++ b/spec/lib/gitlab/ci/build/credentials/registry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Credentials::Registry do
diff --git a/spec/lib/gitlab/ci/build/image_spec.rb b/spec/lib/gitlab/ci/build/image_spec.rb
index 6e20e0ef5c3..04bab9c58b8 100644
--- a/spec/lib/gitlab/ci/build/image_spec.rb
+++ b/spec/lib/gitlab/ci/build/image_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Image do
diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
index 92cf0376c02..48ac2e4e657 100644
--- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Changes do
diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
index 4510b82ca9d..bc2e6fe6b8d 100644
--- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Kubernetes do
diff --git a/spec/lib/gitlab/ci/build/policy/refs_spec.rb b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
index 22ca681cfd3..43c5d3ec980 100644
--- a/spec/lib/gitlab/ci/build/policy/refs_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/refs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Refs do
diff --git a/spec/lib/gitlab/ci/build/policy/variables_spec.rb b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
index 9b016901a20..7140c14facb 100644
--- a/spec/lib/gitlab/ci/build/policy/variables_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy::Variables do
@@ -11,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: '')
@@ -81,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'])
@@ -89,5 +101,38 @@ describe Gitlab::Ci::Build::Policy::Variables do
expect(policy).to be_satisfied_by(pipeline, seed)
end
end
+
+ context 'when using project ci variables in environment scope' do
+ let(:ci_build) do
+ build(:ci_build, pipeline: pipeline,
+ project: project,
+ ref: 'master',
+ stage: 'review',
+ environment: 'test/$CI_JOB_STAGE/1')
+ end
+
+ before do
+ create(:ci_variable, project: project,
+ key: 'SCOPED_VARIABLE',
+ value: 'my-value-1')
+
+ create(:ci_variable, project: project,
+ key: 'SCOPED_VARIABLE',
+ value: 'my-value-2',
+ environment_scope: 'test/review/*')
+ end
+
+ it 'is satisfied by scoped variable match' do
+ policy = described_class.new(['$SCOPED_VARIABLE == "my-value-2"'])
+
+ expect(policy).to be_satisfied_by(pipeline, seed)
+ end
+
+ it 'is not satisfied when matching against overridden variable' do
+ policy = described_class.new(['$SCOPED_VARIABLE == "my-value-1"'])
+
+ expect(policy).not_to be_satisfied_by(pipeline, seed)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/build/policy_spec.rb b/spec/lib/gitlab/ci/build/policy_spec.rb
index 20ee3dd3e89..80d7b2e9dc8 100644
--- a/spec/lib/gitlab/ci/build/policy_spec.rb
+++ b/spec/lib/gitlab/ci/build/policy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Policy do
diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
index d88a2097ba2..775550f2acc 100644
--- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -3,9 +3,9 @@
require 'spec_helper'
describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
- let(:build) { create(:ci_build) }
-
describe '#unmet?' do
+ let(:build) { create(:ci_build) }
+
subject { described_class.new(build).unmet? }
context 'build has no deployment' do
@@ -18,7 +18,6 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
context 'build has a deployment' do
let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) }
- let(:cluster) { nil }
context 'and a cluster to deploy to' do
let(:cluster) { create(:cluster, :group) }
@@ -32,12 +31,17 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'and a namespace is already created for this project' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: build.project) }
+ let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: 'token') }
+
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: kubernetes_namespace))
+ end
it { is_expected.to be_falsey }
context 'and the service_account_token is blank' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :without_token, cluster: cluster, project: build.project) }
+ let(:kubernetes_namespace) { instance_double(Clusters::KubernetesNamespace, service_account_token: nil) }
it { is_expected.to be_truthy }
end
@@ -45,34 +49,79 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
end
context 'and no cluster to deploy to' do
+ let(:cluster) { nil }
+
it { is_expected.to be_falsey }
end
end
end
describe '#complete!' do
- let!(:deployment) { create(:deployment, deployable: build, cluster: cluster) }
- let(:service) { double(execute: true) }
- let(:cluster) { nil }
+ let(:build) { create(:ci_build) }
+ let(:prerequisite) { described_class.new(build) }
- subject { described_class.new(build).complete! }
+ subject { prerequisite.complete! }
context 'completion is required' do
let(:cluster) { create(:cluster, :group) }
+ let(:deployment) { create(:deployment, cluster: cluster) }
+ let(:service) { double(execute: true) }
+ let(:kubernetes_namespace) { double }
+
+ before do
+ allow(prerequisite).to receive(:unmet?).and_return(true)
+ allow(build).to receive(:deployment).and_return(deployment)
+ end
+
+ context 'kubernetes namespace does not exist' do
+ let(:namespace_builder) { double(execute: kubernetes_namespace)}
- it 'creates a kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
- .to receive(:new)
- .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace))
- .and_return(service)
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: nil))
+ end
- expect(service).to receive(:execute).once
+ it 'creates a namespace using a new record' do
+ expect(Clusters::BuildKubernetesNamespaceService)
+ .to receive(:new)
+ .with(cluster, environment: deployment.environment)
+ .and_return(namespace_builder)
- subject
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
+ .and_return(service)
+
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'kubernetes namespace exists (but has no service_account_token)' do
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .and_return(double(execute: kubernetes_namespace))
+ end
+
+ it 'creates a namespace using the tokenless record' do
+ expect(Clusters::BuildKubernetesNamespaceService).not_to receive(:new)
+
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: kubernetes_namespace)
+ .and_return(service)
+
+ subject
+ end
end
end
context 'completion is not required' do
+ before do
+ allow(prerequisite).to receive(:unmet?).and_return(false)
+ end
+
it 'does not create a namespace' do
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
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/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb
index e3136fc925e..84e6e0e177f 100644
--- a/spec/lib/gitlab/ci/build/step_spec.rb
+++ b/spec/lib/gitlab/ci/build/step_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Build::Step do
diff --git a/spec/lib/gitlab/ci/charts_spec.rb b/spec/lib/gitlab/ci/charts_spec.rb
index 1668d3bbaac..cfb7a3f72fa 100644
--- a/spec/lib/gitlab/ci/charts_spec.rb
+++ b/spec/lib/gitlab/ci/charts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Charts do
diff --git a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
index bd1f2c92844..a7f457e0f5e 100644
--- a/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/artifacts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Artifacts do
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 8f711e02f9b..4cb63168ec7 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Cache do
diff --git a/spec/lib/gitlab/ci/config/entry/commands_spec.rb b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
index 8934aeb83db..269a3406913 100644
--- a/spec/lib/gitlab/ci/config/entry/commands_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/commands_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Commands do
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 4c6bd859552..48d0864cfca 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Coverage do
diff --git a/spec/lib/gitlab/ci/config/entry/default_spec.rb b/spec/lib/gitlab/ci/config/entry/default_spec.rb
index a901dd80c2c..27d63dbd407 100644
--- a/spec/lib/gitlab/ci/config/entry/default_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/default_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Default do
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index 0bc9e8bd3cd..7b72b45fd8d 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Environment do
diff --git a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
index c88ee10550c..40b73352676 100644
--- a/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/hidden_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Hidden do
diff --git a/spec/lib/gitlab/ci/config/entry/image_spec.rb b/spec/lib/gitlab/ci/config/entry/image_spec.rb
index 1ebdda398b9..8de2e5de724 100644
--- a/spec/lib/gitlab/ci/config/entry/image_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/image_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Image do
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 25766d34c65..1853efde350 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Job do
@@ -9,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
@@ -84,6 +86,31 @@ describe Gitlab::Ci::Config::Entry::Job do
it { expect(entry).to be_valid }
end
end
+
+ context 'when has needs' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ needs: ['another-job']
+ }
+ end
+
+ it { expect(entry).to be_valid }
+
+ context 'when has dependencies' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ dependencies: ['another-job'],
+ needs: ['another-job']
+ }
+ end
+
+ it { expect(entry).to be_valid }
+ end
+ end
end
context 'when entry value is not correct' do
@@ -174,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
@@ -213,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' } }
@@ -221,6 +357,66 @@ describe Gitlab::Ci::Config::Entry::Job do
expect(entry.errors).to include 'job start in must be blank'
end
end
+
+ context 'when has dependencies' do
+ context 'that are not a array of strings' do
+ let(:config) do
+ { script: 'echo', dependencies: 'build-job' }
+ end
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job dependencies should be an array of strings'
+ end
+ end
+ end
+
+ context 'when has needs' do
+ context 'that are not a array of strings' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ needs: 'build-job'
+ }
+ end
+
+ it 'returns error about invalid type' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job needs should be an array of strings'
+ end
+ end
+
+ context 'when have dependencies that are not subset of needs' do
+ let(:config) do
+ {
+ stage: 'test',
+ script: 'echo',
+ dependencies: ['another-job'],
+ needs: ['build-job']
+ }
+ end
+
+ it 'returns error about invalid data' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job dependencies the another-job should be part of needs'
+ end
+ end
+
+ context 'when stage: is missing' do
+ let(:config) do
+ {
+ script: 'echo',
+ needs: ['build-job']
+ }
+ end
+
+ it 'returns error about invalid data' do
+ expect(entry).not_to be_valid
+ expect(entry.errors).to include 'job config missing required keys: stage'
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index 3e4137930dc..61c8956d41f 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Jobs do
diff --git a/spec/lib/gitlab/ci/config/entry/key_spec.rb b/spec/lib/gitlab/ci/config/entry/key_spec.rb
index 3cbf19bea8b..a7874447725 100644
--- a/spec/lib/gitlab/ci/config/entry/key_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/key_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Key do
diff --git a/spec/lib/gitlab/ci/config/entry/paths_spec.rb b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
index 1d9c5ddee9b..221d5ae5863 100644
--- a/spec/lib/gitlab/ci/config/entry/paths_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/paths_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Paths do
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index fba5671594d..a606eb303e7 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'support/helpers/stub_feature_flags'
require_dependency 'active_model'
@@ -49,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)
@@ -111,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)
@@ -202,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/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
index 38943138cbf..3c352c30e55 100644
--- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Reports do
diff --git a/spec/lib/gitlab/ci/config/entry/retry_spec.rb b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
index 164a9ed4c3d..f9efd2e014d 100644
--- a/spec/lib/gitlab/ci/config/entry/retry_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/retry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Retry do
diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb
index 7a252ed675a..968dbb9c7f2 100644
--- a/spec/lib/gitlab/ci/config/entry/root_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Root 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/config/entry/script_spec.rb b/spec/lib/gitlab/ci/config/entry/script_spec.rb
index 069eaa26422..d523243d3b6 100644
--- a/spec/lib/gitlab/ci/config/entry/script_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/script_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Script do
diff --git a/spec/lib/gitlab/ci/config/entry/service_spec.rb b/spec/lib/gitlab/ci/config/entry/service_spec.rb
index d31866a1987..66cca100688 100644
--- a/spec/lib/gitlab/ci/config/entry/service_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Service do
diff --git a/spec/lib/gitlab/ci/config/entry/services_spec.rb b/spec/lib/gitlab/ci/config/entry/services_spec.rb
index d5a1316f665..764f783b083 100644
--- a/spec/lib/gitlab/ci/config/entry/services_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/services_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Services do
diff --git a/spec/lib/gitlab/ci/config/entry/stage_spec.rb b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
index 70c8a0a355a..574fa00575a 100644
--- a/spec/lib/gitlab/ci/config/entry/stage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Stage do
diff --git a/spec/lib/gitlab/ci/config/entry/stages_spec.rb b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
index 182c8d867c7..97970522104 100644
--- a/spec/lib/gitlab/ci/config/entry/stages_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/stages_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Stages do
diff --git a/spec/lib/gitlab/ci/config/entry/variables_spec.rb b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
index 84bfef9e8ad..1320b366367 100644
--- a/spec/lib/gitlab/ci/config/entry/variables_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/variables_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config::Entry::Variables do
diff --git a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
index d63612053b6..e00104e3c68 100644
--- a/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable/entry_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable::Entry do
diff --git a/spec/lib/gitlab/ci/config/extendable_spec.rb b/spec/lib/gitlab/ci/config/extendable_spec.rb
index 90213f6603d..874b224067b 100644
--- a/spec/lib/gitlab/ci/config/extendable_spec.rb
+++ b/spec/lib/gitlab/ci/config/extendable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Config::Extendable do
diff --git a/spec/lib/gitlab/ci/config/normalizer_spec.rb b/spec/lib/gitlab/ci/config/normalizer_spec.rb
index cd880177170..6b766cc37bf 100644
--- a/spec/lib/gitlab/ci/config/normalizer_spec.rb
+++ b/spec/lib/gitlab/ci/config/normalizer_spec.rb
@@ -49,37 +49,44 @@ describe Gitlab::Ci::Config::Normalizer do
end
end
- context 'when jobs depend on parallelized jobs' do
- let(:config) { { job_name => job_config, other_job: { script: 'echo 1', dependencies: [job_name.to_s] } } }
-
- it 'parallelizes dependencies' do
- job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
-
- expect(subject[:other_job][:dependencies]).to include(*job_names)
+ %i[dependencies needs].each do |context|
+ context "when job has #{context} on parallelized jobs" do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: { script: 'echo 1', context => [job_name.to_s] }
+ }
+ end
+
+ it "parallelizes #{context}" do
+ job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
+
+ expect(subject[:other_job][context]).to include(*job_names)
+ end
+
+ it "does not include original job name in #{context}" do
+ expect(subject[:other_job][context]).not_to include(job_name)
+ end
end
- it 'does not include original job name in dependencies' do
- expect(subject[:other_job][:dependencies]).not_to include(job_name)
- end
- end
+ context "when there are #{context} which are both parallelized and not" do
+ let(:config) do
+ {
+ job_name => job_config,
+ other_job: { script: 'echo 1' },
+ final_job: { script: 'echo 1', context => [job_name.to_s, "other_job"] }
+ }
+ end
- context 'when there are dependencies which are both parallelized and not' do
- let(:config) do
- {
- job_name => job_config,
- other_job: { script: 'echo 1' },
- final_job: { script: 'echo 1', dependencies: [job_name.to_s, "other_job"] }
- }
- end
-
- it 'parallelizes dependencies' do
- job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
+ it "parallelizes #{context}" do
+ job_names = ["rspec 1/5", "rspec 2/5", "rspec 3/5", "rspec 4/5", "rspec 5/5"]
- expect(subject[:final_job][:dependencies]).to include(*job_names)
- end
+ expect(subject[:final_job][context]).to include(*job_names)
+ end
- it 'includes the regular job in dependencies' do
- expect(subject[:final_job][:dependencies]).to include('other_job')
+ it "includes the regular job in #{context}" do
+ expect(subject[:final_job][context]).to include('other_job')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb
index 4e8bff3d738..986cde3540a 100644
--- a/spec/lib/gitlab/ci/config_spec.rb
+++ b/spec/lib/gitlab/ci/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Config do
diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb
index 491e3fba9d9..af4e9d687c4 100644
--- a/spec/lib/gitlab/ci/cron_parser_spec.rb
+++ b/spec/lib/gitlab/ci/cron_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::CronParser do
diff --git a/spec/lib/gitlab/ci/mask_secret_spec.rb b/spec/lib/gitlab/ci/mask_secret_spec.rb
index 3789a142248..6607aaae399 100644
--- a/spec/lib/gitlab/ci/mask_secret_spec.rb
+++ b/spec/lib/gitlab/ci/mask_secret_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::MaskSecret do
diff --git a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
index a49402c7398..8ff60710f67 100644
--- a/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
+++ b/spec/lib/gitlab/ci/parsers/test/junit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Parsers::Test::Junit do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 50cb45c39d1..bf9ff922c05 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Build do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index 5181e9c1583..5775e934cfd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Command do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
index 0edc3f315bb..650ab193997 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/create_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Create do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
index 0302e4090cf..9bccd5be4fe 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/populate_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Populate do
@@ -36,8 +38,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
it 'populates pipeline with stages' do
expect(pipeline.stages).to be_one
expect(pipeline.stages.first).not_to be_persisted
- expect(pipeline.stages.first.builds).to be_one
- expect(pipeline.stages.first.builds.first).not_to be_persisted
+ expect(pipeline.stages.first.statuses).to be_one
+ expect(pipeline.stages.first.statuses.first).not_to be_persisted
end
it 'correctly assigns user' do
@@ -189,8 +191,8 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
step.perform!
expect(pipeline.stages.size).to eq 1
- expect(pipeline.stages.first.builds.size).to eq 1
- expect(pipeline.stages.first.builds.first.name).to eq 'rspec'
+ expect(pipeline.stages.first.statuses.size).to eq 1
+ expect(pipeline.stages.first.statuses.first.name).to eq 'rspec'
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
index eca23694a2b..9cb59442dfd 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/sequence_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Sequence do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
index c7f4fc98ca3..fe46633ed1b 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/skip_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Skip do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
index b3e58c3dfdb..ac370433955 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/abilities_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Abilities do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index 00bbc4c817a..79acd3e4f54 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
index 2e8c9d70098..b866355906e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/repository_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Chain::Validate::Repository do
diff --git a/spec/lib/gitlab/ci/pipeline/duration_spec.rb b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
index 7c9836e2da6..a4984092f35 100644
--- a/spec/lib/gitlab/ci/pipeline/duration_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/duration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Duration do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
index 006ce4d8078..847d613dba3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/and_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'rspec-parameterized'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
index fcbd2863289..0e13681a4cf 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/equals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Equals do
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 a6fdec832a3..4e4f1bf6ad3 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 're2'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
index 38d30c9035a..a3a48f83b27 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/not_equals_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::NotEquals do
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 99110ff8d88..6b81008ffb1 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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require_dependency 're2'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
index b5a59929e11..7013c6bacbb 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/null_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Null do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
index d542eebc613..15505ebc82b 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/or_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'rspec-parameterized'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
index 6ce4b321397..2cc25a07417 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/pattern_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Pattern do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
index f54ef492e6d..2a6b90d127f 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/string_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::String do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
index 599a5411881..29e26930249 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/variable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexeme::Variable do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
index 7c98e729b0b..2b0cee2d6f2 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/lexer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Lexer do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
index e88ec5561b6..10adfa18af6 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Parser do
diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
index b259ef711aa..79ee4d07e3a 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
require 'rspec-parameterized'
diff --git a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
index cedfe270f9d..aa807cecb72 100644
--- a/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/expression/token_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fast_spec_helper'
describe Gitlab::Ci::Pipeline::Expression::Token do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 46ea0d7554b..89431b80be3 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -6,33 +6,104 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:attributes) { { name: 'rspec', ref: 'master' } }
+ let(:previous_stages) { [] }
- let(:seed_build) { described_class.new(pipeline, attributes) }
+ let(:seed_build) { described_class.new(pipeline, attributes, previous_stages) }
describe '#attributes' do
subject { seed_build.attributes }
it { is_expected.to be_a(Hash) }
it { is_expected.to include(:name, :project, :ref) }
+
+ context 'with job:when' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'on_failure' } }
+
+ it { is_expected.to include(when: 'on_failure') }
+ end
+
+ context 'with job:when:delayed' do
+ let(:attributes) { { name: 'rspec', ref: 'master', when: 'delayed', start_in: '3 hours' } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'with job:rules:[when:]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'always') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'always' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules:[when:delayed]' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'delayed', start_in: '3 hours') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null', when: 'delayed', start_in: '3 hours' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
+
+ context 'with job:rules but no explicit when:' do
+ context 'is matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR == null' }] } }
+
+ it { is_expected.to include(when: 'on_success') }
+ end
+
+ context 'is not matched' do
+ let(:attributes) { { name: 'rspec', ref: 'master', rules: [{ if: '$VAR != null' }] } }
+
+ it { is_expected.to include(when: 'never') }
+ end
+ end
end
describe '#bridge?' do
subject { seed_build.bridge? }
- context 'when job is a bridge' do
+ context 'when job is a downstream bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
it { is_expected.to be_truthy }
+
+ context 'when trigger definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
- context 'when trigger definition is empty' do
+ context 'when job is an upstream bridge' do
let(:attributes) do
- { name: 'rspec', ref: 'master', options: { trigger: '' } }
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: 'my/project' } } }
end
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
+
+ context 'when upstream definition is empty' do
+ let(:attributes) do
+ { name: 'rspec', ref: 'master', options: { bridge_needs: { pipeline: '' } } }
+ end
+
+ it { is_expected.to be_falsey }
+ end
end
context 'when job is not a bridge' do
@@ -349,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] } }
@@ -380,5 +467,298 @@ 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
+ subject { seed_build }
+
+ let(:needs_count) { 1 }
+
+ let(:needs_attributes) do
+ Array.new(needs_count, name: 'build')
+ end
+
+ let(:attributes) do
+ {
+ name: 'rspec',
+ needs_attributes: needs_attributes
+ }
+ end
+
+ context 'when build job is not present in prior stages' do
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: needs 'build'")
+ end
+ end
+
+ context 'when build job is part of prior stages' do
+ let(:stage_attributes) do
+ {
+ name: 'build',
+ index: 0,
+ builds: [{ name: 'build' }]
+ }
+ end
+
+ let(:stage_seed) do
+ Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, stage_attributes, [])
+ end
+
+ let(:previous_stages) { [stage_seed] }
+
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ context 'when lower limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: true)
+ end
+
+ let(:needs_count) { described_class::LOW_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 5 others, but you have listed 6. See needs keyword documentation for more details")
+ end
+ end
+
+ context 'when upper limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: false)
+ end
+
+ let(:needs_count) { described_class::HARD_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 50 others, but you have listed 51. See needs keyword documentation for more details")
+ 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/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 493ca3cd7b5..a13335f63d5 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -1,8 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Stage do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:previous_stages) { [] }
let(:attributes) do
{ name: 'test',
@@ -13,7 +16,7 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
subject do
- described_class.new(pipeline, attributes)
+ described_class.new(pipeline, attributes, previous_stages)
end
describe '#size' do
@@ -107,6 +110,27 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
end
+ describe '#seeds_names' do
+ it 'returns all job names' do
+ expect(subject.seeds_names).to contain_exactly(
+ 'rspec', 'spinach')
+ end
+
+ it 'returns a set' do
+ expect(subject.seeds_names).to be_a(Set)
+ end
+ end
+
+ describe '#seeds_errors' do
+ it 'returns all errors from seeds' do
+ expect(subject.seeds.first)
+ .to receive(:errors) { ["build error"] }
+
+ expect(subject.errors).to contain_exactly(
+ "build error")
+ end
+ end
+
describe '#to_resource' do
it 'builds a valid stage object with all builds' do
subject.to_resource.save!
diff --git a/spec/lib/gitlab/ci/reports/test_case_spec.rb b/spec/lib/gitlab/ci/reports/test_case_spec.rb
index 6932f79f0ce..20c489ee94c 100644
--- a/spec/lib/gitlab/ci/reports/test_case_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_case_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestCase do
diff --git a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
index 36582204cc1..48eef0643b2 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_comparer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestReportsComparer do
diff --git a/spec/lib/gitlab/ci/reports/test_reports_spec.rb b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
index 74ff134b239..0b5d05bada3 100644
--- a/spec/lib/gitlab/ci/reports/test_reports_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_reports_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestReports do
diff --git a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
index 579b3e6fd24..cf4690bb334 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_comparer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestSuiteComparer do
diff --git a/spec/lib/gitlab/ci/reports/test_suite_spec.rb b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
index cd34dbaf62f..8646db43bc8 100644
--- a/spec/lib/gitlab/ci/reports/test_suite_spec.rb
+++ b/spec/lib/gitlab/ci/reports/test_suite_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Reports::TestSuite do
diff --git a/spec/lib/gitlab/ci/status/build/action_spec.rb b/spec/lib/gitlab/ci/status/build/action_spec.rb
index bdec582b57b..3aae7e18d6d 100644
--- a/spec/lib/gitlab/ci/status/build/action_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/action_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Action do
diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
index 78d6fa65b5a..3841dae91c7 100644
--- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Cancelable do
diff --git a/spec/lib/gitlab/ci/status/build/canceled_spec.rb b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
index c6b5cc68770..4b43c78f1a7 100644
--- a/spec/lib/gitlab/ci/status/build/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/canceled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Canceled do
diff --git a/spec/lib/gitlab/ci/status/build/common_spec.rb b/spec/lib/gitlab/ci/status/build/common_spec.rb
index ca3c66f0152..5114540708f 100644
--- a/spec/lib/gitlab/ci/status/build/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Common do
diff --git a/spec/lib/gitlab/ci/status/build/created_spec.rb b/spec/lib/gitlab/ci/status/build/created_spec.rb
index 8bdfe6ef7a2..6e3aa442810 100644
--- a/spec/lib/gitlab/ci/status/build/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Created do
diff --git a/spec/lib/gitlab/ci/status/build/erased_spec.rb b/spec/lib/gitlab/ci/status/build/erased_spec.rb
index 0acd271e375..af9c296da0c 100644
--- a/spec/lib/gitlab/ci/status/build/erased_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/erased_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Erased do
diff --git a/spec/lib/gitlab/ci/status/build/factory_spec.rb b/spec/lib/gitlab/ci/status/build/factory_spec.rb
index b6231510b91..de489fa4664 100644
--- a/spec/lib/gitlab/ci/status/build/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Factory do
diff --git a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
index 2a5915d75d0..01500689619 100644
--- a/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_allowed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::FailedAllowed do
diff --git a/spec/lib/gitlab/ci/status/build/failed_spec.rb b/spec/lib/gitlab/ci/status/build/failed_spec.rb
index e424270f7c5..78f5214ca81 100644
--- a/spec/lib/gitlab/ci/status/build/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Failed do
diff --git a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
index a4854bdc6b9..19a3c82eac7 100644
--- a/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/failed_unmet_prerequisites_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.describe Gitlab::Ci::Status::Build::FailedUnmetPrerequisites do
diff --git a/spec/lib/gitlab/ci/status/build/manual_spec.rb b/spec/lib/gitlab/ci/status/build/manual_spec.rb
index 6386296f992..bffe2c10d12 100644
--- a/spec/lib/gitlab/ci/status/build/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/manual_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Manual do
diff --git a/spec/lib/gitlab/ci/status/build/pending_spec.rb b/spec/lib/gitlab/ci/status/build/pending_spec.rb
index 4cf70828e53..64d57954c15 100644
--- a/spec/lib/gitlab/ci/status/build/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/pending_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Pending do
diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb
index 02f8c4c114b..bb12a900b55 100644
--- a/spec/lib/gitlab/ci/status/build/play_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/play_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Play do
diff --git a/spec/lib/gitlab/ci/status/build/retried_spec.rb b/spec/lib/gitlab/ci/status/build/retried_spec.rb
index 76c2fb01e3f..fce497d40a1 100644
--- a/spec/lib/gitlab/ci/status/build/retried_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retried_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Retried do
diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
index 84d98588f2d..5b0ae315927 100644
--- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Retryable do
diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
index 68b87fea75d..8f87da10815 100644
--- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Scheduled do
diff --git a/spec/lib/gitlab/ci/status/build/skipped_spec.rb b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
index 46f6933025a..7ce5142da78 100644
--- a/spec/lib/gitlab/ci/status/build/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/skipped_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Skipped do
diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb
index 5b7534c96c1..d3e98400a53 100644
--- a/spec/lib/gitlab/ci/status/build/stop_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Stop do
diff --git a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
index ed046d66ca5..c18fc3252b4 100644
--- a/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
+++ b/spec/lib/gitlab/ci/status/build/unschedule_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Build::Unschedule do
diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb
index dc74d7e28c5..6cfcea4fdde 100644
--- a/spec/lib/gitlab/ci/status/canceled_spec.rb
+++ b/spec/lib/gitlab/ci/status/canceled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Canceled do
diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb
index ce4333f2aca..aeb41e9cfc3 100644
--- a/spec/lib/gitlab/ci/status/created_spec.rb
+++ b/spec/lib/gitlab/ci/status/created_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Created do
diff --git a/spec/lib/gitlab/ci/status/extended_spec.rb b/spec/lib/gitlab/ci/status/extended_spec.rb
index 6eacb07078b..8accfc4a2f9 100644
--- a/spec/lib/gitlab/ci/status/extended_spec.rb
+++ b/spec/lib/gitlab/ci/status/extended_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Extended do
diff --git a/spec/lib/gitlab/ci/status/external/common_spec.rb b/spec/lib/gitlab/ci/status/external/common_spec.rb
index 0d02c371a92..983522fa2d6 100644
--- a/spec/lib/gitlab/ci/status/external/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::External::Common do
diff --git a/spec/lib/gitlab/ci/status/external/factory_spec.rb b/spec/lib/gitlab/ci/status/external/factory_spec.rb
index 529d02a3e39..3b90fb60cca 100644
--- a/spec/lib/gitlab/ci/status/external/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/external/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::External::Factory do
diff --git a/spec/lib/gitlab/ci/status/factory_spec.rb b/spec/lib/gitlab/ci/status/factory_spec.rb
index bbf9c7c83a3..b51c0bec47e 100644
--- a/spec/lib/gitlab/ci/status/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Factory do
diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb
index a4a92117c7f..5c7393fc8cf 100644
--- a/spec/lib/gitlab/ci/status/failed_spec.rb
+++ b/spec/lib/gitlab/ci/status/failed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Failed do
diff --git a/spec/lib/gitlab/ci/status/group/common_spec.rb b/spec/lib/gitlab/ci/status/group/common_spec.rb
index c0ca05881f5..35fff30ea9d 100644
--- a/spec/lib/gitlab/ci/status/group/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Group::Common do
diff --git a/spec/lib/gitlab/ci/status/group/factory_spec.rb b/spec/lib/gitlab/ci/status/group/factory_spec.rb
index 0cd83123938..be76a1d5a65 100644
--- a/spec/lib/gitlab/ci/status/group/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/group/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Group::Factory do
diff --git a/spec/lib/gitlab/ci/status/manual_spec.rb b/spec/lib/gitlab/ci/status/manual_spec.rb
index 0463f2e1aff..0839452ec22 100644
--- a/spec/lib/gitlab/ci/status/manual_spec.rb
+++ b/spec/lib/gitlab/ci/status/manual_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Manual do
diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb
index 0e25358dd8a..5f830e5bb56 100644
--- a/spec/lib/gitlab/ci/status/pending_spec.rb
+++ b/spec/lib/gitlab/ci/status/pending_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pending do
diff --git a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
index 1a2b952d374..876ba712d05 100644
--- a/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/blocked_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Blocked do
diff --git a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
index 57df8325635..d3251d138b8 100644
--- a/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Common do
diff --git a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
index f89712d2b03..90b797965b3 100644
--- a/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/delayed_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Delayed do
diff --git a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
index 466087a0e31..8a36cd1b658 100644
--- a/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/pipeline/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Pipeline::Factory do
diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb
index 9c9d431bb5d..75ff58c5c98 100644
--- a/spec/lib/gitlab/ci/status/running_spec.rb
+++ b/spec/lib/gitlab/ci/status/running_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Running do
diff --git a/spec/lib/gitlab/ci/status/scheduled_spec.rb b/spec/lib/gitlab/ci/status/scheduled_spec.rb
index b8ca3caa1f7..a0374e1a87b 100644
--- a/spec/lib/gitlab/ci/status/scheduled_spec.rb
+++ b/spec/lib/gitlab/ci/status/scheduled_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Scheduled do
diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb
index 63694ca0ea6..7f68d4a2fa9 100644
--- a/spec/lib/gitlab/ci/status/skipped_spec.rb
+++ b/spec/lib/gitlab/ci/status/skipped_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Skipped do
diff --git a/spec/lib/gitlab/ci/status/stage/common_spec.rb b/spec/lib/gitlab/ci/status/stage/common_spec.rb
index bb2d0a2c75c..26ff0e901fd 100644
--- a/spec/lib/gitlab/ci/status/stage/common_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/common_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Common do
diff --git a/spec/lib/gitlab/ci/status/stage/factory_spec.rb b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
index 4b299170c87..8f5b1ff62a5 100644
--- a/spec/lib/gitlab/ci/status/stage/factory_spec.rb
+++ b/spec/lib/gitlab/ci/status/stage/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Stage::Factory do
diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb
index 2f67df71c4f..d4b3a9f12cc 100644
--- a/spec/lib/gitlab/ci/status/success_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::Success do
diff --git a/spec/lib/gitlab/ci/status/success_warning_spec.rb b/spec/lib/gitlab/ci/status/success_warning_spec.rb
index 9493b1d89f2..af952011e21 100644
--- a/spec/lib/gitlab/ci/status/success_warning_spec.rb
+++ b/spec/lib/gitlab/ci/status/success_warning_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Status::SuccessWarning do
diff --git a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
index 546a9e7d0cc..e0077a5280a 100644
--- a/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
+++ b/spec/lib/gitlab/ci/trace/chunked_io_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
@@ -312,7 +314,7 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
end
context 'when utf-8 is being used' do
- let(:sample_trace_raw) { sample_trace_raw_utf8.force_encoding(Encoding::BINARY) }
+ let(:sample_trace_raw) { sample_trace_raw_utf8.dup.force_encoding(Encoding::BINARY) }
let(:sample_trace_raw_utf8) { "😺\n😺\n😺\n😺" }
before do
@@ -442,5 +444,15 @@ describe Gitlab::Ci::Trace::ChunkedIO, :clean_gitlab_redis_cache do
expect(Ci::BuildTraceChunk.where(build: build).count).to eq(0)
end
+
+ context 'when the job does not have archived trace' do
+ it 'leaves a message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ message: 'The job does not have archived trace but going to be destroyed.',
+ job_id: build.id).and_call_original
+
+ subject
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/trace/section_parser_spec.rb b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
index ca53ff87c6f..5e2efe083be 100644
--- a/spec/lib/gitlab/ci/trace/section_parser_spec.rb
+++ b/spec/lib/gitlab/ci/trace/section_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace::SectionParser do
diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb
index 35250632e86..af519f4bae6 100644
--- a/spec/lib/gitlab/ci/trace/stream_spec.rb
+++ b/spec/lib/gitlab/ci/trace/stream_spec.rb
@@ -65,9 +65,9 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html
expect(result).to eq(
- "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\
- "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\
- "<span class=\"\"></span></span>")
+ "<span>ヾ(´༎ຶД༎ຶ`)ノ<br/></span>"\
+ "<span class=\"term-fg-green\">許功蓋</span>"\
+ "<span><br/></span>")
expect(result.encoding).to eq(Encoding.default_external)
end
end
@@ -253,7 +253,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
it 'returns html content with state' do
result = stream.html_with_state
- expect(result.html).to eq("<span class=\"\">1234</span>")
+ expect(result.html).to eq("<span>1234</span>")
end
context 'follow-up state' do
@@ -269,7 +269,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
result = stream.html_with_state(last_result.state)
expect(result.append).to be_truthy
- expect(result.html).to eq("<span class=\"\">5678</span>")
+ expect(result.html).to eq("<span>5678</span>")
end
end
end
@@ -305,13 +305,11 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do
describe '#html' do
shared_examples_for 'htmls' do
it "returns html" do
- expect(stream.html).to eq(
- "<span class=\"\">12<br/><span class=\"\">34<br/>"\
- "<span class=\"\">56</span></span></span>")
+ expect(stream.html).to eq("<span>12<br/>34<br/>56</span>")
end
it "returns html for last line only" do
- expect(stream.html(last_lines: 1)).to eq("<span class=\"\">56</span>")
+ expect(stream.html(last_lines: 1)).to eq("<span>56</span>")
end
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index d6510649dba..f7bc5686b68 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Trace, :clean_gitlab_redis_shared_state do
diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
index 613814df23f..1bdca753cd3 100644
--- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Variables::Collection::Item do
diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb
index 8e732d44d5d..59b9f7d4fb9 100644
--- a/spec/lib/gitlab/ci/variables/collection_spec.rb
+++ b/spec/lib/gitlab/ci/variables/collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Variables::Collection do
diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb
index 789d6813d5b..91c559dcd9b 100644
--- a/spec/lib/gitlab/ci/yaml_processor_spec.rb
+++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
module Gitlab
@@ -123,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
@@ -724,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")
@@ -738,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
@@ -1083,6 +1116,175 @@ module Gitlab
it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: dependency deploy is not defined in prior stages') }
end
+
+ context 'when a job depends on another job that references a not-yet defined stage' do
+ let(:config) do
+ {
+ "stages" => [
+ "version"
+ ],
+ "version" => {
+ "stage" => "version",
+ "dependencies" => ["release:components:versioning"],
+ "script" => ["./versioning/versioning"]
+ },
+ ".release_go" => {
+ "stage" => "build",
+ "script" => ["cd versioning"]
+ },
+ "release:components:versioning" => {
+ "stage" => "build",
+ "script" => ["cd versioning"]
+ }
+ }
+ end
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, /is not defined in prior stages/) }
+ end
+ end
+
+ describe "Needs" do
+ let(:needs) { }
+ let(:dependencies) { }
+
+ let(:config) do
+ {
+ build1: { stage: 'build', script: 'test' },
+ build2: { stage: 'build', script: 'test' },
+ test1: { stage: 'test', script: 'test', needs: needs, dependencies: dependencies },
+ test2: { stage: 'test', script: 'test' },
+ deploy: { stage: 'test', script: 'test' }
+ }
+ end
+
+ subject { Gitlab::Ci::YamlProcessor.new(YAML.dump(config)) }
+
+ context 'no needs' do
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'needs two builds' do
+ let(:needs) { %w(build1 build2) }
+
+ it "does create jobs with valid specification" do
+ expect(subject.builds.size).to eq(5)
+ expect(subject.builds[0]).to eq(
+ stage: "build",
+ stage_idx: 0,
+ name: "build1",
+ options: {
+ script: ["test"]
+ },
+ when: "on_success",
+ allow_failure: false,
+ yaml_variables: []
+ )
+ expect(subject.builds[2]).to eq(
+ stage: "test",
+ stage_idx: 1,
+ name: "test1",
+ options: {
+ script: ["test"],
+ # This does not make sense, there is a follow-up:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/65569
+ bridge_needs: %w[build1 build2]
+ },
+ needs_attributes: [
+ { name: "build1" },
+ { name: "build2" }
+ ],
+ when: "on_success",
+ allow_failure: false,
+ yaml_variables: []
+ )
+ end
+ end
+
+ context 'needs two builds defined as symbols' do
+ let(:needs) { [:build1, :build2] }
+
+ it { expect { subject }.not_to raise_error }
+ end
+
+ context 'undefined need' do
+ let(:needs) { ['undefined'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: undefined need: undefined') }
+ end
+
+ context 'needs to deploy' do
+ let(:needs) { ['deploy'] }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'test1 job: need deploy is not defined in prior stages') }
+ end
+
+ context 'needs and dependencies that are mismatching' do
+ let(:needs) { %w(build1) }
+ let(:dependencies) { %w(build2) }
+
+ it { expect { subject }.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, 'jobs:test1 dependencies the build2 should be part of needs') }
+ 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
@@ -1403,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/ci_access_spec.rb b/spec/lib/gitlab/ci_access_spec.rb
index 75b90e76083..3c68d209eb6 100644
--- a/spec/lib/gitlab/ci_access_spec.rb
+++ b/spec/lib/gitlab/ci_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CiAccess do
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index 44568f2a653..fdd01f58c9d 100644
--- a/spec/lib/gitlab/closing_issue_extractor_spec.rb
+++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ClosingIssueExtractor do
diff --git a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
index 180520b27e7..6ed9dda08d7 100644
--- a/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
+++ b/spec/lib/gitlab/cluster/puma_worker_killer_observer_spec.rb
@@ -17,9 +17,7 @@ describe Gitlab::Cluster::PumaWorkerKillerObserver do
it 'increments timeout counter' do
worker = double(index: 0)
- expect(counter)
- .to receive(:increment)
- .with({ worker: 'worker_0' })
+ expect(counter).to receive(:increment)
subject.callback.call(worker)
end
diff --git a/spec/lib/gitlab/color_schemes_spec.rb b/spec/lib/gitlab/color_schemes_spec.rb
index c7be45dbcd3..ba5573f6901 100644
--- a/spec/lib/gitlab/color_schemes_spec.rb
+++ b/spec/lib/gitlab/color_schemes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ColorSchemes do
diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb
index abb4fff3ad7..6b548d5c4a8 100644
--- a/spec/lib/gitlab/config/entry/attributable_spec.rb
+++ b/spec/lib/gitlab/config/entry/attributable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Attributable do
@@ -23,7 +25,9 @@ describe Gitlab::Config::Entry::Attributable do
end
it 'returns the value of config' do
+ expect(instance).to have_name
expect(instance.name).to eq 'some name'
+ expect(instance).to have_test
expect(instance.test).to eq 'some test'
end
@@ -40,6 +44,7 @@ describe Gitlab::Config::Entry::Attributable do
end
it 'returns nil' do
+ expect(instance).not_to have_test
expect(instance.test).to be_nil
end
end
diff --git a/spec/lib/gitlab/config/entry/boolean_spec.rb b/spec/lib/gitlab/config/entry/boolean_spec.rb
index 1b7a3f850ec..0b8b720dd80 100644
--- a/spec/lib/gitlab/config/entry/boolean_spec.rb
+++ b/spec/lib/gitlab/config/entry/boolean_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Boolean do
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 62dbf20ddf8..8c3a4490d08 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Configurable do
diff --git a/spec/lib/gitlab/config/entry/factory_spec.rb b/spec/lib/gitlab/config/entry/factory_spec.rb
index 42f8be8e141..a614ef56a78 100644
--- a/spec/lib/gitlab/config/entry/factory_spec.rb
+++ b/spec/lib/gitlab/config/entry/factory_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Factory do
diff --git a/spec/lib/gitlab/config/entry/simplifiable_spec.rb b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
index bc8387ada67..65e18fe3f10 100644
--- a/spec/lib/gitlab/config/entry/simplifiable_spec.rb
+++ b/spec/lib/gitlab/config/entry/simplifiable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Simplifiable do
diff --git a/spec/lib/gitlab/config/entry/undefined_spec.rb b/spec/lib/gitlab/config/entry/undefined_spec.rb
index 48f9d276c95..83c3a6aec72 100644
--- a/spec/lib/gitlab/config/entry/undefined_spec.rb
+++ b/spec/lib/gitlab/config/entry/undefined_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Undefined do
diff --git a/spec/lib/gitlab/config/entry/unspecified_spec.rb b/spec/lib/gitlab/config/entry/unspecified_spec.rb
index 64421824a12..32c52594ecf 100644
--- a/spec/lib/gitlab/config/entry/unspecified_spec.rb
+++ b/spec/lib/gitlab/config/entry/unspecified_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Unspecified do
diff --git a/spec/lib/gitlab/config/entry/validatable_spec.rb b/spec/lib/gitlab/config/entry/validatable_spec.rb
index 5a8f9766d23..925db3594ba 100644
--- a/spec/lib/gitlab/config/entry/validatable_spec.rb
+++ b/spec/lib/gitlab/config/entry/validatable_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Validatable do
diff --git a/spec/lib/gitlab/config/entry/validator_spec.rb b/spec/lib/gitlab/config/entry/validator_spec.rb
index efa16c4265c..7bf350912df 100644
--- a/spec/lib/gitlab/config/entry/validator_spec.rb
+++ b/spec/lib/gitlab/config/entry/validator_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Entry::Validator do
diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb
index 0affeb01f6a..ccddf340c3d 100644
--- a/spec/lib/gitlab/config/loader/yaml_spec.rb
+++ b/spec/lib/gitlab/config/loader/yaml_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Config::Loader::Yaml do
diff --git a/spec/lib/gitlab/conflict/file_collection_spec.rb b/spec/lib/gitlab/conflict/file_collection_spec.rb
index c93912db411..f3cdb1a9e59 100644
--- a/spec/lib/gitlab/conflict/file_collection_spec.rb
+++ b/spec/lib/gitlab/conflict/file_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Conflict::FileCollection do
diff --git a/spec/lib/gitlab/conflict/file_spec.rb b/spec/lib/gitlab/conflict/file_spec.rb
index a955ce54e85..adf5a232a75 100644
--- a/spec/lib/gitlab/conflict/file_spec.rb
+++ b/spec/lib/gitlab/conflict/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Conflict::File do
diff --git a/spec/lib/gitlab/content_security_policy/config_loader_spec.rb b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
new file mode 100644
index 00000000000..1d404915617
--- /dev/null
+++ b/spec/lib/gitlab/content_security_policy/config_loader_spec.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ContentSecurityPolicy::ConfigLoader do
+ let(:policy) { ActionDispatch::ContentSecurityPolicy.new }
+ let(:csp_config) do
+ {
+ enabled: true,
+ report_only: false,
+ directives: {
+ base_uri: 'http://example.com',
+ child_src: "'self' https://child.example.com",
+ default_src: "'self' https://other.example.com",
+ script_src: "'self' https://script.exammple.com ",
+ worker_src: "data: https://worker.example.com",
+ report_uri: "http://example.com"
+ }
+ }
+ end
+
+ context '.default_settings_hash' do
+ it 'returns empty defaults' do
+ settings = described_class.default_settings_hash
+
+ expect(settings['enabled']).to be_falsey
+ expect(settings['report_only']).to be_falsey
+
+ described_class::DIRECTIVES.each do |directive|
+ expect(settings['directives'].has_key?(directive)).to be_truthy
+ expect(settings['directives'][directive]).to be_nil
+ end
+ end
+ end
+
+ context '#load' do
+ subject { described_class.new(csp_config[:directives]) }
+
+ def expected_config(directive)
+ csp_config[:directives][directive].split(' ').map(&:strip)
+ end
+
+ it 'sets the policy properly' do
+ subject.load(policy)
+
+ expect(policy.directives['base-uri']).to eq([csp_config[:directives][:base_uri]])
+ expect(policy.directives['default-src']).to eq(expected_config(:default_src))
+ expect(policy.directives['child-src']).to eq(expected_config(:child_src))
+ expect(policy.directives['worker-src']).to eq(expected_config(:worker_src))
+ expect(policy.directives['report-uri']).to eq(expected_config(:report_uri))
+ end
+
+ it 'ignores malformed policy statements' do
+ csp_config[:directives][:base_uri] = 123
+
+ subject.load(policy)
+
+ expect(policy.directives['base-uri']).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 51e5bdc6307..1154f029a8d 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ContributionsCalendar do
diff --git a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
index a9e7575240e..1aa5480b670 100644
--- a/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_collection_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::CheckCollection do
diff --git a/spec/lib/gitlab/cross_project_access/check_info_spec.rb b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
index ea7393a7006..7d2471b6327 100644
--- a/spec/lib/gitlab/cross_project_access/check_info_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/check_info_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::CheckInfo do
diff --git a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
index 5349685e633..17d265542dd 100644
--- a/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
+++ b/spec/lib/gitlab/cross_project_access/class_methods_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess::ClassMethods do
diff --git a/spec/lib/gitlab/cross_project_access_spec.rb b/spec/lib/gitlab/cross_project_access_spec.rb
index 614b0473c7e..ce18d207413 100644
--- a/spec/lib/gitlab/cross_project_access_spec.rb
+++ b/spec/lib/gitlab/cross_project_access_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CrossProjectAccess do
diff --git a/spec/lib/gitlab/crypto_helper_spec.rb b/spec/lib/gitlab/crypto_helper_spec.rb
index 05cc6cf15de..71bbeccb17b 100644
--- a/spec/lib/gitlab/crypto_helper_spec.rb
+++ b/spec/lib/gitlab/crypto_helper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CryptoHelper do
diff --git a/spec/lib/gitlab/current_settings_spec.rb b/spec/lib/gitlab/current_settings_spec.rb
index db31e5280a3..eced96a4c77 100644
--- a/spec/lib/gitlab/current_settings_spec.rb
+++ b/spec/lib/gitlab/current_settings_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CurrentSettings do
@@ -12,6 +14,16 @@ describe Gitlab::CurrentSettings do
end
end
+ describe '.expire_current_application_settings', :use_clean_rails_memory_store_caching, :request_store do
+ include_context 'with settings in cache'
+
+ it 'expires the cache' do
+ described_class.expire_current_application_settings
+
+ expect(ActiveRecord::QueryRecorder.new { described_class.current_application_settings }.count).not_to eq(0)
+ end
+ end
+
describe '#current_application_settings', :use_clean_rails_memory_store_caching do
it 'allows keys to be called directly' do
db_settings = create(:application_setting,
diff --git a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
index 8b07da11c5d..3eea791d61a 100644
--- a/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/base_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::BaseEventFetcher do
@@ -9,12 +11,12 @@ describe Gitlab::CycleAnalytics::BaseEventFetcher do
let(:options) do
{ start_time_attrs: start_time_attrs,
end_time_attrs: end_time_attrs,
- from: 30.days.ago }
+ from: 30.days.ago,
+ project: project }
end
subject do
- described_class.new(project: project,
- stage: :issue,
+ described_class.new(stage: :issue,
options: options).fetch
end
diff --git a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
index 0267e8c2f69..b521ad0c6ea 100644
--- a/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
index c738cc49c1f..dd1d9ac0f16 100644
--- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -5,40 +7,114 @@ describe Gitlab::CycleAnalytics::CodeStage do
let(:stage_name) { :code }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
- let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) }
+ let(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B')
create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1)
create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2)
end
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that closes issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, source_project: project_2, created_at: 15.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'A') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:mr_3_1) { create(:merge_request, source_project: project_4, created_at: 15.minutes.ago) }
+ let(:mr_3_2) { create(:merge_request, source_project: project_5, created_at: 10.minutes.ago, source_branch: 'A') }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago)
+ create(:merge_requests_closing_issues, merge_request: mr_3_1, issue: issue_3_1)
+ create(:merge_requests_closing_issues, merge_request: mr_3_2, issue: issue_3_2)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title, mr_3_1.title, mr_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == mr_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb
index 5ee02650e49..a163de07967 100644
--- a/spec/lib/gitlab/cycle_analytics/events_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'cycle analytics events' do
diff --git a/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
new file mode 100644
index 00000000000..d5c2f7cc579
--- /dev/null
+++ b/spec/lib/gitlab/cycle_analytics/group_stage_summary_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::CycleAnalytics::GroupStageSummary do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:project_2) { create(:project, :repository, namespace: group) }
+ let(:from) { 1.day.ago }
+ let(:user) { create(:user, :admin) }
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user }).data }
+
+ describe "#new_issues" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:issue, project: project) }
+ Timecop.freeze(5.days.ago) { create(:issue, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "finds the number of issues created after it" do
+ expect(subject.first[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group, parent: group))) }
+ end
+
+ it "finds issues from them" do
+ expect(subject.first[:value]).to eq(3)
+ end
+ end
+
+ context 'with projects specified in options' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: group)) }
+ end
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
+
+ it 'finds issues from those projects' do
+ expect(subject.first[:value]).to eq(2)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) { create(:issue, project: create(:project, namespace: create(:group))) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:issue, project: project_2) }
+ end
+
+ it "doesn't find issues from them" do
+ expect(subject.first[:value]).to eq(2)
+ end
+ end
+ end
+
+ describe "#deploys" do
+ context 'with from date' do
+ before do
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project) }
+ Timecop.freeze(5.days.ago) { create(:deployment, :success, project: project_2) }
+ Timecop.freeze(5.days.from_now) { create(:deployment, :success, project: project_2) }
+ end
+
+ it "finds the number of deploys made created after it" do
+ expect(subject.second[:value]).to eq(2)
+ end
+
+ context 'with subgroups' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group, parent: group)))
+ end
+ end
+
+ it "finds deploys from them" do
+ expect(subject.second[:value]).to eq(3)
+ end
+ end
+
+ context 'with projects specified in options' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: group, name: 'not_applicable'))
+ end
+ end
+
+ subject { described_class.new(group, options: { from: Time.now, current_user: user, projects: [project.id, project_2.id] }).data }
+
+ it 'shows deploys from those projects' do
+ expect(subject.second[:value]).to eq(2)
+ end
+ end
+ end
+
+ context 'with other projects' do
+ before do
+ Timecop.freeze(5.days.from_now) do
+ create(:deployment, :success, project: create(:project, :repository, namespace: create(:group)))
+ end
+ end
+
+ it "doesn't find deploys from them" do
+ expect(subject.second[:value]).to eq(0)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
index fd9fa2fee49..afb7b6a13b0 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
index 3b6af9cbaed..4dd21239cde 100644
--- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb
@@ -1,14 +1,16 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::IssueStage do
let(:stage_name) { :issue }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago )
@@ -24,7 +26,7 @@ describe Gitlab::CycleAnalytics::IssueStage do
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
@@ -36,4 +38,90 @@ describe Gitlab::CycleAnalytics::IssueStage do
expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title)
end
end
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when only part of projects is chosen' do
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group, projects: [project_2.id] }) }
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(1)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title)
+ end
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
+
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
index f670c7f6c75..2896e973a43 100644
--- a/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/permissions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::Permissions do
diff --git a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
index 2e5dc5b5547..17786cd02c6 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
index 506a8160412..98d2593de66 100644
--- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -8,7 +10,7 @@ describe Gitlab::CycleAnalytics::PlanStage do
let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) }
let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
@@ -18,22 +20,88 @@ describe Gitlab::CycleAnalytics::PlanStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes issues with metrics' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ issue_2_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_2_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_2_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title)
+ end
+ end
+
+ context 'when subgroup is given' do
+ let(:subgroup) { create(:group, parent: group) }
+ let(:project_4) { create(:project, group: subgroup) }
+ let(:project_5) { create(:project, group: subgroup) }
+ let(:issue_3_1) { create(:issue, project: project_4, created_at: 90.minutes.ago) }
+ let(:issue_3_2) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+ let(:issue_3_3) { create(:issue, project: project_5, created_at: 60.minutes.ago) }
+
+ before do
+ issue_3_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago)
+ issue_3_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago)
+ issue_3_3.metrics.update!(first_added_to_board_at: 15.minutes.ago)
+ end
+
+ describe '#events' do
+ subject { stage.events }
+
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(4)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(issue_2_1.title, issue_2_2.title, issue_3_1.title, issue_3_2.title)
+ end
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title)
+ it 'exposes merge requests that close issues with full path for subgroup' do
+ expect(subject.count).to eq(4)
+ expect(subject.find { |event| event[:title] == issue_3_1.title }[:url]).to include("#{subgroup.full_path}")
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
index 74001181305..3ecfad49acd 100644
--- a/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
index 916684b81eb..4d0cc91a318 100644
--- a/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/production_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
index 4f67c95ed4c..2c2169be58c 100644
--- a/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
index f072a9644e8..0f36a8c5c36 100644
--- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb
@@ -1,17 +1,19 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::ReviewStage do
let(:stage_name) { :review }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 30.minutes.ago)
@@ -24,22 +26,66 @@ describe Gitlab::CycleAnalytics::ReviewStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes merge requests that close issues' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_2_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let!(:mr_2_4) { create(:merge_request, source_project: project_3, created_at: 10.minutes.ago, source_branch: 'C') }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_2_1.metrics.update!(merged_at: 30.minutes.ago)
+ mr_2_2.metrics.update!(merged_at: 10.minutes.ago)
+
+ create(:merge_requests_closing_issues, merge_request: mr_2_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_2_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:title] }).to contain_exactly(mr_2_1.title, mr_2_2.title)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
index c22d27f60d6..c053af010b3 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_event_spec.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'default query config' do
let(:project) { create(:project) }
- let(:event) { described_class.new(project: project, stage: stage_name, options: { from: 1.day.ago }) }
+ let(:event) { described_class.new(stage: stage_name, options: { from: 1.day.ago, project: project }) }
it 'has the stage attribute' do
expect(event.stage).not_to be_nil
diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
index 1a4b572cc11..cf95741908f 100644
--- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'base stage' do
ISSUES_MEDIAN = 30.minutes.to_i
- let(:stage) { described_class.new(project: double, options: {}) }
+ let(:stage) { described_class.new(options: { project: double }) }
before do
- allow(stage).to receive(:median).and_return(1.12)
+ allow(stage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
diff --git a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
index f8009709ce2..778c2f479b5 100644
--- a/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/stage_summary_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::StageSummary do
diff --git a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
index bbc82496340..016d2e8da5b 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
index 17d5fbb9733..bd64c4aca42 100644
--- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
@@ -5,16 +7,16 @@ describe Gitlab::CycleAnalytics::StagingStage do
let(:stage_name) { :staging }
let(:project) { create(:project) }
- let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
- let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
- let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
- let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
- let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) }
+ let(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') }
let(:build_1) { create(:ci_build, project: project) }
let(:build_2) { create(:ci_build, project: project) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
before do
mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
@@ -28,22 +30,68 @@ describe Gitlab::CycleAnalytics::StagingStage do
it_behaves_like 'base stage'
- describe '#median' do
+ describe '#project_median' do
around do |example|
Timecop.freeze { example.run }
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
describe '#events' do
+ subject { stage.events }
+
it 'exposes builds connected to merge request' do
- result = stage.events
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
+ end
+
+ context 'when group is given' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project_2) { create(:project, group: group) }
+ let(:project_3) { create(:project, group: group) }
+ let(:issue_2_1) { create(:issue, project: project_2, created_at: 90.minutes.ago) }
+ let(:issue_2_2) { create(:issue, project: project_3, created_at: 60.minutes.ago) }
+ let(:issue_2_3) { create(:issue, project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_1) { create(:merge_request, :closed, source_project: project_2, created_at: 60.minutes.ago) }
+ let(:mr_2) { create(:merge_request, :closed, source_project: project_3, created_at: 40.minutes.ago, source_branch: 'A') }
+ let(:mr_3) { create(:merge_request, source_project: project_2, created_at: 10.minutes.ago, source_branch: 'B') }
+ let(:build_1) { create(:ci_build, project: project_2) }
+ let(:build_2) { create(:ci_build, project: project_3) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: user, group: group }) }
+
+ before do
+ group.add_owner(user)
+ mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id)
+ mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id)
+ mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project_2).commit_id)
+
+ create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_2_1)
+ create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2_2)
+ create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_2_3)
+ end
+
+ describe '#group_median' do
+ around do |example|
+ Timecop.freeze { example.run }
+ end
+
+ it 'counts median from issues with metrics' do
+ expect(stage.group_median).to eq(ISSUES_MEDIAN)
+ end
+ end
+
+ describe '#events' do
+ subject { stage.events }
- expect(result.count).to eq(2)
- expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ it 'exposes merge requests that close issues' do
+ expect(subject.count).to eq(2)
+ expect(subject.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name)
+ end
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
index 6639fa54e0e..be7c0e9dd59 100644
--- a/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_event_fetcher_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_event_spec'
diff --git a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
index 8633a63849f..9162686d17d 100644
--- a/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/test_stage_spec.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'lib/gitlab/cycle_analytics/shared_stage_spec'
describe Gitlab::CycleAnalytics::TestStage do
let(:stage_name) { :test }
let(:project) { create(:project) }
- let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) }
+ let(:stage) { described_class.new(options: { from: 2.days.ago, current_user: project.creator, project: project }) }
it_behaves_like 'base stage'
@@ -36,7 +38,7 @@ describe Gitlab::CycleAnalytics::TestStage do
end
it 'counts median from issues with metrics' do
- expect(stage.median).to eq(ISSUES_MEDIAN)
+ expect(stage.project_median).to eq(ISSUES_MEDIAN)
end
end
end
diff --git a/spec/lib/gitlab/cycle_analytics/updater_spec.rb b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
index eff54cd3692..67f386f9144 100644
--- a/spec/lib/gitlab/cycle_analytics/updater_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/updater_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::Updater do
diff --git a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
index 8122e85a981..e568ea633db 100644
--- a/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
+++ b/spec/lib/gitlab/cycle_analytics/usage_data_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::CycleAnalytics::UsageData do
@@ -28,27 +30,7 @@ describe Gitlab::CycleAnalytics::UsageData do
end
end
- shared_examples 'a valid usage data result' do
- it 'returns the aggregated usage data of every selected project' do
- result = subject.to_json
-
- expect(result).to have_key(:avg_cycle_analytics)
-
- CycleAnalytics::Base::STAGES.each do |stage|
- expect(result[:avg_cycle_analytics]).to have_key(stage)
-
- stage_values = result[:avg_cycle_analytics][stage]
- expected_values = expect_values_per_stage[stage]
-
- expected_values.each_pair do |op, value|
- expect(stage_values).to have_key(op)
- expect(stage_values[op]).to eq(value)
- end
- end
- end
- end
-
- context 'when using postgresql', :postgresql do
+ context 'a valid usage data result' do
let(:expect_values_per_stage) do
{
issue: {
@@ -89,51 +71,23 @@ describe Gitlab::CycleAnalytics::UsageData do
}
end
- it_behaves_like 'a valid usage data result'
- end
+ it 'returns the aggregated usage data of every selected project' do
+ result = subject.to_json
- context 'when using mysql', :mysql do
- let(:expect_values_per_stage) do
- {
- issue: {
- average: nil,
- sd: 0,
- missing: 2
- },
- plan: {
- average: nil,
- sd: 0,
- missing: 2
- },
- code: {
- average: nil,
- sd: 0,
- missing: 2
- },
- test: {
- average: nil,
- sd: 0,
- missing: 2
- },
- review: {
- average: nil,
- sd: 0,
- missing: 2
- },
- staging: {
- average: nil,
- sd: 0,
- missing: 2
- },
- production: {
- average: nil,
- sd: 0,
- missing: 2
- }
- }
- end
+ expect(result).to have_key(:avg_cycle_analytics)
+
+ CycleAnalytics::LevelBase::STAGES.each do |stage|
+ expect(result[:avg_cycle_analytics]).to have_key(stage)
- it_behaves_like 'a valid usage data result'
+ stage_values = result[:avg_cycle_analytics][stage]
+ expected_values = expect_values_per_stage[stage]
+
+ expected_values.each_pair do |op, value|
+ expect(stage_values).to have_key(op)
+ expect(stage_values[op]).to eq(value)
+ end
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/daemon_spec.rb b/spec/lib/gitlab/daemon_spec.rb
index 2bdb0dfc736..d3e73314b87 100644
--- a/spec/lib/gitlab/daemon_spec.rb
+++ b/spec/lib/gitlab/daemon_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Daemon do
diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb
index 92d90ac2fef..2990594c538 100644
--- a/spec/lib/gitlab/danger/helper_spec.rb
+++ b/spec/lib/gitlab/danger/helper_spec.rb
@@ -85,15 +85,29 @@ describe Gitlab::Danger::Helper do
end
end
+ describe '#markdown_list' do
+ it 'creates a markdown list of items' do
+ items = %w[a b]
+
+ expect(helper.markdown_list(items)).to eq("* `a`\n* `b`")
+ end
+
+ it 'wraps items in <details> when there are more than 10 items' do
+ items = ('a'..'k').to_a
+
+ expect(helper.markdown_list(items)).to match(%r{<details>[^<]+</details>})
+ end
+ end
+
describe '#changes_by_category' do
it 'categorizes changed files' do
- expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/foo lib/gitlab/database/foo.rb qa/foo ee/changelogs/foo.yml] }
+ expect(fake_git).to receive(:added_files) { %w[foo foo.md foo.rb foo.js db/migrate/foo lib/gitlab/database/foo.rb qa/foo ee/changelogs/foo.yml] }
allow(fake_git).to receive(:modified_files) { [] }
allow(fake_git).to receive(:renamed_files) { [] }
expect(helper.changes_by_category).to eq(
backend: %w[foo.rb],
- database: %w[db/foo lib/gitlab/database/foo.rb],
+ database: %w[db/migrate/foo lib/gitlab/database/foo.rb],
frontend: %w[foo.js],
none: %w[ee/changelogs/foo.yml foo.md],
qa: %w[qa/foo],
@@ -159,8 +173,13 @@ describe Gitlab::Danger::Helper do
'ee/FOO_VERSION' | :unknown
- 'db/foo' | :database
- 'ee/db/foo' | :database
+ 'db/schema.rb' | :database
+ 'db/migrate/foo' | :database
+ 'db/post_migrate/foo' | :database
+ 'ee/db/migrate/foo' | :database
+ 'ee/db/post_migrate/foo' | :database
+ 'ee/db/geo/migrate/foo' | :database
+ 'ee/db/geo/post_migrate/foo' | :database
'app/models/project_authorization.rb' | :database
'app/services/users/refresh_authorized_projects_service.rb' | :database
'lib/gitlab/background_migration.rb' | :database
@@ -174,6 +193,9 @@ describe Gitlab::Danger::Helper do
'lib/gitlab/sql/foo' | :database
'rubocop/cop/migration/foo' | :database
+ 'db/fixtures/foo.rb' | :backend
+ 'ee/db/fixtures/foo.rb' | :backend
+
'qa/foo' | :qa
'ee/qa/foo' | :qa
@@ -224,4 +246,20 @@ describe Gitlab::Danger::Helper do
expect(teammates.map(&:username)).to eq(usernames)
end
end
+
+ describe '#missing_database_labels' do
+ subject { helper.missing_database_labels(current_mr_labels) }
+
+ context 'when current merge request has ~database::review pending' do
+ let(:current_mr_labels) { ['database::review pending', 'feature'] }
+
+ it { is_expected.to match_array(['database']) }
+ end
+
+ context 'when current merge request does not have ~database::review pending' do
+ let(:current_mr_labels) { ['feature'] }
+
+ it { is_expected.to match_array(['database', 'database::review pending']) }
+ end
+ end
end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 6a6cf1429c8..171f2344e82 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -40,6 +40,14 @@ describe Gitlab::Danger::Teammate do
it '#maintainer? returns false' do
expect(subject.maintainer?(project, :test, labels)).to be_falsey
end
+
+ context 'when hyperlink is mangled in the role' do
+ let(:role) { '<a href="#">Test Automation Engineer</a>, Create' }
+
+ it '#reviewer? returns true' do
+ expect(subject.reviewer?(project, :test, labels)).to be_truthy
+ end
+ end
end
context 'when role is Test Automation Engineer, Manage' do
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 14fe196a986..b170ef788d9 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Build do
diff --git a/spec/lib/gitlab/data_builder/note_spec.rb b/spec/lib/gitlab/data_builder/note_spec.rb
index 1b5dd2538e0..3c26daba5a5 100644
--- a/spec/lib/gitlab/data_builder/note_spec.rb
+++ b/spec/lib/gitlab/data_builder/note_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Note do
diff --git a/spec/lib/gitlab/data_builder/pipeline_spec.rb b/spec/lib/gitlab/data_builder/pipeline_spec.rb
index 1f36fd5c6ef..4afb7195b7b 100644
--- a/spec/lib/gitlab/data_builder/pipeline_spec.rb
+++ b/spec/lib/gitlab/data_builder/pipeline_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Pipeline do
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index 46ad674a1eb..e8a9f0b06a8 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -1,9 +1,45 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::Push do
+ include RepoHelpers
+
let(:project) { create(:project, :repository) }
let(:user) { build(:user, public_email: 'public-email@example.com') }
+ describe '.build' do
+ let(:sample) { RepoHelpers.sample_compare }
+ let(:commits) { project.repository.commits_between(sample.commits.first, sample.commits.last) }
+ let(:subject) do
+ described_class.build(project: project,
+ user: user,
+ ref: sample.target_branch,
+ commits: commits,
+ commits_count: commits.length,
+ message: 'test message',
+ with_changed_files: with_changed_files)
+ end
+
+ context 'with changed files' do
+ let(:with_changed_files) { true }
+
+ it 'returns commit hook data' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).to include(*%i(added removed modified))
+ end
+ end
+
+ context 'without changed files' do
+ let(:with_changed_files) { false }
+
+ it 'returns commit hook data without include deltas' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).not_to include(*%i(added removed modified))
+ end
+ end
+ end
+
describe '.build_sample' do
let(:data) { described_class.build_sample(project, user) }
diff --git a/spec/lib/gitlab/data_builder/wiki_page_spec.rb b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
index 9c8bdf4b032..404d54bf2da 100644
--- a/spec/lib/gitlab/data_builder/wiki_page_spec.rb
+++ b/spec/lib/gitlab/data_builder/wiki_page_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DataBuilder::WikiPage do
diff --git a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
index 3991c737a26..111833a506a 100644
--- a/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/exact_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::ExactCountStrategy do
@@ -23,18 +25,4 @@ describe Gitlab::Database::Count::ExactCountStrategy do
expect(subject).to eq({})
end
end
-
- describe '.enabled?' do
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is enabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_truthy
- end
- end
end
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index bd3c66d0548..08032d19d14 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::ReltuplesCountStrategy do
@@ -8,7 +10,7 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
subject { described_class.new(models).count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:models) { [Project, Identity] }
context 'when reltuples is up to date' do
@@ -48,18 +50,4 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
end
end
end
-
- describe '.enabled?' do
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is disabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_falsey
- end
- end
end
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 40d810b195b..0c480709c22 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count::TablesampleCountStrategy do
@@ -12,7 +14,7 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
subject { strategy.count }
- describe '#count', :postgresql do
+ describe '#count' do
let(:estimates) do
{
Project => threshold + 1,
@@ -56,22 +58,4 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
end
end
end
-
- describe '.enabled?' do
- before do
- stub_feature_flags(tablesample_counts: true)
- end
-
- it 'is enabled for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(described_class.enabled?).to be_truthy
- end
-
- it 'is disabled for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(described_class.enabled?).to be_falsey
- end
- end
end
diff --git a/spec/lib/gitlab/database/count_spec.rb b/spec/lib/gitlab/database/count_spec.rb
index 1d096b8fa7c..71c25f23b6b 100644
--- a/spec/lib/gitlab/database/count_spec.rb
+++ b/spec/lib/gitlab/database/count_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Count do
@@ -9,24 +11,13 @@ describe Gitlab::Database::Count do
let(:models) { [Project, Identity] }
context '.approximate_counts' do
- context 'selecting strategies' do
- let(:strategies) { [double('s1', enabled?: true), double('s2', enabled?: false)] }
-
- it 'uses only enabled strategies' do
- expect(strategies[0]).to receive(:new).and_return(double('strategy1', count: {}))
- expect(strategies[1]).not_to receive(:new)
-
- described_class.approximate_counts(models, strategies: strategies)
- end
- end
-
context 'fallbacks' do
subject { described_class.approximate_counts(models, strategies: strategies) }
let(:strategies) do
[
- double('s1', enabled?: true, new: first_strategy),
- double('s2', enabled?: true, new: second_strategy)
+ double('s1', new: first_strategy),
+ double('s2', new: second_strategy)
]
end
diff --git a/spec/lib/gitlab/database/grant_spec.rb b/spec/lib/gitlab/database/grant_spec.rb
index 5ebf3f399b6..02697eb2a16 100644
--- a/spec/lib/gitlab/database/grant_spec.rb
+++ b/spec/lib/gitlab/database/grant_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::Grant do
@@ -8,7 +10,7 @@ describe Gitlab::Database::Grant do
expect(described_class.create_and_execute_trigger?('users')).to eq(true)
end
- it 'returns false when the user can not create and/or execute a trigger', :postgresql do
+ it 'returns false when the user can not create and/or execute a trigger' do
# In case of MySQL the user may have SUPER permissions, making it
# impossible to have `false` returned when running tests; hence we only
# run these tests on PostgreSQL.
diff --git a/spec/lib/gitlab/database/median_spec.rb b/spec/lib/gitlab/database/median_spec.rb
deleted file mode 100644
index 1b5e30089ce..00000000000
--- a/spec/lib/gitlab/database/median_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Database::Median do
- let(:dummy_class) do
- Class.new do
- include Gitlab::Database::Median
- end
- end
-
- subject(:median) { dummy_class.new }
-
- describe '#median_datetimes' do
- it 'raises NotSupportedError', :mysql do
- expect { median.median_datetimes(nil, nil, nil, :project_id) }.to raise_error(dummy_class::NotSupportedError, "partition_column is not supported for MySQL")
- end
- end
-end
diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb
index 7409572288c..2731fc8573f 100644
--- a/spec/lib/gitlab/database/migration_helpers_spec.rb
+++ b/spec/lib/gitlab/database/migration_helpers_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::MigrationHelpers do
@@ -9,35 +11,87 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:puts)
end
+ describe '#remove_timestamps' do
+ it 'can remove the default timestamps' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:remove_column).with(:foo, column_name)
+ end
+
+ model.remove_timestamps(:foo)
+ end
+
+ it 'can remove custom timestamps' do
+ expect(model).to receive(:remove_column).with(:foo, :bar)
+
+ model.remove_timestamps(:foo, columns: [:bar])
+ end
+ end
+
describe '#add_timestamps_with_timezone' do
+ let(:in_transaction) { false }
+
before do
- allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:transaction_open?).and_return(in_transaction)
+ allow(model).to receive(:disable_statement_timeout)
end
- context 'using PostgreSQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(model).to receive(:disable_statement_timeout)
+ it 'adds "created_at" and "updated_at" fields with the "datetime_with_timezone" data type' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:add_column).with(:foo, column_name, :datetime_with_timezone, { null: false })
end
- it 'adds "created_at" and "updated_at" fields with the "datetime_with_timezone" data type' do
- expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false })
- expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false })
+ model.add_timestamps_with_timezone(:foo)
+ end
- model.add_timestamps_with_timezone(:foo)
+ it 'can disable the NOT NULL constraint' do
+ Gitlab::Database::MigrationHelpers::DEFAULT_TIMESTAMP_COLUMNS.each do |column_name|
+ expect(model).to receive(:add_column).with(:foo, column_name, :datetime_with_timezone, { null: true })
end
+
+ model.add_timestamps_with_timezone(:foo, null: true)
+ end
+
+ it 'can add just one column' do
+ expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, anything)
+ expect(model).not_to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, anything)
+
+ model.add_timestamps_with_timezone(:foo, columns: [:created_at])
end
- context 'using MySQL' do
+ it 'can add choice of acceptable columns' do
+ expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, anything)
+ expect(model).to receive(:add_column).with(:foo, :deleted_at, :datetime_with_timezone, anything)
+ expect(model).not_to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, anything)
+
+ model.add_timestamps_with_timezone(:foo, columns: [:created_at, :deleted_at])
+ end
+
+ it 'cannot add unacceptable column names' do
+ expect do
+ model.add_timestamps_with_timezone(:foo, columns: [:bar])
+ end.to raise_error %r/Illegal timestamp column name/
+ end
+
+ context 'in a transaction' do
+ let(:in_transaction) { true }
+
before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ allow(model).to receive(:add_column).with(any_args).and_call_original
+ allow(model).to receive(:add_column)
+ .with(:foo, anything, :datetime_with_timezone, anything)
+ .and_return(nil)
end
- it 'adds "created_at" and "updated_at" fields with "datetime_with_timezone" data type' do
- expect(model).to receive(:add_column).with(:foo, :created_at, :datetime_with_timezone, { null: false })
- expect(model).to receive(:add_column).with(:foo, :updated_at, :datetime_with_timezone, { null: false })
+ it 'cannot add a default value' do
+ expect do
+ model.add_timestamps_with_timezone(:foo, default: :i_cause_an_error)
+ end.to raise_error %r/add_timestamps_with_timezone/
+ end
- model.add_timestamps_with_timezone(:foo)
+ it 'can add columns without defaults' do
+ expect do
+ model.add_timestamps_with_timezone(:foo)
+ end.not_to raise_error
end
end
end
@@ -46,56 +100,29 @@ describe Gitlab::Database::MigrationHelpers do
context 'outside a transaction' do
before do
allow(model).to receive(:transaction_open?).and_return(false)
+ allow(model).to receive(:disable_statement_timeout).and_call_original
end
- context 'using PostgreSQL', :postgresql do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(model).to receive(:disable_statement_timeout).and_call_original
- end
-
- it 'creates the index concurrently' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, algorithm: :concurrently)
-
- model.add_concurrent_index(:users, :foo)
- end
-
- it 'creates unique index concurrently' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, { algorithm: :concurrently, unique: true })
-
- model.add_concurrent_index(:users, :foo, unique: true)
- end
+ it 'creates the index concurrently' do
+ expect(model).to receive(:add_index)
+ .with(:users, :foo, algorithm: :concurrently)
- it 'does nothing if the index exists already' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
- expect(model).not_to receive(:add_index)
-
- model.add_concurrent_index(:users, :foo, unique: true)
- end
+ model.add_concurrent_index(:users, :foo)
end
- context 'using MySQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'creates a regular index' do
- expect(model).to receive(:add_index)
- .with(:users, :foo, {})
+ it 'creates unique index concurrently' do
+ expect(model).to receive(:add_index)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true })
- model.add_concurrent_index(:users, :foo)
- end
+ model.add_concurrent_index(:users, :foo, unique: true)
+ end
- it 'does nothing if the index exists already' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { unique: true }).and_return(true)
- expect(model).not_to receive(:add_index)
+ it 'does nothing if the index exists already' do
+ expect(model).to receive(:index_exists?)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(true)
+ expect(model).not_to receive(:add_index)
- model.add_concurrent_index(:users, :foo, unique: true)
- end
+ model.add_concurrent_index(:users, :foo, unique: true)
end
end
@@ -115,28 +142,23 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:index_exists?).and_return(true)
allow(model).to receive(:disable_statement_timeout).and_call_original
+ allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
end
- context 'using PostgreSQL' do
- before do
- allow(model).to receive(:supports_drop_index_concurrently?).and_return(true)
- end
-
- describe 'by column name' do
- it 'removes the index concurrently' do
- expect(model).to receive(:remove_index)
- .with(:users, { algorithm: :concurrently, column: :foo })
+ describe 'by column name' do
+ it 'removes the index concurrently' do
+ expect(model).to receive(:remove_index)
+ .with(:users, { algorithm: :concurrently, column: :foo })
- model.remove_concurrent_index(:users, :foo)
- end
+ model.remove_concurrent_index(:users, :foo)
+ end
- it 'does nothing if the index does not exist' do
- expect(model).to receive(:index_exists?)
- .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(false)
- expect(model).not_to receive(:remove_index)
+ it 'does nothing if the index does not exist' do
+ expect(model).to receive(:index_exists?)
+ .with(:users, :foo, { algorithm: :concurrently, unique: true }).and_return(false)
+ expect(model).not_to receive(:remove_index)
- model.remove_concurrent_index(:users, :foo, unique: true)
- end
+ model.remove_concurrent_index(:users, :foo, unique: true)
end
describe 'by index name' do
@@ -159,17 +181,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
-
- context 'using MySQL' do
- it 'removes an index' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false).twice
-
- expect(model).to receive(:remove_index)
- .with(:users, { column: :foo })
-
- model.remove_concurrent_index(:users, :foo)
- end
- end
end
context 'inside a transaction' do
@@ -202,88 +213,44 @@ describe Gitlab::Database::MigrationHelpers do
allow(model).to receive(:transaction_open?).and_return(false)
end
- context 'using MySQL' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(true)
- end
-
- it 'creates a regular foreign key' do
- expect(model).to receive(:add_foreign_key)
- .with(:projects, :users, column: :user_id, on_delete: :cascade)
+ it 'creates a concurrent foreign key and validates it' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
-
- it 'allows the use of a custom key name' do
- expect(model).to receive(:add_foreign_key).with(
- :projects,
- :users,
- column: :user_id,
- on_delete: :cascade,
- name: :foo
- )
-
- model.add_concurrent_foreign_key(
- :projects,
- :users,
- column: :user_id,
- name: :foo
- )
- end
-
- it 'does not create a foreign key if it exists already' do
- expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
- expect(model).not_to receive(:add_foreign_key)
-
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
end
- context 'using PostgreSQL' do
- before do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
- allow(Gitlab::Database).to receive(:mysql?).and_return(false)
- end
-
- it 'creates a concurrent foreign key and validates it' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).ordered.with(/NOT VALID/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
- expect(model).to receive(:execute).with(/RESET ALL/)
-
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
-
- it 'appends a valid ON DELETE statement' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
- expect(model).to receive(:execute).with(/RESET ALL/)
+ it 'appends a valid ON DELETE statement' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).with(/ON DELETE SET NULL/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users,
- column: :user_id,
- on_delete: :nullify)
- end
+ model.add_concurrent_foreign_key(:projects, :users,
+ column: :user_id,
+ on_delete: :nullify)
+ end
- it 'does not create a foreign key if it exists already' do
- expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
- expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
- expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
+ it 'does not create a foreign key if it exists already' do
+ expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true)
+ expect(model).not_to receive(:execute).with(/ADD CONSTRAINT/)
+ expect(model).to receive(:execute).with(/VALIDATE CONSTRAINT/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id)
+ end
- it 'allows the use of a custom key name' do
- expect(model).to receive(:disable_statement_timeout).and_call_original
- expect(model).to receive(:execute).with(/statement_timeout/)
- expect(model).to receive(:execute).ordered.with(/NOT VALID/)
- expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
- expect(model).to receive(:execute).with(/RESET ALL/)
+ it 'allows the use of a custom key name' do
+ expect(model).to receive(:disable_statement_timeout).and_call_original
+ expect(model).to receive(:execute).with(/statement_timeout/)
+ expect(model).to receive(:execute).ordered.with(/NOT VALID/)
+ expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/)
+ expect(model).to receive(:execute).with(/RESET ALL/)
- model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
- end
+ model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo)
end
end
end
@@ -322,48 +289,43 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#disable_statement_timeout' do
- context 'using PostgreSQL' do
- it 'disables statement timeouts to current transaction only' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
+ it 'disables statement timeouts to current transaction only' do
+ expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0')
- expect(model).to receive(:execute).with('SET LOCAL statement_timeout TO 0')
+ model.disable_statement_timeout
+ end
- model.disable_statement_timeout
+ # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
+ context 'with real environment', :delete do
+ before do
+ model.execute("SET statement_timeout TO '20000'")
end
- # this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
- context 'with real environment', :postgresql, :delete do
- before do
- model.execute("SET statement_timeout TO '20000'")
- end
-
- after do
- model.execute('RESET ALL')
- end
-
- it 'defines statement to 0 only for current transaction' do
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+ after do
+ model.execute('RESET ALL')
+ end
- model.connection.transaction do
- model.disable_statement_timeout
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
- end
+ it 'defines statement to 0 only for current transaction' do
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
- expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
+ model.connection.transaction do
+ model.disable_statement_timeout
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('0')
end
+
+ expect(model.execute('SHOW statement_timeout').first['statement_timeout']).to eq('20s')
end
context 'when passing a blocks' do
it 'disables statement timeouts on session level and executes the block' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
expect(model).to receive(:execute).with('SET statement_timeout TO 0')
- expect(model).to receive(:execute).with('RESET ALL')
+ expect(model).to receive(:execute).with('RESET ALL').at_least(:once)
expect { |block| model.disable_statement_timeout(&block) }.to yield_control
end
# this specs runs without an enclosing transaction (:delete truncation method for db_cleaner)
- context 'with real environment', :postgresql, :delete do
+ context 'with real environment', :delete do
before do
model.execute("SET statement_timeout TO '20000'")
end
@@ -386,69 +348,17 @@ describe Gitlab::Database::MigrationHelpers do
end
end
end
-
- context 'using MySQL' do
- it 'does nothing' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(model).not_to receive(:execute)
-
- model.disable_statement_timeout
- end
-
- context 'when passing a blocks' do
- it 'executes the block of code' do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(model).not_to receive(:execute)
-
- expect { |block| model.disable_statement_timeout(&block) }.to yield_control
- end
- end
- end
end
describe '#true_value' do
- context 'using PostgreSQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns the appropriate value' do
- expect(model.true_value).to eq("'t'")
- end
- end
-
- context 'using MySQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns the appropriate value' do
- expect(model.true_value).to eq(1)
- end
+ it 'returns the appropriate value' do
+ expect(model.true_value).to eq("'t'")
end
end
describe '#false_value' do
- context 'using PostgreSQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns the appropriate value' do
- expect(model.false_value).to eq("'f'")
- end
- end
-
- context 'using MySQL' do
- before do
- expect(Gitlab::Database).to receive(:postgresql?).and_return(false)
- end
-
- it 'returns the appropriate value' do
- expect(model.false_value).to eq(0)
- end
+ it 'returns the appropriate value' do
+ expect(model.false_value).to eq("'f'")
end
end
@@ -640,77 +550,37 @@ describe Gitlab::Database::MigrationHelpers do
before do
allow(model).to receive(:transaction_open?).and_return(false)
allow(model).to receive(:column_for).and_return(old_column)
-
- # Since MySQL and PostgreSQL use different quoting styles we'll just
- # stub the methods used for this to make testing easier.
- allow(model).to receive(:quote_column_name) { |name| name.to_s }
- allow(model).to receive(:quote_table_name) { |name| name.to_s }
end
- context 'using MySQL' do
- it 'renames a column concurrently' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
+ it 'renames a column concurrently' do
+ expect(model).to receive(:check_trigger_permissions!).with(:users)
- expect(model).to receive(:check_trigger_permissions!).with(:users)
+ expect(model).to receive(:install_rename_triggers_for_postgresql)
+ .with(trigger_name, '"users"', '"old"', '"new"')
- expect(model).to receive(:install_rename_triggers_for_mysql)
- .with(trigger_name, 'users', 'old', 'new')
+ expect(model).to receive(:add_column)
+ .with(:users, :new, :integer,
+ limit: old_column.limit,
+ precision: old_column.precision,
+ scale: old_column.scale)
- expect(model).to receive(:add_column)
- .with(:users, :new, :integer,
- limit: old_column.limit,
- precision: old_column.precision,
- scale: old_column.scale)
+ expect(model).to receive(:change_column_default)
+ .with(:users, :new, old_column.default)
- expect(model).to receive(:change_column_default)
- .with(:users, :new, old_column.default)
+ expect(model).to receive(:update_column_in_batches)
- expect(model).to receive(:update_column_in_batches)
+ expect(model).to receive(:change_column_null).with(:users, :new, false)
- expect(model).to receive(:change_column_null).with(:users, :new, false)
+ expect(model).to receive(:copy_indexes).with(:users, :old, :new)
+ expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
- expect(model).to receive(:copy_indexes).with(:users, :old, :new)
- expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
-
- model.rename_column_concurrently(:users, :old, :new)
- end
- end
-
- context 'using PostgreSQL' do
- it 'renames a column concurrently' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
- expect(model).to receive(:check_trigger_permissions!).with(:users)
-
- expect(model).to receive(:install_rename_triggers_for_postgresql)
- .with(trigger_name, 'users', 'old', 'new')
-
- expect(model).to receive(:add_column)
- .with(:users, :new, :integer,
- limit: old_column.limit,
- precision: old_column.precision,
- scale: old_column.scale)
-
- expect(model).to receive(:change_column_default)
- .with(:users, :new, old_column.default)
-
- expect(model).to receive(:update_column_in_batches)
-
- expect(model).to receive(:change_column_null).with(:users, :new, false)
-
- expect(model).to receive(:copy_indexes).with(:users, :old, :new)
- expect(model).to receive(:copy_foreign_keys).with(:users, :old, :new)
-
- model.rename_column_concurrently(:users, :old, :new)
- end
+ model.rename_column_concurrently(:users, :old, :new)
end
end
end
describe '#cleanup_concurrent_column_rename' do
- it 'cleans up the renaming procedure for PostgreSQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(true)
-
+ it 'cleans up the renaming procedure' do
expect(model).to receive(:check_trigger_permissions!).with(:users)
expect(model).to receive(:remove_rename_triggers_for_postgresql)
@@ -720,19 +590,6 @@ describe Gitlab::Database::MigrationHelpers do
model.cleanup_concurrent_column_rename(:users, :old, :new)
end
-
- it 'cleans up the renaming procedure for MySQL' do
- allow(Gitlab::Database).to receive(:postgresql?).and_return(false)
-
- expect(model).to receive(:check_trigger_permissions!).with(:users)
-
- expect(model).to receive(:remove_rename_triggers_for_mysql)
- .with(/trigger_.{12}/)
-
- expect(model).to receive(:remove_column).with(:users, :old)
-
- model.cleanup_concurrent_column_rename(:users, :old, :new)
- end
end
describe '#change_column_type_concurrently' do
@@ -768,18 +625,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#install_rename_triggers_for_mysql' do
- it 'installs the triggers for MySQL' do
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo_insert.+ON users/m)
-
- expect(model).to receive(:execute)
- .with(/CREATE TRIGGER foo_update.+ON users/m)
-
- model.install_rename_triggers_for_mysql('foo', :users, :old, :new)
- end
- end
-
describe '#remove_rename_triggers_for_postgresql' do
it 'removes the function and trigger' do
expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo ON bar')
@@ -789,15 +634,6 @@ describe Gitlab::Database::MigrationHelpers do
end
end
- describe '#remove_rename_triggers_for_mysql' do
- it 'removes the triggers' do
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_insert')
- expect(model).to receive(:execute).with('DROP TRIGGER IF EXISTS foo_update')
-
- model.remove_rename_triggers_for_mysql('foo')
- end
- end
-
describe '#rename_trigger_name' do
it 'returns a String' do
expect(model.rename_trigger_name(:users, :foo, :bar))
@@ -1017,26 +853,9 @@ describe Gitlab::Database::MigrationHelpers do
end
describe '#replace_sql' do
- context 'using postgres' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(false)
- end
-
- it 'builds the sql with correct functions' do
- expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
- .to include('regexp_replace')
- end
- end
-
- context 'using mysql' do
- before do
- allow(Gitlab::Database).to receive(:mysql?).and_return(true)
- end
-
- it 'builds the sql with the correct functions' do
- expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
- .to include('locate', 'insert')
- end
+ it 'builds the sql with correct functions' do
+ expect(model.replace_sql(Arel::Table.new(:users)[:first_name], "Alice", "Eve").to_s)
+ .to include('regexp_replace')
end
describe 'results' do
@@ -1393,7 +1212,7 @@ describe Gitlab::Database::MigrationHelpers do
.to be_falsy
end
- context 'when an index with a function exists', :postgresql do
+ context 'when an index with a function exists' do
before do
ActiveRecord::Base.connection.execute(
'CREATE INDEX test_index ON projects (LOWER(path));'
diff --git a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
index 6c45f13bb5a..53c001fbc1b 100644
--- a/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
+++ b/spec/lib/gitlab/database/multi_threaded_migration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::MultiThreadedMigration do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 81419e51635..612c418e8bb 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index b6096d4faf6..8c4d7e323fa 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index d4d7a83921c..1ccdb1d9447 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index ddd54a669a3..b09258ae227 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'renames child namespaces' do |type|
diff --git a/spec/lib/gitlab/database/sha_attribute_spec.rb b/spec/lib/gitlab/database/sha_attribute_spec.rb
index 778bfa2cc47..c6fc55291f5 100644
--- a/spec/lib/gitlab/database/sha_attribute_spec.rb
+++ b/spec/lib/gitlab/database/sha_attribute_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database::ShaAttribute do
@@ -10,11 +12,7 @@ describe Gitlab::Database::ShaAttribute do
end
let(:binary_from_db) do
- if Gitlab::Database.postgresql?
- "\\x#{sha}"
- else
- binary_sha
- end
+ "\\x#{sha}"
end
let(:attribute) { described_class.new }
diff --git a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
index 57c8bafd488..eed2a1b7b48 100644
--- a/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
+++ b/spec/lib/gitlab/database_importers/common_metrics/importer_spec.rb
@@ -7,8 +7,8 @@ describe Gitlab::DatabaseImporters::CommonMetrics::Importer do
context "does import common_metrics.yml" do
let(:groups) { subject.content['panel_groups'] }
- let(:panels) { groups.map { |group| group['panels'] }.flatten }
- let(:metrics) { panels.map { |group| group['metrics'] }.flatten }
+ let(:panels) { groups.flat_map { |group| group['panels'] } }
+ let(:metrics) { panels.flat_map { |group| group['metrics'] } }
let(:metric_ids) { metrics.map { |metric| metric['id'] } }
before do
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 5f57cd6b825..77e58b6d5c7 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Database do
@@ -15,28 +17,18 @@ describe Gitlab::Database do
it 'returns the name of the adapter' do
expect(described_class.adapter_name).to be_an_instance_of(String)
end
- end
-
- describe '.human_adapter_name' do
- it 'returns PostgreSQL when using PostgreSQL' do
- allow(described_class).to receive(:postgresql?).and_return(true)
- expect(described_class.human_adapter_name).to eq('PostgreSQL')
- end
-
- it 'returns MySQL when using MySQL' do
+ it 'returns Unknown when using anything else' do
allow(described_class).to receive(:postgresql?).and_return(false)
- expect(described_class.human_adapter_name).to eq('MySQL')
+ expect(described_class.human_adapter_name).to eq('Unknown')
end
end
- # These are just simple smoke tests to check if the methods work (regardless
- # of what they may return).
- describe '.mysql?' do
- subject { described_class.mysql? }
-
- it { is_expected.to satisfy { |val| val == true || val == false } }
+ describe '.human_adapter_name' do
+ it 'returns PostgreSQL when using PostgreSQL' do
+ expect(described_class.human_adapter_name).to eq('PostgreSQL')
+ end
end
describe '.postgresql?' do
@@ -52,15 +44,6 @@ describe Gitlab::Database do
described_class.instance_variable_set(:@version, nil)
end
- context "on mysql" do
- it "extracts the version number" do
- allow(described_class).to receive(:database_version)
- .and_return("5.7.12-standard")
-
- expect(described_class.version).to eq '5.7.12-standard'
- end
- end
-
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version)
@@ -80,21 +63,18 @@ describe Gitlab::Database do
end
describe '.postgresql_9_or_less?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.postgresql_9_or_less?).to eq(false)
+ it 'returns true when using postgresql 8.4' do
+ allow(described_class).to receive(:version).and_return('8.4')
+ expect(described_class.postgresql_9_or_less?).to eq(true)
end
it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.postgresql_9_or_less?).to eq(true)
end
it 'returns false when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.postgresql_9_or_less?).to eq(false)
@@ -102,53 +82,33 @@ describe Gitlab::Database do
end
describe '.postgresql_minimum_supported_version?' do
- it 'returns false when not using PostgreSQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
+ it 'returns false when using PostgreSQL 9.5' do
+ allow(described_class).to receive(:version).and_return('9.5')
expect(described_class.postgresql_minimum_supported_version?).to eq(false)
end
- context 'when using PostgreSQL' do
- before do
- allow(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it 'returns false when using PostgreSQL 9.5' do
- allow(described_class).to receive(:version).and_return('9.5')
-
- expect(described_class.postgresql_minimum_supported_version?).to eq(false)
- end
-
- it 'returns true when using PostgreSQL 9.6' do
- allow(described_class).to receive(:version).and_return('9.6')
+ it 'returns true when using PostgreSQL 9.6' do
+ allow(described_class).to receive(:version).and_return('9.6')
- expect(described_class.postgresql_minimum_supported_version?).to eq(true)
- end
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
- it 'returns true when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:version).and_return('10')
+ it 'returns true when using PostgreSQL 10 or newer' do
+ allow(described_class).to receive(:version).and_return('10')
- expect(described_class.postgresql_minimum_supported_version?).to eq(true)
- end
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
end
end
describe '.join_lateral_supported?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.join_lateral_supported?).to eq(false)
- end
-
it 'returns false when using PostgreSQL 9.2' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.2.1')
expect(described_class.join_lateral_supported?).to eq(false)
end
it 'returns true when using PostgreSQL 9.3.0 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.3.0')
expect(described_class.join_lateral_supported?).to eq(true)
@@ -156,21 +116,13 @@ describe Gitlab::Database do
end
describe '.replication_slots_supported?' do
- it 'returns false when using MySQL' do
- allow(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.replication_slots_supported?).to eq(false)
- end
-
it 'returns false when using PostgreSQL 9.3' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.3.1')
expect(described_class.replication_slots_supported?).to eq(false)
end
it 'returns true when using PostgreSQL 9.4.0 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.4.0')
expect(described_class.replication_slots_supported?).to eq(true)
@@ -179,14 +131,12 @@ describe Gitlab::Database do
describe '.pg_wal_lsn_diff' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_wal_lsn_diff).to eq('pg_xlog_location_diff')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_wal_lsn_diff).to eq('pg_wal_lsn_diff')
@@ -195,14 +145,12 @@ describe Gitlab::Database do
describe '.pg_current_wal_insert_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_xlog_insert_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_current_wal_insert_lsn).to eq('pg_current_wal_insert_lsn')
@@ -211,14 +159,12 @@ describe Gitlab::Database do
describe '.pg_last_wal_receive_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_xlog_receive_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_last_wal_receive_lsn).to eq('pg_last_wal_receive_lsn')
@@ -227,14 +173,12 @@ describe Gitlab::Database do
describe '.pg_last_wal_replay_lsn' do
it 'returns old name when using PostgreSQL 9.6' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('9.6')
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_xlog_replay_location')
end
it 'returns new name when using PostgreSQL 10 or newer' do
- allow(described_class).to receive(:postgresql?).and_return(true)
allow(described_class).to receive(:version).and_return('10')
expect(described_class.pg_last_wal_replay_lsn).to eq('pg_last_wal_replay_lsn')
@@ -248,43 +192,13 @@ describe Gitlab::Database do
end
describe '.nulls_last_order' do
- context 'when using PostgreSQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
- it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
- end
-
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
-
- it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column IS NULL, column ASC'}
- it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC'}
- end
+ it { expect(described_class.nulls_last_order('column', 'ASC')).to eq 'column ASC NULLS LAST'}
+ it { expect(described_class.nulls_last_order('column', 'DESC')).to eq 'column DESC NULLS LAST'}
end
describe '.nulls_first_order' do
- context 'when using PostgreSQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'}
- it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'}
- end
-
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
-
- it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC'}
- it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column IS NULL, column DESC'}
- end
+ it { expect(described_class.nulls_first_order('column', 'ASC')).to eq 'column ASC NULLS FIRST'}
+ it { expect(described_class.nulls_first_order('column', 'DESC')).to eq 'column DESC NULLS FIRST'}
end
describe '.with_connection_pool' do
@@ -394,10 +308,6 @@ describe Gitlab::Database do
end
context 'when using PostgreSQL' do
- before do
- allow(described_class).to receive(:mysql?).and_return(false)
- end
-
it 'allows the returning of the IDs of the inserted rows' do
result = double(:result, values: [['10']])
@@ -463,31 +373,15 @@ describe Gitlab::Database do
end
describe '#true_value' do
- it 'returns correct value for PostgreSQL' do
- expect(described_class).to receive(:postgresql?).and_return(true)
-
+ it 'returns correct value' do
expect(described_class.true_value).to eq "'t'"
end
-
- it 'returns correct value for MySQL' do
- expect(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.true_value).to eq 1
- end
end
describe '#false_value' do
- it 'returns correct value for PostgreSQL' do
- expect(described_class).to receive(:postgresql?).and_return(true)
-
+ it 'returns correct value' do
expect(described_class.false_value).to eq "'f'"
end
-
- it 'returns correct value for MySQL' do
- expect(described_class).to receive(:postgresql?).and_return(false)
-
- expect(described_class.false_value).to eq 0
- end
end
describe '.read_only?' do
@@ -497,43 +391,32 @@ describe Gitlab::Database do
end
describe '.db_read_only?' do
- context 'when using PostgreSQL' do
- before do
- allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
- allow(described_class).to receive(:postgresql?).and_return(true)
- end
-
- it 'detects a read only database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
-
- expect(described_class.db_read_only?).to be_truthy
- end
+ before do
+ allow(ActiveRecord::Base.connection).to receive(:execute).and_call_original
+ end
- it 'detects a read only database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
+ it 'detects a read only database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "t" }])
- expect(described_class.db_read_only?).to be_truthy
- end
+ expect(described_class.db_read_only?).to be_truthy
+ end
- it 'detects a read write database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
+ it 'detects a read only database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => true }])
- expect(described_class.db_read_only?).to be_falsey
- end
+ expect(described_class.db_read_only?).to be_truthy
+ end
- it 'detects a read write database' do
- allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
+ it 'detects a read write database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => "f" }])
- expect(described_class.db_read_only?).to be_falsey
- end
+ expect(described_class.db_read_only?).to be_falsey
end
- context 'when using MySQL' do
- before do
- expect(described_class).to receive(:postgresql?).and_return(false)
- end
+ it 'detects a read write database' do
+ allow(ActiveRecord::Base.connection).to receive(:execute).with('SELECT pg_is_in_recovery()').and_return([{ "pg_is_in_recovery" => false }])
- it { expect(described_class.db_read_only?).to be_falsey }
+ expect(described_class.db_read_only?).to be_falsey
end
end
diff --git a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
index 3a93d5e1e97..3eb5db51224 100644
--- a/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/cartfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::CartfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
index 0ebd8994636..6bef6f57e64 100644
--- a/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/composer_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::ComposerJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
index f00f6b47b94..6ecdb0d1247 100644
--- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GemfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
index 6c6a5d70576..256fe58925c 100644
--- a/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/gemspec_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GemspecLinker do
diff --git a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
index ae5ad39ad11..f620d1b590c 100644
--- a/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/godeps_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::GodepsJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
index 9050127af7f..71e9381eaad 100644
--- a/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/package_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PackageJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
index 8f1b523653e..eb81bc07760 100644
--- a/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podfile_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodfileLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
index d4a398c5948..938726dd434 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_json_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecJsonLinker do
diff --git a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
index bacec830103..540eb2fadfe 100644
--- a/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/podspec_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::PodspecLinker do
diff --git a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
index ef952b3abd5..957a87985a2 100644
--- a/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker/requirements_txt_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker::RequirementsTxtLinker do
diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb
index 10d2f701298..98e46d62ca0 100644
--- a/spec/lib/gitlab/dependency_linker_spec.rb
+++ b/spec/lib/gitlab/dependency_linker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rails_helper'
describe Gitlab::DependencyLinker do
diff --git a/spec/lib/gitlab/diff/diff_refs_spec.rb b/spec/lib/gitlab/diff/diff_refs_spec.rb
index f9bfb4c469e..e12b46c15ad 100644
--- a/spec/lib/gitlab/diff/diff_refs_spec.rb
+++ b/spec/lib/gitlab/diff/diff_refs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::DiffRefs do
diff --git a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
index 0697594c725..d89be6fef4e 100644
--- a/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
+++ b/spec/lib/gitlab/diff/file_collection/merge_request_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::FileCollection::MergeRequestDiff do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index cc36060f864..716fc8ae987 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::File do
diff --git a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
index 2f99febe04e..2e6eb71d37d 100644
--- a/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/image_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Formatters::ImageFormatter do
diff --git a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
index 897dc917f6a..33d4994f5db 100644
--- a/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
+++ b/spec/lib/gitlab/diff/formatters/text_formatter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Formatters::TextFormatter do
diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb
index 5d0a603d11d..f5d3d14ccc5 100644
--- a/spec/lib/gitlab/diff/highlight_spec.rb
+++ b/spec/lib/gitlab/diff/highlight_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Highlight do
diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
index 7e17437fa2a..a668bb464a4 100644
--- a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarkdownMarker do
diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
index 97e65318059..26b99870b31 100644
--- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiffMarker do
diff --git a/spec/lib/gitlab/diff/inline_diff_spec.rb b/spec/lib/gitlab/diff/inline_diff_spec.rb
index 0a41362f606..fdbee3b4230 100644
--- a/spec/lib/gitlab/diff/inline_diff_spec.rb
+++ b/spec/lib/gitlab/diff/inline_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::InlineDiff do
diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb
index 42750bf9ea1..1739bcd14a8 100644
--- a/spec/lib/gitlab/diff/line_mapper_spec.rb
+++ b/spec/lib/gitlab/diff/line_mapper_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::LineMapper do
diff --git a/spec/lib/gitlab/diff/line_spec.rb b/spec/lib/gitlab/diff/line_spec.rb
index 76d411973c7..29b9951ba4c 100644
--- a/spec/lib/gitlab/diff/line_spec.rb
+++ b/spec/lib/gitlab/diff/line_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
describe Gitlab::Diff::Line do
describe '.init_from_hash' do
it 'round-trips correctly with to_hash' do
diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb
index e9fc7be366a..7540da71086 100644
--- a/spec/lib/gitlab/diff/parallel_diff_spec.rb
+++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::ParallelDiff do
diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb
index 80c8c189665..00a446c4e20 100644
--- a/spec/lib/gitlab/diff/parser_spec.rb
+++ b/spec/lib/gitlab/diff/parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Parser do
diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb
index b755cd1aff0..399787635c0 100644
--- a/spec/lib/gitlab/diff/position_spec.rb
+++ b/spec/lib/gitlab/diff/position_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::Position do
diff --git a/spec/lib/gitlab/diff/position_tracer_spec.rb b/spec/lib/gitlab/diff/position_tracer_spec.rb
index 79b33d4d276..47d78e0b18c 100644
--- a/spec/lib/gitlab/diff/position_tracer_spec.rb
+++ b/spec/lib/gitlab/diff/position_tracer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Diff::PositionTracer do
diff --git a/spec/lib/gitlab/downtime_check/message_spec.rb b/spec/lib/gitlab/downtime_check/message_spec.rb
index a5a398abf78..2beb5a19a32 100644
--- a/spec/lib/gitlab/downtime_check/message_spec.rb
+++ b/spec/lib/gitlab/downtime_check/message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DowntimeCheck::Message do
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 1f1e4e0216c..56ad49d528f 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::DowntimeCheck do
diff --git a/spec/lib/gitlab/email/attachment_uploader_spec.rb b/spec/lib/gitlab/email/attachment_uploader_spec.rb
index 45c690842bc..d66a746284d 100644
--- a/spec/lib/gitlab/email/attachment_uploader_spec.rb
+++ b/spec/lib/gitlab/email/attachment_uploader_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::Email::AttachmentUploader do
diff --git a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
index ae61ece8029..65e4e27d56f 100644
--- a/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/additional_headers_interceptor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::AdditionalHeadersInterceptor do
diff --git a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
index 4497d4002da..24da47c42ac 100644
--- a/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
+++ b/spec/lib/gitlab/email/hook/delivery_metrics_observer_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::DeliveryMetricsObserver do
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 91aa3bc7c2e..0c58cf088cc 100644
--- a/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
+++ b/spec/lib/gitlab/email/hook/disable_email_interceptor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Hook::DisableEmailInterceptor do
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 0ec1f931037..84c5b38127e 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Message::RepositoryPush do
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index 0af978eced3..c9fde06cbae 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Email::Receiver do
diff --git a/spec/lib/gitlab/email/reply_parser_spec.rb b/spec/lib/gitlab/email/reply_parser_spec.rb
index 376d3accd55..646575b2edd 100644
--- a/spec/lib/gitlab/email/reply_parser_spec.rb
+++ b/spec/lib/gitlab/email/reply_parser_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
# Inspired in great part by Discourse's Email::Receiver
diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb
index 88ea98eb1e1..b24b71522ec 100644
--- a/spec/lib/gitlab/encoding_helper_spec.rb
+++ b/spec/lib/gitlab/encoding_helper_spec.rb
@@ -1,4 +1,6 @@
# coding: utf-8
+# frozen_string_literal: true
+
require "spec_helper"
describe Gitlab::EncodingHelper do
@@ -9,8 +11,8 @@ describe Gitlab::EncodingHelper do
[
["nil", nil, nil],
["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")],
- ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad string"],
- ["frozen non-ascii string", "é".force_encoding("ASCII-8BIT").freeze, "é".encode("UTF-8")],
+ ["invalid utf-8 encoded string", (+"my bad string\xE5").force_encoding("UTF-8"), "my bad string"],
+ ["frozen non-ascii string", (+"é").force_encoding("ASCII-8BIT").freeze, "é".encode("UTF-8")],
[
'leaves ascii only string as is',
'ascii only string',
@@ -23,7 +25,7 @@ describe Gitlab::EncodingHelper do
],
[
'removes invalid bytes from ASCII-8bit encoded multibyte string. This can occur when a git diff match line truncates in the middle of a multibyte character. This occurs after the second word in this example. The test string is as short as we can get while still triggering the error condition when not looking at `detect[:confidence]`.',
- "mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ".force_encoding('ASCII-8BIT'),
+ (+"mu ns\xC3\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi ").force_encoding('ASCII-8BIT'),
"mu ns\n Lorem ipsum dolor sit amet, consectetur adipisicing ut\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg kia elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non p\n {: .normal_pn}\n \n-Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in\n# *Lorem ipsum\xC3\xB9l\xC3\xB9l\xC3\xA0 dolor\xC3\xB9k\xC3\xB9 sit\xC3\xA8b\xC3\xA8 N\xC3\xA8 amet b\xC3\xA0d\xC3\xAC*\n+# *consectetur\xC3\xB9l\xC3\xB9l\xC3\xA0 adipisicing\xC3\xB9k\xC3\xB9 elit\xC3\xA8b\xC3\xA8 N\xC3\xA8 sed do\xC3\xA0d\xC3\xAC*{: .italic .smcaps}\n \n \xEF\x9B\xA1 eiusmod tempor incididunt, ut\xC3\xAAn\xC3\xB9 labore et dolore. Tw\xC4\x83nj\xC3\xAC magna aliqua. Ut enim ad minim veniam\n {: .normal}\n@@ -9,5 +9,5 @@ quis nostrud\xC3\xAAt\xC3\xB9 exercitiation ullamco laboris m\xC3\xB9s\xC3\xB9k\xC3\xB9abc\xC3\xB9 nisi "
],
[
@@ -93,7 +95,7 @@ describe Gitlab::EncodingHelper do
[
["nil", nil, nil],
["empty string", "".encode("ASCII-8BIT"), "".encode("UTF-8")],
- ["invalid utf-8 encoded string", "my bad string\xE5".force_encoding("UTF-8"), "my bad stringå"],
+ ["invalid utf-8 encoded string", (+"my bad string\xE5").force_encoding("UTF-8"), "my bad stringå"],
[
"encodes valid utf8 encoded string to utf8",
"λ, λ, λ".encode("UTF-8"),
@@ -160,12 +162,12 @@ describe Gitlab::EncodingHelper do
],
[
'removes invalid bytes from ASCII-8bit encoded multibyte string.',
- "Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg".force_encoding('ASCII-8BIT'),
+ (+"Lorem ipsum\xC3\n dolor sit amet, xy\xC3\xA0y\xC3\xB9abcd\xC3\xB9efg").force_encoding('ASCII-8BIT'),
"Lorem ipsum\n dolor sit amet, xyàyùabcdùefg"
],
[
'handles UTF-16BE encoded strings',
- "\xFE\xFF\x00\x41".force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM
+ (+"\xFE\xFF\x00\x41").force_encoding('ASCII-8BIT'), # An "A" prepended with UTF-16 BOM
"\xEF\xBB\xBFA" # An "A" prepended with UTF-8 BOM
]
].each do |description, test_string, xpect|
diff --git a/spec/lib/gitlab/etag_caching/middleware_spec.rb b/spec/lib/gitlab/etag_caching/middleware_spec.rb
index 4a54d641b4e..9ead55075fa 100644
--- a/spec/lib/gitlab/etag_caching/middleware_spec.rb
+++ b/spec/lib/gitlab/etag_caching/middleware_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::EtagCaching::Middleware do
diff --git a/spec/lib/gitlab/etag_caching/router_spec.rb b/spec/lib/gitlab/etag_caching/router_spec.rb
index a7cb0bb2a87..fbc49d894a6 100644
--- a/spec/lib/gitlab/etag_caching/router_spec.rb
+++ b/spec/lib/gitlab/etag_caching/router_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::EtagCaching::Router do
diff --git a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
index 5107e1efbbd..c3b706fc538 100644
--- a/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
+++ b/spec/lib/gitlab/exclusive_lease_helpers_spec.rb
@@ -25,13 +25,13 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
end
it 'calls the given block' do
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
end
it 'calls the given block continuously' do
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
- expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_control.once
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(false)
end
it 'cancels the exclusive lease after the block' do
@@ -68,6 +68,15 @@ describe Gitlab::ExclusiveLeaseHelpers, :clean_gitlab_redis_shared_state do
expect { subject }.to raise_error('Failed to obtain a lock')
end
+
+ context 'when lease is granted after retry' do
+ it 'yields block with true' do
+ expect(lease).to receive(:try_obtain).exactly(3).times { nil }
+ expect(lease).to receive(:try_obtain).once { unique_key }
+
+ expect { |b| class_instance.in_lock(unique_key, &b) }.to yield_with_args(true)
+ end
+ end
end
context 'when sleep second is specified' do
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index ef52a25f47e..d24f5c45107 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -55,6 +55,17 @@ describe Gitlab::Gfm::UploadsRewriter do
end
end
+ it 'does not rewrite plain links as embedded' do
+ embedded_link = image_uploader.markdown_link
+ plain_image_link = embedded_link.sub(/\A!/, "")
+ text = "#{plain_image_link} and #{embedded_link}"
+
+ moved_text = described_class.new(text, old_project, user).rewrite(new_project)
+
+ expect(moved_text.scan(/!\[.*?\]/).count).to eq(1)
+ expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1)
+ end
+
context "file are stored locally" do
include_examples "files are accessible"
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 41b898df112..dccd50bc472 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -186,6 +186,12 @@ describe Gitlab::Git::Repository, :seed_helper do
it { is_expected.to be < 2 }
end
+ describe '#to_s' do
+ subject { repository.to_s }
+
+ it { is_expected.to eq("<Gitlab::Git::Repository: group/project>") }
+ end
+
describe '#object_directory_size' do
before do
allow(repository.gitaly_repository_client)
diff --git a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
index e7ef9d08f80..1a4168f7317 100644
--- a/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
+++ b/spec/lib/gitlab/git/rugged_impl/use_rugged_spec.rb
@@ -16,21 +16,44 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
subject(:wrapper) do
- klazz = Class.new { include Gitlab::Git::RuggedImpl::UseRugged }
+ klazz = Class.new do
+ include Gitlab::Git::RuggedImpl::UseRugged
+
+ def rugged_test(ref, test_number)
+ end
+ end
+
klazz.new
end
before do
+ allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_call_original
Gitlab::GitalyClient.instance_variable_set(:@can_use_disk, {})
end
+ context '#execute_rugged_call', :request_store do
+ let(:args) { ['refs/heads/master', 1] }
+
+ before do
+ allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ end
+
+ it 'instruments Rugged call' do
+ expect(subject).to receive(:rugged_test).with(args)
+
+ subject.execute_rugged_call(:rugged_test, args)
+
+ expect(Gitlab::RuggedInstrumentation.query_count).to eq(1)
+ expect(Gitlab::RuggedInstrumentation.list_call_details.count).to eq(1)
+ end
+ end
+
context 'when feature flag is not persisted' do
before do
allow(Feature).to receive(:persisted?).with(feature_flag).and_return(false)
end
it 'returns true when gitaly matches disk' do
- pending('temporary disabled because of https://gitlab.com/gitlab-org/gitlab-ce/issues/64338')
expect(subject.use_rugged?(repository, feature_flag_name)).to be true
end
@@ -49,7 +72,6 @@ describe Gitlab::Git::RuggedImpl::UseRugged, :seed_helper do
end
it "doesn't lead to a second rpc call because gitaly client should use the cached value" do
- pending('temporary disabled because of https://gitlab.com/gitlab-org/gitlab-ce/issues/64338')
expect(subject.use_rugged?(repository, feature_flag_name)).to be true
expect(Gitlab::GitalyClient).not_to receive(:filesystem_id)
diff --git a/spec/lib/gitlab/git_post_receive_spec.rb b/spec/lib/gitlab/git_post_receive_spec.rb
new file mode 100644
index 00000000000..f0df3794e29
--- /dev/null
+++ b/spec/lib/gitlab/git_post_receive_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ::Gitlab::GitPostReceive do
+ set(:project) { create(:project, :repository) }
+
+ subject { described_class.new(project, "project-#{project.id}", changes.dup, {}) }
+
+ describe '#includes_branches?' do
+ context 'with no branches' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/nobranches/tag1
+ 654322 210986 refs/tags/test1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_branches?).to be_falsey
+ end
+ end
+
+ context 'with branches' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/tag1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_branches?).to be_truthy
+ end
+ end
+
+ context 'with malformed changes' do
+ let(:changes) do
+ <<~EOF
+ ref/heads/1 a
+ somebranch refs/heads/2
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_branches?).to be_falsey
+ end
+ end
+ end
+
+ describe '#includes_tags?' do
+ context 'with no tags' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/notags/tag1
+ 654322 210986 refs/heads/test1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_tags?).to be_falsey
+ end
+ end
+
+ context 'with tags' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/tag1
+ 654323 210985 refs/merge-requests/mr1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_tags?).to be_truthy
+ end
+ end
+
+ context 'with malformed changes' do
+ let(:changes) do
+ <<~EOF
+ ref/tags/1 a
+ sometag refs/tags/2
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_tags?).to be_falsey
+ end
+ end
+ end
+
+ describe '#includes_default_branch?' do
+ context 'with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ 654322 210986 refs/tags/#{project.default_branch}
+ 654323 210985 refs/heads/test3
+ EOF
+ end
+
+ it 'returns false' do
+ expect(subject.includes_default_branch?).to be_falsey
+ end
+ end
+
+ context 'with a project with no default branch' do
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/heads/test1
+ EOF
+ end
+
+ it 'returns true' do
+ expect(project).to receive(:default_branch).and_return(nil)
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+
+ context 'with default branch' do
+ let(:changes) do
+ <<~EOF
+ 654322 210986 refs/heads/test1
+ 654321 210987 refs/tags/test2
+ 654323 210985 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'returns true' do
+ expect(subject.includes_default_branch?).to be_truthy
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git_spec.rb b/spec/lib/gitlab/git_spec.rb
index ce15057dd7d..6515be85ae3 100644
--- a/spec/lib/gitlab/git_spec.rb
+++ b/spec/lib/gitlab/git_spec.rb
@@ -39,6 +39,26 @@ describe Gitlab::Git do
end
end
+ describe '.commit_id?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:sha, :result) do
+ '' | false
+ 'foobar' | false
+ '4b825dc' | false
+ 'zzz25dc642cb6eb9a060e54bf8d69288fbee4904' | false
+
+ '4b825dc642cb6eb9a060e54bf8d69288fbee4904' | true
+ Gitlab::Git::BLANK_SHA | true
+ end
+
+ with_them do
+ it 'returns the expected result' do
+ expect(described_class.commit_id?(sha)).to eq(result)
+ end
+ end
+ end
+
describe '.shas_eql?' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb b/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
deleted file mode 100644
index ffc3a09be30..00000000000
--- a/spec/lib/gitlab/gitaly_client/notification_service_spec.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::GitalyClient::NotificationService do
- describe '#post_receive' do
- let(:project) { create(:project) }
- let(:storage_name) { project.repository_storage }
- let(:relative_path) { project.disk_path + '.git' }
- subject { described_class.new(project.repository) }
-
- it 'sends a post_receive message' do
- expect_any_instance_of(Gitaly::NotificationService::Stub)
- .to receive(:post_receive).with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
-
- subject.post_receive
- end
- end
-end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index b8debee3b58..e9fb6c0125c 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -17,6 +17,16 @@ describe Gitlab::GitalyClient do
})
end
+ describe '.filesystem_id_from_disk' do
+ it 'catches errors' do
+ [Errno::ENOENT, Errno::EACCES, JSON::ParserError].each do |error|
+ allow(File).to receive(:read).with(described_class.storage_metadata_file_path('default')).and_raise(error)
+
+ expect(described_class.filesystem_id_from_disk('default')).to be_nil
+ end
+ 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)
@@ -119,6 +129,19 @@ describe Gitlab::GitalyClient do
end
end
+ describe '.can_use_disk?' do
+ it 'properly caches a false result' do
+ # spec_helper stubs this globally
+ allow(described_class).to receive(:can_use_disk?).and_call_original
+ expect(described_class).to receive(:filesystem_id).once
+ expect(described_class).to receive(:filesystem_id_from_disk).once
+
+ 2.times do
+ described_class.can_use_disk?('unknown')
+ end
+ end
+ end
+
describe '.connection_data' do
it 'returns connection data' do
address = 'tcp://localhost:9876'
diff --git a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
index 23ae026fb14..0e5419e6c5e 100644
--- a/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/releases_importer_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
let(:importer) { described_class.new(project, client) }
let(:created_at) { Time.new(2017, 1, 1, 12, 00) }
let(:updated_at) { Time.new(2017, 1, 1, 12, 15) }
+ let(:released_at) { Time.new(2017, 1, 1, 12, 00) }
let(:release) do
double(
@@ -13,7 +14,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
tag_name: '1.0',
body: 'This is my release',
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ published_at: released_at
)
end
@@ -23,7 +25,8 @@ describe Gitlab::GithubImport::Importer::ReleasesImporter do
tag_name: '1.0',
description: 'This is my release',
created_at: created_at,
- updated_at: updated_at
+ updated_at: updated_at,
+ released_at: released_at
}
expect(importer).to receive(:build_releases).and_return([release_hash])
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 2734fcef0a0..53a91a35ec9 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -20,7 +20,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the subgroup matching the search query', :nested_groups do
+ it 'returns the user belonging to the subgroup matching the search query' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
@@ -32,7 +32,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'returns the user belonging to the parent group matching the search query', :nested_groups do
+ it 'returns the user belonging to the parent group matching the search query' do
user1 = create(:user, username: 'gob_bluth')
parent_group = create(:group, children: [group])
create(:group_member, :developer, user: user1, group: parent_group)
@@ -44,7 +44,7 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [user1]
end
- it 'does not return the user belonging to the private subgroup', :nested_groups do
+ it 'does not return the user belonging to the private subgroup' do
user1 = create(:user, username: 'gob_bluth')
subgroup = create(:group, :private, parent: group)
create(:group_member, :developer, user: user1, group: subgroup)
diff --git a/spec/lib/gitlab/http_connection_adapter_spec.rb b/spec/lib/gitlab/http_connection_adapter_spec.rb
index 930d1f62272..1532fd1103e 100644
--- a/spec/lib/gitlab/http_connection_adapter_spec.rb
+++ b/spec/lib/gitlab/http_connection_adapter_spec.rb
@@ -3,7 +3,13 @@
require 'spec_helper'
describe Gitlab::HTTPConnectionAdapter do
+ include StubRequests
+
describe '#connection' do
+ before do
+ stub_all_dns('https://example.org', ip_address: '93.184.216.34')
+ end
+
context 'when local requests are not allowed' do
it 'sets up the connection' do
uri = URI('https://example.org')
diff --git a/spec/lib/gitlab/http_spec.rb b/spec/lib/gitlab/http_spec.rb
index 158f77cab2c..d3f9be845dd 100644
--- a/spec/lib/gitlab/http_spec.rb
+++ b/spec/lib/gitlab/http_spec.rb
@@ -23,14 +23,14 @@ describe Gitlab::HTTP do
end
end
- describe 'allow_local_requests_from_hooks_and_services is' do
+ describe 'allow_local_requests_from_web_hooks_and_services is' do
before do
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
end
context 'disabled' do
before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(false)
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(false)
end
it 'deny requests to localhost' do
@@ -52,7 +52,7 @@ describe Gitlab::HTTP do
context 'enabled' do
before do
- allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_hooks_and_services?).and_return(true)
+ allow(Gitlab::CurrentSettings).to receive(:allow_local_requests_from_web_hooks_and_services?).and_return(true)
end
it 'allow requests to localhost' do
diff --git a/spec/lib/gitlab/import/database_helpers_spec.rb b/spec/lib/gitlab/import/database_helpers_spec.rb
index e716155b7d5..3ac34455177 100644
--- a/spec/lib/gitlab/import/database_helpers_spec.rb
+++ b/spec/lib/gitlab/import/database_helpers_spec.rb
@@ -15,32 +15,15 @@ describe Gitlab::Import::DatabaseHelpers do
let(:attributes) { { iid: 1, title: 'foo' } }
let(:project) { create(:project) }
- context 'on PostgreSQL' do
- it 'returns the ID returned by the query' do
- expect(Gitlab::Database)
- .to receive(:bulk_insert)
- .with(Issue.table_name, [attributes], return_ids: true)
- .and_return([10])
+ it 'returns the ID returned by the query' do
+ expect(Gitlab::Database)
+ .to receive(:bulk_insert)
+ .with(Issue.table_name, [attributes], return_ids: true)
+ .and_return([10])
- id = subject.insert_and_return_id(attributes, project.issues)
+ id = subject.insert_and_return_id(attributes, project.issues)
- expect(id).to eq(10)
- end
- end
-
- context 'on MySQL' do
- it 'uses a separate query to retrieve the ID' do
- issue = create(:issue, project: project, iid: attributes[:iid])
-
- expect(Gitlab::Database)
- .to receive(:bulk_insert)
- .with(Issue.table_name, [attributes], return_ids: true)
- .and_return([])
-
- id = subject.insert_and_return_id(attributes, project.issues)
-
- expect(id).to eq(issue.id)
- end
+ expect(id).to eq(10)
end
end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 7baa52ffb4f..3c6b17c10ec 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -242,6 +242,7 @@ project:
- cluster_project
- cluster_ingresses
- creator
+- cycle_analytics_stages
- group
- namespace
- boards
@@ -277,7 +278,6 @@ project:
- bugzilla_service
- gitlab_issue_tracker_service
- external_wiki_service
-- kubernetes_service
- mock_ci_service
- mock_deployment_service
- mock_monitoring_service
@@ -343,7 +343,6 @@ project:
- fork_network_member
- fork_network
- custom_attributes
-- prometheus_metrics
- lfs_file_locks
- project_badges
- source_of_merge_requests
@@ -470,3 +469,17 @@ incident_management_setting:
merge_trains:
- project
- merge_request
+boards:
+- group
+- lists
+- destroyable_lists
+- milestone
+- board_labels
+- board_assignee
+- assignee
+- labels
+lists:
+- user
+- milestone
+- board
+- label
diff --git a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
index 70eeb9ee66b..2b0bdb909ae 100644
--- a/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_restorer_spec.rb
@@ -6,6 +6,7 @@ describe Gitlab::ImportExport::LfsRestorer do
let(:export_path) { "#{Dir.tmpdir}/lfs_object_restorer_spec" }
let(:project) { create(:project) }
let(:shared) { project.import_export_shared }
+ let(:saver) { Gitlab::ImportExport::LfsSaver.new(project: project, shared: shared) }
subject(:restorer) { described_class.new(project: project, shared: shared) }
before do
@@ -19,49 +20,98 @@ describe Gitlab::ImportExport::LfsRestorer do
describe '#restore' do
context 'when the archive contains lfs files' do
- let(:dummy_lfs_file_path) { File.join(shared.export_path, 'lfs-objects', 'dummy') }
-
- def create_lfs_object_with_content(content)
- dummy_lfs_file = Tempfile.new('existing')
- File.write(dummy_lfs_file.path, content)
- size = dummy_lfs_file.size
- oid = LfsObject.calculate_oid(dummy_lfs_file.path)
- LfsObject.create!(oid: oid, size: size, file: dummy_lfs_file)
+ let(:lfs_object) { create(:lfs_object, :correct_oid, :with_file) }
+
+ # Use the LfsSaver to save data to be restored
+ def save_lfs_data
+ %w(project wiki).each do |repository_type|
+ create(
+ :lfs_objects_project,
+ project: project,
+ repository_type: repository_type,
+ lfs_object: lfs_object
+ )
+ end
+
+ saver.save
+
+ project.lfs_objects.delete_all
end
before do
- FileUtils.mkdir_p(File.dirname(dummy_lfs_file_path))
- File.write(dummy_lfs_file_path, 'not very large')
- allow(restorer).to receive(:lfs_file_paths).and_return([dummy_lfs_file_path])
+ save_lfs_data
+ project.reload
end
- it 'creates an lfs object for the project' do
- expect { restorer.restore }.to change { project.reload.lfs_objects.size }.by(1)
+ it 'succeeds' do
+ expect(restorer.restore).to eq(true)
+ expect(shared.errors).to be_empty
end
- it 'assigns the file correctly' do
+ it 'does not create a new `LfsObject` records, as one already exists' do
+ expect { restorer.restore }.not_to change { LfsObject.count }
+ end
+
+ it 'creates new `LfsObjectsProject` records in order to link the project to the existing `LfsObject`' do
+ expect { restorer.restore }.to change { LfsObjectsProject.count }.by(2)
+ end
+
+ it 'restores the correct `LfsObject` records' do
restorer.restore
- expect(project.lfs_objects.first.file.read).to eq('not very large')
+ expect(project.lfs_objects).to contain_exactly(lfs_object)
end
- it 'links an existing LFS object if it existed' do
- lfs_object = create_lfs_object_with_content('not very large')
+ it 'restores the correct `LfsObjectsProject` records for the project' do
+ restorer.restore
+ expect(
+ project.lfs_objects_projects.pluck(:repository_type)
+ ).to contain_exactly('project', 'wiki')
+ end
+
+ it 'assigns the file correctly' do
restorer.restore
- expect(project.lfs_objects).to include(lfs_object)
+ expect(project.lfs_objects.first.file.read).to eq(lfs_object.file.read)
end
- it 'succeeds' do
- expect(restorer.restore).to be_truthy
- expect(shared.errors).to be_empty
+ context 'when there is not an existing `LfsObject`' do
+ before do
+ lfs_object.destroy
+ end
+
+ it 'creates a new lfs object' do
+ expect { restorer.restore }.to change { LfsObject.count }.by(1)
+ end
+
+ it 'stores the upload' do
+ expect_any_instance_of(LfsObjectUploader).to receive(:store!)
+
+ restorer.restore
+ end
end
- it 'stores the upload' do
- expect_any_instance_of(LfsObjectUploader).to receive(:store!)
+ context 'when there is no lfs-objects.json file' do
+ before do
+ json_file = File.join(shared.export_path, ::Gitlab::ImportExport.lfs_objects_filename)
- restorer.restore
+ FileUtils.rm_rf(json_file)
+ end
+
+ it 'restores the correct `LfsObject` records' do
+ restorer.restore
+
+ expect(project.lfs_objects).to contain_exactly(lfs_object)
+ end
+
+ it 'restores a single `LfsObjectsProject` record for the project with "project" for the `repository_type`' do
+ restorer.restore
+
+ expect(
+ project.lfs_objects_projects.pluck(:repository_type)
+ ).to contain_exactly('project')
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/lfs_saver_spec.rb b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
index 9b0e21deb2e..c3c88486e16 100644
--- a/spec/lib/gitlab/import_export/lfs_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/lfs_saver_spec.rb
@@ -19,6 +19,11 @@ describe Gitlab::ImportExport::LfsSaver do
describe '#save' do
context 'when the project has LFS objects locally stored' do
let(:lfs_object) { create(:lfs_object, :with_file) }
+ let(:lfs_json_file) { File.join(shared.export_path, Gitlab::ImportExport.lfs_objects_filename) }
+
+ def lfs_json
+ JSON.parse(IO.read(lfs_json_file))
+ end
before do
project.lfs_objects << lfs_object
@@ -35,6 +40,45 @@ describe Gitlab::ImportExport::LfsSaver do
expect(File).to exist("#{shared.export_path}/lfs-objects/#{lfs_object.oid}")
end
+
+ describe 'saving a json file' do
+ before do
+ # Create two more LfsObjectProject records with different `repository_type`s
+ %w(wiki design).each do |repository_type|
+ create(
+ :lfs_objects_project,
+ project: project,
+ repository_type: repository_type,
+ lfs_object: lfs_object
+ )
+ end
+
+ FileUtils.rm_rf(lfs_json_file)
+ end
+
+ it 'saves a json file correctly' do
+ saver.save
+
+ expect(File.exist?(lfs_json_file)).to eq(true)
+ expect(lfs_json).to eq(
+ {
+ lfs_object.oid => [
+ LfsObjectsProject.repository_types['wiki'],
+ LfsObjectsProject.repository_types['design'],
+ nil
+ ]
+ }
+ )
+ end
+
+ it 'does not save a json file if feature is disabled' do
+ stub_feature_flags(export_lfs_objects_projects: false)
+
+ saver.save
+
+ expect(File.exist?(lfs_json_file)).to eq(false)
+ end
+ end
end
context 'when the LFS objects are stored in object storage' do
@@ -42,8 +86,11 @@ describe Gitlab::ImportExport::LfsSaver do
before do
allow(LfsObjectUploader).to receive(:object_store_enabled?).and_return(true)
- allow(lfs_object.file).to receive(:url).and_return('http://my-object-storage.local')
project.lfs_objects << lfs_object
+
+ expect_next_instance_of(LfsObjectUploader) do |instance|
+ expect(instance).to receive(:url).and_return('http://my-object-storage.local')
+ end
end
it 'downloads the file to include in an archive' do
diff --git a/spec/lib/gitlab/import_export/members_mapper_spec.rb b/spec/lib/gitlab/import_export/members_mapper_spec.rb
index b95b5dfe791..a9e8431acba 100644
--- a/spec/lib/gitlab/import_export/members_mapper_spec.rb
+++ b/spec/lib/gitlab/import_export/members_mapper_spec.rb
@@ -154,5 +154,15 @@ describe Gitlab::ImportExport::MembersMapper do
expect(members_mapper.map[exported_user_id]).to eq(user2.id)
end
end
+
+ context 'when importer mapping fails' do
+ let(:exception_message) { 'Something went wrong' }
+
+ it 'includes importer specific error message' do
+ expect(ProjectMember).to receive(:create!).and_raise(StandardError.new(exception_message))
+
+ expect { members_mapper.map }.to raise_error(StandardError, "Error adding importer user to project members. #{exception_message}")
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json
index 1a561e81e4a..66f5bb4c87b 100644
--- a/spec/lib/gitlab/import_export/project.group.json
+++ b/spec/lib/gitlab/import_export/project.group.json
@@ -19,7 +19,7 @@
"labels": [
{
"id": 2,
- "title": "project label",
+ "title": "A project label",
"color": "#428bca",
"project_id": 8,
"created_at": "2016-07-22T08:55:44.161Z",
@@ -105,7 +105,7 @@
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 6,
- "title": "project label",
+ "title": "A project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
@@ -162,7 +162,7 @@
"updated_at": "2017-08-15T18:37:40.795Z",
"label": {
"id": 2,
- "title": "project label",
+ "title": "A project label",
"color": "#A8D695",
"project_id": null,
"created_at": "2017-08-15T18:37:19.698Z",
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index c0b97486eeb..a211675bbf2 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -2450,7 +2450,21 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": []
+ "events": [],
+ "suggestions": [
+ {
+ "id": 1,
+ "note_id": 674,
+ "relative_order": 0,
+ "applied": false,
+ "commit_id": null,
+ "from_content": "Original line\n",
+ "to_content": "New line\n",
+ "lines_above": 0,
+ "lines_below": 0,
+ "outdated": false
+ }
+ ]
},
{
"id": 675,
@@ -2775,7 +2789,8 @@
"action": 1,
"author_id": 1
}
- ]
+ ],
+ "approvals_before_merge": 1
},
{
"id": 26,
@@ -7146,5 +7161,65 @@
"link_url": "http://www.example.com",
"image_url": "http://www.example.com"
}
+ ],
+ "boards": [
+ {
+ "id": 29,
+ "project_id": 49,
+ "created_at": "2019-06-06T14:01:06.204Z",
+ "updated_at": "2019-06-06T14:22:37.045Z",
+ "name": "TestBoardABC",
+ "milestone_id": null,
+ "group_id": null,
+ "weight": null,
+ "lists": [
+ {
+ "id": 59,
+ "board_id": 29,
+ "label_id": null,
+ "list_type": "backlog",
+ "position": null,
+ "created_at": "2019-06-06T14:01:06.214Z",
+ "updated_at": "2019-06-06T14:01:06.214Z",
+ "user_id": null,
+ "milestone_id": null
+ },
+ {
+ "id": 61,
+ "board_id": 29,
+ "label_id": 20,
+ "list_type": "label",
+ "position": 0,
+ "created_at": "2019-06-06T14:01:43.197Z",
+ "updated_at": "2019-06-06T14:01:43.197Z",
+ "user_id": null,
+ "milestone_id": null,
+ "label": {
+ "id": 20,
+ "title": "testlabel",
+ "color": "#0033CC",
+ "project_id": 49,
+ "created_at": "2019-06-06T14:01:19.698Z",
+ "updated_at": "2019-06-06T14:01:19.698Z",
+ "template": false,
+ "description": null,
+ "group_id": null,
+ "type": "ProjectLabel",
+ "priorities": []
+ }
+ },
+ {
+ "id": 60,
+ "board_id": 29,
+ "label_id": null,
+ "list_type": "closed",
+ "position": null,
+ "created_at": "2019-06-06T14:01:06.221Z",
+ "updated_at": "2019-06-06T14:01:06.221Z",
+ "user_id": null,
+ "milestone_id": null
+ }
+ ]
+ }
]
}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index e6ce3f1bcea..d6e1fbaa979 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -125,6 +125,13 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(MergeRequest.find_by(title: 'MR1').resource_label_events).not_to be_empty
end
+ it 'restores suggestion' do
+ note = Note.find_by("note LIKE 'Saepe asperiores exercitationem non dignissimos laborum reiciendis et ipsum%'")
+
+ expect(note.suggestions.count).to eq(1)
+ expect(note.suggestions.first.from_content).to eq("Original line\n")
+ end
+
context 'event at forth level of the tree' do
let(:event) { Event.where(action: 6).first }
@@ -160,13 +167,21 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'has project labels' do
- expect(ProjectLabel.count).to eq(2)
+ expect(ProjectLabel.count).to eq(3)
end
it 'has no group labels' do
expect(GroupLabel.count).to eq(0)
end
+ it 'has issue boards' do
+ expect(Project.find_by_path('project').boards.count).to eq(1)
+ end
+
+ it 'has lists associated with the issue board' do
+ expect(Project.find_by_path('project').boards.find_by_name('TestBoardABC').lists.count).to eq(3)
+ end
+
it 'has a project feature' do
expect(@project.project_feature).not_to be_nil
end
@@ -272,7 +287,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
it 'has label priorities' do
- expect(project.labels.first.priorities).not_to be_empty
+ expect(project.labels.find_by(title: 'A project label').priorities).not_to be_empty
end
it 'has milestones' do
@@ -325,7 +340,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project correctly',
issues: 1,
- labels: 1,
+ labels: 2,
milestones: 1,
first_issue_labels: 1,
services: 1
@@ -402,7 +417,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it_behaves_like 'restores project successfully'
it_behaves_like 'restores project correctly',
issues: 2,
- labels: 1,
+ labels: 2,
milestones: 2,
first_issue_labels: 1
@@ -496,6 +511,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
end
end
+ context 'with restricted internal visibility' do
+ describe 'internal project' do
+ let(:visibility) { Gitlab::VisibilityLevel::INTERNAL }
+
+ it 'uses private visibility' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
+ end
+
context 'with group visibility' do
before do
group = create(:group, visibility_level: group_visibility)
@@ -528,6 +555,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
it 'uses the group visibility' do
expect(restorer.restored_project.visibility_level).to eq(group_visibility)
end
+
+ context 'with restricted internal visibility' do
+ it 'sets private visibility' do
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+
+ expect(restorer.restored_project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE)
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index bc4f867e891..fefbed93316 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -42,6 +42,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json).to include({ 'description' => 'description', 'visibility_level' => 20 })
end
+ it 'has approvals_before_merge set' do
+ expect(saved_project_json['approvals_before_merge']).to eq(1)
+ end
+
it 'has milestones' do
expect(saved_project_json['milestones']).not_to be_empty
end
@@ -175,9 +179,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
end
it 'has priorities associated to labels' do
- priorities = saved_project_json['issues'].first['label_links'].map { |link| link['label']['priorities'] }
+ priorities = saved_project_json['issues'].first['label_links'].flat_map { |link| link['label']['priorities'] }
- expect(priorities.flatten).not_to be_empty
+ expect(priorities).not_to be_empty
end
it 'has issue resource label events' do
@@ -268,6 +272,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json).not_to include("runners_token" => 'token')
end
end
+
+ it 'has a board and a list' do
+ expect(saved_project_json['boards'].first['lists']).not_to be_empty
+ end
end
end
@@ -287,7 +295,8 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
issues: [issue],
snippets: [snippet],
releases: [release],
- group: group
+ group: group,
+ approvals_before_merge: 1
)
project_label = create(:label, project: project)
group_label = create(:group_label, group: group)
@@ -322,6 +331,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:project_badge, project: project)
create(:project_badge, project: project)
+ board = create(:board, project: project, name: 'TestBoard')
+ create(:list, board: board, position: 0, label: project_label)
+
project
end
diff --git a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
index a20a844a492..15748407f0c 100644
--- a/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_rename_service_spec.rb
@@ -28,6 +28,7 @@ describe Gitlab::ImportExport::RelationRenameService do
before do
allow(shared).to receive(:export_path).and_return(import_path)
+ allow(ActiveSupport::JSON).to receive(:decode).and_call_original
allow(ActiveSupport::JSON).to receive(:decode).with(file_content).and_return(json_file)
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 28b187c3676..f0545176a90 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -235,6 +235,12 @@ MergeRequest::Metrics:
- latest_build_started_at
- latest_build_finished_at
- first_deployed_to_production_at
+- first_comment_at
+- first_commit_at
+- last_commit_at
+- diff_size
+- modified_paths_size
+- commits_count
Ci::Pipeline:
- id
- project_id
@@ -687,3 +693,22 @@ ProjectMetricsSetting:
- external_dashboard_url
- created_at
- updated_at
+Board:
+- id
+- project_id
+- created_at
+- updated_at
+- group_id
+- milestone_id
+- weight
+- name
+List:
+- id
+- board_id
+- label_id
+- list_type
+- position
+- created_at
+- updated_at
+- milestone_id
+- user_id
diff --git a/spec/lib/gitlab/kubernetes/default_namespace_spec.rb b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
new file mode 100644
index 00000000000..1fda547f35c
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/default_namespace_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::DefaultNamespace do
+ let(:generator) { described_class.new(cluster, project: environment.project) }
+
+ describe '#from_environment_name' do
+ let(:cluster) { create(:cluster) }
+ let(:environment) { create(:environment) }
+
+ subject { generator.from_environment_name(environment.name) }
+
+ it 'generates a slug and passes it to #from_environment_slug' do
+ expect(Gitlab::Slug::Environment).to receive(:new)
+ .with(environment.name)
+ .and_return(double(generate: environment.slug))
+
+ expect(generator).to receive(:from_environment_slug)
+ .with(environment.slug)
+ .and_return(:mock_namespace)
+
+ expect(subject).to eq :mock_namespace
+ end
+ end
+
+ describe '#from_environment_slug' do
+ let(:platform) { create(:cluster_platform_kubernetes, namespace: platform_namespace) }
+ let(:cluster) { create(:cluster, platform_kubernetes: platform) }
+ let(:project) { create(:project, path: "Path-With-Capitals") }
+ let(:environment) { create(:environment, project: project) }
+
+ subject { generator.from_environment_slug(environment.slug) }
+
+ context 'namespace per environment is enabled' do
+ context 'platform namespace is specified' do
+ let(:platform_namespace) { 'platform-namespace' }
+
+ it { is_expected.to eq "#{platform_namespace}-#{environment.slug}" }
+
+ context 'cluster is unmanaged' do
+ let(:cluster) { create(:cluster, :not_managed, platform_kubernetes: platform) }
+
+ it { is_expected.to eq platform_namespace }
+ end
+ end
+
+ context 'platform namespace is blank' do
+ let(:platform_namespace) { nil }
+ let(:mock_namespace) { 'mock-namespace' }
+
+ it 'constructs a namespace from the project and environment' do
+ expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
+ .with("#{project.path}-#{project.id}-#{environment.slug}".downcase)
+ .and_return(mock_namespace)
+
+ expect(subject).to eq mock_namespace
+ end
+ end
+ end
+
+ context 'namespace per environment is disabled' do
+ let(:cluster) { create(:cluster, :namespace_per_environment_disabled, platform_kubernetes: platform) }
+
+ context 'platform namespace is specified' do
+ let(:platform_namespace) { 'platform-namespace' }
+
+ it { is_expected.to eq platform_namespace }
+ end
+
+ context 'platform namespace is blank' do
+ let(:platform_namespace) { nil }
+ let(:mock_namespace) { 'mock-namespace' }
+
+ it 'constructs a namespace from the project and environment' do
+ expect(Gitlab::NamespaceSanitizer).to receive(:sanitize)
+ .with("#{project.path}-#{project.id}".downcase)
+ .and_return(mock_namespace)
+
+ expect(subject).to eq mock_namespace
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
index 06c8d127951..fce2aded786 100644
--- a/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/pod_spec.rb
@@ -30,7 +30,7 @@ describe Gitlab::Kubernetes::Helm::Pod do
it 'generates the appropriate specifications for the container' do
container = subject.generate.spec.containers.first
expect(container.name).to eq('helm')
- expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.12.3-kube-1.11.7')
+ expect(container.image).to eq('registry.gitlab.com/gitlab-org/cluster-integration/helm-install-image/releases/2.14.3-kube-1.11.10')
expect(container.env.count).to eq(3)
expect(container.env.map(&:name)).to match_array([:HELM_VERSION, :TILLER_NAMESPACE, :COMMAND_SCRIPT])
expect(container.command).to match_array(["/bin/sh"])
diff --git a/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
new file mode 100644
index 00000000000..d49d4779735
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/helm/reset_command_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Kubernetes::Helm::ResetCommand do
+ let(:rbac) { true }
+ let(:name) { 'helm' }
+ let(:files) { {} }
+ let(:reset_command) { described_class.new(name: name, rbac: rbac, files: files) }
+
+ subject { reset_command }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS
+ helm reset
+ kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ EOS
+ end
+ end
+
+ context 'when there is a ca.pem file' do
+ let(:files) { { 'ca.pem': 'some file content' } }
+
+ it_behaves_like 'helm commands' do
+ let(:commands) do
+ <<~EOS1.squish + "\n" + <<~EOS2
+ helm reset
+ --tls
+ --tls-ca-cert /data/helm/helm/config/ca.pem
+ --tls-cert /data/helm/helm/config/cert.pem
+ --tls-key /data/helm/helm/config/key.pem
+ EOS1
+ kubectl delete replicaset -n gitlab-managed-apps -l name\\=tiller
+ EOS2
+ end
+ end
+ end
+
+ describe '#pod_resource' do
+ subject { reset_command.pod_resource }
+
+ context 'rbac is enabled' do
+ let(:rbac) { true }
+
+ it 'generates a pod that uses the tiller serviceAccountName' do
+ expect(subject.spec.serviceAccountName).to eq('tiller')
+ end
+ end
+
+ context 'rbac is not enabled' do
+ let(:rbac) { false }
+
+ it 'generates a pod that uses the default serviceAccountName' do
+ expect(subject.spec.serviceAcccountName).to be_nil
+ end
+ end
+ end
+
+ describe '#pod_name' do
+ subject { reset_command.pod_name }
+
+ it { is_expected.to eq('uninstall-helm') }
+ end
+end
diff --git a/spec/lib/gitlab/kubernetes/kube_client_spec.rb b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
index 97ebb5f1554..f49d4e23e39 100644
--- a/spec/lib/gitlab/kubernetes/kube_client_spec.rb
+++ b/spec/lib/gitlab/kubernetes/kube_client_spec.rb
@@ -58,7 +58,7 @@ describe Gitlab::Kubernetes::KubeClient do
context 'when local requests are allowed' do
before do
- stub_application_setting(allow_local_requests_from_hooks_and_services: true)
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
end
it 'allows local addresses' do
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
new file mode 100644
index 00000000000..f24ab5579df
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Kubernetes::KubectlCmd do
+ describe '.delete' do
+ it 'constructs string properly' do
+ args = %w(resource_type type --flag-1 --flag-2)
+
+ expected_command = 'kubectl delete resource_type type --flag-1 --flag-2'
+
+ expect(described_class.delete(*args)).to eq expected_command
+ end
+ end
+
+ describe '.apply_file' do
+ context 'without optional args' do
+ it 'requires filename to be present' do
+ expect { described_class.apply_file(nil) }.to raise_error(ArgumentError, "filename is not present")
+ expect { described_class.apply_file(" ") }.to raise_error(ArgumentError, "filename is not present")
+ end
+
+ it 'constructs string properly' do
+ expected_command = 'kubectl apply -f filename'
+
+ expect(described_class.apply_file('filename')).to eq expected_command
+ end
+ end
+
+ context 'with optional args' do
+ it 'constructs command properly with many args' do
+ args = %w(arg-1 --flag-0-1 arg-2 --flag-0-2)
+
+ expected_command = 'kubectl apply -f filename arg-1 --flag-0-1 arg-2 --flag-0-2'
+
+ expect(described_class.apply_file('filename', *args)).to eq expected_command
+ end
+
+ it 'constructs command properly with single arg' do
+ args = "arg-1"
+
+ expected_command = 'kubectl apply -f filename arg-1'
+
+ expect(described_class.apply_file('filename', args)).to eq(expected_command)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/manifest_import/manifest_spec.rb b/spec/lib/gitlab/manifest_import/manifest_spec.rb
index ab305fb2316..ded93e23c08 100644
--- a/spec/lib/gitlab/manifest_import/manifest_spec.rb
+++ b/spec/lib/gitlab/manifest_import/manifest_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::Manifest, :postgresql do
+describe Gitlab::ManifestImport::Manifest do
let(:file) { File.open(Rails.root.join('spec/fixtures/aosp_manifest.xml')) }
let(:manifest) { described_class.new(file) }
diff --git a/spec/lib/gitlab/manifest_import/project_creator_spec.rb b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
index 1d01d437535..a7487972f51 100644
--- a/spec/lib/gitlab/manifest_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/manifest_import/project_creator_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ManifestImport::ProjectCreator, :postgresql do
+describe Gitlab::ManifestImport::ProjectCreator do
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:repository) do
diff --git a/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
new file mode 100644
index 00000000000..420b246b3f5
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/defaults_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::Defaults do
+ it { is_expected.to be_const_defined(:DEFAULT_PANEL_TYPE) }
+ it { is_expected.to be_const_defined(:DEFAULT_PANEL_WEIGHT) }
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
deleted file mode 100644
index 79a78df44ae..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/dynamic_dashboard_service_spec.rb
+++ /dev/null
@@ -1,36 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Metrics::Dashboard::DynamicDashboardService, :use_clean_rails_memory_store_caching do
- include MetricsDashboardHelpers
-
- set(:project) { build(:project) }
- set(:user) { create(:user) }
- set(:environment) { create(:environment, project: project) }
-
- before do
- project.add_maintainer(user)
- end
-
- describe '#get_dashboard' do
- let(:service_params) { [project, user, { environment: environment, dashboard_path: nil }] }
- let(:service_call) { described_class.new(*service_params).get_dashboard }
-
- it_behaves_like 'valid embedded dashboard service response'
- it_behaves_like 'raises error for users with insufficient permissions'
-
- it 'caches the unprocessed dashboard for subsequent calls' do
- expect(YAML).to receive(:safe_load).once.and_call_original
-
- described_class.new(*service_params).get_dashboard
- described_class.new(*service_params).get_dashboard
- end
-
- context 'when called with a non-system dashboard' do
- let(:dashboard_path) { 'garbage/dashboard/path' }
-
- it_behaves_like 'valid embedded dashboard service response'
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
index d8ed54c0248..ce1bb49f5c9 100644
--- a/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/finder_spec.rb
@@ -5,10 +5,9 @@ require 'spec_helper'
describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_caching do
include MetricsDashboardHelpers
- set(:project) { build(:project) }
+ set(:project) { create(:project) }
set(:user) { create(:user) }
set(:environment) { create(:environment, project: project) }
- let(:system_dashboard_path) { Gitlab::Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH}
before do
project.add_maintainer(user)
@@ -52,9 +51,80 @@ describe Gitlab::Metrics::Dashboard::Finder, :use_clean_rails_memory_store_cachi
end
context 'when the dashboard is expected to be embedded' do
- let(:service_call) { described_class.find(project, user, environment, dashboard_path: nil, embedded: true) }
+ let(:service_call) { described_class.find(project, user, environment, **params) }
+ let(:params) { { embedded: true } }
it_behaves_like 'valid embedded dashboard service response'
+
+ context 'when params are incomplete' do
+ let(:params) { { embedded: true, dashboard_path: system_dashboard_path } }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+
+ context 'when the panel is specified' do
+ context 'as a custom metric' do
+ let(:params) do
+ { embedded: true,
+ dashboard_path: system_dashboard_path,
+ group: business_metric_title,
+ title: 'title',
+ y_label: 'y_label' }
+ end
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+
+ context 'when the metric exists' do
+ before do
+ create(:prometheus_metric, project: project)
+ end
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+ end
+
+ context 'as a project-defined panel' do
+ let(:dashboard_path) { '.gitlab/dashboard/test.yml' }
+ let(:params) do
+ { embedded: true,
+ dashboard_path: dashboard_path,
+ group: 'Group A',
+ title: 'Super Chart A1',
+ y_label: 'y_label' }
+ end
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+
+ context 'when the metric exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+ end
+ end
+ end
+ end
+
+ describe '.find_raw' do
+ let(:dashboard) { YAML.load_file(Rails.root.join('config', 'prometheus', 'common_metrics.yml')) }
+ let(:params) { {} }
+
+ subject { described_class.find_raw(project, **params) }
+
+ it { is_expected.to eq dashboard }
+
+ context 'when the system dashboard is specified' do
+ let(:params) { { dashboard_path: system_dashboard_path } }
+
+ it { is_expected.to eq dashboard }
+ end
+
+ context 'when an existing project dashboard is specified' do
+ let(:dashboard) { YAML.safe_load(fixture_file('lib/gitlab/metrics/dashboard/sample_dashboard.yml')) }
+ let(:params) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
+ let(:project) { project_with_dashboard(params[:dashboard_path]) }
+
+ it { is_expected.to eq dashboard }
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 797d4daabe3..d7891e69dd0 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -101,9 +101,9 @@ describe Gitlab::Metrics::Dashboard::Processor do
private
def all_metrics
- dashboard[:panel_groups].map do |group|
- group[:panels].map { |panel| panel[:metrics] }
- end.flatten
+ dashboard[:panel_groups].flat_map do |group|
+ group[:panels].flat_map { |panel| panel[:metrics] }
+ end
end
def get_metric_details(metric)
diff --git a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
deleted file mode 100644
index 468e8ec9ef2..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/project_dashboard_service_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Gitlab::Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
- include MetricsDashboardHelpers
-
- set(:user) { create(:user) }
- set(:project) { create(:project) }
- set(:environment) { create(:environment, project: project) }
-
- before do
- project.add_maintainer(user)
- end
-
- describe 'get_dashboard' do
- let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
- let(:service_call) { described_class.new(*service_params).get_dashboard }
-
- context 'when the dashboard does not exist' do
- it_behaves_like 'misconfigured dashboard service response', :not_found
- end
-
- it_behaves_like 'raises error for users with insufficient permissions'
-
- context 'when the dashboard exists' do
- let(:project) { project_with_dashboard(dashboard_path) }
-
- it_behaves_like 'valid dashboard service response'
-
- it 'caches the unprocessed dashboard for subsequent calls' do
- expect_any_instance_of(described_class)
- .to receive(:get_raw_dashboard)
- .once
- .and_call_original
-
- described_class.new(*service_params).get_dashboard
- described_class.new(*service_params).get_dashboard
- end
-
- context 'and the dashboard is then deleted' do
- it 'does not return the previously cached dashboard' do
- described_class.new(*service_params).get_dashboard
-
- delete_project_dashboard(project, user, dashboard_path)
-
- expect_any_instance_of(described_class)
- .to receive(:get_raw_dashboard)
- .once
- .and_call_original
-
- described_class.new(*service_params).get_dashboard
- end
- end
- end
-
- context 'when the dashboard is configured incorrectly' do
- let(:project) { project_with_dashboard(dashboard_path, {}) }
-
- it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
- end
- end
-
- describe '::all_dashboard_paths' do
- let(:all_dashboards) { described_class.all_dashboard_paths(project) }
-
- context 'when there are no project dashboards' do
- it 'returns an empty array' do
- expect(all_dashboards).to be_empty
- end
- end
-
- context 'when there are project dashboards available' do
- let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
- let(:project) { project_with_dashboard(dashboard_path) }
-
- it 'returns the dashboard attributes' do
- expect(all_dashboards).to eq(
- [{
- path: dashboard_path,
- display_name: 'test.yml',
- default: false
- }]
- )
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
new file mode 100644
index 00000000000..095d0a2df78
--- /dev/null
+++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Metrics::Dashboard::ServiceSelector do
+ include MetricsDashboardHelpers
+
+ describe '#call' do
+ let(:arguments) { {} }
+
+ subject { described_class.call(arguments) }
+
+ it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
+
+ context 'when just the dashboard path is provided' do
+ let(:arguments) { { dashboard_path: '.gitlab/dashboards/test.yml' } }
+
+ it { is_expected.to be Metrics::Dashboard::ProjectDashboardService }
+
+ context 'when the path is for the system dashboard' do
+ let(:arguments) { { dashboard_path: system_dashboard_path } }
+
+ it { is_expected.to be Metrics::Dashboard::SystemDashboardService }
+ end
+ end
+
+ context 'when the embedded flag is provided' do
+ let(:arguments) { { embedded: true } }
+
+ it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
+
+ context 'when an incomplete set of dashboard identifiers are provided' do
+ let(:arguments) { { embedded: true, dashboard_path: '.gitlab/dashboards/test.yml' } }
+
+ it { is_expected.to be Metrics::Dashboard::DefaultEmbedService }
+ end
+
+ context 'when all the chart identifiers are provided' do
+ let(:arguments) do
+ {
+ embedded: true,
+ dashboard_path: '.gitlab/dashboards/test.yml',
+ group: 'Important Metrics',
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
+ end
+
+ context 'when all chart params expect dashboard_path are provided' do
+ let(:arguments) do
+ {
+ embedded: true,
+ group: 'Important Metrics',
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::DynamicEmbedService }
+ end
+
+ context 'with a system dashboard and "custom" group' do
+ let(:arguments) do
+ {
+ embedded: true,
+ dashboard_path: system_dashboard_path,
+ group: business_metric_title,
+ title: 'Total Requests',
+ y_label: 'req/sec'
+ }
+ end
+
+ it { is_expected.to be Metrics::Dashboard::CustomMetricEmbedService }
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb b/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
deleted file mode 100644
index 13f22dd01c5..00000000000
--- a/spec/lib/gitlab/metrics/dashboard/system_dashboard_service_spec.rb
+++ /dev/null
@@ -1,52 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Gitlab::Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
- include MetricsDashboardHelpers
-
- set(:user) { create(:user) }
- set(:project) { create(:project) }
- set(:environment) { create(:environment, project: project) }
-
- before do
- project.add_maintainer(user)
- end
-
- describe 'get_dashboard' do
- let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH }
- let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
- let(:service_call) { described_class.new(*service_params).get_dashboard }
-
- it_behaves_like 'valid dashboard service response'
- it_behaves_like 'raises error for users with insufficient permissions'
-
- it 'caches the unprocessed dashboard for subsequent calls' do
- expect(YAML).to receive(:safe_load).once.and_call_original
-
- described_class.new(*service_params).get_dashboard
- described_class.new(*service_params).get_dashboard
- end
-
- context 'when called with a non-system dashboard' do
- let(:dashboard_path) { 'garbage/dashboard/path' }
-
- # We want to alwaus return the system dashboard.
- it_behaves_like 'valid dashboard service response'
- end
- end
-
- describe '::all_dashboard_paths' do
- it 'returns the dashboard attributes' do
- all_dashboards = described_class.all_dashboard_paths(project)
-
- expect(all_dashboards).to eq(
- [{
- path: described_class::SYSTEM_DASHBOARD_PATH,
- display_name: described_class::SYSTEM_DASHBOARD_NAME,
- default: true
- }]
- )
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/dashboard/url_spec.rb b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
index 34bc6359414..e0dc6d98efc 100644
--- a/spec/lib/gitlab/metrics/dashboard/url_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/url_spec.rb
@@ -9,14 +9,22 @@ describe Gitlab::Metrics::Dashboard::Url do
end
it 'matches a metrics dashboard link with named params' do
- url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url('foo', 'bar', 1, start: 123345456, anchor: 'title')
+ url = Gitlab::Routing.url_helpers.metrics_namespace_project_environment_url(
+ 'foo',
+ 'bar',
+ 1,
+ start: '2019-08-02T05:43:09.000Z',
+ dashboard: 'config/prometheus/common_metrics.yml',
+ group: 'awesome group',
+ anchor: 'title'
+ )
expected_params = {
'url' => url,
'namespace' => 'foo',
'project' => 'bar',
'environment' => '1',
- 'query' => '?start=123345456',
+ 'query' => '?dashboard=config%2Fprometheus%2Fcommon_metrics.yml&group=awesome+group&start=2019-08-02T05%3A43%3A09.000Z',
'anchor' => '#title'
}
diff --git a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
index 81954fcf8c5..2923048f742 100644
--- a/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/influx_sampler_spec.rb
@@ -17,18 +17,10 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
it 'samples various statistics' do
expect(sampler).to receive(:sample_memory_usage)
expect(sampler).to receive(:sample_file_descriptors)
- expect(sampler).to receive(:sample_gc)
expect(sampler).to receive(:flush)
sampler.sample
end
-
- it 'clears any GC profiles' do
- expect(sampler).to receive(:flush)
- expect(GC::Profiler).to receive(:clear)
-
- sampler.sample
- end
end
describe '#flush' do
@@ -67,18 +59,6 @@ describe Gitlab::Metrics::Samplers::InfluxSampler do
end
end
- describe '#sample_gc' do
- it 'adds a metric containing garbage collection statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
-
- expect(sampler).to receive(:add_metric)
- .with(/gc_statistics/, an_instance_of(Hash))
- .and_call_original
-
- sampler.sample_gc
- end
- end
-
describe '#add_metric' do
it 'prefixes the series name for a Rails process' do
expect(sampler).to receive(:sidekiq?).and_return(false)
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index f4a6e1fc7d9..b8add3c1324 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -46,8 +46,6 @@ describe Gitlab::Metrics::Samplers::PumaSampler do
expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1)
subject.sample
end
diff --git a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
index 4d93b70e6e3..5005a5d9ebc 100644
--- a/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/ruby_sampler_spec.rb
@@ -59,17 +59,29 @@ describe Gitlab::Metrics::Samplers::RubySampler do
end
it 'clears any GC profiles' do
- expect(GC::Profiler).to receive(:clear)
+ expect(GC::Profiler).to receive(:clear).at_least(:once)
sampler.sample
end
end
describe '#sample_gc' do
- it 'adds a metric containing garbage collection time statistics' do
- expect(GC::Profiler).to receive(:total_time).and_return(0.24)
+ let!(:sampler) { described_class.new(5) }
- expect(sampler.metrics[:total_time]).to receive(:increment).with({}, 0.24)
+ let(:gc_reports) { [{ GC_TIME: 0.1 }, { GC_TIME: 0.2 }, { GC_TIME: 0.3 }] }
+
+ it 're-enables GC::Profiler if needed' do
+ expect(GC::Profiler).to receive(:enable)
+
+ sampler.sample
+ end
+
+ it 'observes GC cycles time' do
+ expect(sampler).to receive(:sample_gc_reports).and_return(gc_reports)
+
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.1).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.2).ordered
+ expect(sampler.metrics[:gc_duration_seconds]).to receive(:observe).with({}, 0.3).ordered
sampler.sample
end
diff --git a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
index 6795c1ab56b..e04056b3450 100644
--- a/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/rails_cache_spec.rb
@@ -201,7 +201,15 @@ describe Gitlab::Metrics::Subscribers::RailsCache do
it 'observes cache metric' do
expect(subscriber.send(:metric_cache_operation_duration_seconds))
.to receive(:observe)
- .with(transaction.labels.merge(operation: :delete), event.duration / 1000.0)
+ .with({ operation: :delete }, event.duration / 1000.0)
+
+ subscriber.observe(:delete, event.duration)
+ end
+
+ it 'increments the operations total' do
+ expect(subscriber.send(:metric_cache_operations_total))
+ .to receive(:increment)
+ .with(transaction.labels.merge(operation: :delete))
subscriber.observe(:delete, event.duration)
end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index e6e9ae3223e..bfd456cdd7e 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::ObjectHierarchy, :postgresql do
+describe Gitlab::ObjectHierarchy do
let!(:parent) { create(:group) }
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
diff --git a/spec/lib/gitlab/octokit/middleware_spec.rb b/spec/lib/gitlab/octokit/middleware_spec.rb
new file mode 100644
index 00000000000..43f6d13f7ba
--- /dev/null
+++ b/spec/lib/gitlab/octokit/middleware_spec.rb
@@ -0,0 +1,68 @@
+require 'spec_helper'
+
+describe Gitlab::Octokit::Middleware do
+ let(:app) { double(:app) }
+ let(:middleware) { described_class.new(app) }
+
+ shared_examples 'Public URL' do
+ it 'does not raise an error' do
+ expect(app).to receive(:call).with(env)
+
+ expect { middleware.call(env) }.not_to raise_error
+ end
+ end
+
+ shared_examples 'Local URL' do
+ it 'raises an error' do
+ expect { middleware.call(env) }.to raise_error(Gitlab::UrlBlocker::BlockedUrlError)
+ end
+ end
+
+ describe '#call' do
+ context 'when the URL is a public URL' do
+ let(:env) { { url: 'https://public-url.com' } }
+
+ it_behaves_like 'Public URL'
+ end
+
+ context 'when the URL is a localhost adresss' do
+ let(:env) { { url: 'http://127.0.0.1' } }
+
+ context 'when localhost requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when localhost requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+
+ context 'when the URL is a local network address' do
+ let(:env) { { url: 'http://172.16.0.0' } }
+
+ context 'when local network requests are not allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: false)
+ end
+
+ it_behaves_like 'Local URL'
+ end
+
+ context 'when local network requests are allowed' do
+ before do
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
+ end
+
+ it_behaves_like 'Public URL'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/performance_bar_spec.rb b/spec/lib/gitlab/performance_bar_spec.rb
index ee3c571c9c0..71c109db1f1 100644
--- a/spec/lib/gitlab/performance_bar_spec.rb
+++ b/spec/lib/gitlab/performance_bar_spec.rb
@@ -96,7 +96,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when allowed group is nested', :nested_groups do
+ context 'when allowed group is nested' do
let!(:nested_my_group) { create(:group, parent: create(:group, path: 'my-org'), path: 'my-group') }
before do
@@ -110,7 +110,7 @@ describe Gitlab::PerformanceBar do
end
end
- context 'when a nested group has the same path', :nested_groups do
+ context 'when a nested group has the same path' do
before do
create(:group, :nested, path: 'my-group').add_developer(user)
end
diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb
index bd0bc2c9044..75e2d5e1319 100644
--- a/spec/lib/gitlab/project_authorizations_spec.rb
+++ b/spec/lib/gitlab/project_authorizations_spec.rb
@@ -20,13 +20,7 @@ describe Gitlab::ProjectAuthorizations do
end
let(:authorizations) do
- klass = if Group.supports_nested_objects?
- Gitlab::ProjectAuthorizations::WithNestedGroups
- else
- Gitlab::ProjectAuthorizations::WithoutNestedGroups
- end
-
- klass.new(user).calculate
+ described_class.new(user).calculate
end
it 'returns the correct number of authorizations' do
@@ -46,28 +40,26 @@ describe Gitlab::ProjectAuthorizations do
expect(mapping[group_project.id]).to eq(Gitlab::Access::DEVELOPER)
end
- if Group.supports_nested_objects?
- context 'with nested groups' do
- let!(:nested_group) { create(:group, parent: group) }
- let!(:nested_project) { create(:project, namespace: nested_group) }
+ context 'with nested groups' do
+ let!(:nested_group) { create(:group, parent: group) }
+ let!(:nested_project) { create(:project, namespace: nested_group) }
- it 'includes nested groups' do
- expect(authorizations.pluck(:project_id)).to include(nested_project.id)
- end
+ it 'includes nested groups' do
+ expect(authorizations.pluck(:project_id)).to include(nested_project.id)
+ end
- it 'inherits access levels when the user is not a member of a nested group' do
- mapping = map_access_levels(authorizations)
+ it 'inherits access levels when the user is not a member of a nested group' do
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::DEVELOPER)
+ end
- it 'uses the greatest access level when a user is a member of a nested group' do
- nested_group.add_maintainer(user)
+ it 'uses the greatest access level when a user is a member of a nested group' do
+ nested_group.add_maintainer(user)
- mapping = map_access_levels(authorizations)
+ mapping = map_access_levels(authorizations)
- expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
- end
+ expect(mapping[nested_project.id]).to eq(Gitlab::Access::MAINTAINER)
end
end
end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 4a41d5cf51e..c7462500c82 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -22,6 +22,28 @@ describe Gitlab::ProjectSearchResults do
it { expect(results.query).to eq('hello world') }
end
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:results) { described_class.new(user, project, query) }
+
+ where(:scope, :count_method, :expected) do
+ 'blobs' | :blobs_count | '1234'
+ 'notes' | :limited_notes_count | '1000+'
+ 'wiki_blobs' | :wiki_blobs_count | '1234'
+ 'commits' | :commits_count | '1234'
+ 'projects' | :limited_projects_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
+
shared_examples 'general blob search' do |entity_type, blob_kind|
let(:query) { 'files' }
subject(:results) { described_class.new(user, project, query).objects(blob_type) }
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 8c2fc048a54..c7c82d07508 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -28,6 +28,18 @@ describe Gitlab::ProjectTemplate do
end
end
+ describe '#project_path' do
+ subject { described_class.new('name', 'title', 'description', 'https://gitlab.com/some/project/path').project_path }
+
+ it { is_expected.to eq 'some/project/path' }
+ end
+
+ describe '#uri_encoded_project_path' do
+ subject { described_class.new('name', 'title', 'description', 'https://gitlab.com/some/project/path').uri_encoded_project_path }
+
+ it { is_expected.to eq 'some%2Fproject%2Fpath' }
+ end
+
describe '.find' do
subject { described_class.find(query) }
@@ -44,6 +56,12 @@ describe Gitlab::ProjectTemplate do
end
end
+ describe '.archive_directory' do
+ subject { described_class.archive_directory }
+
+ it { is_expected.to be_a Pathname }
+ end
+
describe 'instance methods' do
subject { described_class.new('phoenix', 'Phoenix Framework', 'Phoenix description', 'link-to-template') }
diff --git a/spec/lib/gitlab/prometheus/metric_group_spec.rb b/spec/lib/gitlab/prometheus/metric_group_spec.rb
index 5cc6827488b..a45dd0af91e 100644
--- a/spec/lib/gitlab/prometheus/metric_group_spec.rb
+++ b/spec/lib/gitlab/prometheus/metric_group_spec.rb
@@ -17,7 +17,7 @@ describe Gitlab::Prometheus::MetricGroup do
end
it 'returns exactly three metric queries' do
- expect(subject.map(&:metrics).flatten.map(&:id)).to contain_exactly(
+ expect(subject.flat_map(&:metrics).map(&:id)).to contain_exactly(
common_metric_group_a.id, common_metric_group_b_q1.id,
common_metric_group_b_q2.id)
end
@@ -37,7 +37,7 @@ describe Gitlab::Prometheus::MetricGroup do
subject do
described_class.for_project(other_project)
- .map(&:metrics).flatten
+ .flat_map(&:metrics)
.map(&:id)
end
diff --git a/spec/lib/gitlab/prometheus/query_variables_spec.rb b/spec/lib/gitlab/prometheus/query_variables_spec.rb
index 6dc99ef26ec..3f9b245a3fb 100644
--- a/spec/lib/gitlab/prometheus/query_variables_spec.rb
+++ b/spec/lib/gitlab/prometheus/query_variables_spec.rb
@@ -23,7 +23,7 @@ describe Gitlab::Prometheus::QueryVariables do
context 'with deployment platform' do
context 'with project cluster' do
- let(:kube_namespace) { environment.deployment_platform.cluster.kubernetes_namespace_for(project) }
+ let(:kube_namespace) { environment.deployment_namespace }
before do
create(:cluster, :project, :provided_by_user, projects: [project])
@@ -38,8 +38,8 @@ describe Gitlab::Prometheus::QueryVariables do
let(:project2) { create(:project) }
let(:kube_namespace) { k8s_ns.namespace }
- let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project) }
- let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2) }
+ let!(:k8s_ns) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project, environment: environment) }
+ let!(:k8s_ns2) { create(:cluster_kubernetes_namespace, cluster: cluster, project: project2, environment: environment) }
before do
group.projects << project
diff --git a/spec/lib/gitlab/prometheus_client_spec.rb b/spec/lib/gitlab/prometheus_client_spec.rb
index f15ae83a02c..0a4e8dbced5 100644
--- a/spec/lib/gitlab/prometheus_client_spec.rb
+++ b/spec/lib/gitlab/prometheus_client_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::PrometheusClient do
include PrometheusHelpers
- subject { described_class.new(RestClient::Resource.new('https://prometheus.example.com')) }
+ subject { described_class.new('https://prometheus.example.com') }
describe '#ping' do
it 'issues a "query" request to the API endpoint' do
@@ -79,8 +79,16 @@ describe Gitlab::PrometheusClient do
expect(req_stub).to have_been_requested
end
- it 'raises a Gitlab::PrometheusClient::Error error when a RestClient::Exception is rescued' do
- req_stub = stub_prometheus_request_with_exception(prometheus_url, RestClient::Exception)
+ it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError is rescued' do
+ req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError)
+
+ expect { subject }
+ .to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
+ expect(req_stub).to have_been_requested
+ end
+
+ it 'raises a Gitlab::PrometheusClient::Error error when a Gitlab::HTTP::ResponseError with a code is rescued' do
+ req_stub = stub_prometheus_request_with_exception(prometheus_url, Gitlab::HTTP::ResponseError.new(code: 400))
expect { subject }
.to raise_error(Gitlab::PrometheusClient::Error, "Network connection error")
@@ -89,13 +97,13 @@ describe Gitlab::PrometheusClient do
end
context 'ping' do
- subject { described_class.new(RestClient::Resource.new(prometheus_url)).ping }
+ subject { described_class.new(prometheus_url).ping }
it_behaves_like 'exceptions are raised'
end
context 'proxy' do
- subject { described_class.new(RestClient::Resource.new(prometheus_url)).proxy('query', { query: '1' }) }
+ subject { described_class.new(prometheus_url).proxy('query', { query: '1' }) }
it_behaves_like 'exceptions are raised'
end
@@ -310,15 +318,32 @@ describe Gitlab::PrometheusClient do
end
end
- context 'when RestClient::Exception is raised' do
+ context 'when Gitlab::HTTP::ResponseError is raised' do
before do
- stub_prometheus_request_with_exception(query_url, RestClient::Exception)
+ stub_prometheus_request_with_exception(query_url, response_error)
+ end
+
+ context "without response code" do
+ let(:response_error) { Gitlab::HTTP::ResponseError }
+ it 'raises PrometheusClient::Error' do
+ expect { subject.proxy('query', { query: prometheus_query }) }.to(
+ raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
+ )
+ end
end
- it 'raises PrometheusClient::Error' do
- expect { subject.proxy('query', { query: prometheus_query }) }.to(
- raise_error(Gitlab::PrometheusClient::Error, 'Network connection error')
- )
+ context "with response code" do
+ let(:response_error) do
+ response = Net::HTTPResponse.new(1.1, 400, '{}sumpthin')
+ allow(response).to receive(:body) { '{}' }
+ Gitlab::HTTP::ResponseError.new(response)
+ end
+
+ it 'raises Gitlab::PrometheusClient::QueryError' do
+ expect { subject.proxy('query', { query: prometheus_query }) }.to(
+ raise_error(Gitlab::PrometheusClient::QueryError, 'Bad data received')
+ )
+ end
end
end
end
diff --git a/spec/lib/gitlab/quick_actions/command_definition_spec.rb b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
index b6e0adbc1c2..21f2c87a755 100644
--- a/spec/lib/gitlab/quick_actions/command_definition_spec.rb
+++ b/spec/lib/gitlab/quick_actions/command_definition_spec.rb
@@ -219,6 +219,52 @@ describe Gitlab::QuickActions::CommandDefinition do
end
end
+ describe "#execute_message" do
+ context "when the command is a noop" do
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is not a noop" do
+ before do
+ subject.action_block = proc { self.run = true }
+ end
+
+ context "when the command is not available" do
+ before do
+ subject.condition_block = proc { false }
+ end
+
+ it 'returns nil' do
+ expect(subject.execute_message({}, nil)).to be_nil
+ end
+ end
+
+ context "when the command is available" do
+ context 'when the execution_message is a static string' do
+ before do
+ subject.execution_message = 'Assigned jacopo'
+ end
+
+ it 'returns this static string' do
+ expect(subject.execute_message({}, nil)).to eq('Assigned jacopo')
+ end
+ end
+
+ context 'when the explanation is dynamic' do
+ before do
+ subject.execution_message = proc { |arg| "Assigned #{arg}" }
+ end
+
+ it 'invokes the proc' do
+ expect(subject.execute_message({}, 'Jacopo')).to eq('Assigned Jacopo')
+ end
+ end
+ end
+ end
+ end
+
describe '#explain' do
context 'when the command is not available' do
before do
diff --git a/spec/lib/gitlab/quick_actions/dsl_spec.rb b/spec/lib/gitlab/quick_actions/dsl_spec.rb
index 185adab1ff6..78b9b3804c3 100644
--- a/spec/lib/gitlab/quick_actions/dsl_spec.rb
+++ b/spec/lib/gitlab/quick_actions/dsl_spec.rb
@@ -20,6 +20,9 @@ describe Gitlab::QuickActions::Dsl do
desc do
"A dynamic description for #{noteable.upcase}"
end
+ execution_message do |arg|
+ "A dynamic execution message for #{noteable.upcase} passing #{arg}"
+ end
params 'The first argument', 'The second argument'
command :dynamic_description do |args|
args.split
@@ -30,6 +33,7 @@ describe Gitlab::QuickActions::Dsl do
explanation do |arg|
"Action does something with #{arg}"
end
+ execution_message 'Command applied correctly'
condition do
project == 'foo'
end
@@ -67,6 +71,7 @@ describe Gitlab::QuickActions::Dsl do
expect(no_args_def.aliases).to eq([:none])
expect(no_args_def.description).to eq('A command with no args')
expect(no_args_def.explanation).to eq('')
+ expect(no_args_def.execution_message).to eq('')
expect(no_args_def.params).to eq([])
expect(no_args_def.condition_block).to be_nil
expect(no_args_def.types).to eq([])
@@ -78,6 +83,8 @@ describe Gitlab::QuickActions::Dsl do
expect(explanation_with_aliases_def.aliases).to eq([:once, :first])
expect(explanation_with_aliases_def.description).to eq('')
expect(explanation_with_aliases_def.explanation).to eq('Static explanation')
+ expect(explanation_with_aliases_def.execution_message).to eq('')
+ expect(no_args_def.params).to eq([])
expect(explanation_with_aliases_def.params).to eq(['The first argument'])
expect(explanation_with_aliases_def.condition_block).to be_nil
expect(explanation_with_aliases_def.types).to eq([])
@@ -88,7 +95,7 @@ describe Gitlab::QuickActions::Dsl do
expect(dynamic_description_def.name).to eq(:dynamic_description)
expect(dynamic_description_def.aliases).to eq([])
expect(dynamic_description_def.to_h(OpenStruct.new(noteable: 'issue'))[:description]).to eq('A dynamic description for ISSUE')
- expect(dynamic_description_def.explanation).to eq('')
+ expect(dynamic_description_def.execute_message(OpenStruct.new(noteable: 'issue'), 'arg')).to eq('A dynamic execution message for ISSUE passing arg')
expect(dynamic_description_def.params).to eq(['The first argument', 'The second argument'])
expect(dynamic_description_def.condition_block).to be_nil
expect(dynamic_description_def.types).to eq([])
@@ -100,6 +107,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cc_def.aliases).to eq([])
expect(cc_def.description).to eq('')
expect(cc_def.explanation).to eq('')
+ expect(cc_def.execution_message).to eq('')
expect(cc_def.params).to eq([])
expect(cc_def.condition_block).to be_nil
expect(cc_def.types).to eq([])
@@ -111,6 +119,7 @@ describe Gitlab::QuickActions::Dsl do
expect(cond_action_def.aliases).to eq([])
expect(cond_action_def.description).to eq('')
expect(cond_action_def.explanation).to be_a_kind_of(Proc)
+ expect(cond_action_def.execution_message).to eq('Command applied correctly')
expect(cond_action_def.params).to eq([])
expect(cond_action_def.condition_block).to be_a_kind_of(Proc)
expect(cond_action_def.types).to eq([])
@@ -122,6 +131,7 @@ describe Gitlab::QuickActions::Dsl do
expect(with_params_parsing_def.aliases).to eq([])
expect(with_params_parsing_def.description).to eq('')
expect(with_params_parsing_def.explanation).to eq('')
+ expect(with_params_parsing_def.execution_message).to eq('')
expect(with_params_parsing_def.params).to eq([])
expect(with_params_parsing_def.condition_block).to be_nil
expect(with_params_parsing_def.types).to eq([])
@@ -133,6 +143,7 @@ describe Gitlab::QuickActions::Dsl do
expect(substitution_def.aliases).to eq([])
expect(substitution_def.description).to eq('')
expect(substitution_def.explanation).to eq('')
+ expect(substitution_def.execution_message).to eq('')
expect(substitution_def.params).to eq(['<Comment>'])
expect(substitution_def.condition_block).to be_nil
expect(substitution_def.types).to eq([])
@@ -144,6 +155,7 @@ describe Gitlab::QuickActions::Dsl do
expect(has_types.aliases).to eq([])
expect(has_types.description).to eq('A command with types')
expect(has_types.explanation).to eq('')
+ expect(has_types.execution_message).to eq('')
expect(has_types.params).to eq([])
expect(has_types.condition_block).to be_nil
expect(has_types.types).to eq([Issue, Commit])
diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb
index a1079e54975..ba295386a55 100644
--- a/spec/lib/gitlab/regex_spec.rb
+++ b/spec/lib/gitlab/regex_spec.rb
@@ -32,6 +32,14 @@ describe Gitlab::Regex do
it { is_expected.not_to match('/') }
end
+ describe '.environment_scope_regex' do
+ subject { described_class.environment_scope_regex }
+
+ it { is_expected.to match('foo') }
+ it { is_expected.to match('foo*Z') }
+ it { is_expected.not_to match('!!()()') }
+ end
+
describe '.environment_slug_regex' do
subject { described_class.environment_slug_regex }
diff --git a/spec/lib/gitlab/repository_cache_adapter_spec.rb b/spec/lib/gitlab/repository_cache_adapter_spec.rb
index 0295138fc3a..04fc24b6205 100644
--- a/spec/lib/gitlab/repository_cache_adapter_spec.rb
+++ b/spec/lib/gitlab/repository_cache_adapter_spec.rb
@@ -4,6 +4,7 @@ describe Gitlab::RepositoryCacheAdapter do
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
let(:cache) { repository.send(:cache) }
+ let(:redis_set_cache) { repository.send(:redis_set_cache) }
describe '#cache_method_output', :use_clean_rails_memory_store_caching do
let(:fallback) { 10 }
@@ -206,9 +207,11 @@ describe Gitlab::RepositoryCacheAdapter do
describe '#expire_method_caches' do
it 'expires the caches of the given methods' do
expect(cache).to receive(:expire).with(:rendered_readme)
- expect(cache).to receive(:expire).with(:gitignore)
+ expect(cache).to receive(:expire).with(:branch_names)
+ expect(redis_set_cache).to receive(:expire).with(:rendered_readme)
+ expect(redis_set_cache).to receive(:expire).with(:branch_names)
- repository.expire_method_caches(%i(rendered_readme gitignore))
+ repository.expire_method_caches(%i(rendered_readme branch_names))
end
it 'does not expire caches for non-existent methods' do
diff --git a/spec/lib/gitlab/repository_set_cache_spec.rb b/spec/lib/gitlab/repository_set_cache_spec.rb
new file mode 100644
index 00000000000..9695e13d842
--- /dev/null
+++ b/spec/lib/gitlab/repository_set_cache_spec.rb
@@ -0,0 +1,73 @@
+require 'spec_helper'
+
+describe Gitlab::RepositorySetCache, :clean_gitlab_redis_cache do
+ let(:project) { create(:project) }
+ let(:repository) { project.repository }
+ let(:namespace) { "#{repository.full_path}:#{project.id}" }
+ let(:cache) { described_class.new(repository) }
+
+ describe '#cache_key' do
+ subject { cache.cache_key(:foo) }
+
+ it 'includes the namespace' do
+ is_expected.to eq("foo:#{namespace}:set")
+ end
+
+ context 'with a given namespace' do
+ let(:extra_namespace) { 'my:data' }
+ let(:cache) { described_class.new(repository, extra_namespace: extra_namespace) }
+
+ it 'includes the full namespace' do
+ is_expected.to eq("foo:#{namespace}:#{extra_namespace}:set")
+ end
+ end
+ end
+
+ describe '#expire' do
+ it 'expires the given key from the cache' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.read(:foo)).to contain_exactly('value')
+ expect(cache.expire(:foo)).to eq(1)
+ expect(cache.read(:foo)).to be_empty
+ end
+ end
+
+ describe '#exist?' do
+ it 'checks whether the key exists' do
+ expect(cache.exist?(:foo)).to be(false)
+
+ cache.write(:foo, ['value'])
+
+ expect(cache.exist?(:foo)).to be(true)
+ end
+ end
+
+ describe '#fetch' do
+ let(:blk) { -> { ['block value'] } }
+
+ subject { cache.fetch(:foo, &blk) }
+
+ it 'fetches the key from the cache when filled' do
+ cache.write(:foo, ['value'])
+
+ is_expected.to contain_exactly('value')
+ end
+
+ it 'writes the value of the provided block when empty' do
+ cache.expire(:foo)
+
+ is_expected.to contain_exactly('block value')
+ expect(cache.read(:foo)).to contain_exactly('block value')
+ end
+ end
+
+ describe '#include?' do
+ it 'checks inclusion in the Redis set' do
+ cache.write(:foo, ['value'])
+
+ expect(cache.include?(:foo, 'value')).to be(true)
+ expect(cache.include?(:foo, 'bar')).to be(false)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_profiler/profile_spec.rb b/spec/lib/gitlab/request_profiler/profile_spec.rb
new file mode 100644
index 00000000000..b37ee558e1a
--- /dev/null
+++ b/spec/lib/gitlab/request_profiler/profile_spec.rb
@@ -0,0 +1,59 @@
+require 'fast_spec_helper'
+
+describe Gitlab::RequestProfiler::Profile do
+ let(:profile) { described_class.new(filename) }
+
+ describe '.new' do
+ context 'using old filename' do
+ let(:filename) { '|api|v4|version.txt_1562854738.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.request_path).to eq('/api/v4/version.txt')
+ expect(profile.time).to eq(Time.at(1562854738).utc)
+ expect(profile.type).to eq('html')
+ end
+ end
+
+ context 'using new filename' do
+ let(:filename) { '|api|v4|version.txt_1563547949_execution.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.request_path).to eq('/api/v4/version.txt')
+ expect(profile.profile_mode).to eq('execution')
+ expect(profile.time).to eq(Time.at(1563547949).utc)
+ expect(profile.type).to eq('html')
+ end
+ end
+ end
+
+ describe '#content_type' do
+ context 'when using html file' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.html' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.content_type).to eq('text/html')
+ end
+ end
+
+ context 'when using text file' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.txt' }
+
+ it 'returns valid data' do
+ expect(profile).to be_valid
+ expect(profile.content_type).to eq('text/plain')
+ end
+ end
+
+ context 'when file is unknown' do
+ let(:filename) { '|api|v4|version.txt_1562854738_memory.xxx' }
+
+ it 'returns valid data' do
+ expect(profile).not_to be_valid
+ expect(profile.content_type).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/request_profiler_spec.rb b/spec/lib/gitlab/request_profiler_spec.rb
index fd8cbf39bce..498c045b6cd 100644
--- a/spec/lib/gitlab/request_profiler_spec.rb
+++ b/spec/lib/gitlab/request_profiler_spec.rb
@@ -13,15 +13,42 @@ describe Gitlab::RequestProfiler do
end
end
- describe '.remove_all_profiles' do
- it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do
- dir = described_class::PROFILES_DIR
- FileUtils.mkdir_p(dir)
+ context 'with temporary PROFILES_DIR' do
+ let(:tmpdir) { Dir.mktmpdir('profiler-test') }
+ let(:profile_name) { '|api|v4|version.txt_1562854738_memory.html' }
+ let(:profile_path) { File.join(tmpdir, profile_name) }
- expect(Dir.exist?(dir)).to be true
+ before do
+ stub_const('Gitlab::RequestProfiler::PROFILES_DIR', tmpdir)
+ FileUtils.touch(profile_path)
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ describe '.remove_all_profiles' do
+ it 'removes Gitlab::RequestProfiler::PROFILES_DIR directory' do
+ described_class.remove_all_profiles
+
+ expect(Dir.exist?(tmpdir)).to be false
+ end
+ end
+
+ describe '.all' do
+ subject { described_class.all }
+
+ it 'returns all profiles' do
+ expect(subject.map(&:name)).to contain_exactly(profile_name)
+ end
+ end
+
+ describe '.find' do
+ subject { described_class.find(profile_name) }
- described_class.remove_all_profiles
- expect(Dir.exist?(dir)).to be false
+ it 'returns all profiles' do
+ expect(subject.name).to eq(profile_name)
+ end
end
end
end
diff --git a/spec/lib/gitlab/rugged_instrumentation_spec.rb b/spec/lib/gitlab/rugged_instrumentation_spec.rb
new file mode 100644
index 00000000000..4dcc8ae514a
--- /dev/null
+++ b/spec/lib/gitlab/rugged_instrumentation_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::RuggedInstrumentation, :request_store do
+ subject { described_class }
+
+ describe '.query_time' do
+ it 'increments query times' do
+ subject.query_time += 0.451
+ subject.query_time += 0.322
+
+ expect(subject.query_time).to be_within(0.001).of(0.773)
+ expect(subject.query_time_ms).to eq(773.0)
+ end
+ end
+
+ context '.increment_query_count' do
+ it 'tracks query counts' do
+ expect(subject.query_count).to eq(0)
+
+ 2.times { subject.increment_query_count }
+
+ expect(subject.query_count).to eq(2)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 3d27156b356..c287da19343 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -29,6 +29,43 @@ describe Gitlab::SearchResults do
end
end
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:scope, :count_method, :expected) do
+ 'projects' | :limited_projects_count | '1000+'
+ 'issues' | :limited_issues_count | '1000+'
+ 'merge_requests' | :limited_merge_requests_count | '1000+'
+ 'milestones' | :limited_milestones_count | '1000+'
+ 'users' | :limited_users_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
+
+ describe '#formatted_limited_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:count, :expected) do
+ 23 | '23'
+ 1000 | '1000'
+ 1001 | '1000+'
+ 1234 | '1000+'
+ end
+
+ with_them do
+ it 'returns the expected formatted limited count' do
+ expect(results.formatted_limited_count(count)).to eq(expected)
+ end
+ end
+ end
+
context "when count_limit is lower than total amount" do
before do
allow(results).to receive(:count_limit).and_return(1)
diff --git a/spec/lib/gitlab/sentry_spec.rb b/spec/lib/gitlab/sentry_spec.rb
index af8b059b984..a48cc0d128a 100644
--- a/spec/lib/gitlab/sentry_spec.rb
+++ b/spec/lib/gitlab/sentry_spec.rb
@@ -65,6 +65,7 @@ describe Gitlab::Sentry do
context '.track_acceptable_exception' do
let(:exception) { RuntimeError.new('boom') }
+ let(:issue_url) { 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1' }
before do
allow(described_class).to receive(:enabled?).and_return(true)
@@ -74,7 +75,7 @@ describe Gitlab::Sentry do
it 'calls Raven.capture_exception' do
expected_extras = {
some_other_info: 'info',
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1'
+ issue_url: issue_url
}
expected_tags = {
@@ -88,9 +89,33 @@ describe Gitlab::Sentry do
described_class.track_acceptable_exception(
exception,
- issue_url: 'http://gitlab.com/gitlab-org/gitlab-ce/issues/1',
+ issue_url: issue_url,
extra: { some_other_info: 'info' }
)
end
+
+ context 'the exception implements :sentry_extra_data' do
+ let(:extra_info) { { event: 'explosion', size: :massive } }
+ let(:exception) { double(message: 'bang!', sentry_extra_data: extra_info) }
+
+ it 'includes the extra data from the exception in the tracking information' do
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception)
+ end
+ end
+
+ context 'the exception implements :sentry_extra_data, which returns nil' do
+ let(:exception) { double(message: 'bang!', sentry_extra_data: nil) }
+
+ it 'just includes the other extra info' do
+ extra_info = { issue_url: issue_url }
+ expect(Raven).to receive(:capture_exception)
+ .with(exception, a_hash_including(extra: a_hash_including(extra_info)))
+
+ described_class.track_acceptable_exception(exception, extra_info)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
index 7bc4599e20f..5621d3d17d1 100644
--- a/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
+++ b/spec/lib/gitlab/sidekiq_logging/structured_logger_spec.rb
@@ -2,7 +2,10 @@ require 'spec_helper'
describe Gitlab::SidekiqLogging::StructuredLogger do
describe '#call' do
- let(:timestamp) { Time.new('2018-01-01 12:00:00').utc }
+ let(:timestamp) { Time.iso8601('2018-01-01T12:00:00Z') }
+ let(:created_at) { timestamp - 1.second }
+ let(:scheduling_latency_s) { 1.0 }
+
let(:job) do
{
"class" => "TestWorker",
@@ -11,19 +14,21 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
"queue" => "cronjob:test_queue",
"queue_namespace" => "cronjob",
"jid" => "da883554ee4fe414012f5f42",
- "created_at" => timestamp.to_f,
- "enqueued_at" => timestamp.to_f,
+ "created_at" => created_at.to_f,
+ "enqueued_at" => created_at.to_f,
"correlation_id" => 'cid'
}
end
+
let(:logger) { double }
let(:start_payload) do
job.merge(
'message' => 'TestWorker JID-da883554ee4fe414012f5f42: start',
'job_status' => 'start',
'pid' => Process.pid,
- 'created_at' => timestamp.iso8601(3),
- 'enqueued_at' => timestamp.iso8601(3)
+ 'created_at' => created_at.iso8601(3),
+ 'enqueued_at' => created_at.iso8601(3),
+ 'scheduling_latency_s' => scheduling_latency_s
)
end
let(:end_payload) do
@@ -118,6 +123,59 @@ describe Gitlab::SidekiqLogging::StructuredLogger do
subject.call(job, 'test_queue') { }
end
end
+
+ it 'logs without created_at and enqueued_at fields' do
+ Timecop.freeze(timestamp) do
+ excluded_fields = %w(created_at enqueued_at args scheduling_latency_s)
+
+ expect(logger).to receive(:info).with(start_payload.except(*excluded_fields)).ordered
+ expect(logger).to receive(:info).with(end_payload.except(*excluded_fields)).ordered
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job.except("created_at", "enqueued_at"), 'test_queue') { }
+ end
+ end
+ end
+
+ context 'with latency' do
+ let(:created_at) { Time.iso8601('2018-01-01T10:00:00Z') }
+ let(:scheduling_latency_s) { 7200.0 }
+
+ it 'logs with scheduling latency' 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
+ expect(subject).to receive(:log_job_start).and_call_original
+ expect(subject).to receive(:log_job_done).and_call_original
+
+ subject.call(job, 'test_queue') { }
+ end
+ end
+ end
+
+ context 'with Gitaly and Rugged calls' do
+ let(:timing_data) do
+ {
+ gitaly_calls: 10,
+ gitaly_duration: 10000,
+ rugged_calls: 1,
+ rugged_duration_ms: 5000
+ }
+ end
+
+ before do
+ job.merge!(timing_data)
+ end
+
+ it 'logs with Gitaly and Rugged timing 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 1a5a38b5d99..1de9a644610 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
let(:pid) { 999 }
- let(:worker) { double(:worker, class: 'TestWorker') }
+ let(:worker) { double(:worker, class: ProjectCacheWorker) }
let(:job) { { 'jid' => 123 } }
let(:queue) { 'test_queue' }
@@ -45,6 +45,12 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do
expect(subject).to receive(:sleep).with(10).ordered
expect(Process).to receive(:kill).with('SIGKILL', pid).ordered
+ expect(Sidekiq.logger)
+ .to receive(:warn).with(class: 'ProjectCacheWorker',
+ message: anything,
+ pid: pid,
+ signal: anything).at_least(:once)
+
run
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
new file mode 100644
index 00000000000..e430599bd94
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/metrics_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SidekiqMiddleware::Metrics do
+ describe '#call' do
+ let(:middleware) { described_class.new }
+ let(:worker) { double(:worker) }
+
+ let(:completion_seconds_metric) { double('completion seconds metric') }
+ let(:failed_total_metric) { double('failed total metric') }
+ let(:retried_total_metric) { double('retried total metric') }
+ let(:running_jobs_metric) { double('running jobs metric') }
+
+ before do
+ 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)
+
+ allow(running_jobs_metric).to receive(:increment)
+ end
+
+ it 'yields block' do
+ allow(completion_seconds_metric).to receive(:observe)
+
+ expect { |b| middleware.call(worker, {}, :test, &b) }.to yield_control.once
+ end
+
+ it 'sets metrics' do
+ labels = { queue: :test }
+
+ expect(running_jobs_metric).to receive(:increment).with(labels, 1)
+ expect(running_jobs_metric).to receive(:increment).with(labels, -1)
+ expect(completion_seconds_metric).to receive(:observe).with(labels, kind_of(Numeric))
+
+ middleware.call(worker, {}, :test) { nil }
+ end
+
+ context 'when job is retried' do
+ it 'sets sidekiq_jobs_retried_total metric' do
+ allow(completion_seconds_metric).to receive(:observe)
+
+ expect(retried_total_metric).to receive(:increment)
+
+ middleware.call(worker, { 'retry_count' => 1 }, :test) { nil }
+ end
+ end
+
+ context 'when error is raised' do
+ it 'sets sidekiq_jobs_failed_total and reraises' do
+ expect(failed_total_metric).to receive(:increment)
+ expect { middleware.call(worker, {}, :test) { raise } }.to raise_error
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/slug/environment_spec.rb b/spec/lib/gitlab/slug/environment_spec.rb
new file mode 100644
index 00000000000..7dc583a94b8
--- /dev/null
+++ b/spec/lib/gitlab/slug/environment_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Slug::Environment do
+ describe '#generate' do
+ {
+ "staging-12345678901234567" => "staging-123456789-q517sa",
+ "9-staging-123456789012345" => "env-9-staging-123-q517sa",
+ "staging-1234567890123456" => "staging-1234567890123456",
+ "staging-1234567890123456-" => "staging-123456789-q517sa",
+ "production" => "production",
+ "PRODUCTION" => "production-q517sa",
+ "review/1-foo" => "review-1-foo-q517sa",
+ "1-foo" => "env-1-foo-q517sa",
+ "1/foo" => "env-1-foo-q517sa",
+ "foo-" => "foo",
+ "foo--bar" => "foo-bar-q517sa",
+ "foo**bar" => "foo-bar-q517sa",
+ "*-foo" => "env-foo-q517sa",
+ "staging-12345678-" => "staging-12345678",
+ "staging-12345678-01234567" => "staging-12345678-q517sa",
+ "" => "env-q517sa",
+ nil => "env-q517sa"
+ }.each do |name, matcher|
+ before do
+ # ('a' * 64).to_i(16).to_s(36).last(6) gives 'q517sa'
+ allow(Digest::SHA2).to receive(:hexdigest).with(name).and_return('a' * 64)
+ end
+
+ it "returns a slug matching #{matcher}, given #{name}" do
+ slug = described_class.new(name).generate
+
+ expect(slug).to match(/\A#{matcher}\z/)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/snippet_search_results_spec.rb b/spec/lib/gitlab/snippet_search_results_spec.rb
index b661a894c0c..35df38f052b 100644
--- a/spec/lib/gitlab/snippet_search_results_spec.rb
+++ b/spec/lib/gitlab/snippet_search_results_spec.rb
@@ -16,4 +16,22 @@ describe Gitlab::SnippetSearchResults do
expect(results.snippet_blobs_count).to eq(1)
end
end
+
+ describe '#formatted_count' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:scope, :count_method, :expected) do
+ 'snippet_titles' | :snippet_titles_count | '1234'
+ 'snippet_blobs' | :snippet_blobs_count | '1234'
+ 'projects' | :limited_projects_count | '1000+'
+ 'unknown' | nil | nil
+ end
+
+ with_them do
+ it 'returns the expected formatted count' do
+ expect(results).to receive(count_method).and_return(1234) if count_method
+ expect(results.formatted_count(scope)).to eq(expected)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/snowplow_tracker_spec.rb b/spec/lib/gitlab/snowplow_tracker_spec.rb
new file mode 100644
index 00000000000..073a33e5973
--- /dev/null
+++ b/spec/lib/gitlab/snowplow_tracker_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::SnowplowTracker do
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
+
+ context '.track_event' do
+ context 'when Snowplow tracker is disabled' do
+ it 'does not track the event' do
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ subject
+ end
+ end
+
+ context 'when Snowplow tracker is enabled' do
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_site_id: 'awesome gitlab')
+ stub_application_setting(snowplow_collector_hostname: 'url.com')
+ end
+
+ it 'tracks the event' do
+ tracker = double
+
+ expect(::SnowplowTracker::Tracker).to receive(:new)
+ .with(
+ an_instance_of(::SnowplowTracker::Emitter),
+ an_instance_of(::SnowplowTracker::Subject),
+ 'cf', 'awesome gitlab'
+ ).and_return(tracker)
+ expect(tracker).to receive(:track_struct_event)
+ .with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/sql/cte_spec.rb b/spec/lib/gitlab/sql/cte_spec.rb
index d6763c7b2e1..5d2164491b5 100644
--- a/spec/lib/gitlab/sql/cte_spec.rb
+++ b/spec/lib/gitlab/sql/cte_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SQL::CTE, :postgresql do
+describe Gitlab::SQL::CTE do
describe '#to_arel' do
it 'generates an Arel relation for the CTE body' do
relation = User.where(id: 1)
diff --git a/spec/lib/gitlab/sql/recursive_cte_spec.rb b/spec/lib/gitlab/sql/recursive_cte_spec.rb
index 7fe39dd5a96..407a4d8a247 100644
--- a/spec/lib/gitlab/sql/recursive_cte_spec.rb
+++ b/spec/lib/gitlab/sql/recursive_cte_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::SQL::RecursiveCTE, :postgresql do
+describe Gitlab::SQL::RecursiveCTE do
let(:cte) { described_class.new(:cte_name) }
describe '#to_arel' do
diff --git a/spec/lib/gitlab/submodule_links_spec.rb b/spec/lib/gitlab/submodule_links_spec.rb
new file mode 100644
index 00000000000..a84602cd07d
--- /dev/null
+++ b/spec/lib/gitlab/submodule_links_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::SubmoduleLinks do
+ let(:submodule_item) { double(id: 'hash', path: 'gitlab-ce') }
+ let(:repo) { double }
+ let(:links) { described_class.new(repo) }
+
+ describe '#for' do
+ subject { links.for(submodule_item, 'ref') }
+
+ context 'when there is no .gitmodules file' do
+ before do
+ stub_urls(nil)
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is unknown' do
+ before do
+ stub_urls({ 'path' => 'url' })
+ end
+
+ it 'returns no links' do
+ expect(subject).to eq([nil, nil])
+ end
+ end
+
+ context 'when the submodule is known' do
+ before do
+ stub_urls({ 'gitlab-ce' => 'git@gitlab.com:gitlab-org/gitlab-ce.git' })
+ end
+
+ it 'returns links' do
+ expect(subject).to eq(['https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash'])
+ end
+ end
+ end
+
+ def stub_urls(urls)
+ allow(repo).to receive(:submodule_urls_for).and_return(urls)
+ end
+end
diff --git a/spec/lib/gitlab/url_blocker_spec.rb b/spec/lib/gitlab/url_blocker_spec.rb
index f8b0cbfb6f6..45d9022abeb 100644
--- a/spec/lib/gitlab/url_blocker_spec.rb
+++ b/spec/lib/gitlab/url_blocker_spec.rb
@@ -68,6 +68,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://example.org'))
expect(hostname).to eq(nil)
end
+
+ context 'when it cannot be resolved' do
+ let(:import_url) { 'http://foobar.x' }
+
+ it 'raises error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
context 'when the URL hostname is an IP address' do
@@ -79,6 +89,16 @@ describe Gitlab::UrlBlocker do
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to be(nil)
end
+
+ context 'when it is invalid' do
+ let(:import_url) { 'http://1.1.1.1.1' }
+
+ it 'raises an error' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect { described_class.validate!(import_url) }.to raise_error(described_class::BlockedUrlError)
+ end
+ end
end
end
end
@@ -180,8 +200,6 @@ describe Gitlab::UrlBlocker do
end
it 'returns true for a non-alphanumeric hostname' do
- stub_resolv
-
aggregate_failures do
expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
@@ -220,53 +238,53 @@ describe Gitlab::UrlBlocker do
end
let(:fake_domain) { 'www.fakedomain.fake' }
- context 'true (default)' do
+ shared_examples 'allows local requests' do |url_blocker_attributes|
it 'does not block urls from private networks' do
local_ips.each do |ip|
- stub_domain_resolv(fake_domain, ip)
-
- expect(described_class).not_to be_blocked_url("http://#{fake_domain}")
-
- unstub_domain_resolv
+ stub_domain_resolv(fake_domain, ip) do
+ expect(described_class).not_to be_blocked_url("http://#{fake_domain}", url_blocker_attributes)
+ end
- expect(described_class).not_to be_blocked_url("http://#{ip}")
+ expect(described_class).not_to be_blocked_url("http://#{ip}", url_blocker_attributes)
end
end
it 'allows localhost endpoints' do
- expect(described_class).not_to be_blocked_url('http://0.0.0.0', allow_localhost: true)
- expect(described_class).not_to be_blocked_url('http://localhost', allow_localhost: true)
- expect(described_class).not_to be_blocked_url('http://127.0.0.1', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://0.0.0.0', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://localhost', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.1', url_blocker_attributes)
end
it 'allows loopback endpoints' do
- expect(described_class).not_to be_blocked_url('http://127.0.0.2', allow_localhost: true)
+ expect(described_class).not_to be_blocked_url('http://127.0.0.2', url_blocker_attributes)
end
it 'allows IPv4 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://169.254.169.254')
- expect(described_class).not_to be_blocked_url('http://169.254.168.100')
+ expect(described_class).not_to be_blocked_url('http://169.254.169.254', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://169.254.168.100', url_blocker_attributes)
end
it 'allows IPv6 link-local endpoints' do
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]')
- expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]')
- expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]')
- expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]')
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.169.254]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.169.254]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a9fe]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[0:0:0:0:0:ffff:169.254.168.100]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:169.254.168.100]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[::ffff:a9fe:a864]', url_blocker_attributes)
+ expect(described_class).not_to be_blocked_url('http://[fe80::c800:eff:fe74:8]', url_blocker_attributes)
end
end
+ context 'true (default)' do
+ it_behaves_like 'allows local requests', { allow_localhost: true, allow_local_network: true }
+ end
+
context 'false' do
it 'blocks urls from private networks' do
local_ips.each do |ip|
- stub_domain_resolv(fake_domain, ip)
-
- expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
-
- unstub_domain_resolv
+ stub_domain_resolv(fake_domain, ip) do
+ expect(described_class).to be_blocked_url("http://#{fake_domain}", allow_local_network: false)
+ end
expect(described_class).to be_blocked_url("http://#{ip}", allow_local_network: false)
end
@@ -286,24 +304,174 @@ describe Gitlab::UrlBlocker do
expect(described_class).to be_blocked_url('http://[::ffff:a9fe:a864]', allow_local_network: false)
expect(described_class).to be_blocked_url('http://[fe80::c800:eff:fe74:8]', allow_local_network: false)
end
+
+ context 'when local domain/IP is whitelisted' do
+ let(:url_blocker_attributes) do
+ {
+ allow_localhost: false,
+ allow_local_network: false
+ }
+ end
+
+ before do
+ stub_application_setting(outbound_local_requests_whitelist: whitelist)
+ end
+
+ context 'with IPs in whitelist' do
+ let(:whitelist) do
+ [
+ '0.0.0.0',
+ '127.0.0.1',
+ '127.0.0.2',
+ '192.168.1.1',
+ '192.168.1.2',
+ '0:0:0:0:0:ffff:192.168.1.2',
+ '::ffff:c0a8:102',
+ '10.0.0.2',
+ '0:0:0:0:0:ffff:10.0.0.2',
+ '::ffff:a00:2',
+ '172.16.0.2',
+ '0:0:0:0:0:ffff:172.16.0.2',
+ '::ffff:ac10:20',
+ 'feef::1',
+ 'fee2::',
+ 'fc00:bf8b:e62c:abcd:abcd:aaaa:aaaa:aaaa',
+ '0:0:0:0:0:ffff:169.254.169.254',
+ '::ffff:a9fe:a9fe',
+ '::ffff:169.254.168.100',
+ '::ffff:a9fe:a864',
+ 'fe80::c800:eff:fe74:8',
+
+ # garbage IPs
+ '45645632345',
+ 'garbage456:more345gar:bage'
+ ]
+ end
+
+ it_behaves_like 'allows local requests', { allow_localhost: false, allow_local_network: false }
+
+ it 'whitelists IP when dns_rebind_protection is disabled' do
+ stub_domain_resolv('example.com', '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://example.com",
+ url_blocker_attributes.merge(dns_rebind_protection: false))
+ end
+ end
+ end
+
+ context 'with domains in whitelist' do
+ let(:whitelist) do
+ [
+ 'www.example.com',
+ 'example.com',
+ 'xn--itlab-j1a.com',
+ 'garbage$^$%#$^&$'
+ ]
+ end
+
+ it 'allows domains present in whitelist' do
+ domain = 'example.com'
+ subdomain1 = 'www.example.com'
+ subdomain2 = 'subdomain.example.com'
+
+ stub_domain_resolv(domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{domain}",
+ url_blocker_attributes)
+ end
+
+ stub_domain_resolv(subdomain1, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{subdomain1}",
+ url_blocker_attributes)
+ end
+
+ # subdomain2 is not part of the whitelist so it should be blocked
+ stub_domain_resolv(subdomain2, '192.168.1.1') do
+ expect(described_class).to be_blocked_url("http://#{subdomain2}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'works with unicode and idna encoded domains' do
+ unicode_domain = 'ğitlab.com'
+ idna_encoded_domain = 'xn--itlab-j1a.com'
+
+ stub_domain_resolv(unicode_domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{unicode_domain}",
+ url_blocker_attributes)
+ end
+
+ stub_domain_resolv(idna_encoded_domain, '192.168.1.1') do
+ expect(described_class).not_to be_blocked_url("http://#{idna_encoded_domain}",
+ url_blocker_attributes)
+ end
+ end
+ end
+
+ context 'with ip ranges in whitelist' do
+ let(:ipv4_range) { '127.0.0.0/28' }
+ let(:ipv6_range) { 'fd84:6d02:f6d8:c89e::/124' }
+
+ let(:whitelist) do
+ [
+ ipv4_range,
+ ipv6_range
+ ]
+ end
+
+ it 'blocks ipv4 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_blocked_url("http://#{ip}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'allows all ipv4s in the range when in whitelist' do
+ IPAddr.new(ipv4_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_blocked_url("http://#{ip}",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'blocks ipv6 range when not in whitelist' do
+ stub_application_setting(outbound_local_requests_whitelist: [])
+
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).to be_blocked_url("http://[#{ip}]",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'allows all ipv6s in the range when in whitelist' do
+ IPAddr.new(ipv6_range).to_range.to_a.each do |ip|
+ expect(described_class).not_to be_blocked_url("http://[#{ip}]",
+ url_blocker_attributes)
+ end
+ end
+
+ it 'blocks IPs outside the range' do
+ expect(described_class).to be_blocked_url("http://[fd84:6d02:f6d8:c89e:0:0:1:f]",
+ url_blocker_attributes)
+
+ expect(described_class).to be_blocked_url("http://127.0.1.15",
+ url_blocker_attributes)
+ end
+ end
+ end
end
- def stub_domain_resolv(domain, ip)
+ def stub_domain_resolv(domain, ip, &block)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
- end
- def unstub_domain_resolv
+ yield
+
allow(Addrinfo).to receive(:getaddrinfo).and_call_original
end
end
context 'when enforce_user is' do
- before do
- stub_resolv
- end
-
context 'false (default)' do
it 'does not block urls with a non-alphanumeric username' do
expect(described_class).not_to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
@@ -351,6 +519,18 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://git‌lab.com/foo/foo.bar', ascii_only: true)).to be true
end
end
+
+ it 'blocks urls with invalid ip address' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://8.8.8.8.8')
+ end
+
+ it 'blocks urls whose hostname cannot be resolved' do
+ stub_env('RSPEC_ALLOW_INVALID_URLS', 'false')
+
+ expect(described_class).to be_blocked_url('http://foobar.x')
+ end
end
describe '#validate_hostname' do
@@ -382,10 +562,4 @@ describe Gitlab::UrlBlocker do
end
end
end
-
- # Resolv does not support resolving UTF-8 domain names
- # See https://bugs.ruby-lang.org/issues/4270
- def stub_resolv
- allow(Resolv).to receive(:getaddresses).and_return([])
- end
end
diff --git a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
new file mode 100644
index 00000000000..71be37692e2
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
+ it_behaves_like 'a redis usage counter', 'CycleAnalytics', :views
+
+ it_behaves_like 'a redis usage counter with totals', :cycle_analytics, views: 3
+end
diff --git a/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
new file mode 100644
index 00000000000..b385d1b07c7
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/note_counter_spec.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::NoteCounter, :clean_gitlab_redis_shared_state do
+ shared_examples 'a note usage counter' do |event, noteable_type|
+ describe ".count(#{event})" do
+ it "increments the Note #{event} counter by 1" do
+ expect do
+ described_class.count(event, noteable_type)
+ end.to change { described_class.read(event, noteable_type) }.by 1
+ end
+ end
+
+ describe ".read(#{event})" do
+ event_count = 5
+
+ it "returns the total number of #{event} events" do
+ event_count.times do
+ described_class.count(event, noteable_type)
+ end
+
+ expect(described_class.read(event, noteable_type)).to eq(event_count)
+ end
+ end
+ 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, 'MergeRequest', 4],
+ [:create, 'Commit', 5]
+ ]
+ end
+
+ let(:expected_totals) do
+ { snippet_comment: 3,
+ merge_request_comment: 4,
+ commit_comment: 5 }
+ end
+
+ before do
+ combinations.each do |event, noteable_type, n|
+ n.times do
+ described_class.count(event, noteable_type)
+ end
+ end
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(expected_totals)
+ end
+ end
+
+ describe 'unknown events or noteable_type' do
+ using RSpec::Parameterized::TableSyntax
+
+ 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 | '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
+ if should_raise
+ expect { described_class.count(event, noteable_type) }.to raise_error(unknown_event_error)
+ else
+ described_class.count(event, noteable_type)
+
+ expect(described_class.read(event, noteable_type)).to eq(expected_count)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
index 38b4c22e186..c34ac7867ab 100644
--- a/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
+++ b/spec/lib/gitlab/usage_data_counters/redis_counter_spec.rb
@@ -3,52 +3,31 @@
require 'spec_helper'
describe Gitlab::UsageDataCounters::RedisCounter, :clean_gitlab_redis_shared_state do
- context 'when redis_key is not defined' do
- subject do
- Class.new.extend(described_class)
- end
+ let(:redis_key) { 'foobar' }
- describe '.increment' do
- it 'raises a NotImplementedError exception' do
- expect { subject.increment}.to raise_error(NotImplementedError)
- end
- end
+ subject { Class.new.extend(described_class) }
- describe '.total_count' do
- it 'raises a NotImplementedError exception' do
- expect { subject.total_count}.to raise_error(NotImplementedError)
- end
- end
+ before do
+ stub_application_setting(usage_ping_enabled: setting_value)
end
- context 'when redis_key is defined' do
- subject do
- counter_module = described_class
-
- Class.new do
- extend counter_module
+ context 'when usage_ping is disabled' do
+ let(:setting_value) { false }
- def self.redis_counter_key
- 'foo_redis_key'
- end
- end
- end
-
- describe '.increment' do
- it 'increments the web ide commits counter by 1' do
- expect do
- subject.increment
- end.to change { subject.total_count }.from(0).to(1)
- end
+ it 'counter is not increased' do
+ expect do
+ subject.increment(redis_key)
+ end.not_to change { subject.total_count(redis_key) }
end
+ end
- describe '.total_count' do
- it 'returns the total amount of web ide commits' do
- subject.increment
- subject.increment
+ context 'when usage_ping is enabled' do
+ let(:setting_value) { true }
- expect(subject.total_count).to eq(2)
- end
+ it 'counter is increased' do
+ expect do
+ subject.increment(redis_key)
+ end.to change { subject.total_count(redis_key) }.by(1)
end
end
end
diff --git a/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
new file mode 100644
index 00000000000..50a9f980dc7
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/search_counter_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SearchCounter, :clean_gitlab_redis_shared_state do
+ it 'increments counter and return the total count' do
+ expect(described_class.total_navbar_searches_count).to eq(0)
+
+ 2.times { described_class.increment_navbar_searches_count }
+
+ expect(described_class.total_navbar_searches_count).to eq(2)
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
new file mode 100644
index 00000000000..65381ed36d1
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/snippet_counter_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SnippetCounter do
+ it_behaves_like 'a redis usage counter', 'Snippet', :create
+ it_behaves_like 'a redis usage counter', 'Snippet', :update
+
+ it_behaves_like 'a redis usage counter with totals', :snippet,
+ create: 3,
+ update: 2
+end
diff --git a/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
new file mode 100644
index 00000000000..47077345e0c
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/source_code_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::SourceCodeCounter do
+ it_behaves_like 'a redis usage counter', 'Source Code', :pushes
+
+ it_behaves_like 'a redis usage counter with totals', :source_code, pushes: 5
+end
diff --git a/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
new file mode 100644
index 00000000000..7a01f7d1de8
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/web_ide_counter_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::WebIdeCounter, :clean_gitlab_redis_shared_state do
+ shared_examples 'counter examples' do
+ it 'increments counter and return the total count' do
+ expect(described_class.public_send(total_counter_method)).to eq(0)
+
+ 2.times { described_class.public_send(increment_counter_method) }
+
+ expect(described_class.public_send(total_counter_method)).to eq(2)
+ end
+ end
+
+ describe 'commits counter' do
+ let(:increment_counter_method) { :increment_commits_count }
+ let(:total_counter_method) { :total_commits_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe 'merge requests counter' do
+ let(:increment_counter_method) { :increment_merge_requests_count }
+ let(:total_counter_method) { :total_merge_requests_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe 'views counter' do
+ let(:increment_counter_method) { :increment_views_count }
+ let(:total_counter_method) { :total_views_count }
+
+ it_behaves_like 'counter examples'
+ end
+
+ describe '.totals' do
+ commits = 5
+ merge_requests = 3
+ views = 2
+
+ before do
+ commits.times { described_class.increment_commits_count }
+ merge_requests.times { described_class.increment_merge_requests_count }
+ views.times { described_class.increment_views_count }
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(
+ web_ide_commits: commits,
+ web_ide_views: views,
+ web_ide_merge_requests: merge_requests
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
new file mode 100644
index 00000000000..4e8ae35187e
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/wiki_page_counter_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::WikiPageCounter do
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :create
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :update
+ it_behaves_like 'a redis usage counter', 'Wiki Page', :delete
+
+ it_behaves_like 'a redis usage counter with totals', :wiki_pages,
+ create: 5,
+ update: 3,
+ delete: 2
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 90a534de202..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
@@ -24,7 +26,7 @@ describe Gitlab::UsageData do
create(:cluster, :group, :disabled)
create(:clusters_applications_helm, :installed, cluster: gcp_cluster)
create(:clusters_applications_ingress, :installed, cluster: gcp_cluster)
- create(:clusters_applications_cert_managers, :installed, cluster: gcp_cluster)
+ create(:clusters_applications_cert_manager, :installed, cluster: gcp_cluster)
create(:clusters_applications_prometheus, :installed, cluster: gcp_cluster)
create(:clusters_applications_runner, :installed, cluster: gcp_cluster)
create(:clusters_applications_knative, :installed, cluster: gcp_cluster)
@@ -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
@@ -57,13 +59,30 @@ describe Gitlab::UsageData do
gitaly
database
avg_cycle_analytics
- web_ide_commits
influxdb_metrics_enabled
prometheus_metrics_enabled
+ cycle_analytics_views
))
+
+ expect(subject).to include(
+ 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),
+ web_ide_views: a_kind_of(Integer),
+ web_ide_commits: a_kind_of(Integer),
+ web_ide_merge_requests: a_kind_of(Integer),
+ navbar_searches: a_kind_of(Integer),
+ cycle_analytics_views: a_kind_of(Integer),
+ source_code_pushes: a_kind_of(Integer)
+ )
end
- it "gathers usage counts" do
+ it 'gathers usage counts' do
expected_keys = %i(
assignee_lists
boards
@@ -139,11 +158,6 @@ describe Gitlab::UsageData do
expect(expected_keys - count_data.keys).to be_empty
end
- it 'does not gather user preferences usage data when the feature is disabled' do
- stub_feature_flags(group_overview_security_dashboard: false)
- expect(subject[:counts].keys).not_to include(:user_preferences)
- end
-
it 'gathers projects data correctly' do
count_data = subject[:counts]
@@ -182,6 +196,28 @@ describe Gitlab::UsageData do
end
end
+ describe '#usage_data_counters' do
+ subject { described_class.usage_data_counters }
+
+ it { is_expected.to all(respond_to :totals) }
+
+ describe 'the results of calling #totals on all objects in the array' do
+ subject { described_class.usage_data_counters.map(&:totals) }
+
+ it do
+ is_expected
+ .to all(be_a Hash)
+ .and all(have_attributes(keys: all(be_a Symbol), values: all(be_a Integer)))
+ end
+ end
+
+ it 'does not have any conflicts' do
+ all_keys = subject.flat_map { |counter| counter.totals.keys }
+
+ expect(all_keys.size).to eq all_keys.to_set.size
+ end
+ end
+
describe '#features_usage_data_ce' do
subject { described_class.features_usage_data_ce }
@@ -216,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')
@@ -234,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/utils/sanitize_node_link_spec.rb b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
new file mode 100644
index 00000000000..064c2707d06
--- /dev/null
+++ b/spec/lib/gitlab/utils/sanitize_node_link_spec.rb
@@ -0,0 +1,72 @@
+require 'spec_helper'
+
+describe Gitlab::Utils::SanitizeNodeLink do
+ let(:klass) do
+ struct = Struct.new(:value)
+ struct.include(described_class)
+
+ struct
+ end
+
+ subject(:object) { klass.new(:value) }
+
+ invalid_schemes = [
+ "javascript:",
+ "JaVaScRiPt:",
+ "\u0001java\u0003script:",
+ "javascript :",
+ "javascript: ",
+ "javascript : ",
+ ":javascript:",
+ "javascript&#58;",
+ "javascript&#0058;",
+ " &#14; javascript:"
+ ]
+
+ invalid_schemes.each do |scheme|
+ context "with the scheme: #{scheme}" do
+ describe "#remove_unsafe_links" do
+ tags = {
+ a: {
+ doc: HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>"),
+ attr: "href",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ img: {
+ doc: HTML::Pipeline.parse("<img src='#{scheme}alert(1);'>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first }
+ },
+ video: {
+ doc: HTML::Pipeline.parse("<video><source src='#{scheme}alert(1);'></video>"),
+ attr: "src",
+ node_to_check: -> (doc) { doc.children.first.children.filter("source").first }
+ }
+ }
+
+ tags.each do |tag, opts|
+ context "<#{tag}> tags" do
+ it "removes the unsafe link" do
+ node = opts[:node_to_check].call(opts[:doc])
+
+ expect { object.remove_unsafe_links({ node: node }, remove_invalid_links: true) }
+ .to change { node[opts[:attr]] }
+
+ expect(node[opts[:attr]]).to be_blank
+ end
+ end
+ end
+ end
+
+ describe "#safe_protocol?" do
+ let(:doc) { HTML::Pipeline.parse("<a href='#{scheme}alert(1);'>foo</a>") }
+ let(:node) { doc.children.first }
+ let(:uri) { Addressable::URI.parse(node['href'])}
+
+ it "returns false" do
+ expect(object.safe_protocol?(scheme)).to be_falsy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 4645339f439..0c20b3aa4c8 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -231,4 +231,23 @@ describe Gitlab::Utils do
end
end
end
+
+ describe '.string_to_ip_object' do
+ it 'returns nil when string is nil' do
+ expect(described_class.string_to_ip_object(nil)).to eq(nil)
+ end
+
+ it 'returns nil when string is invalid IP' do
+ expect(described_class.string_to_ip_object('invalid ip')).to eq(nil)
+ expect(described_class.string_to_ip_object('')).to eq(nil)
+ end
+
+ it 'returns IP object when string is valid IP' do
+ expect(described_class.string_to_ip_object('192.168.1.1')).to eq(IPAddr.new('192.168.1.1'))
+ expect(described_class.string_to_ip_object('::ffff:a9fe:a864')).to eq(IPAddr.new('::ffff:a9fe:a864'))
+ expect(described_class.string_to_ip_object('[::ffff:a9fe:a864]')).to eq(IPAddr.new('::ffff:a9fe:a864'))
+ expect(described_class.string_to_ip_object('127.0.0.0/28')).to eq(IPAddr.new('127.0.0.0/28'))
+ expect(described_class.string_to_ip_object('1:0:0:0:0:0:0:0/124')).to eq(IPAddr.new('1:0:0:0:0:0:0:0/124'))
+ end
+ end
end
diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb
index f8332757fcd..451e18ed91b 100644
--- a/spec/lib/gitlab/workhorse_spec.rb
+++ b/spec/lib/gitlab/workhorse_spec.rb
@@ -404,6 +404,7 @@ describe Gitlab::Workhorse do
end
it 'set and notify' do
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_call_original
expect_any_instance_of(::Redis).to receive(:publish)
.with(described_class::NOTIFICATION_CHANNEL, "test-key=test-value")
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
index 52387fc3688..c3d1679d031 100644
--- a/spec/lib/gitlab/zoom_link_extractor_spec.rb
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -20,5 +20,15 @@ describe Gitlab::ZoomLinkExtractor do
it { is_expected.to eq(links) }
end
+
+ describe '#match?' do
+ it 'is true when a zoom link found' do
+ expect(described_class.new('issue text https://zoom.us/j/123')).to be_match
+ end
+
+ it 'is false when no zoom link found' do
+ expect(described_class.new('issue text')).not_to be_match
+ end
+ end
end
end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index 82b0e819063..c293f58c9cb 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -136,6 +136,12 @@ describe Gitlab do
expect(described_class.ee?).to eq(false)
end
+
+ it 'returns true when the IS_GITLAB_EE variable is not empty' do
+ stub_env('IS_GITLAB_EE', '1')
+
+ expect(described_class.ee?).to eq(true)
+ end
end
describe '.http_proxy_env?' do
diff --git a/spec/lib/peek/views/redis_detailed_spec.rb b/spec/lib/peek/views/redis_detailed_spec.rb
index da13b6df53b..61096e6c69e 100644
--- a/spec/lib/peek/views/redis_detailed_spec.rb
+++ b/spec/lib/peek/views/redis_detailed_spec.rb
@@ -2,14 +2,8 @@
require 'spec_helper'
-describe Peek::Views::RedisDetailed do
- let(:redis_detailed_class) do
- Class.new do
- include Peek::Views::RedisDetailed
- end
- end
-
- subject { redis_detailed_class.new }
+describe Peek::Views::RedisDetailed, :request_store do
+ subject { described_class.new }
using RSpec::Parameterized::TableSyntax
@@ -22,15 +16,24 @@ describe Peek::Views::RedisDetailed do
end
with_them do
- it 'scrubs Redis commands', :request_store do
+ it 'scrubs Redis commands' do
subject.detail_store << { cmd: cmd, duration: 1.second }
- expect(subject.details.count).to eq(1)
- expect(subject.details.first)
+ expect(subject.results[:details].count).to eq(1)
+ expect(subject.results[:details].first)
.to eq({
cmd: expected,
duration: 1000
})
end
end
+
+ it 'returns aggregated results' do
+ subject.detail_store << { cmd: [:get, 'test'], duration: 0.001 }
+ subject.detail_store << { cmd: [:get, 'test'], duration: 1.second }
+
+ expect(subject.results[:calls]).to eq(2)
+ expect(subject.results[:duration]).to eq('1001.00ms')
+ expect(subject.results[:details].count).to eq(2)
+ end
end
diff --git a/spec/lib/peek/views/rugged_spec.rb b/spec/lib/peek/views/rugged_spec.rb
new file mode 100644
index 00000000000..d07d6b51a1f
--- /dev/null
+++ b/spec/lib/peek/views/rugged_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Peek::Views::Rugged, :request_store do
+ subject { described_class.new }
+
+ let(:project) { create(:project) }
+
+ before do
+ allow(Gitlab::RuggedInstrumentation).to receive(:peek_enabled?).and_return(true)
+ end
+
+ it 'returns no results' do
+ expect(subject.results).to eq({})
+ end
+
+ it 'returns aggregated results' do
+ ::Gitlab::RuggedInstrumentation.query_time += 1.234
+ ::Gitlab::RuggedInstrumentation.increment_query_count
+ ::Gitlab::RuggedInstrumentation.increment_query_count
+
+ ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test,
+ args: [project.repository.raw, 'HEAD'],
+ duration: 0.123)
+ ::Gitlab::RuggedInstrumentation.add_call_details(feature: :rugged_test2,
+ args: [project.repository, 'refs/heads/master'],
+ duration: 0.456)
+
+ results = subject.results
+ expect(results[:calls]).to eq(2)
+ expect(results[:duration]).to eq('1234.00ms')
+ expect(results[:details].count).to eq(2)
+
+ expected = [
+ [project.repository.raw.to_s, "HEAD"],
+ [project.repository.to_s, "refs/heads/master"]
+ ]
+
+ expect(results[:details].map { |data| data[:args] }).to match_array(expected)
+ end
+end
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/prometheus/pid_provider_spec.rb b/spec/lib/prometheus/pid_provider_spec.rb
new file mode 100644
index 00000000000..ba843b27254
--- /dev/null
+++ b/spec/lib/prometheus/pid_provider_spec.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Prometheus::PidProvider do
+ describe '.worker_id' do
+ subject { described_class.worker_id }
+
+ let(:sidekiq_module) { Module.new }
+
+ before do
+ allow(sidekiq_module).to receive(:server?).and_return(false)
+ stub_const('Sidekiq', sidekiq_module)
+ end
+
+ context 'when running in Sidekiq server mode' do
+ before do
+ expect(Sidekiq).to receive(:server?).and_return(true)
+ end
+
+ it { is_expected.to eq 'sidekiq' }
+ end
+
+ context 'when running in Unicorn mode' do
+ before do
+ stub_const('Unicorn::Worker', Class.new)
+ hide_const('Puma')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
+ end
+
+ context 'when unicorn master is specified in process name' do
+ context 'when running in Omnibus' do
+ context 'before the process was renamed' do
+ let(:process_name) { "/opt/gitlab/embedded/bin/unicorn"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn master -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+
+ context 'when in development env' do
+ context 'before the process was renamed' do
+ let(:process_name) { "path_to_bindir/bin/unicorn_rails"}
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+
+ context 'after the process was renamed' do
+ let(:process_name) { "unicorn_rails master -c /gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_master' }
+ end
+ end
+ end
+
+ context 'when unicorn worker id is specified in process name' do
+ context 'when running in Omnibus' do
+ let(:process_name) { "unicorn worker[1] -D -E production -c /var/opt/gitlab/gitlab-rails/etc/unicorn.rb /opt/gitlab/embedded/service/gitlab-rails/config.ru" }
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+
+ context 'when in development env' do
+ let(:process_name) { "unicorn_rails worker[1] -c gitlab_dir/config/unicorn.rb -E development" }
+
+ it { is_expected.to eq 'unicorn_1' }
+ end
+ end
+
+ context 'when no specified unicorn master or worker id in process name' do
+ let(:process_name) { "bin/unknown_process"}
+
+ it { is_expected.to eq "process_#{Process.pid}" }
+ end
+ end
+
+ context 'when running in Puma mode' do
+ before do
+ stub_const('Puma', Module.new)
+ hide_const('Unicorn::Worker')
+
+ expect(described_class).to receive(:process_name)
+ .at_least(:once)
+ .and_return(process_name)
+ end
+
+ context 'when cluster worker id is specified in process name' do
+ let(:process_name) { 'puma: cluster worker 1: 17483 [gitlab-puma-worker]' }
+
+ it { is_expected.to eq 'puma_1' }
+ end
+
+ context 'when no worker id is specified in process name' do
+ let(:process_name) { 'bin/puma' }
+
+ it { is_expected.to eq 'puma_master' }
+ end
+ end
+
+ context 'when running in unknown mode' do
+ before do
+ hide_const('Puma')
+ hide_const('Unicorn::Worker')
+ end
+
+ it { is_expected.to eq "process_#{Process.pid}" }
+ end
+ end
+end
diff --git a/spec/lib/quality/test_level_spec.rb b/spec/lib/quality/test_level_spec.rb
index 3465c3a050b..59870ce44a7 100644
--- a/spec/lib/quality/test_level_spec.rb
+++ b/spec/lib/quality/test_level_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a pattern' do
expect(subject.pattern(:unit))
- .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
+ .to eq("spec/{bin,config,db,dependencies,factories,finders,frontend,graphql,haml_lint,helpers,initializers,javascripts,lib,migrations,models,policies,presenters,rack_servers,routing,rubocop,serializers,services,sidekiq,tasks,uploaders,validators,views,workers,elastic_integration}{,/**/}*_spec.rb")
end
end
@@ -47,7 +47,7 @@ RSpec.describe Quality::TestLevel do
context 'when level is unit' do
it 'returns a regexp' do
expect(subject.regexp(:unit))
- .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
+ .to eq(%r{spec/(bin|config|db|dependencies|factories|finders|frontend|graphql|haml_lint|helpers|initializers|javascripts|lib|migrations|models|policies|presenters|rack_servers|routing|rubocop|serializers|services|sidekiq|tasks|uploaders|validators|views|workers|elastic_integration)})
end
end
diff --git a/spec/lib/sentry/client_spec.rb b/spec/lib/sentry/client_spec.rb
index cb14204b99a..ca2b17b44e0 100644
--- a/spec/lib/sentry/client_spec.rb
+++ b/spec/lib/sentry/client_spec.rb
@@ -63,7 +63,7 @@ describe Sentry::Client do
shared_examples 'maps exceptions' do
exceptions = {
- HTTParty::Error => 'Error when connecting to Sentry',
+ Gitlab::HTTP::Error => 'Error when connecting to Sentry',
Net::OpenTimeout => 'Connection to Sentry timed out',
SocketError => 'Received SocketError when trying to connect to Sentry',
OpenSSL::SSL::SSLError => 'Sentry returned invalid SSL data',
diff --git a/spec/lib/serializers/json_spec.rb b/spec/lib/serializers/json_spec.rb
index 5d59d66e8b8..847a01d186c 100644
--- a/spec/lib/serializers/json_spec.rb
+++ b/spec/lib/serializers/json_spec.rb
@@ -6,24 +6,8 @@ describe Serializers::JSON do
subject { described_class.dump(obj) }
- context 'when MySQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
- end
-
- it 'encodes as string' do
- is_expected.to eq('{"key":"value"}')
- end
- end
-
- context 'when PostgreSQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
- end
-
- it 'returns a hash' do
- is_expected.to eq(obj)
- end
+ it 'returns a hash' do
+ is_expected.to eq(obj)
end
end
@@ -31,7 +15,13 @@ describe Serializers::JSON do
let(:data_string) { '{"key":"value","variables":[{"key":"VAR1","value":"VALUE1"}]}' }
let(:data_hash) { JSON.parse(data_string) }
- shared_examples 'having consistent accessor' do
+ context 'when loading a hash' do
+ subject { described_class.load(data_hash) }
+
+ it 'decodes a string' do
+ is_expected.to be_a(Hash)
+ end
+
it 'allows to access with symbols' do
expect(subject[:key]).to eq('value')
expect(subject[:variables].first[:key]).to eq('VAR1')
@@ -43,59 +33,11 @@ describe Serializers::JSON do
end
end
- context 'when MySQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'mysql2' }
- end
-
- context 'when loading a string' do
- subject { described_class.load(data_string) }
-
- it 'decodes a string' do
- is_expected.to be_a(Hash)
- end
-
- it_behaves_like 'having consistent accessor'
- end
-
- context 'when loading a different type' do
- subject { described_class.load({ key: 'hash' }) }
-
- it 'raises an exception' do
- expect { subject }.to raise_error(TypeError)
- end
- end
-
- context 'when loading a nil' do
- subject { described_class.load(nil) }
-
- it 'returns nil' do
- is_expected.to be_nil
- end
- end
- end
-
- context 'when PostgreSQL is used' do
- before do
- allow(Gitlab::Database).to receive(:adapter_name) { 'postgresql' }
- end
-
- context 'when loading a hash' do
- subject { described_class.load(data_hash) }
-
- it 'decodes a string' do
- is_expected.to be_a(Hash)
- end
-
- it_behaves_like 'having consistent accessor'
- end
-
- context 'when loading a nil' do
- subject { described_class.load(nil) }
+ context 'when loading a nil' do
+ subject { described_class.load(nil) }
- it 'returns nil' do
- is_expected.to be_nil
- end
+ it 'returns nil' do
+ is_expected.to be_nil
end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 56bbcc4c306..dcc4b70a382 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -640,7 +640,7 @@ describe Notify do
project.request_access(user)
project.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('project', project_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('project', project_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@@ -737,9 +737,9 @@ describe Notify do
describe 'project invitation accepted' do
let(:invited_user) { create(:user, name: 'invited user') }
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.accept_invite!(invited_user)
invitee
end
@@ -747,6 +747,7 @@ describe Notify do
subject { described_class.member_invite_accepted_email('project', project_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -762,16 +763,17 @@ describe Notify do
end
describe 'project invitation declined' do
- let(:maintainer) { create(:user).tap { |u| project.add_maintainer(u) } }
+ let(:recipient) { create(:user).tap { |u| project.add_maintainer(u) } }
let(:project_member) do
- invitee = invite_to_project(project, inviter: maintainer)
+ invitee = invite_to_project(project, inviter: recipient)
invitee.decline_invite!
invitee
end
- subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, maintainer.id) }
+ subject { described_class.member_invite_declined_email('Project', project.id, project_member.invite_email, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1087,9 +1089,10 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
- subject { described_class.member_access_requested_email('group', group_member.id, recipient.notification_email) }
+ subject { described_class.member_access_requested_email('group', group_member.id, recipient.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1111,9 +1114,11 @@ describe Notify do
group.request_access(user)
group.requesters.find_by(user_id: user.id)
end
+ let(:recipient) { user }
subject { described_class.member_access_denied_email('group', group.id, user.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
@@ -1128,10 +1133,12 @@ describe Notify do
describe 'group access changed' do
let(:group_member) { create(:group_member, group: group, user: user) }
+ let(:recipient) { user }
subject { described_class.member_access_granted_email('group', group_member.id) }
it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'an email sent to a user'
it_behaves_like 'it should not have Gmail Actions links'
it_behaves_like "a user cannot unsubscribe through footer link"
it_behaves_like 'appearance header and footer enabled'
diff --git a/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
new file mode 100644
index 00000000000..232f6f090c3
--- /dev/null
+++ b/spec/migrations/change_outbound_local_requests_whitelist_default_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190725012225_change_outbound_local_requests_whitelist_default.rb')
+
+describe ChangeOutboundLocalRequestsWhitelistDefault, :migration do
+ let(:application_settings) { table(:application_settings) }
+
+ it 'defaults to empty array' do
+ setting = application_settings.create!
+ setting_with_value = application_settings.create!(outbound_local_requests_whitelist: '{a,b}')
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(1)
+
+ migrate!
+
+ expect(application_settings.where(outbound_local_requests_whitelist: nil).count).to eq(0)
+ expect(setting.reload.outbound_local_requests_whitelist).to eq([])
+ expect(setting_with_value.reload.outbound_local_requests_whitelist).to eq(%w[a b])
+ end
+end
diff --git a/spec/migrations/set_issue_id_for_all_versions_spec.rb b/spec/migrations/set_issue_id_for_all_versions_spec.rb
new file mode 100644
index 00000000000..bfc2731181b
--- /dev/null
+++ b/spec/migrations/set_issue_id_for_all_versions_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20190715043954_set_issue_id_for_all_versions.rb')
+
+describe SetIssueIdForAllVersions, :migration do
+ let(:projects) { table(:projects) }
+ let(:issues) { table(:issues) }
+ let(:designs) { table(:design_management_designs) }
+ let(:designs_versions) { table(:design_management_designs_versions) }
+ let(:versions) { table(:design_management_versions) }
+
+ before do
+ @project = projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+
+ @issue_1 = issues.create!(description: 'first', project_id: @project.id)
+ @issue_2 = issues.create!(description: 'second', project_id: @project.id)
+
+ @design_1 = designs.create!(issue_id: @issue_1.id, filename: 'homepage-1.jpg', project_id: @project.id)
+ @design_2 = designs.create!(issue_id: @issue_2.id, filename: 'homepage-2.jpg', project_id: @project.id)
+
+ @version_1 = versions.create!(sha: 'foo')
+ @version_2 = versions.create!(sha: 'bar')
+
+ designs_versions.create!(version_id: @version_1.id, design_id: @design_1.id)
+ designs_versions.create!(version_id: @version_2.id, design_id: @design_2.id)
+ end
+
+ it 'correctly sets issue_id' do
+ expect(versions.where(issue_id: nil).count).to eq(2)
+
+ migrate!
+
+ expect(versions.where(issue_id: nil).count).to eq(0)
+ expect(versions.find(@version_1.id).issue_id).to eq(@issue_1.id)
+ expect(versions.find(@version_2.id).issue_id).to eq(@issue_2.id)
+ end
+end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 09c2878663a..2a689754ee0 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -114,7 +114,7 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
redis.sadd("session:lookup:user:gitlab:#{user.id}", session_ids)
end
- expect(ActiveSession.session_ids_for_user(user)).to eq(session_ids)
+ expect(ActiveSession.session_ids_for_user(user.id)).to eq(session_ids)
end
end
diff --git a/spec/models/analytics/cycle_analytics/project_stage_spec.rb b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
new file mode 100644
index 00000000000..4e3923e82b1
--- /dev/null
+++ b/spec/models/analytics/cycle_analytics/project_stage_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Analytics::CycleAnalytics::ProjectStage do
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index ab6f6dfe720..db80b85360f 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -37,6 +37,17 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value("myemail@example.com").for(:lets_encrypt_notification_email) }
it { is_expected.to allow_value("myemail@test.example.com").for(:lets_encrypt_notification_email) }
+ it { is_expected.to allow_value(['192.168.1.1'] * 1_000).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['192.168.1.1'] * 1_001).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['1' * 255]).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['1' * 256]).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['ğitlab.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['xn--itlab-j1a.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(['<h1></h1>']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value(['gitlab.com']).for(:outbound_local_requests_whitelist) }
+ it { is_expected.not_to allow_value(nil).for(:outbound_local_requests_whitelist) }
+ it { is_expected.to allow_value([]).for(:outbound_local_requests_whitelist) }
+
context "when user accepted let's encrypt terms of service" do
before do
setting.update(lets_encrypt_terms_of_service_accepted: true)
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/bridge_spec.rb b/spec/models/ci/bridge_spec.rb
index eb32198265b..a871f9b3fe6 100644
--- a/spec/models/ci/bridge_spec.rb
+++ b/spec/models/ci/bridge_spec.rb
@@ -23,7 +23,7 @@ describe Ci::Bridge do
let(:status) { bridge.detailed_status(user) }
it 'returns detailed status object' do
- expect(status).to be_a Gitlab::Ci::Status::Success
+ expect(status).to be_a Gitlab::Ci::Status::Created
end
end
diff --git a/spec/models/ci/build_need_spec.rb b/spec/models/ci/build_need_spec.rb
new file mode 100644
index 00000000000..450dd550a8f
--- /dev/null
+++ b/spec/models/ci/build_need_spec.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildNeed, model: true do
+ let(:build_need) { build(:ci_build_need) }
+
+ it { is_expected.to belong_to(:build) }
+
+ it { is_expected.to validate_presence_of(:build) }
+ it { is_expected.to validate_presence_of(:name) }
+ it { is_expected.to validate_length_of(:name).is_at_most(128) }
+end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 78862de0657..4aac4b640f4 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -19,9 +19,11 @@ describe Ci::Build do
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
- it { is_expected.to have_many(:trace_sections)}
+ it { is_expected.to have_many(:trace_sections) }
+ it { is_expected.to have_many(:needs) }
it { is_expected.to have_one(:deployment) }
- it { is_expected.to have_one(:runner_session)}
+ it { is_expected.to have_one(:runner_session) }
+ it { is_expected.to have_many(:job_variables) }
it { is_expected.to validate_presence_of(:ref) }
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
@@ -181,6 +183,47 @@ describe Ci::Build do
end
end
+ describe '.with_needs' do
+ let!(:build) { create(:ci_build) }
+ let!(:build_b) { create(:ci_build) }
+ let!(:build_need_a) { create(:ci_build_need, build: build) }
+ let!(:build_need_b) { create(:ci_build_need, build: build_b) }
+
+ context 'when passing build name' do
+ subject { described_class.with_needs(build_need_a.name) }
+
+ it { is_expected.to contain_exactly(build) }
+ end
+
+ context 'when not passing any build name' do
+ subject { described_class.with_needs }
+
+ it { is_expected.to contain_exactly(build, build_b) }
+ end
+
+ context 'when not matching build name' do
+ subject { described_class.with_needs('undefined') }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
+ describe '.without_needs' do
+ let!(:build) { create(:ci_build) }
+
+ subject { described_class.without_needs }
+
+ context 'when no build_need is created' do
+ it { is_expected.to contain_exactly(build) }
+ end
+
+ context 'when a build_need is created' do
+ let!(:need_a) { create(:ci_build_need, build: build) }
+
+ it { is_expected.to be_empty }
+ end
+ end
+
describe '#enqueue' do
let(:build) { create(:ci_build, :created) }
@@ -594,6 +637,59 @@ describe Ci::Build do
expect(staging.depends_on_builds.map(&:id))
.to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
end
+
+ describe '#dependencies' do
+ let(:dependencies) { }
+ let(:needs) { }
+
+ let!(:final) do
+ create(:ci_build,
+ pipeline: pipeline, name: 'final',
+ stage_idx: 3, stage: 'deploy', options: {
+ dependencies: dependencies
+ }
+ )
+ end
+
+ before do
+ needs.to_a.each do |need|
+ create(:ci_build_need, build: final, name: need)
+ end
+ end
+
+ subject { final.dependencies }
+
+ context 'when depedencies are defined' do
+ let(:dependencies) { %w(rspec staging) }
+
+ it { is_expected.to contain_exactly(rspec_test, staging) }
+ end
+
+ context 'when needs are defined' do
+ let(:needs) { %w(build rspec staging) }
+
+ it { is_expected.to contain_exactly(build, rspec_test, staging) }
+
+ context 'when ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
+ end
+ end
+
+ context 'when needs and dependencies are defined' do
+ let(:dependencies) { %w(rspec staging) }
+ let(:needs) { %w(build rspec staging) }
+
+ it { is_expected.to contain_exactly(rspec_test, staging) }
+ end
+
+ context 'when nor dependencies or needs are defined' do
+ it { is_expected.to contain_exactly(build, rspec_test, rubocop_test, staging) }
+ end
+ end
end
describe '#triggered_by?' do
@@ -692,6 +788,34 @@ describe Ci::Build do
end
end
+ describe '#has_live_trace?' do
+ subject { build.has_live_trace? }
+
+ let(:build) { create(:ci_build, :trace_live) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when build does not have live trace' do
+ let(:build) { create(:ci_build) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#has_archived_trace?' do
+ subject { build.has_archived_trace? }
+
+ let(:build) { create(:ci_build, :trace_artifact) }
+
+ it { is_expected.to be_truthy }
+
+ context 'when build does not have archived trace' do
+ let(:build) { create(:ci_build) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe '#has_job_artifacts?' do
subject { build.has_job_artifacts? }
@@ -2216,6 +2340,32 @@ describe Ci::Build do
it_behaves_like 'containing environment variables'
end
end
+
+ context 'when project has an environment specific variable' do
+ let(:environment_specific_variable) do
+ { key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false }
+ end
+
+ before do
+ create(:ci_variable, environment_specific_variable.slice(:key, :value)
+ .merge(project: project, environment_scope: 'stag*'))
+ end
+
+ it_behaves_like 'containing environment variables'
+
+ context 'when environment scope does not match build environment' do
+ it { is_expected.not_to include(environment_specific_variable) }
+ end
+
+ context 'when environment scope matches build environment' do
+ before do
+ create(:environment, name: 'staging', project: project)
+ build.update!(environment: 'staging')
+ end
+
+ it { is_expected.to include(environment_specific_variable) }
+ end
+ end
end
context 'when build started manually' do
@@ -2230,6 +2380,16 @@ describe Ci::Build do
it { is_expected.to include(manual_variable) }
end
+ context 'when job variable is defined' do
+ let(:job_variable) { { key: 'first', value: 'first', public: false, masked: false } }
+
+ before do
+ create(:ci_job_variable, job_variable.slice(:key, :value).merge(job: build))
+ end
+
+ it { is_expected.to include(job_variable) }
+ end
+
context 'when build is for tag' do
let(:tag_variable) do
{ key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false }
@@ -3575,6 +3735,7 @@ describe Ci::Build do
before do
build.ensure_metadata
+ build.needs.create!(name: 'another-job')
end
it 'drops metadata' do
@@ -3582,6 +3743,7 @@ describe Ci::Build do
expect(build.reload).to be_degenerated
expect(build.metadata).to be_nil
+ expect(build.needs).to be_empty
end
end
diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb
index 1ba66565e03..1413da231e0 100644
--- a/spec/models/ci/job_artifact_spec.rb
+++ b/spec/models/ci/job_artifact_spec.rb
@@ -70,6 +70,31 @@ describe Ci::JobArtifact do
end
end
+ describe '.archived_trace_exists_for?' do
+ subject { described_class.archived_trace_exists_for?(job_id) }
+
+ let!(:artifact) { create(:ci_job_artifact, :trace, job: job) }
+ let(:job) { create(:ci_build) }
+
+ context 'when the specified job_id exists' do
+ let(:job_id) { job.id }
+
+ it { is_expected.to be_truthy }
+
+ context 'when the job does have archived trace' do
+ let!(:artifact) { }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when the specified job_id does not exist' do
+ let(:job_id) { 10000 }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
describe 'callbacks' do
subject { create(:ci_job_artifact, :archive) }
diff --git a/spec/models/ci/job_variable_spec.rb b/spec/models/ci/job_variable_spec.rb
new file mode 100644
index 00000000000..b94a914c784
--- /dev/null
+++ b/spec/models/ci/job_variable_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::JobVariable do
+ subject { build(:ci_job_variable) }
+
+ it_behaves_like "CI variable"
+
+ it { is_expected.to belong_to(:job) }
+ it { is_expected.to validate_uniqueness_of(:key).scoped_to(:job_id) }
+end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index e24bbc39761..78be4a8131a 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -1799,7 +1799,7 @@ describe Ci::Pipeline, :mailer do
end
end
- describe '.latest_successful_for' do
+ describe '.latest_successful_for_ref' do
include_context 'with some outdated pipelines'
let!(:latest_successful_pipeline) do
@@ -1807,7 +1807,20 @@ describe Ci::Pipeline, :mailer do
end
it 'returns the latest successful pipeline' do
- expect(described_class.latest_successful_for('ref'))
+ expect(described_class.latest_successful_for_ref('ref'))
+ .to eq(latest_successful_pipeline)
+ end
+ end
+
+ describe '.latest_successful_for_sha' do
+ include_context 'with some outdated pipelines'
+
+ let!(:latest_successful_pipeline) do
+ create_pipeline(:success, 'ref', 'awesomesha', project)
+ end
+
+ it 'returns the latest successful pipeline' do
+ expect(described_class.latest_successful_for_sha('awesomesha'))
.to eq(latest_successful_pipeline)
end
end
@@ -1916,6 +1929,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to be_an(Array) }
end
+ describe '.bridgeable_statuses' do
+ subject { described_class.bridgeable_statuses }
+
+ it { is_expected.to be_an(Array) }
+ it { is_expected.not_to include('created', 'preparing', 'pending') }
+ end
+
describe '#status' do
let(:build) do
create(:ci_build, :created, pipeline: pipeline, name: 'test')
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index f735a89f69f..78b151631c1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -146,7 +146,7 @@ describe Ci::Runner do
expect(described_class.belonging_to_parent_group_of_project(project.id)).to contain_exactly(runner)
end
- context 'with a parent group with a runner', :nested_groups do
+ context 'with a parent group with a runner' do
let(:runner) { create(:ci_runner, :group, groups: [parent_group]) }
let(:project) { create(:project, group: group) }
let(:group) { create(:group, parent: parent_group) }
@@ -554,7 +554,7 @@ describe Ci::Runner do
end
def expect_value_in_queues
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
runner_queue_key = runner.send(:runner_queue_key)
expect(redis.get(runner_queue_key))
end
@@ -627,7 +627,7 @@ describe Ci::Runner do
end
it 'cleans up the queue' do
- Gitlab::Redis::Queues.with do |redis|
+ Gitlab::Redis::SharedState.with do |redis|
expect(redis.get(queue_key)).to be_nil
end
end
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index a231c7eaed8..3ff547456c6 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -10,6 +10,7 @@ describe Ci::Variable do
describe 'validations' do
it { is_expected.to include_module(Presentable) }
it { is_expected.to include_module(Maskable) }
+ it { is_expected.to include_module(HasEnvironmentScope) }
it { is_expected.to validate_uniqueness_of(:key).scoped_to(:project_id, :environment_scope).with_message(/\(\w+\) has already been taken/) }
end
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index 8d853a04e33..93050e80b07 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -3,17 +3,17 @@
require 'rails_helper'
describe Clusters::Applications::CertManager do
- let(:cert_manager) { create(:clusters_applications_cert_managers) }
+ let(:cert_manager) { create(:clusters_applications_cert_manager) }
- include_examples 'cluster application core specs', :clusters_applications_cert_managers
- include_examples 'cluster application status specs', :clusters_applications_cert_managers
- include_examples 'cluster application version specs', :clusters_applications_cert_managers
+ include_examples 'cluster application core specs', :clusters_applications_cert_manager
+ include_examples 'cluster application status specs', :clusters_applications_cert_manager
+ include_examples 'cluster application version specs', :clusters_applications_cert_manager
include_examples 'cluster application initial status specs'
describe '#can_uninstall?' do
subject { cert_manager.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#install_command' do
@@ -48,7 +48,7 @@ describe Clusters::Applications::CertManager do
expect(subject.version).to eq('v0.5.2')
expect(subject).to be_rbac
expect(subject.files).to eq(cert_manager.files.merge(cluster_issuer_file))
- expect(subject.postinstall).to eq(['/usr/bin/kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
+ expect(subject.postinstall).to eq(['kubectl create -f /data/helm/certmanager/config/cluster_issuer.yaml'])
end
context 'for a specific user' do
@@ -72,7 +72,7 @@ describe Clusters::Applications::CertManager do
end
context 'application failed to install previously' do
- let(:cert_manager) { create(:clusters_applications_cert_managers, :errored, version: '0.0.1') }
+ let(:cert_manager) { create(:clusters_applications_cert_manager, :errored, version: '0.0.1') }
it 'is initialized with the locked version' do
expect(subject.version).to eq('v0.5.2')
@@ -80,6 +80,44 @@ describe Clusters::Applications::CertManager do
end
end
+ describe '#uninstall_command' do
+ subject { cert_manager.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it 'is initialized with cert_manager arguments' do
+ expect(subject.name).to eq('certmanager')
+ expect(subject).to be_rbac
+ expect(subject.files).to eq(cert_manager.files)
+ end
+
+ it 'specifies a post delete command to remove custom resource definitions' do
+ expect(subject.postdelete).to eq([
+ "kubectl delete secret -n gitlab-managed-apps letsencrypt-prod --ignore-not-found",
+ 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ ])
+ end
+
+ context 'secret key name is not found' do
+ before do
+ allow(File).to receive(:read).and_call_original
+ expect(File).to receive(:read)
+ .with(Rails.root.join('vendor', 'cert_manager', 'cluster_issuer.yaml'))
+ .and_return('key: value')
+ end
+
+ it 'does not try and delete the secret' do
+ expect(subject.postdelete).to eq([
+ 'kubectl delete crd certificates.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd clusterissuers.certmanager.k8s.io --ignore-not-found',
+ 'kubectl delete crd issuers.certmanager.k8s.io --ignore-not-found'
+ ])
+ end
+ end
+ end
+
describe '#files' do
let(:application) { cert_manager }
let(:values) { subject[:'values.yaml'] }
diff --git a/spec/models/clusters/applications/helm_spec.rb b/spec/models/clusters/applications/helm_spec.rb
index 6ea6c110d62..00b5c72a3d3 100644
--- a/spec/models/clusters/applications/helm_spec.rb
+++ b/spec/models/clusters/applications/helm_spec.rb
@@ -19,11 +19,35 @@ describe Clusters::Applications::Helm do
end
describe '#can_uninstall?' do
- let(:helm) { create(:clusters_applications_helm) }
+ context "with other existing applications" do
+ Clusters::Cluster::APPLICATIONS.keys.each do |application_name|
+ next if application_name == 'helm'
+
+ it "is false when #{application_name} is installed" do
+ cluster_application = create("clusters_applications_#{application_name}".to_sym)
+
+ helm = cluster_application.cluster.application_helm
+
+ expect(helm.allowed_to_uninstall?).to be_falsy
+ end
+ end
- subject { helm.can_uninstall? }
+ it 'executes a single query only' do
+ cluster_application = create(:clusters_applications_ingress)
+ helm = cluster_application.cluster.application_helm
- it { is_expected.to be_falsey }
+ query_count = ActiveRecord::QueryRecorder.new { helm.allowed_to_uninstall? }.count
+ expect(query_count).to eq(1)
+ end
+ end
+
+ context "without other existing applications" do
+ subject { helm.can_uninstall? }
+
+ let(:helm) { create(:clusters_applications_helm) }
+
+ it { is_expected.to be_truthy }
+ end
end
describe '#issue_client_cert' do
@@ -73,4 +97,41 @@ describe Clusters::Applications::Helm do
end
end
end
+
+ describe '#uninstall_command' do
+ let(:helm) { create(:clusters_applications_helm) }
+
+ subject { helm.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::ResetCommand) }
+
+ it 'has name' do
+ expect(subject.name).to eq('helm')
+ end
+
+ it 'has cert files' do
+ expect(subject.files[:'ca.pem']).to be_present
+ expect(subject.files[:'ca.pem']).to eq(helm.ca_cert)
+
+ expect(subject.files[:'cert.pem']).to be_present
+ expect(subject.files[:'key.pem']).to be_present
+
+ cert = OpenSSL::X509::Certificate.new(subject.files[:'cert.pem'])
+ expect(cert.not_after).to be > 999.years.from_now
+ end
+
+ describe 'rbac' do
+ context 'rbac cluster' do
+ it { expect(subject).to be_rbac }
+ end
+
+ context 'non rbac cluster' do
+ before do
+ helm.cluster.platform_kubernetes.abac!
+ end
+
+ it { expect(subject).not_to be_rbac }
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 7f4819cbb9a..334f10526cb 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -39,7 +39,7 @@ describe Clusters::Applications::Knative do
describe '#can_uninstall?' do
subject { knative.can_uninstall? }
- it { is_expected.to be_falsey }
+ it { is_expected.to be_truthy }
end
describe '#schedule_status_update with external_ip' do
@@ -91,7 +91,7 @@ describe Clusters::Applications::Knative do
end
it 'does not install metrics for prometheus' do
- expect(subject.postinstall).to be_nil
+ expect(subject.postinstall).to be_empty
end
context 'with prometheus installed' do
@@ -101,7 +101,7 @@ describe Clusters::Applications::Knative do
subject { knative.install_command }
it 'installs metrics' do
- expect(subject.postinstall).not_to be_nil
+ expect(subject.postinstall).not_to be_empty
expect(subject.postinstall.length).to be(1)
expect(subject.postinstall[0]).to eql("kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}")
end
@@ -129,6 +129,46 @@ describe Clusters::Applications::Knative do
it_behaves_like 'a command'
end
+ describe '#uninstall_command' do
+ subject { knative.uninstall_command }
+
+ it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::Helm::DeleteCommand) }
+
+ it "removes knative deployed services before uninstallation" do
+ 2.times do |i|
+ cluster_project = create(:cluster_project, cluster: knative.cluster)
+
+ create(:cluster_kubernetes_namespace,
+ cluster: cluster_project.cluster,
+ cluster_project: cluster_project,
+ project: cluster_project.project,
+ namespace: "namespace_#{i}")
+ end
+
+ remove_namespaced_services_script = [
+ "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.first.namespace}",
+ "kubectl delete ksvc --all -n #{knative.cluster.kubernetes_namespaces.second.namespace}"
+ ]
+
+ expect(subject.predelete).to match_array(remove_namespaced_services_script)
+ end
+
+ it "initializes command with all necessary postdelete script" do
+ api_resources = YAML.safe_load(File.read(Rails.root.join(Clusters::Applications::Knative::API_RESOURCES_PATH)))
+
+ remove_knative_istio_leftovers_script = [
+ "kubectl delete --ignore-not-found ns knative-serving",
+ "kubectl delete --ignore-not-found ns knative-build"
+ ]
+
+ full_delete_commands_size = api_resources.size + remove_knative_istio_leftovers_script.size
+
+ expect(subject.postdelete).to include(*remove_knative_istio_leftovers_script)
+ expect(subject.postdelete.size).to eq(full_delete_commands_size)
+ expect(subject.postdelete[2]).to eq("kubectl delete --ignore-not-found crd #{api_resources[0]}")
+ end
+ end
+
describe '#files' do
let(:application) { knative }
let(:values) { subject[:'values.yaml'] }
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 26267c64112..eb6ccba5584 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -86,16 +86,15 @@ describe Clusters::Applications::Prometheus do
project: cluster.cluster_project.project)
end
- it 'creates proxy prometheus rest client' do
- expect(subject.prometheus_client).to be_instance_of(RestClient::Resource)
+ it 'creates proxy prometheus_client' do
+ expect(subject.prometheus_client).to be_instance_of(Gitlab::PrometheusClient)
end
- it 'creates proper url' do
- expect(subject.prometheus_client.url).to eq("#{kubernetes_url}/api/v1/namespaces/gitlab-managed-apps/services/prometheus-prometheus-server:80/proxy")
- end
-
- it 'copies options and headers from kube client to proxy client' do
- expect(subject.prometheus_client.options).to eq(kube_client.rest_client.options.merge(headers: kube_client.headers))
+ it 'copies proxy_url, options and headers from kube client to prometheus_client' do
+ expect(Gitlab::PrometheusClient)
+ .to(receive(:new))
+ .with(a_valid_url, kube_client.rest_client.options.merge(headers: kube_client.headers))
+ subject.prometheus_client
end
context 'when cluster is not reachable' do
@@ -142,7 +141,7 @@ describe Clusters::Applications::Prometheus do
end
it 'does not install knative metrics' do
- expect(subject.postinstall).to be_nil
+ expect(subject.postinstall).to be_empty
end
context 'with knative installed' do
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 52661178d76..9afbe6328ca 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -38,11 +38,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
it { is_expected.to respond_to :project }
- it do
- expect(subject.knative_services_finder(subject.project))
- .to be_instance_of(Clusters::KnativeServicesFinder)
- end
-
describe '.enabled' do
subject { described_class.enabled }
@@ -121,26 +116,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '.missing_kubernetes_namespace' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :project) }
- let(:project) { cluster.project }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject do
- described_class.joins(:projects).where(projects: { id: project.id }).missing_kubernetes_namespace(kubernetes_namespaces)
- end
-
- it { is_expected.to contain_exactly(cluster) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validations' do
subject { cluster.valid? }
@@ -342,7 +317,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- context 'when sub-group has configured kubernetes cluster', :nested_groups do
+ context 'when sub-group has configured kubernetes cluster' do
let(:sub_group_cluster) { create(:cluster, :provided_by_gcp, :group) }
let(:sub_group) { sub_group_cluster.group }
let(:project) { create(:project, group: sub_group) }
@@ -423,31 +398,6 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '#all_projects' do
- let(:project) { create(:project) }
- let(:cluster) { create(:cluster, projects: [project]) }
-
- subject { cluster.all_projects }
-
- context 'project cluster' do
- it 'returns project' do
- is_expected.to eq([project])
- end
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group) }
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:subproject) { create(:project, group: subgroup) }
-
- it 'returns all projects for group' do
- is_expected.to contain_exactly(project, subproject)
- end
- end
- end
-
describe '#first_project' do
subject { cluster.first_project }
@@ -496,7 +446,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
context 'when applications are created' do
let!(:helm) { create(:clusters_applications_helm, cluster: cluster) }
let!(:ingress) { create(:clusters_applications_ingress, cluster: cluster) }
- let!(:cert_manager) { create(:clusters_applications_cert_managers, cluster: cluster) }
+ let!(:cert_manager) { create(:clusters_applications_cert_manager, cluster: cluster) }
let!(:prometheus) { create(:clusters_applications_prometheus, cluster: cluster) }
let!(:runner) { create(:clusters_applications_runner, cluster: cluster) }
let!(:jupyter) { create(:clusters_applications_jupyter, cluster: cluster) }
@@ -579,60 +529,39 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
end
end
- describe '#find_or_initialize_kubernetes_namespace_for_project' do
- let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.projects.first }
-
- subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project) }
-
- context 'kubernetes namespace exists' do
- context 'with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
+ describe '#kubernetes_namespace_for' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:environment) { create(:environment) }
- it { is_expected.to eq kubernetes_namespace }
- end
-
- context 'with a service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) }
-
- it { is_expected.to eq kubernetes_namespace }
- end
- end
+ subject { cluster.kubernetes_namespace_for(environment) }
- context 'kubernetes namespace does not exist' do
- it 'initializes a new namespace and sets default values' do
- expect(subject).to be_new_record
- expect(subject.project).to eq project
- expect(subject.cluster).to eq cluster
- expect(subject.namespace).to be_present
- expect(subject.service_account_name).to be_present
- end
+ before do
+ expect(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .with(cluster, project: environment.project, environment_slug: environment.slug)
+ .and_return(double(execute: persisted_namespace))
end
- context 'a custom scope is provided' do
- let(:scope) { cluster.kubernetes_namespaces.has_service_account_token }
-
- subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project, scope: scope) }
+ context 'a persisted namespace exists' do
+ let(:persisted_namespace) { create(:cluster_kubernetes_namespace) }
- context 'kubernetes namespace exists' do
- context 'with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
-
- it 'initializes a new namespace and sets default values' do
- expect(subject).to be_new_record
- expect(subject.project).to eq project
- expect(subject.cluster).to eq cluster
- expect(subject.namespace).to be_present
- expect(subject.service_account_name).to be_present
- end
- end
+ it { is_expected.to eq persisted_namespace.namespace }
+ end
- context 'with a service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) }
+ context 'no persisted namespace exists' do
+ let(:persisted_namespace) { nil }
+ let(:namespace_generator) { double }
+ let(:default_namespace) { 'a-default-namespace' }
- it { is_expected.to eq kubernetes_namespace }
- end
+ before do
+ expect(Gitlab::Kubernetes::DefaultNamespace).to receive(:new)
+ .with(cluster, project: environment.project)
+ .and_return(namespace_generator)
+ expect(namespace_generator).to receive(:from_environment_slug)
+ .with(environment.slug)
+ .and_return(default_namespace)
end
+
+ it { is_expected.to eq default_namespace }
end
end
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index b5cba80b806..d4e3a0ac84d 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -24,70 +24,60 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end
end
- describe 'namespace uniqueness validation' do
- let(:cluster_project) { create(:cluster_project) }
- let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
+ describe '.with_environment_slug' do
+ let(:cluster) { create(:cluster, :group) }
+ let(:environment) { create(:environment, slug: slug) }
- subject { kubernetes_namespace }
+ let(:slug) { 'production' }
- context 'when cluster is using the namespace' do
- before do
- create(:cluster_kubernetes_namespace,
- cluster: kubernetes_namespace.cluster,
- namespace: 'my-namespace')
- end
+ subject { described_class.with_environment_slug(slug) }
- it { is_expected.not_to be_valid }
- end
+ context 'there is no associated environment' do
+ let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) }
- context 'when cluster is not using the namespace' do
- it { is_expected.to be_valid }
+ it { is_expected.to be_empty }
end
- end
- describe '#set_defaults' do
- let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace) }
- let(:cluster) { kubernetes_namespace.cluster }
- let(:platform) { kubernetes_namespace.platform_kubernetes }
-
- subject { kubernetes_namespace.set_defaults }
-
- describe '#namespace' do
- before do
- platform.update_column(:namespace, namespace)
+ context 'there is an assicated environment' do
+ let!(:namespace) do
+ create(
+ :cluster_kubernetes_namespace,
+ cluster: cluster,
+ project: environment.project,
+ environment: environment
+ )
end
- context 'when platform has a namespace assigned' do
- let(:namespace) { 'platform-namespace' }
-
- it 'copies the namespace' do
- subject
-
- expect(kubernetes_namespace.namespace).to eq('platform-namespace')
- end
+ context 'with a matching slug' do
+ it { is_expected.to eq [namespace] }
end
- context 'when platform does not have namespace assigned' do
- let(:project) { kubernetes_namespace.project }
- let(:namespace) { nil }
- let(:project_slug) { "#{project.path}-#{project.id}" }
-
- it 'fallbacks to project namespace' do
- subject
+ context 'without a matching slug' do
+ let(:environment) { create(:environment, slug: 'staging') }
- expect(kubernetes_namespace.namespace).to eq(project_slug)
- end
+ it { is_expected.to be_empty }
end
end
+ end
- describe '#service_account_name' do
- let(:service_account_name) { "#{kubernetes_namespace.namespace}-service-account" }
+ describe 'namespace uniqueness validation' do
+ let(:kubernetes_namespace) { build(:cluster_kubernetes_namespace, namespace: 'my-namespace') }
- it 'sets a service account name based on namespace' do
- subject
+ subject { kubernetes_namespace }
- expect(kubernetes_namespace.service_account_name).to eq(service_account_name)
+ context 'when cluster is using the namespace' do
+ before do
+ create(:cluster_kubernetes_namespace,
+ cluster: kubernetes_namespace.cluster,
+ environment: kubernetes_namespace.environment,
+ namespace: 'my-namespace')
end
+
+ it { is_expected.not_to be_valid }
+ end
+
+ context 'when cluster is not using the namespace' do
+ it { is_expected.to be_valid }
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 471769e4aab..0c4cf291d20 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -106,7 +106,7 @@ describe Clusters::Platforms::Kubernetes do
before do
allow(ApplicationSetting)
.to receive(:current)
- .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
+ .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: true))
end
it { expect(kubernetes.save).to be_truthy }
@@ -205,192 +205,77 @@ describe Clusters::Platforms::Kubernetes do
it { is_expected.to be_truthy }
end
- describe '#kubernetes_namespace_for' do
- let(:cluster) { create(:cluster, :project) }
- let(:project) { cluster.project }
-
- let(:platform) do
- create(:cluster_platform_kubernetes,
- cluster: cluster,
- namespace: namespace)
- end
-
- subject { platform.kubernetes_namespace_for(project) }
-
- context 'with a namespace assigned' do
- let(:namespace) { 'namespace-123' }
-
- it { is_expected.to eq(namespace) }
-
- context 'kubernetes namespace is present but has no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
-
- it { is_expected.to eq(namespace) }
- end
- end
-
- context 'with no namespace assigned' do
- let(:namespace) { nil }
-
- context 'when kubernetes namespace is present' do
- let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }
-
- before do
- kubernetes_namespace
- end
-
- it { is_expected.to eq(kubernetes_namespace.namespace) }
-
- context 'kubernetes namespace has no service account token' do
- before do
- kubernetes_namespace.update!(namespace: 'old-namespace', service_account_token: nil)
- end
+ describe '#predefined_variables' do
+ let(:project) { create(:project) }
+ let(:cluster) { create(:cluster, :group, platform_kubernetes: platform) }
+ let(:platform) { create(:cluster_platform_kubernetes) }
+ let(:persisted_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) }
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
+ let(:environment_name) { 'env/production' }
+ let(:environment_slug) { Gitlab::Slug::Environment.new(environment_name).generate }
- context 'when kubernetes namespace is not present' do
- it { is_expected.to eq("#{project.path}-#{project.id}") }
- end
- end
- end
+ subject { platform.predefined_variables(project: project, environment_name: environment_name) }
- describe '#predefined_variables' do
- let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
- let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
- let(:api_url) { 'https://kube.domain.com' }
- let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
-
- subject { kubernetes.predefined_variables(project: cluster.project) }
-
- shared_examples 'setting variables' do
- it 'sets the variables' do
- expect(subject).to include(
- { key: 'KUBE_URL', value: api_url, public: true },
- { key: 'KUBE_CA_PEM', value: ca_pem, public: true },
- { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
- )
- end
+ before do
+ allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
+ .with(cluster, project: project, environment_slug: environment_slug)
+ .and_return(double(execute: persisted_namespace))
end
- context 'kubernetes namespace is created with no service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
+ it { is_expected.to include(key: 'KUBE_URL', value: platform.api_url, public: true) }
- it_behaves_like 'setting variables'
+ context 'platform has a CA certificate' do
+ let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
+ let(:platform) { create(:cluster_platform_kubernetes, ca_cert: ca_pem) }
- it 'does not set KUBE_TOKEN' do
- expect(subject).not_to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
+ it { is_expected.to include(key: 'KUBE_CA_PEM', value: ca_pem, public: true) }
+ it { is_expected.to include(key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true) }
end
- context 'kubernetes namespace is created with service account token' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }
-
- it_behaves_like 'setting variables'
+ context 'kubernetes namespace exists' do
+ let(:variable) { Hash(key: :fake_key, value: 'fake_value') }
+ let(:namespace_variables) { Gitlab::Ci::Variables::Collection.new([variable]) }
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
+ before do
+ expect(persisted_namespace).to receive(:predefined_variables).and_return(namespace_variables)
end
- context 'the cluster has been set to unmanaged after the namespace was created' do
- before do
- cluster.update!(managed: false)
- end
-
- it_behaves_like 'setting variables'
-
- it 'sets KUBE_TOKEN from the platform' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
-
- context 'the platform has a custom namespace set' do
- before do
- kubernetes.update!(namespace: 'custom-namespace')
- end
-
- it 'sets KUBE_NAMESPACE from the platform' do
- expect(subject).to include(
- { key: 'KUBE_NAMESPACE', value: kubernetes.namespace, public: true, masked: false }
- )
- end
- end
-
- context 'there is no namespace specified on the platform' do
- let(:project) { cluster.project }
-
- before do
- kubernetes.update!(namespace: nil)
- end
-
- it 'sets KUBE_NAMESPACE to a default for the project' do
- expect(subject).to include(
- { key: 'KUBE_NAMESPACE', value: "#{project.path}-#{project.id}", public: true, masked: false }
- )
- end
- end
- end
+ it { is_expected.to include(variable) }
end
- context 'group level cluster' do
- let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) }
-
- let(:project) { create(:project, group: cluster.group) }
-
- subject { kubernetes.predefined_variables(project: project) }
-
- context 'no kubernetes namespace for the project' do
- it_behaves_like 'setting variables'
-
- it 'does not return KUBE_TOKEN' do
- expect(subject).not_to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
- )
- end
-
- context 'the cluster is not managed' do
- let!(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: kubernetes) }
+ context 'kubernetes namespace does not exist' do
+ let(:persisted_namespace) { nil }
+ let(:namespace) { 'kubernetes-namespace' }
+ let(:kubeconfig) { 'kubeconfig' }
- it_behaves_like 'setting variables'
-
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true }
- )
- end
- end
+ before do
+ allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new)
+ .with(cluster, project: project).and_return(double(from_environment_name: namespace))
+ allow(platform).to receive(:kubeconfig).with(namespace).and_return(kubeconfig)
end
- context 'kubernetes namespace exists for the project' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) }
+ it { is_expected.not_to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) }
+ it { is_expected.not_to include(key: 'KUBE_NAMESPACE', value: namespace) }
+ it { is_expected.not_to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) }
- it_behaves_like 'setting variables'
+ context 'cluster is unmanaged' do
+ let(:cluster) { create(:cluster, :group, :not_managed, platform_kubernetes: platform) }
- it 'sets KUBE_TOKEN' do
- expect(subject).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
- end
+ it { is_expected.to include(key: 'KUBE_TOKEN', value: platform.token, public: false, masked: true) }
+ it { is_expected.to include(key: 'KUBE_NAMESPACE', value: namespace) }
+ it { is_expected.to include(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true) }
end
end
- context 'with a domain' do
- let!(:cluster) do
- create(:cluster, :provided_by_gcp, :with_domain,
- platform_kubernetes: kubernetes)
- end
+ context 'cluster variables' do
+ let(:variable) { Hash(key: :fake_key, value: 'fake_value') }
+ let(:cluster_variables) { Gitlab::Ci::Variables::Collection.new([variable]) }
- it 'sets KUBE_INGRESS_BASE_DOMAIN' do
- expect(subject).to include(
- { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true }
- )
+ before do
+ expect(cluster).to receive(:predefined_variables).and_return(cluster_variables)
end
+
+ it { is_expected.to include(variable) }
end
end
@@ -410,7 +295,7 @@ describe Clusters::Platforms::Kubernetes do
end
context 'with valid pods' do
- let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(project), project_slug: project.full_path_slug) }
+ let(:pod) { kube_pod(environment_slug: environment.slug, namespace: cluster.kubernetes_namespace_for(environment), project_slug: project.full_path_slug) }
let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
let(:pods) { [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")] }
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index e76186fb280..7b35c2ffd36 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -556,6 +556,7 @@ eos
it 'returns the URI type at the given path' do
expect(commit.uri_type('files/html')).to be(:tree)
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
+ expect(commit.uri_type('files/images/wm.svg')).to be(:raw)
expect(project.commit('video').uri_type('files/videos/intro.mp4')).to be(:raw)
expect(commit.uri_type('files/js/application.js')).to be(:blob)
end
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/concerns/cacheable_attributes_spec.rb b/spec/models/concerns/cacheable_attributes_spec.rb
index eeacdadab9c..da46effe411 100644
--- a/spec/models/concerns/cacheable_attributes_spec.rb
+++ b/spec/models/concerns/cacheable_attributes_spec.rb
@@ -7,6 +7,7 @@ describe CacheableAttributes do
Class.new do
include ActiveModel::Model
extend ActiveModel::Callbacks
+ include ActiveModel::AttributeMethods
define_model_callbacks :commit
include CacheableAttributes
@@ -34,44 +35,60 @@ describe CacheableAttributes do
end
end
+ before do
+ stub_const("MinimalTestClass", minimal_test_class)
+ end
+
shared_context 'with defaults' do
before do
- minimal_test_class.define_singleton_method(:defaults) do
+ MinimalTestClass.define_singleton_method(:defaults) do
{ foo: 'a', bar: 'b', baz: 'c' }
end
end
end
+ describe '.expire', :use_clean_rails_memory_store_caching, :request_store do
+ it 'wipes the cache' do
+ obj = MinimalTestClass.new
+ obj.cache!
+ expect(MinimalTestClass.cached).not_to eq(nil)
+
+ MinimalTestClass.expire
+
+ expect(MinimalTestClass.cached).to eq(nil)
+ end
+ end
+
describe '.current_without_cache' do
it 'defaults to last' do
- expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.last)
+ expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.last)
end
it 'can be overridden' do
- minimal_test_class.define_singleton_method(:current_without_cache) do
+ MinimalTestClass.define_singleton_method(:current_without_cache) do
first
end
- expect(minimal_test_class.current_without_cache).to eq(minimal_test_class.first)
+ expect(MinimalTestClass.current_without_cache).to eq(MinimalTestClass.first)
end
end
describe '.cache_key' do
it 'excludes cache attributes' do
- expect(minimal_test_class.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}")
+ expect(MinimalTestClass.cache_key).to eq("TestClass:#{Gitlab::VERSION}:#{Rails.version}")
end
end
describe '.defaults' do
it 'defaults to {}' do
- expect(minimal_test_class.defaults).to eq({})
+ expect(MinimalTestClass.defaults).to eq({})
end
context 'with defaults defined' do
include_context 'with defaults'
it 'can be overridden' do
- expect(minimal_test_class.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' })
+ expect(MinimalTestClass.defaults).to eq({ foo: 'a', bar: 'b', baz: 'c' })
end
end
end
@@ -81,13 +98,13 @@ describe CacheableAttributes do
context 'without any attributes given' do
it 'intializes a new object with the defaults' do
- expect(minimal_test_class.build_from_defaults.attributes).to eq(minimal_test_class.defaults.stringify_keys)
+ expect(MinimalTestClass.build_from_defaults.attributes).to eq(MinimalTestClass.defaults.stringify_keys)
end
end
context 'with attributes given' do
it 'intializes a new object with the given attributes merged into the defaults' do
- expect(minimal_test_class.build_from_defaults(foo: 'd').attributes['foo']).to eq('d')
+ expect(MinimalTestClass.build_from_defaults(foo: 'd').attributes['foo']).to eq('d')
end
end
@@ -108,8 +125,8 @@ describe CacheableAttributes do
describe '.current', :use_clean_rails_memory_store_caching do
context 'redis unavailable' do
before do
- allow(minimal_test_class).to receive(:last).and_return(:last)
- expect(Rails.cache).to receive(:read).with(minimal_test_class.cache_key).and_raise(Redis::BaseError)
+ allow(MinimalTestClass).to receive(:last).and_return(:last)
+ expect(Rails.cache).to receive(:read).with(MinimalTestClass.cache_key).and_raise(Redis::BaseError)
end
context 'in production environment' do
@@ -120,7 +137,7 @@ describe CacheableAttributes do
it 'returns an uncached record and logs a warning' do
expect(Rails.logger).to receive(:warn).with("Cached record for TestClass couldn't be loaded, falling back to uncached record: Redis::BaseError")
- expect(minimal_test_class.current).to eq(:last)
+ expect(MinimalTestClass.current).to eq(:last)
end
end
@@ -132,7 +149,7 @@ describe CacheableAttributes do
it 'returns an uncached record and logs a warning' do
expect(Rails.logger).not_to receive(:warn)
- expect { minimal_test_class.current }.to raise_error(Redis::BaseError)
+ expect { MinimalTestClass.current }.to raise_error(Redis::BaseError)
end
end
end
@@ -202,7 +219,7 @@ describe CacheableAttributes do
describe '.cached', :use_clean_rails_memory_store_caching do
context 'when cache is cold' do
it 'returns nil' do
- expect(minimal_test_class.cached).to be_nil
+ expect(MinimalTestClass.cached).to be_nil
end
end
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
index d6d41a25eac..9819f656f0d 100644
--- a/spec/models/concerns/case_sensitivity_spec.rb
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -28,28 +28,13 @@ describe CaseSensitivity do
.to contain_exactly(model_1)
end
- # Using `mysql` & `postgresql` metadata-tags here because both adapters build
- # the query slightly differently
- context 'for MySQL', :mysql do
- it 'builds a simple query' do
- query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
- expected_query = <<~QRY.strip
- SELECT `namespaces`.* FROM `namespaces` WHERE (`namespaces`.`path` IN ('MODEL-1', 'model-2')) AND (`namespaces`.`name` = 'model 1')
- QRY
-
- expect(query).to eq(expected_query)
- end
- end
+ it 'builds a query using LOWER' do
+ query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
+ expected_query = <<~QRY.strip
+ SELECT \"namespaces\".* FROM \"namespaces\" WHERE (LOWER(\"namespaces\".\"path\") IN (LOWER('MODEL-1'), LOWER('model-2'))) AND (LOWER(\"namespaces\".\"name\") = LOWER('model 1'))
+ QRY
- context 'for PostgreSQL', :postgresql do
- it 'builds a query using LOWER' do
- query = model.iwhere(path: %w(MODEL-1 model-2), name: 'model 1').to_sql
- expected_query = <<~QRY.strip
- SELECT \"namespaces\".* FROM \"namespaces\" WHERE (LOWER(\"namespaces\".\"path\") IN (LOWER('MODEL-1'), LOWER('model-2'))) AND (LOWER(\"namespaces\".\"name\") = LOWER('model 1'))
- QRY
-
- expect(query).to eq(expected_query)
- end
+ expect(query).to eq(expected_query)
end
end
end
diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index 2378f400540..27f535487c8 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -45,13 +45,12 @@ describe DeploymentPlatform do
is_expected.to eq(group_cluster.platform_kubernetes)
end
- context 'when child group has configured kubernetes cluster', :nested_groups do
- let!(:child_group1_cluster) { create(:cluster, :provided_by_gcp, :group) }
- let(:child_group1) { child_group1_cluster.group }
+ context 'when child group has configured kubernetes cluster' do
+ let(:child_group1) { create(:group, parent: group) }
+ let!(:child_group1_cluster) { create(:cluster_for_group, groups: [child_group1]) }
before do
project.update!(group: child_group1)
- child_group1.update!(parent: group)
end
it 'returns the Kubernetes platform for the child group' do
@@ -59,11 +58,10 @@ describe DeploymentPlatform do
end
context 'deeply nested group' do
- let!(:child_group2_cluster) { create(:cluster, :provided_by_gcp, :group) }
- let(:child_group2) { child_group2_cluster.group }
+ let(:child_group2) { create(:group, parent: child_group1) }
+ let!(:child_group2_cluster) { create(:cluster_for_group, groups: [child_group2]) }
before do
- child_group2.update!(parent: child_group1)
project.update!(group: child_group2)
end
diff --git a/spec/models/concerns/group_descendant_spec.rb b/spec/models/concerns/group_descendant_spec.rb
index 194caac3fce..192e884f3e8 100644
--- a/spec/models/concerns/group_descendant_spec.rb
+++ b/spec/models/concerns/group_descendant_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe GroupDescendant, :nested_groups do
+describe GroupDescendant do
let(:parent) { create(:group) }
let(:subgroup) { create(:group, parent: parent) }
let(:subsub_group) { create(:group, parent: subgroup) }
@@ -84,7 +84,7 @@ describe GroupDescendant, :nested_groups do
it 'tracks the exception when a parent was not preloaded' do
expect(Gitlab::Sentry).to receive(:track_exception).and_call_original
- expect { GroupDescendant.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
+ expect { described_class.build_hierarchy([subsub_group]) }.to raise_error(ArgumentError)
end
it 'recovers if a parent was not reloaded by querying for the parent' do
@@ -93,7 +93,7 @@ describe GroupDescendant, :nested_groups do
# this does not raise in production, so stubbing it here.
allow(Gitlab::Sentry).to receive(:track_exception)
- expect(GroupDescendant.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
+ expect(described_class.build_hierarchy([subsub_group])).to eq(expected_hierarchy)
end
it 'raises an error if not all elements were preloaded' do
diff --git a/spec/models/concerns/has_environment_scope_spec.rb b/spec/models/concerns/has_environment_scope_spec.rb
new file mode 100644
index 00000000000..a6e1ba59263
--- /dev/null
+++ b/spec/models/concerns/has_environment_scope_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe HasEnvironmentScope do
+ subject { build(:ci_variable) }
+
+ it { is_expected.to allow_value('*').for(:environment_scope) }
+ it { is_expected.to allow_value('review/*').for(:environment_scope) }
+ it { is_expected.not_to allow_value('').for(:environment_scope) }
+ it { is_expected.not_to allow_value('!!()()').for(:environment_scope) }
+
+ it do
+ is_expected.to validate_uniqueness_of(:key)
+ .scoped_to(:project_id, :environment_scope)
+ .with_message(/\(\w+\) has already been taken/)
+ end
+
+ describe '.on_environment' do
+ let(:project) { create(:project) }
+
+ it 'returns scoped objects' do
+ variable1 = create(:ci_variable, project: project, environment_scope: '*')
+ variable2 = create(:ci_variable, project: project, environment_scope: 'product/*')
+ create(:ci_variable, project: project, environment_scope: 'staging/*')
+
+ expect(project.variables.on_environment('product/canary-1')).to eq([variable1, variable2])
+ end
+
+ it 'returns only the most relevant object if relevant_only is true' do
+ create(:ci_variable, project: project, environment_scope: '*')
+ variable2 = create(:ci_variable, project: project, environment_scope: 'product/*')
+ create(:ci_variable, project: project, environment_scope: 'staging/*')
+
+ expect(project.variables.on_environment('product/canary-1', relevant_only: true)).to eq([variable2])
+ end
+
+ it 'returns scopes ordered by lowest precedence first' do
+ create(:ci_variable, project: project, environment_scope: '*')
+ create(:ci_variable, project: project, environment_scope: 'production*')
+ create(:ci_variable, project: project, environment_scope: 'production')
+
+ result = project.variables.on_environment('production').map(&:environment_scope)
+
+ expect(result).to eq(['*', 'production*', 'production'])
+ end
+ end
+
+ describe '#environment_scope=' do
+ context 'when the new environment_scope is nil' do
+ it 'strips leading and trailing whitespaces' do
+ subject.environment_scope = nil
+
+ expect(subject.environment_scope).to eq('')
+ end
+ end
+
+ context 'when the new environment_scope has leadind and trailing whitespaces' do
+ it 'strips leading and trailing whitespaces' do
+ subject.environment_scope = ' * '
+
+ expect(subject.environment_scope).to eq('*')
+ end
+ end
+ end
+end
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 68224a56515..39680c0e51a 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -128,7 +128,7 @@ describe Issuable do
expect(build_issuable(milestone.id).milestone_available?).to be_truthy
end
- it 'returns true with a milestone from the the parent of the issue project group', :nested_groups do
+ it 'returns true with a milestone from the the parent of the issue project group' do
parent = create(:group)
group.update(parent: parent)
milestone = create(:milestone, group: parent)
@@ -774,4 +774,25 @@ describe Issuable do
end
end
end
+
+ describe '#supports_milestone?' do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+
+ context "for issues" do
+ let(:issue) { build(:issue, project: project) }
+
+ it 'returns true' do
+ expect(issue.supports_milestone?).to be_truthy
+ end
+ end
+
+ context "for merge requests" do
+ let(:merge_request) { build(:merge_request, target_project: project, source_project: project) }
+
+ it 'returns true' do
+ expect(merge_request.supports_milestone?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/models/concerns/project_api_compatibility_spec.rb b/spec/models/concerns/project_api_compatibility_spec.rb
index 8cecd4fe7bc..f5722f88aac 100644
--- a/spec/models/concerns/project_api_compatibility_spec.rb
+++ b/spec/models/concerns/project_api_compatibility_spec.rb
@@ -16,23 +16,44 @@ describe ProjectAPICompatibility do
expect(project.build_allow_git_fetch).to eq(false)
end
- # auto_devops_enabled
- it "converts auto_devops_enabled=false to auto_devops_enabled?=false" do
- expect(project.auto_devops_enabled?).to eq(true)
- project.update!(auto_devops_enabled: false)
- expect(project.auto_devops_enabled?).to eq(false)
- end
+ describe '#auto_devops_enabled' do
+ where(
+ initial: [:missing, nil, false, true],
+ final: [nil, false, true]
+ )
+
+ with_them do
+ before do
+ project.build_auto_devops(enabled: initial) unless initial == :missing
+ end
+
+ # Implicit auto devops when enabled is nil
+ let(:expected) { final.nil? ? true : final }
+
+ it 'sets the correct value' do
+ project.update!(auto_devops_enabled: final)
- it "converts auto_devops_enabled=true to auto_devops_enabled?=true" do
- expect(project.auto_devops_enabled?).to eq(true)
- project.update!(auto_devops_enabled: true)
- expect(project.auto_devops_enabled?).to eq(true)
+ expect(project.auto_devops_enabled?).to eq(expected)
+ end
+ end
end
- # auto_devops_deploy_strategy
- it "converts auto_devops_deploy_strategy=timed_incremental to auto_devops.deploy_strategy=timed_incremental" do
- expect(project.auto_devops).to be_nil
- project.update!(auto_devops_deploy_strategy: 'timed_incremental')
- expect(project.auto_devops.deploy_strategy).to eq('timed_incremental')
+ describe '#auto_devops_deploy_strategy' do
+ where(
+ initial: [:missing, *ProjectAutoDevops.deploy_strategies.keys],
+ final: ProjectAutoDevops.deploy_strategies.keys
+ )
+
+ with_them do
+ before do
+ project.build_auto_devops(deploy_strategy: initial) unless initial == :missing
+ end
+
+ it 'sets the correct value' do
+ project.update!(auto_devops_deploy_strategy: final)
+
+ expect(project.auto_devops.deploy_strategy).to eq(final)
+ end
+ end
end
end
diff --git a/spec/models/concerns/prometheus_adapter_spec.rb b/spec/models/concerns/prometheus_adapter_spec.rb
index 25a2d290f76..3d26ba95192 100644
--- a/spec/models/concerns/prometheus_adapter_spec.rb
+++ b/spec/models/concerns/prometheus_adapter_spec.rb
@@ -40,13 +40,13 @@ describe PrometheusAdapter, :use_clean_rails_memory_store_caching do
describe 'matched_metrics' do
let(:matched_metrics_query) { Gitlab::Prometheus::Queries::MatchedMetricQuery }
- let(:prometheus_client_wrapper) { double(:prometheus_client_wrapper, label_values: nil) }
+ let(:prometheus_client) { double(:prometheus_client, label_values: nil) }
context 'with valid data' do
subject { service.query(:matched_metrics) }
before do
- allow(service).to receive(:prometheus_client_wrapper).and_return(prometheus_client_wrapper)
+ allow(service).to receive(:prometheus_client).and_return(prometheus_client)
synchronous_reactive_cache(service)
end
diff --git a/spec/models/concerns/relative_positioning_spec.rb b/spec/models/concerns/relative_positioning_spec.rb
deleted file mode 100644
index d0ae45f7871..00000000000
--- a/spec/models/concerns/relative_positioning_spec.rb
+++ /dev/null
@@ -1,242 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe RelativePositioning do
- let(:project) { create(:project) }
- let(:issue) { create(:issue, project: project) }
- let(:issue1) { create(:issue, project: project) }
- let(:new_issue) { create(:issue, project: project) }
-
- describe '.move_to_end' do
- it 'moves the object to the end' do
- Issue.move_to_end([issue, issue1])
-
- expect(issue1.prev_relative_position).to eq issue.relative_position
- expect(issue.prev_relative_position).to eq nil
- expect(issue1.next_relative_position).to eq nil
- end
-
- it 'does not perform any moves if all issues have their relative_position set' do
- issue.update!(relative_position: 1)
-
- expect(issue).not_to receive(:save)
-
- Issue.move_to_end([issue])
- end
- end
-
- describe '#max_relative_position' do
- it 'returns maximum position' do
- expect(issue.max_relative_position).to eq issue1.relative_position
- end
- end
-
- describe '#prev_relative_position' do
- it 'returns previous position if there is an issue above' do
- expect(issue1.prev_relative_position).to eq issue.relative_position
- end
-
- it 'returns nil if there is no issue above' do
- expect(issue.prev_relative_position).to eq nil
- end
- end
-
- describe '#next_relative_position' do
- it 'returns next position if there is an issue below' do
- expect(issue.next_relative_position).to eq issue1.relative_position
- end
-
- it 'returns nil if there is no issue below' do
- expect(issue1.next_relative_position).to eq nil
- end
- end
-
- describe '#move_before' do
- it 'moves issue before' do
- [issue1, issue].each(&:move_to_end)
-
- issue.move_before(issue1)
-
- expect(issue.relative_position).to be < issue1.relative_position
- end
- end
-
- describe '#move_after' do
- it 'moves issue after' do
- [issue, issue1].each(&:move_to_end)
-
- issue.move_after(issue1)
-
- expect(issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#move_to_end' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'moves issue to the end' do
- new_issue.move_to_end
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
- end
-
- describe '#shift_after?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position - 1)
-
- expect(issue.shift_after?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position - 2)
-
- expect(issue.shift_after?).to be_falsey
- end
- end
-
- describe '#shift_before?' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'returns true' do
- issue.update(relative_position: issue1.relative_position + 1)
-
- expect(issue.shift_before?).to be_truthy
- end
-
- it 'returns false' do
- issue.update(relative_position: issue1.relative_position + 2)
-
- expect(issue.shift_before?).to be_falsey
- end
- end
-
- describe '#move_between' do
- before do
- [issue, issue1].each do |issue|
- issue.move_to_end && issue.save
- end
- end
-
- it 'positions issue between two other' do
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(new_issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue between on top' do
- new_issue.move_between(nil, issue)
-
- expect(new_issue.relative_position).to be < issue.relative_position
- end
-
- it 'positions issue between to end' do
- new_issue.move_between(issue1, nil)
-
- expect(new_issue.relative_position).to be > issue1.relative_position
- end
-
- it 'positions issues even when after and before positions are the same' do
- issue1.update relative_position: issue.relative_position
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issues between other two if distance is 1' do
- issue1.update relative_position: issue.relative_position + 1
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be > issue.relative_position
- expect(issue.relative_position).to be < issue1.relative_position
- end
-
- it 'positions issue in the middle of other two if distance is big enough' do
- issue.update relative_position: 6000
- issue1.update relative_position: 10000
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(8000)
- end
-
- it 'positions issue closer to the middle if we are at the very top' do
- issue1.update relative_position: 6000
-
- new_issue.move_between(nil, issue1)
-
- expect(new_issue.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue closer to the middle if we are at the very bottom' do
- issue.update relative_position: 6000
- issue1.update relative_position: nil
-
- new_issue.move_between(issue, nil)
-
- expect(new_issue.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
- end
-
- it 'positions issue in the middle of other two if distance is not big enough' do
- issue.update relative_position: 100
- issue1.update relative_position: 400
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to eq(250)
- end
-
- it 'positions issue in the middle of other two is there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
-
- new_issue.move_between(issue, issue1)
-
- expect(new_issue.relative_position).to be_between(issue.relative_position, issue1.relative_position)
- end
-
- it 'uses rebalancing if there is no place' do
- issue.update relative_position: 100
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue1, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
- expect(issue.reload.relative_position).not_to eq(100)
- end
-
- it 'positions issue right if we pass none-sequential parameters' do
- issue.update relative_position: 99
- issue1.update relative_position: 101
- issue2 = create(:issue, relative_position: 102, project: project)
- new_issue.update relative_position: 103
-
- new_issue.move_between(issue, issue2)
- new_issue.save!
-
- expect(new_issue.relative_position).to be(100)
- end
- end
-end
diff --git a/spec/models/container_repository_spec.rb b/spec/models/container_repository_spec.rb
index 013112d1d51..935838ce294 100644
--- a/spec/models/container_repository_spec.rb
+++ b/spec/models/container_repository_spec.rb
@@ -16,7 +16,7 @@ describe ContainerRepository do
host_port: 'registry.gitlab')
stub_request(:get, 'http://registry.gitlab/v2/group/test/my_image/tags/list')
- .with(headers: { 'Accept' => 'application/vnd.docker.distribution.manifest.v2+json' })
+ .with(headers: { 'Accept' => ContainerRegistry::Client::ACCEPTED_TYPES.join(', ') })
.to_return(
status: 200,
body: JSON.dump(tags: ['test_tag']),
diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb
index db6e70973ae..808659552ff 100644
--- a/spec/models/cycle_analytics/code_spec.rb
+++ b/spec/models/cycle_analytics/code_spec.rb
@@ -38,7 +38,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
deploy_master(user, project)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
@@ -68,7 +68,7 @@ describe 'CycleAnalytics#code' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:code].median).to be_nil
+ expect(subject[:code].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/group_level_spec.rb b/spec/models/cycle_analytics/group_level_spec.rb
new file mode 100644
index 00000000000..154c1b9c0f8
--- /dev/null
+++ b/spec/models/cycle_analytics/group_level_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe CycleAnalytics::GroupLevel do
+ let(:group) { create(:group)}
+ let(:project) { create(:project, :repository, namespace: group) }
+ let(:from_date) { 10.days.ago }
+ let(:user) { create(:user, :admin) }
+ let(:issue) { create(:issue, project: project, created_at: 2.days.ago) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:mr) { create_merge_request_closing_issue(user, project, issue, commit_message: "References #{issue.to_reference}") }
+ let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: mr.source_branch, sha: mr.source_branch_sha, head_pipeline_of: mr) }
+
+ subject { described_class.new(group: group, options: { from: from_date, current_user: user }) }
+
+ describe '#permissions' do
+ it 'returns true for all stages' do
+ expect(subject.permissions.values.uniq).to eq([true])
+ end
+ end
+
+ describe '#stats' do
+ before do
+ allow_any_instance_of(Gitlab::ReferenceExtractor).to receive(:issues).and_return([issue])
+
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.no_stats?).to eq(false)
+ end
+ end
+
+ describe '#summary' do
+ before do
+ create_cycle(user, project, issue, mr, milestone, pipeline)
+ deploy_master(user, project)
+ end
+
+ it 'returns medians for each stage for a specific group' do
+ expect(subject.summary.map { |summary| summary[:value] }).to contain_exactly(1, 1)
+ end
+ end
+end
diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb
index 4ccbdf29df6..8cdf83b1292 100644
--- a/spec/models/cycle_analytics/issue_spec.rb
+++ b/spec/models/cycle_analytics/issue_spec.rb
@@ -43,7 +43,7 @@ describe 'CycleAnalytics#issue' do
create_merge_request_closing_issue(user, project, issue)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb
index c99c38e9cf3..28ad9bd194d 100644
--- a/spec/models/cycle_analytics/plan_spec.rb
+++ b/spec/models/cycle_analytics/plan_spec.rb
@@ -47,7 +47,7 @@ describe 'CycleAnalytics#plan' do
create_merge_request_closing_issue(user, project, issue, source_branch: branch_name)
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:issue].median).to be_nil
+ expect(subject[:issue].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb
index ddd199362d1..613c1786540 100644
--- a/spec/models/cycle_analytics/production_spec.rb
+++ b/spec/models/cycle_analytics/production_spec.rb
@@ -41,7 +41,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
@@ -52,7 +52,7 @@ describe 'CycleAnalytics#production' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:production].median).to be_nil
+ expect(subject[:production].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/project_level_spec.rb b/spec/models/cycle_analytics/project_level_spec.rb
index 77bd0bfeb9c..4de01b1c679 100644
--- a/spec/models/cycle_analytics/project_level_spec.rb
+++ b/spec/models/cycle_analytics/project_level_spec.rb
@@ -23,7 +23,7 @@ describe CycleAnalytics::ProjectLevel do
it 'returns every median for each stage for a specific project' do
values = described_class::STAGES.each_with_object({}) do |stage_name, hsh|
- hsh[stage_name] = subject[stage_name].median.presence
+ hsh[stage_name] = subject[stage_name].project_median.presence
end
expect(subject.all_medians_by_stage).to eq(values)
diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb
index 63c481ed465..ef88fd86340 100644
--- a/spec/models/cycle_analytics/review_spec.rb
+++ b/spec/models/cycle_analytics/review_spec.rb
@@ -28,7 +28,7 @@ describe 'CycleAnalytics#review' do
it "returns nil" do
MergeRequests::MergeService.new(project, user).execute(create(:merge_request))
- expect(subject[:review].median).to be_nil
+ expect(subject[:review].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb
index c134b97553f..571792559d8 100644
--- a/spec/models/cycle_analytics/staging_spec.rb
+++ b/spec/models/cycle_analytics/staging_spec.rb
@@ -45,7 +45,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project)
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
@@ -56,7 +56,7 @@ describe 'CycleAnalytics#staging' do
MergeRequests::MergeService.new(project, user).execute(merge_request)
deploy_master(user, project, environment: 'staging')
- expect(subject[:staging].median).to be_nil
+ expect(subject[:staging].project_median).to be_nil
end
end
end
diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb
index a6ea73b2699..7b3001d2bd8 100644
--- a/spec/models/cycle_analytics/test_spec.rb
+++ b/spec/models/cycle_analytics/test_spec.rb
@@ -36,7 +36,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -47,7 +47,7 @@ describe 'CycleAnalytics#test' do
pipeline.run!
pipeline.succeed!
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -62,7 +62,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
@@ -77,7 +77,7 @@ describe 'CycleAnalytics#test' do
merge_merge_requests_closing_issue(user, project, issue)
- expect(subject[:test].median).to be_nil
+ expect(subject[:test].project_median).to be_nil
end
end
end
diff --git a/spec/models/deployment_metrics_spec.rb b/spec/models/deployment_metrics_spec.rb
index 0aadb1f3a5e..7c574a8b6c8 100644
--- a/spec/models/deployment_metrics_spec.rb
+++ b/spec/models/deployment_metrics_spec.rb
@@ -49,18 +49,6 @@ describe DeploymentMetrics do
it { is_expected.to be_truthy }
end
-
- context 'fallback deployment platform' do
- let(:cluster) { create(:cluster, :provided_by_user, environment_scope: '*', projects: [deployment.project]) }
- let!(:prometheus) { create(:clusters_applications_prometheus, :installed, cluster: cluster) }
-
- before do
- expect(deployment.project).to receive(:deployment_platform).and_return(cluster.platform)
- expect(cluster.application_prometheus).to receive(:can_query?).and_return(true)
- end
-
- it { is_expected.to be_truthy }
- end
end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index d9e1fe4b165..8d7dafc523d 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -34,6 +34,10 @@ describe DiffNote do
subject { create(:diff_note_on_merge_request, project: project, position: position, noteable: merge_request) }
+ describe 'validations' do
+ it_behaves_like 'a valid diff positionable note', :diff_note_on_commit
+ end
+
describe "#position=" do
context "when provided a string" do
it "sets the position" do
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index fe4d64818b4..521c4704c87 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Environment, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
+ using RSpec::Parameterized::TableSyntax
let(:project) { create(:project, :stubbed_repository) }
subject(:environment) { create(:environment, project: project) }
@@ -574,6 +575,34 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
end
+ describe '#deployment_namespace' do
+ let(:environment) { create(:environment) }
+
+ subject { environment.deployment_namespace }
+
+ before do
+ allow(environment).to receive(:deployment_platform).and_return(deployment_platform)
+ end
+
+ context 'no deployment platform available' do
+ let(:deployment_platform) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'deployment platform is available' do
+ let(:cluster) { create(:cluster, :provided_by_user, :project, projects: [environment.project]) }
+ let(:deployment_platform) { cluster.platform }
+
+ it 'retrieves a namespace from the cluster' do
+ expect(cluster).to receive(:kubernetes_namespace_for)
+ .with(environment).and_return('mock-namespace')
+
+ expect(subject).to eq 'mock-namespace'
+ end
+ end
+ end
+
describe '#terminals' do
subject { environment.terminals }
@@ -762,32 +791,6 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
end
- describe '#generate_slug' do
- SUFFIX = "-[a-z0-9]{6}".freeze
- {
- "staging-12345678901234567" => "staging-123456789" + SUFFIX,
- "9-staging-123456789012345" => "env-9-staging-123" + SUFFIX,
- "staging-1234567890123456" => "staging-1234567890123456",
- "production" => "production",
- "PRODUCTION" => "production" + SUFFIX,
- "review/1-foo" => "review-1-foo" + SUFFIX,
- "1-foo" => "env-1-foo" + SUFFIX,
- "1/foo" => "env-1-foo" + SUFFIX,
- "foo-" => "foo" + SUFFIX,
- "foo--bar" => "foo-bar" + SUFFIX,
- "foo**bar" => "foo-bar" + SUFFIX,
- "*-foo" => "env-foo" + SUFFIX,
- "staging-12345678-" => "staging-12345678" + SUFFIX,
- "staging-12345678-01234567" => "staging-12345678" + SUFFIX
- }.each do |name, matcher|
- it "returns a slug matching #{matcher}, given #{name}" do
- slug = described_class.new(name: name).generate_slug
-
- expect(slug).to match(/\A#{matcher}\z/)
- end
- end
- end
-
describe '#ref_path' do
subject(:environment) do
create(:environment, name: 'staging / review-1')
@@ -808,12 +811,9 @@ describe Environment, :use_clean_rails_memory_store_caching do
let(:source_path) { 'source/file.html' }
let(:sha) { RepoHelpers.sample_commit.id }
- before do
- environment.external_url = 'http://example.com'
- end
-
context 'when the public path is not known' do
before do
+ environment.external_url = 'http://example.com'
allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(nil)
end
@@ -823,12 +823,23 @@ describe Environment, :use_clean_rails_memory_store_caching do
end
context 'when the public path is known' do
- before do
- allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return('file.html')
- end
-
- it 'returns the full external URL' do
- expect(environment.external_url_for(source_path, sha)).to eq('http://example.com/file.html')
+ where(:external_url, :public_path, :full_url) do
+ 'http://example.com' | 'file.html' | 'http://example.com/file.html'
+ 'http://example.com/' | 'file.html' | 'http://example.com/file.html'
+ 'http://example.com' | '/file.html' | 'http://example.com/file.html'
+ 'http://example.com/' | '/file.html' | 'http://example.com/file.html'
+ 'http://example.com/subpath' | 'public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath/' | 'public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath' | '/public/file.html' | 'http://example.com/subpath/public/file.html'
+ 'http://example.com/subpath/' | '/public/file.html' | 'http://example.com/subpath/public/file.html'
+ end
+ with_them do
+ it 'returns the full external URL' do
+ environment.external_url = external_url
+ allow(project).to receive(:public_path_for_source_path).with(source_path, sha).and_return(public_path)
+
+ expect(environment.external_url_for(source_path, sha)).to eq(full_url)
+ end
end
end
end
@@ -840,4 +851,35 @@ describe Environment, :use_clean_rails_memory_store_caching do
subject.prometheus_adapter
end
end
+
+ describe '#knative_services_finder' do
+ let(:environment) { create(:environment) }
+
+ subject { environment.knative_services_finder }
+
+ context 'environment has no deployments' do
+ it { is_expected.to be_nil }
+ end
+
+ context 'environment has a deployment' do
+ let!(:deployment) { create(:deployment, :success, environment: environment, cluster: cluster) }
+
+ context 'with no cluster associated' do
+ let(:cluster) { nil }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with a cluster associated' do
+ let(:cluster) { create(:cluster) }
+
+ it 'calls the service finder' do
+ expect(Clusters::KnativeServicesFinder).to receive(:new)
+ .with(cluster, environment).and_return(:finder)
+
+ is_expected.to eq :finder
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 470ce65707d..1c41ceb7deb 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -23,6 +23,7 @@ describe Group do
it { is_expected.to have_many(:badges).class_name('GroupBadge') }
it { is_expected.to have_many(:cluster_groups).class_name('Clusters::Group') }
it { is_expected.to have_many(:clusters).class_name('Clusters::Cluster') }
+ it { is_expected.to have_many(:container_repositories) }
describe '#members & #requesters' do
let(:requester) { create(:user) }
@@ -71,7 +72,7 @@ describe Group do
end
end
- describe '#notification_settings', :nested_groups do
+ describe '#notification_settings' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:sub_group) { create(:group, parent_id: group.id) }
@@ -95,6 +96,43 @@ describe Group do
end
end
+ describe '#notification_email_for' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+
+ let(:group_notification_email) { 'user+group@example.com' }
+ let(:subgroup_notification_email) { 'user+subgroup@example.com' }
+
+ subject { subgroup.notification_email_for(user) }
+
+ context 'when both group notification emails are set' do
+ it 'returns subgroup notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+ create(:notification_setting, user: user, source: subgroup, notification_email: subgroup_notification_email)
+
+ is_expected.to eq(subgroup_notification_email)
+ end
+ end
+
+ context 'when subgroup notification email is blank' do
+ it 'returns parent group notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+ create(:notification_setting, user: user, source: subgroup, notification_email: '')
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+
+ context 'when only the parent group notification email is set' do
+ it 'returns parent group notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+ end
+
describe '#visibility_level_allowed_by_parent' do
let(:parent) { create(:group, :internal) }
let(:sub_group) { build(:group, parent_id: parent.id) }
@@ -200,7 +238,7 @@ describe Group do
it { is_expected.to match_array([private_group, internal_group, group]) }
end
- context 'when user is a member of private subgroup', :postgresql do
+ context 'when user is a member of private subgroup' do
let!(:private_subgroup) { create(:group, :private, parent: private_group) }
before do
@@ -379,7 +417,7 @@ describe Group do
it { expect(group.last_owner?(@members[:owner])).to be_falsy }
end
- context 'with owners from a parent', :postgresql do
+ context 'with owners from a parent' do
before do
parent_group = create(:group)
create(:group_member, :owner, group: parent_group)
@@ -487,7 +525,7 @@ describe Group do
it { expect(subject.parent).to be_kind_of(described_class) }
end
- describe '#members_with_parents', :nested_groups do
+ describe '#members_with_parents' do
let!(:group) { create(:group, :nested) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
let!(:developer) { group.add_user(create(:user), GroupMember::DEVELOPER) }
@@ -498,7 +536,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_members', :nested_groups do
+ describe '#direct_and_indirect_members' do
let!(:group) { create(:group, :nested) }
let!(:sub_group) { create(:group, parent: group) }
let!(:maintainer) { group.parent.add_user(create(:user), GroupMember::MAINTAINER) }
@@ -515,7 +553,7 @@ describe Group do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -534,7 +572,7 @@ describe Group do
end
end
- describe '#direct_and_indirect_users', :nested_groups do
+ describe '#direct_and_indirect_users' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -564,7 +602,7 @@ describe Group do
end
end
- describe '#project_users_with_descendants', :nested_groups do
+ describe '#project_users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
let(:user_c) { create(:user) }
@@ -641,7 +679,7 @@ describe Group do
end
end
- context 'sub groups and projects', :nested_groups do
+ context 'sub groups and projects' do
it 'enables two_factor_requirement for group member' do
group.add_user(user, GroupMember::OWNER)
@@ -650,7 +688,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_truthy
end
- context 'expanded group members', :nested_groups do
+ context 'expanded group members' do
let(:indirect_user) { create(:user) }
it 'enables two_factor_requirement for subgroup member' do
@@ -683,7 +721,7 @@ describe Group do
expect(user.reload.require_two_factor_authentication_from_group).to be_falsey
end
- it 'does not enable two_factor_requirement for subgroup child project member', :nested_groups do
+ it 'does not enable two_factor_requirement for subgroup child project member' do
subgroup = create(:group, :nested, parent: group)
project = create(:project, group: subgroup)
project.add_maintainer(user)
@@ -783,7 +821,7 @@ describe Group do
it_behaves_like 'ref is protected'
end
- context 'when group has children', :postgresql do
+ context 'when group has children' do
let(:group_child) { create(:group, parent: group) }
let(:group_child_2) { create(:group, parent: group_child) }
let(:group_child_3) { create(:group, parent: group_child_2) }
@@ -806,7 +844,7 @@ describe Group do
end
end
- describe '#highest_group_member', :nested_groups do
+ describe '#highest_group_member' do
let(:nested_group) { create(:group, parent: group) }
let(:nested_group_2) { create(:group, parent: nested_group) }
let(:user) { create(:user) }
@@ -895,7 +933,7 @@ describe Group do
it { is_expected.to eq(config) }
end
- context 'with parent groups', :nested_groups do
+ context 'with parent groups' do
where(:instance_value, :parent_value, :group_value, :config) do
# Instance level enabled
true | nil | nil | { status: true, scope: :instance }
@@ -994,4 +1032,11 @@ describe Group do
expect(group.project_creation_level).to eq(Gitlab::CurrentSettings.default_project_creation)
end
end
+
+ describe 'subgroup_creation_level' do
+ it 'defaults to maintainers' do
+ expect(group.subgroup_creation_level)
+ .to eq(Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+ end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index d5b016dc8f6..2e7d78d77a8 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -871,4 +871,12 @@ describe Issue do
expect(issue.labels_hook_attrs).to eq([label.hook_attrs])
end
end
+
+ context "relative positioning" do
+ it_behaves_like "a class that supports relative positioning" do
+ let(:project) { create(:project) }
+ let(:factory) { :issue }
+ let(:default_params) { { project: project } }
+ end
+ end
end
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 5174c590a10..c2e2298823e 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -193,4 +193,17 @@ describe Label do
expect(described_class.optionally_subscribed_by(nil)).to match_array([label, label2])
end
end
+
+ describe '#templates' do
+ context 'with invalid template labels' do
+ it 'returns only valid template labels' do
+ create(:label)
+ # Project labels should not have template set to true
+ create(:label, template: true)
+ valid_template_label = described_class.create!(title: 'test', template: true, type: nil)
+
+ expect(described_class.templates).to eq([valid_template_label])
+ end
+ end
+ end
end
diff --git a/spec/models/lfs_download_object_spec.rb b/spec/models/lfs_download_object_spec.rb
index effd8b08124..8b53effe98f 100644
--- a/spec/models/lfs_download_object_spec.rb
+++ b/spec/models/lfs_download_object_spec.rb
@@ -50,7 +50,7 @@ describe LfsDownloadObject do
before do
allow(ApplicationSetting)
.to receive(:current)
- .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: setting))
+ .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_web_hooks_and_services: setting))
end
context 'are allowed' do
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 782a84f922b..2cb4f222ea4 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -172,6 +172,19 @@ describe Member do
it { expect(described_class.non_request).to include @accepted_request_member }
end
+ describe '.search_invite_email' do
+ it 'returns only members the matching e-mail' do
+ create(:group_member, :invited)
+
+ invited = described_class.search_invite_email(@invited_member.invite_email)
+
+ expect(invited.count).to eq(1)
+ expect(invited.first).to eq(@invited_member)
+
+ expect(described_class.search_invite_email('bad-email@example.com').count).to eq(0)
+ end
+ end
+
describe '.developers' do
subject { described_class.developers.to_a }
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index f227abd3dae..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
@@ -69,7 +79,7 @@ describe GroupMember do
end
end
- context 'access levels', :nested_groups do
+ context 'access levels' do
context 'with parent group' do
it_behaves_like 'inherited access level as a member of entity' do
let(:entity) { create(:group, parent: parent_entity) }
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 497764b6825..79c39b81196 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -130,7 +130,7 @@ describe ProjectMember do
end
end
- context 'with parent group and a subgroup', :nested_groups do
+ context 'with parent group and a subgroup' do
it_behaves_like 'inherited access level as a member of entity' do
let(:subgroup) { create(:group, parent: parent_entity) }
let(:entity) { create(:project, group: subgroup) }
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index a53add67066..e7dd7287a75 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -484,4 +484,12 @@ describe MergeRequestDiff do
end
end
end
+
+ describe '#lines_count' do
+ subject { diff_with_commits }
+
+ it 'returns sum of all changed lines count in diff files' do
+ expect(subject.lines_count).to eq 109
+ end
+ end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 6a5bd276233..d344a6d0f0d 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1988,6 +1988,7 @@ describe MergeRequest do
params = {}
merge_jid = 'hash-123'
+ expect(merge_request).to receive(:expire_etag_cache)
expect(MergeWorker).to receive(:perform_async).with(merge_request.id, user_id, params) do
merge_jid
end
@@ -2011,6 +2012,7 @@ describe MergeRequest do
.with(merge_request.id, user_id)
.and_return(rebase_jid)
+ expect(merge_request).to receive(:expire_etag_cache)
expect(merge_request).to receive(:lock!).and_call_original
execute
@@ -3013,9 +3015,6 @@ describe MergeRequest do
subject { merge_request.rebase_in_progress? }
it do
- # Stub out the legacy gitaly implementation
- allow(merge_request).to receive(:gitaly_rebase_in_progress?) { false }
-
allow(Gitlab::SidekiqStatus).to receive(:running?).with(rebase_jid) { jid_valid }
merge_request.rebase_jid = rebase_jid
@@ -3025,42 +3024,6 @@ describe MergeRequest do
end
end
- describe '#gitaly_rebase_in_progress?' do
- let(:repo_path) do
- Gitlab::GitalyClient::StorageSettings.allow_disk_access do
- subject.source_project.repository.path
- end
- end
- let(:rebase_path) { File.join(repo_path, "gitlab-worktree", "rebase-#{subject.id}") }
-
- before do
- system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} worktree add --detach #{rebase_path} master))
- end
-
- it 'returns true when there is a current rebase directory' do
- expect(subject.rebase_in_progress?).to be_truthy
- end
-
- it 'returns false when there is no rebase directory' do
- FileUtils.rm_rf(rebase_path)
-
- expect(subject.rebase_in_progress?).to be_falsey
- end
-
- it 'returns false when the rebase directory has expired' do
- time = 20.minutes.ago.to_time
- File.utime(time, time, rebase_path)
-
- expect(subject.rebase_in_progress?).to be_falsey
- end
-
- it 'returns false when the source project has been removed' do
- allow(subject).to receive(:source_project).and_return(nil)
-
- expect(subject.rebase_in_progress?).to be_falsey
- end
- end
-
describe '#allow_collaboration' do
let(:merge_request) do
build(:merge_request, source_branch: 'fixes', allow_collaboration: true)
diff --git a/spec/models/namespace/aggregation_schedule_spec.rb b/spec/models/namespace/aggregation_schedule_spec.rb
index 0f1283717e0..38bf8089411 100644
--- a/spec/models/namespace/aggregation_schedule_spec.rb
+++ b/spec/models/namespace/aggregation_schedule_spec.rb
@@ -7,24 +7,6 @@ RSpec.describe Namespace::AggregationSchedule, :clean_gitlab_redis_shared_state,
it { is_expected.to belong_to :namespace }
- describe '.delay_timeout' do
- context 'when timeout is set on redis' do
- it 'uses personalized timeout' do
- Gitlab::Redis::SharedState.with do |redis|
- redis.set(described_class::REDIS_SHARED_KEY, 1.hour)
- end
-
- expect(described_class.delay_timeout).to eq(1.hour)
- end
- end
-
- context 'when timeout is not set on redis' do
- it 'uses default timeout' do
- expect(described_class.delay_timeout).to eq(3.hours)
- end
- end
- end
-
describe '#schedule_root_storage_statistics' do
let(:namespace) { create(:namespace) }
let(:aggregation_schedule) { namespace.build_aggregation_schedule }
@@ -87,21 +69,5 @@ RSpec.describe Namespace::AggregationSchedule, :clean_gitlab_redis_shared_state,
aggregation_schedule.schedule_root_storage_statistics
end
end
-
- context 'with a personalized lease timeout' do
- before do
- Gitlab::Redis::SharedState.with do |redis|
- redis.set(described_class::REDIS_SHARED_KEY, 1.hour)
- end
- end
-
- it 'uses a personalized time' do
- expect(Namespaces::RootStatisticsWorker)
- .to receive(:perform_in)
- .with(1.hour, aggregation_schedule.namespace_id)
-
- aggregation_schedule.save!
- end
- end
end
end
diff --git a/spec/models/namespace/root_storage_statistics_spec.rb b/spec/models/namespace/root_storage_statistics_spec.rb
index 3229a32234e..5341278db7c 100644
--- a/spec/models/namespace/root_storage_statistics_spec.rb
+++ b/spec/models/namespace/root_storage_statistics_spec.rb
@@ -56,7 +56,7 @@ RSpec.describe Namespace::RootStorageStatistics, type: :model do
it_behaves_like 'data refresh'
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:subgroup1) { create(:group, parent: namespace)}
let(:subgroup2) { create(:group, parent: subgroup1)}
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index f908f3504e0..972f26ac745 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -191,7 +191,7 @@ describe Namespace do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -271,7 +271,7 @@ describe Namespace do
end
end
- context 'with subgroups', :nested_groups do
+ context 'with subgroups' do
let(:parent) { create(:group, name: 'parent', path: 'parent') }
let(:new_parent) { create(:group, name: 'new_parent', path: 'new_parent') }
let(:child) { create(:group, name: 'child', path: 'child', parent: parent) }
@@ -475,7 +475,7 @@ describe Namespace do
end
end
- describe '#self_and_hierarchy', :nested_groups do
+ describe '#self_and_hierarchy' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -490,7 +490,7 @@ describe Namespace do
end
end
- describe '#ancestors', :nested_groups do
+ describe '#ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -504,7 +504,7 @@ describe Namespace do
end
end
- describe '#self_and_ancestors', :nested_groups do
+ describe '#self_and_ancestors' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -518,7 +518,7 @@ describe Namespace do
end
end
- describe '#descendants', :nested_groups do
+ describe '#descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -534,7 +534,7 @@ describe Namespace do
end
end
- describe '#self_and_descendants', :nested_groups do
+ describe '#self_and_descendants' do
let!(:group) { create(:group, path: 'git_lab') }
let!(:nested_group) { create(:group, parent: group) }
let!(:deep_nested_group) { create(:group, parent: nested_group) }
@@ -550,7 +550,7 @@ describe Namespace do
end
end
- describe '#users_with_descendants', :nested_groups do
+ describe '#users_with_descendants' do
let(:user_a) { create(:user) }
let(:user_b) { create(:user) }
@@ -597,7 +597,7 @@ describe Namespace do
it { expect(group.all_pipelines.to_a).to match_array([pipeline1, pipeline2]) }
end
- describe '#share_with_group_lock with subgroups', :nested_groups do
+ describe '#share_with_group_lock with subgroups' do
context 'when creating a subgroup' do
let(:subgroup) { create(:group, parent: root_group )}
@@ -738,7 +738,7 @@ describe Namespace do
end
describe '#root_ancestor' do
- it 'returns the top most ancestor', :nested_groups do
+ it 'returns the top most ancestor' do
root_group = create(:group)
nested_group = create(:group, parent: root_group)
deep_nested_group = create(:group, parent: nested_group)
@@ -853,4 +853,64 @@ describe Namespace do
it { is_expected.to be_falsy }
end
end
+
+ describe '#emails_disabled?' do
+ context 'when not a subgroup' do
+ it 'returns false' do
+ group = create(:group, emails_disabled: false)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+
+ it 'returns true' do
+ group = create(:group, emails_disabled: true)
+
+ expect(group.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'when a subgroup' do
+ let(:grandparent) { create(:group) }
+ let(:parent) { create(:group, parent: grandparent) }
+ let(:group) { create(:group, parent: parent) }
+
+ it 'returns false' do
+ expect(group.emails_disabled?).to be_falsey
+ end
+
+ context 'when ancestor emails are disabled' do
+ it 'returns true' do
+ grandparent.update_attribute(:emails_disabled, true)
+
+ expect(group.emails_disabled?).to be_truthy
+ end
+ end
+ end
+
+ context 'when :emails_disabled feature flag is off' do
+ before do
+ stub_feature_flags(emails_disabled: false)
+ end
+
+ context 'when not a subgroup' do
+ it 'returns false' do
+ group = create(:group, emails_disabled: true)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+ end
+
+ context 'when a subgroup and ancestor emails are disabled' do
+ let(:grandparent) { create(:group) }
+ let(:parent) { create(:group, parent: grandparent) }
+ let(:group) { create(:group, parent: parent) }
+
+ it 'returns false' do
+ grandparent.update_attribute(:emails_disabled, true)
+
+ expect(group.emails_disabled?).to be_falsey
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 03003e3dd7d..bfd0e5f0558 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -913,6 +913,22 @@ describe Note do
end
end
+ describe '#special_role=' do
+ let(:role) { Note::SpecialRole::FIRST_TIME_CONTRIBUTOR }
+
+ it 'assigns role' do
+ subject.special_role = role
+
+ expect(subject.special_role).to eq(role)
+ end
+
+ it 'does not assign unknown role' do
+ expect { subject.special_role = :bogus }.to raise_error(/Role is undefined/)
+
+ expect(subject.special_role).to be_nil
+ end
+ end
+
describe '#parent' do
it 'returns project for project notes' do
project = create(:project)
diff --git a/spec/models/notification_recipient_spec.rb b/spec/models/notification_recipient_spec.rb
index 20278d81f6d..2ba53818e54 100644
--- a/spec/models/notification_recipient_spec.rb
+++ b/spec/models/notification_recipient_spec.rb
@@ -9,6 +9,38 @@ describe NotificationRecipient do
subject(:recipient) { described_class.new(user, :watch, target: target, project: project) }
+ describe '#notifiable?' do
+ let(:recipient) { described_class.new(user, :mention, target: target, project: project) }
+
+ context 'when emails are disabled' do
+ it 'returns false if group disabled' do
+ expect(project.namespace).to receive(:emails_disabled?).and_return(true)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq false
+ end
+
+ it 'returns false if project disabled' do
+ expect(project).to receive(:emails_disabled?).and_return(true)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq false
+ end
+ end
+
+ context 'when emails are enabled' do
+ it 'returns true if group enabled' do
+ expect(project.namespace).to receive(:emails_disabled?).and_return(false)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq true
+ end
+
+ it 'returns true if project enabled' do
+ expect(project).to receive(:emails_disabled?).and_return(false)
+ expect(recipient).to receive(:emails_disabled?).and_call_original
+ expect(recipient.notifiable?).to eq true
+ end
+ end
+ end
+
describe '#has_access?' do
before do
allow(user).to receive(:can?).and_call_original
@@ -49,7 +81,7 @@ describe NotificationRecipient do
end
context '#notification_setting' do
- context 'for child groups', :nested_groups do
+ context 'for child groups' do
let!(:moved_group) { create(:group) }
let(:group) { create(:group) }
let(:sub_group_1) { create(:group, parent: group) }
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 973c67937b7..519c519fbcf 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -127,6 +127,30 @@ describe PagesDomain do
it { is_expected.not_to be_valid }
end
+
+ context 'when certificate is expired' do
+ let(:domain) do
+ build(:pages_domain, :with_trusted_expired_chain)
+ end
+
+ context 'when certificate is being changed' do
+ it "adds error to certificate" do
+ domain.valid?
+
+ expect(domain.errors.keys).to contain_exactly(:key, :certificate)
+ end
+ end
+
+ context 'when certificate is already saved' do
+ it "doesn't add error to certificate" do
+ domain.save(validate: false)
+
+ domain.valid?
+
+ expect(domain.errors.keys).to contain_exactly(:key)
+ end
+ end
+ end
end
describe 'validations' do
diff --git a/spec/models/postgresql/replication_slot_spec.rb b/spec/models/postgresql/replication_slot_spec.rb
index 95ae204a8a8..d435fccc09a 100644
--- a/spec/models/postgresql/replication_slot_spec.rb
+++ b/spec/models/postgresql/replication_slot_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe Postgresql::ReplicationSlot, :postgresql do
+describe Postgresql::ReplicationSlot do
describe '.in_use?' do
it 'returns true when replication slots are present' do
expect(described_class).to receive(:exists?).and_return(true)
diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb
index 7bdd2367a68..da9e56ef897 100644
--- a/spec/models/project_auto_devops_spec.rb
+++ b/spec/models/project_auto_devops_spec.rb
@@ -15,7 +15,7 @@ describe ProjectAutoDevops do
it { is_expected.to respond_to(:updated_at) }
describe '#predefined_variables' do
- let(:auto_devops) { build_stubbed(:project_auto_devops, project: project, domain: domain) }
+ let(:auto_devops) { build_stubbed(:project_auto_devops, project: project) }
context 'when deploy_strategy is manual' do
let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) }
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index 31e55bf6be6..dc7a8433064 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -8,11 +8,7 @@ describe ProjectFeature do
describe '.quoted_access_level_column' do
it 'returns the table name and quoted column name for a feature' do
- expected = if Gitlab::Database.postgresql?
- '"project_features"."issues_access_level"'
- else
- '`project_features`.`issues_access_level`'
- end
+ expected = '"project_features"."issues_access_level"'
expect(described_class.quoted_access_level_column(:issues)).to eq(expected)
end
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index dad5506900b..cd997224122 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -25,7 +25,7 @@ describe ProjectGroupLink do
expect(project_group_link).not_to be_valid
end
- it "doesn't allow a project to be shared with an ancestor of the group it is in", :nested_groups do
+ it "doesn't allow a project to be shared with an ancestor of the group it is in" do
project_group_link.group = parent_group
expect(project_group_link).not_to be_valid
diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb
index 8f9fa310ad4..cf7c7bf7e61 100644
--- a/spec/models/project_services/chat_message/pipeline_message_spec.rb
+++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb
@@ -1,12 +1,9 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe ChatMessage::PipelineMessage do
subject { described_class.new(args) }
- let(:user) { { name: "The Hacker", username: 'hacker' } }
- let(:duration) { 7210 }
let(:args) do
{
object_attributes: {
@@ -14,122 +11,437 @@ describe ChatMessage::PipelineMessage do
sha: '97de212e80737a608d939f648d959671fb0a0142',
tag: false,
ref: 'develop',
- status: status,
- duration: duration
+ status: 'success',
+ detailed_status: nil,
+ duration: 7210,
+ finished_at: "2019-05-27 11:56:36 -0300"
},
project: {
- path_with_namespace: 'project_name',
- web_url: 'http://example.gitlab.com'
+ id: 234,
+ name: "project_name",
+ path_with_namespace: 'group/project_name',
+ web_url: 'http://example.gitlab.com',
+ avatar_url: 'http://example.com/project_avatar'
+ },
+ user: {
+ id: 345,
+ name: "The Hacker",
+ username: "hacker",
+ email: "hacker@example.gitlab.com",
+ avatar_url: "http://example.com/avatar"
+ },
+ commit: {
+ id: "abcdef"
},
- user: user
+ builds: nil,
+ markdown: false
}
end
- let(:combined_name) { "The Hacker (hacker)" }
- context 'without markdown' do
- context 'pipeline succeeded' do
- let(:status) { 'success' }
- let(:color) { 'good' }
- let(:message) { build_message('passed', combined_name) }
+ let(:has_yaml_errors) { false }
+
+ before do
+ test_commit = double("A test commit", committer: args[:user], title: "A test commit message")
+ test_project = double("A test project", commit_by: test_commit, name: args[:project][:name], web_url: args[:project][:web_url])
+ allow(test_project).to receive(:avatar_url).with(no_args).and_return("/avatar")
+ allow(test_project).to receive(:avatar_url).with(only_path: false).and_return(args[:project][:avatar_url])
+ allow(Project).to receive(:find) { test_project }
+
+ test_pipeline = double("A test pipeline", has_yaml_errors?: has_yaml_errors,
+ yaml_errors: "yaml error description here")
+ allow(Ci::Pipeline).to receive(:find) { test_pipeline }
+
+ allow(Gitlab::UrlBuilder).to receive(:build).with(test_commit).and_return("http://example.com/commit")
+ allow(Gitlab::UrlBuilder).to receive(:build).with(args[:user]).and_return("http://example.gitlab.com/hacker")
+ end
+
+ context 'when the fancy_pipeline_slack_notifications feature flag is disabled' do
+ before do
+ stub_feature_flags(fancy_pipeline_slack_notifications: false)
+ end
+
+ it 'returns an empty pretext' do
+ expect(subject.pretext).to be_empty
+ end
+
+ it "returns the pipeline summary in the activity's title" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) passed"
+ )
+ end
- it 'returns a message with information about succeeded build' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns the summary with a 'failed' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) failed"
+ )
end
end
- context 'pipeline failed' do
- let(:status) { 'failed' }
- let(:color) { 'danger' }
- let(:message) { build_message(status, combined_name) }
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
- it 'returns a message with information about failed build' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
+ it "returns the summary with 'API' as the username" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by API passed"
+ )
end
+ end
- context 'when triggered by API therefore lacking user' do
- let(:user) { nil }
- let(:message) { build_message(status, 'API') }
+ it "returns a link to the project in the activity's subtitle" do
+ expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
+ end
- it 'returns a message stating it is by API' do
- expect(subject.pretext).to be_empty
- expect(subject.fallback).to eq(message)
- expect(subject.attachments).to eq([text: message, color: color])
- end
+ it "returns the build duration in the activity's text property" do
+ expect(subject.activity[:text]).to eq("in 02:00:10")
+ end
+
+ it "returns the user's avatar image URL in the activity's image property" do
+ expect(subject.activity[:image]).to eq("http://example.com/avatar")
+ end
+
+ context 'when the user does not have an avatar' do
+ before do
+ args[:user][:avatar_url] = nil
+ end
+
+ it "returns an empty string in the activity's image property" do
+ expect(subject.activity[:image]).to be_empty
+ end
+ end
+
+ it "returns the pipeline summary as the attachment's text property" do
+ expect(subject.attachments.first[:text]).to eq(
+ "<http://example.gitlab.com|project_name>:" \
+ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
+ " of branch <http://example.gitlab.com/commits/develop|develop>" \
+ " by The Hacker (hacker) passed in 02:00:10"
+ )
+ end
+
+ it "returns 'good' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('good')
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns 'danger' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('danger')
end
end
- def build_message(status_text = status, name = user[:name])
- "<http://example.gitlab.com|project_name>:" \
- " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
- " of branch <http://example.gitlab.com/commits/develop|develop>" \
- " by #{name} #{status_text} in 02:00:10"
+ context 'when rendering markdown' do
+ before do
+ args[:markdown] = true
+ end
+
+ it 'returns the pipeline summary as the attachments in markdown format' do
+ expect(subject.attachments).to eq(
+ "[project_name](http://example.gitlab.com):" \
+ " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) passed in 02:00:10"
+ )
+ end
end
end
- context 'with markdown' do
+ context 'when the fancy_pipeline_slack_notifications feature flag is enabled' do
before do
- args[:markdown] = true
- end
-
- context 'pipeline succeeded' do
- let(:status) { 'success' }
- let(:color) { 'good' }
- let(:message) { build_markdown_message('passed', combined_name) }
-
- it 'returns a message with information about succeeded build' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) passed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
- })
+ stub_feature_flags(fancy_pipeline_slack_notifications: true)
+ end
+
+ it 'returns an empty pretext' do
+ expect(subject.pretext).to be_empty
+ end
+
+ it "returns the pipeline summary in the activity's title" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed"
+ )
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns the summary with a 'failed' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has failed"
+ )
+ end
+ end
+
+ context "when the pipeline passed with warnings" do
+ before do
+ args[:object_attributes][:detailed_status] = 'passed with warnings'
+ end
+
+ it "returns the summary with a 'passed with warnings' status" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed with warnings"
+ )
+ end
+ end
+
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
+
+ it "returns the summary with 'API' as the username" do
+ expect(subject.activity[:title]).to eq(
+ "Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by API has passed"
+ )
+ end
+ end
+
+ it "returns a link to the project in the activity's subtitle" do
+ expect(subject.activity[:subtitle]).to eq("in [project_name](http://example.gitlab.com)")
+ end
+
+ it "returns the build duration in the activity's text property" do
+ expect(subject.activity[:text]).to eq("in 02:00:10")
+ end
+
+ it "returns the user's avatar image URL in the activity's image property" do
+ expect(subject.activity[:image]).to eq("http://example.com/avatar")
+ end
+
+ context 'when the user does not have an avatar' do
+ before do
+ args[:user][:avatar_url] = nil
+ end
+
+ it "returns an empty string in the activity's image property" do
+ expect(subject.activity[:image]).to be_empty
+ end
+ end
+
+ it "returns the pipeline summary as the attachment's fallback property" do
+ expect(subject.attachments.first[:fallback]).to eq(
+ "<http://example.gitlab.com|project_name>:" \
+ " Pipeline <http://example.gitlab.com/pipelines/123|#123>" \
+ " of branch <http://example.gitlab.com/commits/develop|develop>" \
+ " by The Hacker (hacker) has passed in 02:00:10"
+ )
+ end
+
+ it "returns 'good' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('good')
+ end
+
+ context "when the pipeline failed" do
+ before do
+ args[:object_attributes][:status] = 'failed'
+ end
+
+ it "returns 'danger' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('danger')
+ end
+ end
+
+ context "when the pipeline passed with warnings" do
+ before do
+ args[:object_attributes][:detailed_status] = 'passed with warnings'
+ end
+
+ it "returns 'warning' as the attachment's color property" do
+ expect(subject.attachments.first[:color]).to eq('warning')
end
end
- context 'pipeline failed' do
- let(:status) { 'failed' }
- let(:color) { 'danger' }
- let(:message) { build_markdown_message(status, combined_name) }
+ it "returns the committer's name and username as the attachment's author_name property" do
+ expect(subject.attachments.first[:author_name]).to eq('The Hacker (hacker)')
+ end
+
+ it "returns the committer's avatar URL as the attachment's author_icon property" do
+ expect(subject.attachments.first[:author_icon]).to eq('http://example.com/avatar')
+ end
+
+ it "returns the committer's GitLab profile URL as the attachment's author_link property" do
+ expect(subject.attachments.first[:author_link]).to eq('http://example.gitlab.com/hacker')
+ end
+
+ context 'when no user is provided because the pipeline was triggered by the API' do
+ before do
+ args[:user] = nil
+ end
+
+ it "returns the committer's name and username as the attachment's author_name property" do
+ expect(subject.attachments.first[:author_name]).to eq('API')
+ end
+
+ it "returns nil as the attachment's author_icon property" do
+ expect(subject.attachments.first[:author_icon]).to be_nil
+ end
+
+ it "returns nil as the attachment's author_link property" do
+ expect(subject.attachments.first[:author_link]).to be_nil
+ end
+ end
+
+ it "returns the pipeline ID, status, and duration as the attachment's title property" do
+ expect(subject.attachments.first[:title]).to eq("Pipeline #123 has passed in 02:00:10")
+ end
+
+ it "returns the pipeline URL as the attachment's title_link property" do
+ expect(subject.attachments.first[:title_link]).to eq("http://example.gitlab.com/pipelines/123")
+ end
+
+ it "returns two attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(2)
+ end
+
+ it "returns the commit message as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][0]).to eq({
+ title: "Branch",
+ value: "<http://example.gitlab.com/commits/develop|develop>",
+ short: true
+ })
+ end
+
+ it "returns the ref name and link as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][1]).to eq({
+ title: "Commit",
+ value: "<http://example.com/commit|A test commit message>",
+ short: true
+ })
+ end
+
+ context "when a job in the pipeline fails" do
+ before do
+ args[:builds] = [
+ { id: 1, name: "rspec", status: "failed", stage: "test" },
+ { id: 2, name: "karma", status: "success", stage: "test" }
+ ]
+ end
+
+ it "returns four attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(4)
+ end
- it 'returns a message with information about failed build' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by The Hacker (hacker) failed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
+ it "returns the stage name and link to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Failed stage",
+ value: "<http://example.gitlab.com/pipelines/123/failures|test>",
+ short: true
})
end
- context 'when triggered by API therefore lacking user' do
- let(:user) { nil }
- let(:message) { build_markdown_message(status, 'API') }
+ it "returns the job name and link as the attachment's fourth field property" do
+ expect(subject.attachments.first[:fields][3]).to eq({
+ title: "Failed job",
+ value: "<http://example.gitlab.com/-/jobs/1|rspec>",
+ short: true
+ })
+ end
+ end
+
+ context "when lots of jobs across multiple stages fail" do
+ before do
+ args[:builds] = (1..25).map do |i|
+ { id: i, name: "job-#{i}", status: "failed", stage: "stage-" + ((i % 3) + 1).to_s }
+ end
+ end
+
+ it "returns the stage names and links to the 'Failed jobs' tab on the pipeline's page as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Failed stages",
+ value: "<http://example.gitlab.com/pipelines/123/failures|stage-2>, <http://example.gitlab.com/pipelines/123/failures|stage-1>, <http://example.gitlab.com/pipelines/123/failures|stage-3>",
+ short: true
+ })
+ end
- it 'returns a message stating it is by API' do
- expect(subject.pretext).to be_empty
- expect(subject.attachments).to eq(message)
- expect(subject.activity).to eq({
- title: 'Pipeline [#123](http://example.gitlab.com/pipelines/123) of branch [develop](http://example.gitlab.com/commits/develop) by API failed',
- subtitle: 'in [project_name](http://example.gitlab.com)',
- text: 'in 02:00:10',
- image: ''
- })
+ it "returns the job names and links as the attachment's fourth field property" do
+ expected_jobs = 25.downto(16).map do |i|
+ "<http://example.gitlab.com/-/jobs/#{i}|job-#{i}>"
end
+
+ expected_jobs << "and <http://example.gitlab.com/pipelines/123/failures|15 more>"
+
+ expect(subject.attachments.first[:fields][3]).to eq({
+ title: "Failed jobs",
+ value: expected_jobs.join(", "),
+ short: true
+ })
end
end
- def build_markdown_message(status_text = status, name = user[:name])
- "[project_name](http://example.gitlab.com):" \
- " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
- " of branch [develop](http://example.gitlab.com/commits/develop)" \
- " by #{name} #{status_text} in 02:00:10"
+ context "when the CI config file contains a YAML error" do
+ let(:has_yaml_errors) { true }
+
+ it "returns three attachment fields" do
+ expect(subject.attachments.first[:fields].count).to eq(3)
+ end
+
+ it "returns the YAML error deatils as the attachment's third field property" do
+ expect(subject.attachments.first[:fields][2]).to eq({
+ title: "Invalid CI config YAML file",
+ value: "yaml error description here",
+ short: false
+ })
+ end
+ end
+
+ it "returns the stage name and link as the attachment's second field property" do
+ expect(subject.attachments.first[:fields][1]).to eq({
+ title: "Commit",
+ value: "<http://example.com/commit|A test commit message>",
+ short: true
+ })
+ end
+
+ it "returns the project's name as the attachment's footer property" do
+ expect(subject.attachments.first[:footer]).to eq("project_name")
+ end
+
+ it "returns the project's avatar URL as the attachment's footer_icon property" do
+ expect(subject.attachments.first[:footer_icon]).to eq("http://example.com/project_avatar")
+ end
+
+ it "returns the pipeline's timestamp as the attachment's ts property" do
+ expected_ts = Time.parse(args[:object_attributes][:finished_at]).to_i
+ expect(subject.attachments.first[:ts]).to eq(expected_ts)
+ end
+
+ context 'when rendering markdown' do
+ before do
+ args[:markdown] = true
+ end
+
+ it 'returns the pipeline summary as the attachments in markdown format' do
+ expect(subject.attachments).to eq(
+ "[project_name](http://example.gitlab.com):" \
+ " Pipeline [#123](http://example.gitlab.com/pipelines/123)" \
+ " of branch [develop](http://example.gitlab.com/commits/develop)" \
+ " by The Hacker (hacker) has passed in 02:00:10"
+ )
+ end
end
end
end
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
index 0a58eb367e3..ffe241aa880 100644
--- a/spec/models/project_services/emails_on_push_service_spec.rb
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -20,4 +20,24 @@ describe EmailsOnPushService do
it { is_expected.not_to validate_presence_of(:recipients) }
end
end
+
+ context 'project emails' do
+ let(:push_data) { { object_kind: 'push' } }
+ let(:project) { create(:project, :repository) }
+ let(:service) { create(:emails_on_push_service, project: project) }
+
+ it 'does not send emails when disabled' do
+ expect(project).to receive(:emails_disabled?).and_return(true)
+ expect(EmailsOnPushWorker).not_to receive(:perform_async)
+
+ service.execute(push_data)
+ end
+
+ it 'does send emails when enabled' do
+ expect(project).to receive(:emails_disabled?).and_return(false)
+ expect(EmailsOnPushWorker).to receive(:perform_async)
+
+ service.execute(push_data)
+ end
+ end
end
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 235cf314af5..02060699e9a 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -236,7 +236,7 @@ describe JiraService do
allow(JIRA::Resource::Remotelink).to receive(:all).and_return(nil)
expect { @jira_service.close_issue(resource, ExternalIssue.new('JIRA-123', project)) }
- .not_to raise_error(NoMethodError)
+ .not_to raise_error
end
# Check https://developer.atlassian.com/jiradev/jira-platform/guides/other/guide-jira-remote-issue-links/fields-in-remote-issue-links
@@ -606,6 +606,12 @@ describe JiraService do
expect(service.properties['api_url']).to eq('http://jira.sample/api')
end
end
+
+ it 'removes trailing slashes from url' do
+ service = described_class.new(url: 'http://jira.test.com/path/')
+
+ expect(service.url).to eq('http://jira.test.com/path')
+ end
end
describe 'favicon urls', :request_store do
@@ -621,4 +627,20 @@ describe JiraService do
expect(props[:object][:icon][:url16x16]).to match %r{^http://localhost/uploads/-/system/appearance/favicon/\d+/dk.png$}
end
end
+
+ context 'generating external URLs' do
+ let(:service) { described_class.new(url: 'http://jira.test.com/path/') }
+
+ describe '#issues_url' do
+ it 'handles trailing slashes' do
+ expect(service.issues_url).to eq('http://jira.test.com/path/browse/:id')
+ end
+ end
+
+ describe '#new_issue_url' do
+ it 'handles trailing slashes' do
+ expect(service.new_issue_url).to eq('http://jira.test.com/path/secure/CreateIssue.jspa')
+ end
+ end
+ end
end
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
deleted file mode 100644
index d33bbb0470f..00000000000
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ /dev/null
@@ -1,167 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe KubernetesService, :use_clean_rails_memory_store_caching do
- include KubernetesHelpers
- include ReactiveCachingHelpers
-
- let(:project) { create(:kubernetes_project) }
- let(:service) { create(:kubernetes_service, project: project) }
-
- describe 'Associations' do
- it { is_expected.to belong_to :project }
- end
-
- describe 'Validations' do
- context 'when service is active' do
- before do
- subject.active = true
- subject.skip_deprecation_validation = true
- end
-
- it { is_expected.not_to validate_presence_of(:namespace) }
- it { is_expected.to validate_presence_of(:api_url) }
- it { is_expected.to validate_presence_of(:token) }
-
- context 'namespace format' do
- before do
- subject.project = project
- subject.api_url = "http://example.com"
- subject.token = "test"
- end
-
- {
- 'foo' => true,
- '1foo' => true,
- 'foo1' => true,
- 'foo-bar' => true,
- '-foo' => false,
- 'foo-' => false,
- 'a' * 63 => true,
- 'a' * 64 => false,
- 'a.b' => false,
- 'a*b' => false,
- 'FOO' => true
- }.each do |namespace, validity|
- it "validates #{namespace} as #{validity ? 'valid' : 'invalid'}" do
- subject.namespace = namespace
-
- expect(subject.valid?).to eq(validity)
- end
- end
- end
- end
-
- context 'when service is inactive' do
- before do
- subject.project = project
- subject.active = false
- end
-
- it { is_expected.not_to validate_presence_of(:api_url) }
- it { is_expected.not_to validate_presence_of(:token) }
- end
-
- context 'with a deprecated service' do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- before do
- kubernetes_service.update_attribute(:active, false)
- kubernetes_service.skip_deprecation_validation = false
- kubernetes_service.properties['namespace'] = "foo"
- end
-
- it 'does not update attributes' do
- expect(kubernetes_service.save).to be_falsy
- end
-
- it 'includes an error with a deprecation message' do
- kubernetes_service.valid?
- expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been disabled/)
- end
- end
-
- context 'with an active and deprecated service' do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- before do
- kubernetes_service.skip_deprecation_validation = false
- kubernetes_service.active = false
- kubernetes_service.properties['namespace'] = 'foo'
- kubernetes_service.save
- end
-
- it 'deactivates the service' do
- expect(kubernetes_service.active?).to be_falsy
- end
-
- it 'does not include a deprecation message as error' do
- expect(kubernetes_service.errors.messages.count).to eq(0)
- end
-
- it 'updates attributes' do
- expect(kubernetes_service.properties['namespace']).to eq("foo")
- end
- end
- end
-
- describe '#initialize_properties' do
- context 'without a project' do
- it 'leaves the namespace unset' do
- expect(described_class.new.namespace).to be_nil
- end
- end
- end
-
- describe '#fields' do
- let(:kube_namespace) do
- subject.fields.find { |h| h[:name] == 'namespace' }
- end
-
- context 'as template' do
- before do
- subject.template = true
- end
-
- it 'sets the namespace to the default' do
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to eq(subject.class::TEMPLATE_PLACEHOLDER)
- end
- end
-
- context 'with associated project' do
- before do
- subject.project = project
- end
-
- it 'sets the namespace to the default' do
- expect(kube_namespace).not_to be_nil
- expect(kube_namespace[:placeholder]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
- end
- end
- end
-
- describe "#deprecated?" do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- it 'returns true' do
- expect(kubernetes_service.deprecated?).to be_truthy
- end
- end
-
- describe "#deprecation_message" do
- let(:kubernetes_service) { create(:kubernetes_service) }
-
- it 'indicates the service is deprecated' do
- expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been disabled/)
- end
-
- context 'if the service is not active' do
- it 'returns a message' do
- kubernetes_service.update_attribute(:active, false)
- expect(kubernetes_service.deprecation_message).to match(/Fields on this page are not used by GitLab/)
- end
- end
- end
-end
diff --git a/spec/models/project_services/microsoft_teams_service_spec.rb b/spec/models/project_services/microsoft_teams_service_spec.rb
index 3ffe633868f..73c20359091 100644
--- a/spec/models/project_services/microsoft_teams_service_spec.rb
+++ b/spec/models/project_services/microsoft_teams_service_spec.rb
@@ -292,7 +292,8 @@ describe MicrosoftTeamsService do
context 'when disabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project,
+ sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
diff --git a/spec/models/project_services/prometheus_service_spec.rb b/spec/models/project_services/prometheus_service_spec.rb
index e9c7c94ad70..e5ac6ca65d6 100644
--- a/spec/models/project_services/prometheus_service_spec.rb
+++ b/spec/models/project_services/prometheus_service_spec.rb
@@ -105,10 +105,6 @@ describe PrometheusService, :use_clean_rails_memory_store_caching do
context 'manual configuration is enabled' do
let(:manual_configuration) { true }
- it 'returns rest client from api_url' do
- expect(service.prometheus_client.url).to eq(api_url)
- end
-
it 'calls valid?' do
allow(service).to receive(:valid?).and_call_original
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 927c072be10..ff9e94afc12 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -98,6 +98,7 @@ describe Project do
it { is_expected.to have_many(:lfs_file_locks) }
it { is_expected.to have_many(:project_deploy_tokens) }
it { is_expected.to have_many(:deploy_tokens).through(:project_deploy_tokens) }
+ it { is_expected.to have_many(:cycle_analytics_stages) }
it 'has an inverse relationship with merge requests' do
expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project)
@@ -173,24 +174,6 @@ describe Project do
it { is_expected.to include_module(Sortable) }
end
- describe '.missing_kubernetes_namespace' do
- let!(:project) { create(:project) }
- let!(:cluster) { create(:cluster, :provided_by_user, :group) }
- let(:kubernetes_namespaces) { project.kubernetes_namespaces }
-
- subject { described_class.missing_kubernetes_namespace(kubernetes_namespaces) }
-
- it { is_expected.to contain_exactly(project) }
-
- context 'kubernetes namespace exists' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- it { is_expected.to be_empty }
- end
- end
-
describe 'validation' do
let!(:project) { create(:project) }
@@ -1174,7 +1157,6 @@ describe Project do
describe '#pipeline_for' do
let(:project) { create(:project, :repository) }
- let!(:pipeline) { create_pipeline(project) }
shared_examples 'giving the correct pipeline' do
it { is_expected.to eq(pipeline) }
@@ -1186,19 +1168,47 @@ describe Project do
end
end
- context 'with explicit sha' do
- subject { project.pipeline_for('master', pipeline.sha) }
+ context 'with a matching pipeline' do
+ let!(:pipeline) { create_pipeline(project) }
- it_behaves_like 'giving the correct pipeline'
+ context 'with explicit sha' do
+ subject { project.pipeline_for('master', pipeline.sha) }
+
+ it_behaves_like 'giving the correct pipeline'
+
+ context 'with supplied id' do
+ let!(:other_pipeline) { create_pipeline(project) }
+
+ subject { project.pipeline_for('master', pipeline.sha, other_pipeline.id) }
+
+ it { is_expected.to eq(other_pipeline) }
+ end
+ end
+
+ context 'with implicit sha' do
+ subject { project.pipeline_for('master') }
+
+ it_behaves_like 'giving the correct pipeline'
+ end
end
- context 'with implicit sha' do
+ context 'when there is no matching pipeline' do
subject { project.pipeline_for('master') }
- it_behaves_like 'giving the correct pipeline'
+ it { is_expected.to be_nil }
end
end
+ describe '#pipelines_for' do
+ let(:project) { create(:project, :repository) }
+ let!(:pipeline) { create_pipeline(project) }
+ let!(:other_pipeline) { create_pipeline(project) }
+
+ subject { project.pipelines_for(project.default_branch, project.commit.sha) }
+
+ it { is_expected.to contain_exactly(pipeline, other_pipeline) }
+ end
+
describe '#builds_enabled' do
let(:project) { create(:project) }
@@ -1675,26 +1685,6 @@ describe Project do
end
end
- describe '.paginate_in_descending_order_using_id' do
- let!(:project1) { create(:project) }
- let!(:project2) { create(:project) }
-
- it 'orders the relation in descending order' do
- expect(described_class.paginate_in_descending_order_using_id)
- .to eq([project2, project1])
- end
-
- it 'applies a limit to the relation' do
- expect(described_class.paginate_in_descending_order_using_id(limit: 1))
- .to eq([project2])
- end
-
- it 'limits projects by and ID when given' do
- expect(described_class.paginate_in_descending_order_using_id(before: project2.id))
- .to eq([project1])
- end
- end
-
describe '.including_namespace_and_owner' do
it 'eager loads the namespace and namespace owner' do
create(:project)
@@ -2019,62 +2009,33 @@ describe Project do
end
end
- describe '#latest_successful_build_for' do
+ describe '#latest_successful_build_for_ref' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
- context 'with many builds' do
- it 'gives the latest builds from latest pipeline' do
- pipeline1 = create_pipeline(project)
- pipeline2 = create_pipeline(project)
- create_build(pipeline1, 'test')
- create_build(pipeline1, 'test2')
- build1_p2 = create_build(pipeline2, 'test')
- create_build(pipeline2, 'test2')
-
- expect(project.latest_successful_build_for(build1_p2.name))
- .to eq(build1_p2)
- end
- end
-
- context 'with succeeded pipeline' do
- let!(:build) { create_build }
+ it_behaves_like 'latest successful build for sha or ref'
- context 'standalone pipeline' do
- it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
+ subject { project.latest_successful_build_for_ref(build_name) }
- it 'returns empty relation if the build cannot be found' do
- expect(project.latest_successful_build_for('TAIL'))
- .to be_nil
- end
- end
+ context 'with a specified ref' do
+ let(:build) { create_build }
- context 'with some pending pipeline' do
- before do
- create_build(create_pipeline(project, 'pending'))
- end
+ subject { project.latest_successful_build_for_ref(build.name, project.default_branch) }
- it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for(build.name))
- .to eq(build)
- end
- end
+ it { is_expected.to eq(build) }
end
+ end
- context 'with pending pipeline' do
- it 'returns empty relation' do
- pipeline.update(status: 'pending')
- pending_build = create_build(pipeline)
+ describe '#latest_successful_build_for_sha' do
+ let(:project) { create(:project, :repository) }
+ let(:pipeline) { create_pipeline(project) }
- expect(project.latest_successful_build_for(pending_build.name)).to be_nil
- end
- end
+ it_behaves_like 'latest successful build for sha or ref'
+
+ subject { project.latest_successful_build_for_sha(build_name, project.commit.sha) }
end
- describe '#latest_successful_build_for!' do
+ describe '#latest_successful_build_for_ref!' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create_pipeline(project) }
@@ -2087,7 +2048,7 @@ describe Project do
build1_p2 = create_build(pipeline2, 'test')
create_build(pipeline2, 'test2')
- expect(project.latest_successful_build_for(build1_p2.name))
+ expect(project.latest_successful_build_for_ref!(build1_p2.name))
.to eq(build1_p2)
end
end
@@ -2097,12 +2058,12 @@ describe Project do
context 'standalone pipeline' do
it 'returns builds for ref for default_branch' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
it 'returns exception if the build cannot be found' do
- expect { project.latest_successful_build_for!(build.name, 'TAIL') }
+ expect { project.latest_successful_build_for_ref!(build.name, 'TAIL') }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2113,7 +2074,7 @@ describe Project do
end
it 'gives the latest build from latest pipeline' do
- expect(project.latest_successful_build_for!(build.name))
+ expect(project.latest_successful_build_for_ref!(build.name))
.to eq(build)
end
end
@@ -2124,7 +2085,7 @@ describe Project do
pipeline.update(status: 'pending')
pending_build = create_build(pipeline)
- expect { project.latest_successful_build_for!(pending_build.name) }
+ expect { project.latest_successful_build_for_ref!(pending_build.name) }
.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -2292,7 +2253,22 @@ describe Project do
end
end
- describe '#ancestors_upto', :nested_groups do
+ describe '#mark_stuck_remote_mirrors_as_failed!' do
+ it 'fails stuck remote mirrors' do
+ project = create(:project, :repository, :remote_mirror)
+
+ project.remote_mirrors.first.update(
+ update_status: :started,
+ last_update_started_at: 2.days.ago
+ )
+
+ expect do
+ project.mark_stuck_remote_mirrors_as_failed!
+ end.to change { project.remote_mirrors.stuck.count }.from(1).to(0)
+ end
+ end
+
+ describe '#ancestors_upto' do
let(:parent) { create(:group) }
let(:child) { create(:group, parent: parent) }
let(:child2) { create(:group, parent: child) }
@@ -2331,7 +2307,7 @@ describe Project do
it { is_expected.to eq(group) }
end
- context 'in a nested group', :nested_groups do
+ context 'in a nested group' do
let(:root) { create(:group) }
let(:child) { create(:group, parent: root) }
let(:project) { create(:project, group: child) }
@@ -2340,6 +2316,57 @@ describe Project do
end
end
+ describe '#emails_disabled?' do
+ let(:project) { create(:project, emails_disabled: false) }
+
+ context 'emails disabled in group' do
+ it 'returns true' do
+ allow(project.namespace).to receive(:emails_disabled?) { true }
+
+ expect(project.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'emails enabled in group' do
+ before do
+ allow(project.namespace).to receive(:emails_disabled?) { false }
+ end
+
+ it 'returns false' do
+ expect(project.emails_disabled?).to be_falsey
+ end
+
+ it 'returns true' do
+ project.update_attribute(:emails_disabled, true)
+
+ expect(project.emails_disabled?).to be_truthy
+ end
+ end
+
+ context 'when :emails_disabled feature flag is off' do
+ before do
+ stub_feature_flags(emails_disabled: false)
+ end
+
+ context 'emails disabled in group' do
+ it 'returns false' do
+ allow(project.namespace).to receive(:emails_disabled?) { true }
+
+ expect(project.emails_disabled?).to be_falsey
+ end
+ end
+
+ context 'emails enabled in group' do
+ it 'returns false' do
+ allow(project.namespace).to receive(:emails_disabled?) { false }
+ project.update_attribute(:emails_disabled, true)
+
+ expect(project.emails_disabled?).to be_falsey
+ end
+ end
+ end
+ end
+
describe '#lfs_enabled?' do
let(:project) { create(:project) }
@@ -2479,7 +2506,7 @@ describe Project do
expect(forked_project.in_fork_network_of?(project)).to be_truthy
end
- it 'is true for a fork of a fork', :postgresql do
+ it 'is true for a fork of a fork' do
other_fork = fork_project(forked_project)
expect(other_fork.in_fork_network_of?(project)).to be_truthy
@@ -2634,45 +2661,33 @@ describe Project do
end
describe '#deployment_variables' do
- context 'when project has no deployment service' do
- let(:project) { create(:project) }
+ let(:project) { create(:project) }
+ let(:environment) { 'production' }
- it 'returns an empty array' do
- expect(project.deployment_variables).to eq []
- end
+ subject { project.deployment_variables(environment: environment) }
+
+ before do
+ expect(project).to receive(:deployment_platform).with(environment: environment)
+ .and_return(deployment_platform)
end
- context 'when project uses mock deployment service' do
- let(:project) { create(:mock_deployment_project) }
+ context 'when project has no deployment platform' do
+ let(:deployment_platform) { nil }
- it 'returns an empty array' do
- expect(project.deployment_variables).to eq []
- end
+ it { is_expected.to eq [] }
end
- context 'when project has a deployment service' do
- context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
- let(:project) { cluster.project }
+ context 'when project has a deployment platform' do
+ let(:platform_variables) { %w(platform variables) }
+ let(:deployment_platform) { double }
- it 'does not return variables from this service' do
- expect(project.deployment_variables).not_to include(
- { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true }
- )
- end
+ before do
+ expect(deployment_platform).to receive(:predefined_variables)
+ .with(project: project, environment_name: environment)
+ .and_return(platform_variables)
end
- context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do
- let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token) }
- let!(:cluster) { kubernetes_namespace.cluster }
- let(:project) { kubernetes_namespace.project }
-
- it 'returns token from kubernetes namespace' do
- expect(project.deployment_variables).to include(
- { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true }
- )
- end
- end
+ it { is_expected.to eq platform_variables }
end
end
@@ -2700,9 +2715,10 @@ describe Project do
describe '#ci_variables_for' do
let(:project) { create(:project) }
+ let(:environment_scope) { '*' }
let!(:ci_variable) do
- create(:ci_variable, value: 'secret', project: project)
+ create(:ci_variable, value: 'secret', project: project, environment_scope: environment_scope)
end
let!(:protected_variable) do
@@ -2747,6 +2763,96 @@ describe Project do
it_behaves_like 'ref is protected'
end
+
+ context 'when environment name is specified' do
+ let(:environment) { 'review/name' }
+
+ subject do
+ project.ci_variables_for(ref: 'ref', environment: environment)
+ end
+
+ context 'when environment scope is exactly matched' do
+ let(:environment_scope) { 'review/name' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope is matched by wildcard' do
+ let(:environment_scope) { 'review/*' }
+
+ it { is_expected.to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope does not match' do
+ let(:environment_scope) { 'review/*/special' }
+
+ it { is_expected.not_to contain_exactly(ci_variable) }
+ end
+
+ context 'when environment scope has _' do
+ let(:environment_scope) { '*_*' }
+
+ it 'does not treat it as wildcard' do
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains underscore' do
+ let(:environment) { 'foo_bar/test' }
+ let(:environment_scope) { 'foo_bar/*' }
+
+ it 'matches literally for _' do
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ # The environment name and scope cannot have % at the moment,
+ # but we're considering relaxing it and we should also make sure
+ # it doesn't break in case some data sneaked in somehow as we're
+ # not checking this integrity in database level.
+ context 'when environment scope has %' do
+ it 'does not treat it as wildcard' do
+ ci_variable.update_attribute(:environment_scope, '*%*')
+
+ is_expected.not_to contain_exactly(ci_variable)
+ end
+
+ context 'when environment name contains a percent' do
+ let(:environment) { 'foo%bar/test' }
+
+ it 'matches literally for _' do
+ ci_variable.update(environment_scope: 'foo%bar/*')
+
+ is_expected.to contain_exactly(ci_variable)
+ end
+ end
+ end
+
+ context 'when variables with the same name have different environment scopes' do
+ let!(:partially_matched_variable) do
+ create(:ci_variable,
+ key: ci_variable.key,
+ value: 'partial',
+ environment_scope: 'review/*',
+ project: project)
+ end
+
+ let!(:perfectly_matched_variable) do
+ create(:ci_variable,
+ key: ci_variable.key,
+ value: 'prefect',
+ environment_scope: 'review/name',
+ project: project)
+ end
+
+ it 'puts variables matching environment scope more in the end' do
+ is_expected.to eq(
+ [ci_variable,
+ partially_matched_variable,
+ perfectly_matched_variable])
+ end
+ end
+ end
end
describe '#any_lfs_file_locks?', :request_store do
@@ -2997,6 +3103,16 @@ describe Project do
expect(project.public_path_for_source_path('file.html', sha)).to be_nil
end
end
+
+ it 'returns a public path with a leading slash unmodified' do
+ route_map = Gitlab::RouteMap.new(<<-MAP.strip_heredoc)
+ - source: 'source/file.html'
+ public: '/public/file'
+ MAP
+ allow(project).to receive(:route_map_for).with(sha).and_return(route_map)
+
+ expect(project.public_path_for_source_path('source/file.html', sha)).to eq('/public/file')
+ end
end
context 'when there is no route map' do
@@ -3117,11 +3233,8 @@ describe Project do
let(:project) { create(:project) }
it 'shows full error updating an invalid MR' do
- error_message = 'Failed to replace merge_requests because one or more of the new records could not be saved.'\
- ' Validate fork Source project is not a fork of the target project'
-
expect { project.append_or_update_attribute(:merge_requests, [create(:merge_request)]) }
- .to raise_error(ActiveRecord::RecordNotSaved, error_message)
+ .to raise_error(ActiveRecord::RecordInvalid, /Failed to set merge_requests:/)
end
it 'updates the project successfully' do
@@ -3804,7 +3917,7 @@ describe Project do
end
end
- context 'when enabled on root parent', :nested_groups do
+ context 'when enabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }
context 'when auto devops instance enabled' do
@@ -3824,7 +3937,7 @@ describe Project do
end
end
- context 'when disabled on root parent', :nested_groups do
+ context 'when disabled on root parent' do
let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }
context 'when auto devops instance enabled' do
@@ -4036,7 +4149,7 @@ describe Project do
context 'with a ref that is not the default branch' do
it 'returns the latest successful pipeline for the given ref' do
- expect(project.ci_pipelines).to receive(:latest_successful_for).with('foo')
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref).with('foo')
project.latest_successful_pipeline_for('foo')
end
@@ -4064,7 +4177,7 @@ describe Project do
it 'memoizes and returns the latest successful pipeline for the default branch' do
pipeline = double(:pipeline)
- expect(project.ci_pipelines).to receive(:latest_successful_for)
+ expect(project.ci_pipelines).to receive(:latest_successful_for_ref)
.with(project.default_branch)
.and_return(pipeline)
.once
@@ -4251,6 +4364,39 @@ describe Project do
end
end
+ describe '#has_active_hooks?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_hooks?).to be_falsey }
+
+ it 'returns true when a matching push hook exists' do
+ create(:project_hook, push_events: true, project: project)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+
+ it 'returns true when a matching system hook exists' do
+ create(:system_hook, push_events: true)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+ end
+
+ describe '#has_active_services?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_services?).to be_falsey }
+
+ it 'returns true when a matching service exists' do
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
+
+ expect(project.has_active_services?(:merge_request_hooks)).to be_falsey
+ expect(project.has_active_services?).to be_truthy
+ end
+ end
+
describe '#badges' do
let(:project_group) { create(:group) }
let(:project) { create(:project, path: 'avatar', namespace: project_group) }
@@ -4267,18 +4413,16 @@ describe Project do
expect(project.badges.count).to eq 3
end
- if Group.supports_nested_objects?
- context 'with nested_groups' do
- let(:parent_group) { create(:group) }
+ context 'with nested_groups' do
+ let(:parent_group) { create(:group) }
- before do
- create_list(:group_badge, 2, group: project_group)
- project_group.update(parent: parent_group)
- end
+ before do
+ create_list(:group_badge, 2, group: project_group)
+ project_group.update(parent: parent_group)
+ end
- it 'returns the project and the project nested groups badges' do
- expect(project.badges.count).to eq 5
- end
+ it 'returns the project and the project nested groups badges' do
+ expect(project.badges.count).to eq 5
end
end
end
diff --git a/spec/models/project_statistics_spec.rb b/spec/models/project_statistics_spec.rb
index db3e4902c64..a164ed9bbea 100644
--- a/spec/models/project_statistics_spec.rb
+++ b/spec/models/project_statistics_spec.rb
@@ -140,18 +140,7 @@ describe ProjectStatistics do
let(:namespace) { create(:group) }
let(:project) { create(:project, namespace: namespace) }
- context 'when the feature flag is off' do
- it 'does not schedule the aggregation worker' do
- stub_feature_flags(update_statistics_namespace: false, namespace: namespace)
-
- expect(Namespaces::ScheduleAggregationWorker)
- .not_to receive(:perform_async)
-
- statistics.refresh!(only: [:lfs_objects_size])
- end
- end
-
- context 'when the feature flag is on' do
+ context 'when arguments are passed' do
it 'schedules the aggregation worker' do
expect(Namespaces::ScheduleAggregationWorker)
.to receive(:perform_async)
diff --git a/spec/models/prometheus_metric_spec.rb b/spec/models/prometheus_metric_spec.rb
index 3610408c138..a123ff5a2a6 100644
--- a/spec/models/prometheus_metric_spec.rb
+++ b/spec/models/prometheus_metric_spec.rb
@@ -150,4 +150,17 @@ describe PrometheusMetric do
expect(subject.to_query_metric.queries).to eq(queries)
end
end
+
+ describe '#to_metric_hash' do
+ it 'returns a hash suitable for inclusion on a metrics dashboard' do
+ expected_output = {
+ query_range: subject.query,
+ unit: subject.unit,
+ label: subject.legend,
+ metric_id: subject.id
+ }
+
+ expect(subject.to_metric_hash).to eq(expected_output)
+ end
+ end
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index e14b19db915..7edeb56efe2 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -113,7 +113,7 @@ describe RemoteMirror, :mailer do
remote_mirror = create(:remote_mirror)
- expect(remote_mirror.remote_name).to eq("remote_mirror_secret")
+ expect(remote_mirror.remote_name).to eq('remote_mirror_secret')
end
end
@@ -153,14 +153,14 @@ describe RemoteMirror, :mailer do
end
end
- describe '#mark_as_failed' do
+ describe '#mark_as_failed!' do
let(:remote_mirror) { create(:remote_mirror) }
let(:error_message) { 'http://user:pass@test.com/root/repoC.git/' }
let(:sanitized_error_message) { 'http://*****:*****@test.com/root/repoC.git/' }
subject do
remote_mirror.update_start
- remote_mirror.mark_as_failed(error_message)
+ remote_mirror.mark_as_failed!(error_message)
end
it 'sets the update_status to failed' do
@@ -201,11 +201,20 @@ describe RemoteMirror, :mailer do
end
context 'stuck mirrors' do
- it 'includes mirrors stuck in started with no last_update_at set' do
+ it 'includes mirrors that were started over an hour ago' do
+ mirror = create_mirror(url: 'http://cantbeblank',
+ update_status: 'started',
+ last_update_started_at: 3.hours.ago,
+ last_update_at: 2.hours.ago)
+
+ expect(described_class.stuck.last).to eq(mirror)
+ end
+
+ it 'includes mirrors started over 3 hours ago for their first sync' do
mirror = create_mirror(url: 'http://cantbeblank',
update_status: 'started',
last_update_at: nil,
- updated_at: 25.hours.ago)
+ last_update_started_at: 4.hours.ago)
expect(described_class.stuck.last).to eq(mirror)
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 12dff440ce2..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
@@ -1744,12 +1774,23 @@ describe Repository do
end
end
- describe '#before_push_tag' do
+ describe '#expires_caches_for_tags' do
it 'flushes the cache' do
expect(repository).to receive(:expire_statistics_caches)
expect(repository).to receive(:expire_emptiness_caches)
expect(repository).to receive(:expire_tags_cache)
+ repository.expire_caches_for_tags
+ end
+ end
+
+ describe '#before_push_tag' do
+ it 'logs an event' do
+ expect(repository).not_to receive(:expire_statistics_caches)
+ expect(repository).not_to receive(:expire_emptiness_caches)
+ expect(repository).not_to receive(:expire_tags_cache)
+ expect(repository).to receive(:repository_event).with(:push_tag)
+
repository.before_push_tag
end
end
@@ -1781,6 +1822,12 @@ describe Repository do
repository.after_create_branch
end
+
+ it 'does not expire the branch caches when specified' do
+ expect(repository).not_to receive(:expire_branches_cache)
+
+ repository.after_create_branch(expire_cache: false)
+ end
end
describe '#after_remove_branch' do
@@ -1789,25 +1836,45 @@ describe Repository do
repository.after_remove_branch
end
+
+ it 'does not expire the branch caches when specified' do
+ expect(repository).not_to receive(:expire_branches_cache)
+
+ repository.after_remove_branch(expire_cache: false)
+ end
end
describe '#after_create' do
+ it 'calls expire_status_cache' do
+ expect(repository).to receive(:expire_status_cache)
+
+ repository.after_create
+ end
+
+ it 'logs an event' do
+ expect(repository).to receive(:repository_event).with(:create_repository)
+
+ repository.after_create
+ end
+ end
+
+ describe '#expire_status_cache' do
it 'flushes the exists cache' do
expect(repository).to receive(:expire_exists_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the root ref cache' do
expect(repository).to receive(:expire_root_ref_cache)
- repository.after_create
+ repository.expire_status_cache
end
it 'flushes the emptiness caches' do
expect(repository).to receive(:expire_emptiness_caches)
- repository.after_create
+ repository.expire_status_cache
end
end
diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb
index b5bf294790a..9aeef7c3b4b 100644
--- a/spec/models/todo_spec.rb
+++ b/spec/models/todo_spec.rb
@@ -262,11 +262,7 @@ describe Todo do
todo2 = create(:todo, group: child_group)
todos = described_class.for_group_and_descendants(parent_group)
- expect(todos).to include(todo1)
-
- # Nested groups only work on PostgreSQL, so on MySQL todo2 won't be
- # present.
- expect(todos).to include(todo2) if Gitlab::Database.postgresql?
+ expect(todos).to contain_exactly(todo1, todo2)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 5cfa64fd764..46b86e8393d 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -794,6 +794,24 @@ describe User do
end
end
+ describe '#accessible_deploy_keys' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let!(:private_deploy_keys_project) { create(:deploy_keys_project) }
+ let!(:public_deploy_keys_project) { create(:deploy_keys_project) }
+ let!(:accessible_deploy_keys_project) { create(:deploy_keys_project, project: project) }
+
+ before do
+ public_deploy_keys_project.deploy_key.update(public: true)
+ project.add_developer(user)
+ end
+
+ it 'user can only see deploy keys accessible to right projects' do
+ expect(user.accessible_deploy_keys).to match_array([public_deploy_keys_project.deploy_key,
+ accessible_deploy_keys_project.deploy_key])
+ end
+ end
+
describe '#deploy_keys' do
include_context 'user keys'
@@ -985,7 +1003,7 @@ describe User do
it { expect(user.namespaces).to contain_exactly(user.namespace, group) }
it { expect(user.manageable_namespaces).to contain_exactly(user.namespace, group) }
- context 'with child groups', :nested_groups do
+ context 'with child groups' do
let!(:subgroup) { create(:group, parent: group) }
describe '#manageable_namespaces' do
@@ -2082,11 +2100,7 @@ describe User do
subject { user.membership_groups }
- if Group.supports_nested_objects?
- it { is_expected.to contain_exactly parent_group, child_group }
- else
- it { is_expected.to contain_exactly parent_group }
- end
+ it { is_expected.to contain_exactly parent_group, child_group }
end
describe '#authorizations_for_projects' do
@@ -2386,7 +2400,7 @@ describe User do
it_behaves_like :member
end
- context 'with subgroup with different owner for project runner', :nested_groups do
+ context 'with subgroup with different owner for project runner' do
let(:group) { create(:group) }
let(:another_user) { create(:user) }
let(:subgroup) { create(:group, parent: group) }
@@ -2490,22 +2504,16 @@ describe User do
group.add_owner(user)
end
- if Group.supports_nested_objects?
- it 'returns all groups' do
- is_expected.to match_array [
- group,
- nested_group_1, nested_group_1_1,
- nested_group_2, nested_group_2_1
- ]
- end
- else
- it 'returns the top-level groups' do
- is_expected.to match_array [group]
- end
+ it 'returns all groups' do
+ is_expected.to match_array [
+ group,
+ nested_group_1, nested_group_1_1,
+ nested_group_2, nested_group_2_1
+ ]
end
end
- context 'user is member of the first child (internal node), branch 1', :nested_groups do
+ context 'user is member of the first child (internal node), branch 1' do
before do
nested_group_1.add_owner(user)
end
@@ -2518,7 +2526,7 @@ describe User do
end
end
- context 'user is member of the first child (internal node), branch 2', :nested_groups do
+ context 'user is member of the first child (internal node), branch 2' do
before do
nested_group_2.add_owner(user)
end
@@ -2531,7 +2539,7 @@ describe User do
end
end
- context 'user is member of the last child (leaf node)', :nested_groups do
+ context 'user is member of the last child (leaf node)' do
before do
nested_group_1_1.add_owner(user)
end
@@ -2687,7 +2695,7 @@ describe User do
end
end
- context 'with 2FA requirement from expanded groups', :nested_groups do
+ context 'with 2FA requirement from expanded groups' do
let!(:group1) { create :group, require_two_factor_authentication: true }
let!(:group1a) { create :group, parent: group1 }
@@ -2702,7 +2710,7 @@ describe User do
end
end
- context 'with 2FA requirement on nested child group', :nested_groups do
+ context 'with 2FA requirement on nested child group' do
let!(:group1) { create :group, require_two_factor_authentication: false }
let!(:group1a) { create :group, require_two_factor_authentication: true, parent: group1 }
@@ -3504,4 +3512,37 @@ describe User do
expect(described_class.reorder_by_name).to eq([user1, user2])
end
end
+
+ describe '#notification_email_for' do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+
+ subject { user.notification_email_for(group) }
+
+ context 'when group is nil' do
+ let(:group) { nil }
+
+ it 'returns global notification email' do
+ is_expected.to eq(user.notification_email)
+ end
+ end
+
+ context 'when group has no notification email set' do
+ it 'returns global notification email' do
+ create(:notification_setting, user: user, source: group, notification_email: '')
+
+ is_expected.to eq(user.notification_email)
+ end
+ end
+
+ context 'when group has notification email set' do
+ it 'returns group notification email' do
+ group_notification_email = 'user+group@example.com'
+
+ create(:notification_setting, user: user, source: group, notification_email: group_notification_email)
+
+ is_expected.to eq(group_notification_email)
+ end
+ end
+ end
end
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index 520a06e138e..18c62c917dc 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -81,10 +81,9 @@ describe WikiPage do
grouped_entries = described_class.group_by_directory(wiki.list_pages)
actual_order =
- grouped_entries.map do |page_or_dir|
+ grouped_entries.flat_map do |page_or_dir|
get_slugs(page_or_dir)
end
- .flatten
expect(actual_order).to eq(expected_order)
end
end
diff --git a/spec/policies/clusters/instance_policy_spec.rb b/spec/policies/clusters/instance_policy_spec.rb
index 7b61819e079..2373fef8aa6 100644
--- a/spec/policies/clusters/instance_policy_spec.rb
+++ b/spec/policies/clusters/instance_policy_spec.rb
@@ -9,6 +9,8 @@ describe Clusters::InstancePolicy do
describe 'rules' do
context 'when user' do
it { expect(policy).to be_disallowed :read_cluster }
+ it { expect(policy).to be_disallowed :add_cluster }
+ it { expect(policy).to be_disallowed :create_cluster }
it { expect(policy).to be_disallowed :update_cluster }
it { expect(policy).to be_disallowed :admin_cluster }
end
@@ -17,6 +19,8 @@ describe Clusters::InstancePolicy do
let(:user) { create(:admin) }
it { expect(policy).to be_allowed :read_cluster }
+ it { expect(policy).to be_allowed :add_cluster }
+ it { expect(policy).to be_allowed :create_cluster }
it { expect(policy).to be_allowed :update_cluster }
it { expect(policy).to be_allowed :admin_cluster }
end
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index 12be3927e18..df6cc526eb0 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -226,4 +226,32 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:read_instance_statistics) }
end
end
+
+ describe 'slash commands' do
+ context 'regular user' do
+ it { is_expected.to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when internal' do
+ let(:current_user) { User.ghost }
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when blocked' do
+ before do
+ current_user.block
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when access locked' do
+ before do
+ current_user.lock_access!
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+ end
end
diff --git a/spec/policies/group_member_policy_spec.rb b/spec/policies/group_member_policy_spec.rb
index 7bd7184cffe..a4f3301a064 100644
--- a/spec/policies/group_member_policy_spec.rb
+++ b/spec/policies/group_member_policy_spec.rb
@@ -58,7 +58,7 @@ describe GroupMemberPolicy do
end
end
- context 'with the group parent', :postgresql do
+ context 'with the group parent' do
let(:current_user) { create :user }
let(:subgroup) { create(:group, :private, parent: group)}
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 59f3a961d50..be55d94daec 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -51,7 +51,7 @@ describe GroupPolicy do
it { expect_allowed(:read_label, :read_list) }
- context 'in subgroups', :nested_groups do
+ context 'in subgroups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:project) { create(:project, namespace: subgroup) }
@@ -98,12 +98,34 @@ describe GroupPolicy do
context 'maintainer' do
let(:current_user) { maintainer }
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*reporter_permissions)
- expect_allowed(*developer_permissions)
- expect_allowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
+ context 'with subgroup_creation level set to maintainer' do
+ let(:group) do
+ create(:group, :private, subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+
+ it 'allows every maintainer permission plus creating subgroups' do
+ create_subgroup_permission = [:create_subgroup]
+ updated_maintainer_permissions =
+ maintainer_permissions + create_subgroup_permission
+ updated_owner_permissions =
+ owner_permissions - create_subgroup_permission
+
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*updated_maintainer_permissions)
+ expect_disallowed(*updated_owner_permissions)
+ end
+ end
+
+ context 'with subgroup_creation_level set to owner' do
+ it 'allows every maintainer permission' do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_allowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
end
end
@@ -111,8 +133,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -125,8 +145,6 @@ describe GroupPolicy do
let(:current_user) { admin }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -135,38 +153,10 @@ describe GroupPolicy do
end
end
- describe 'when nested group support feature is disabled' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- context 'admin' do
- let(:current_user) { admin }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
- end
-
- context 'owner' do
- let(:current_user) { owner }
-
- it 'allows every owner permission except creating subgroups' do
- create_subgroup_permission = [:create_subgroup]
- updated_owner_permissions = owner_permissions - create_subgroup_permission
-
- expect_disallowed(*create_subgroup_permission)
- expect_allowed(*updated_owner_permissions)
- end
+ describe 'private nested group use the highest access level from the group and inherited permissions' do
+ let(:nested_group) do
+ create(:group, :private, :owner_subgroup_creation_only, parent: group)
end
- end
-
- describe 'private nested group use the highest access level from the group and inherited permissions', :nested_groups do
- let(:nested_group) { create(:group, :private, parent: group) }
before do
nested_group.add_guest(guest)
@@ -246,8 +236,6 @@ describe GroupPolicy do
let(:current_user) { owner }
it do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
-
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*developer_permissions)
@@ -461,6 +449,72 @@ describe GroupPolicy do
end
end
+ context "create_subgroup" do
+ context 'when group has subgroup creation level set to owner' do
+ let(:group) do
+ create(
+ :group,
+ subgroup_creation_level: ::Gitlab::Access::OWNER_SUBGROUP_ACCESS)
+ end
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+ end
+
+ context 'when group has subgroup creation level set to maintainer' do
+ let(:group) do
+ create(
+ :group,
+ subgroup_creation_level: ::Gitlab::Access::MAINTAINER_SUBGROUP_ACCESS)
+ end
+
+ context 'reporter' do
+ let(:current_user) { reporter }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'developer' do
+ let(:current_user) { developer }
+
+ it { is_expected.to be_disallowed(:create_subgroup) }
+ end
+
+ context 'maintainer' do
+ let(:current_user) { maintainer }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+
+ context 'owner' do
+ let(:current_user) { owner }
+
+ it { is_expected.to be_allowed(:create_subgroup) }
+ end
+ end
+ end
+
it_behaves_like 'clusterable policies' do
let(:clusterable) { create(:group) }
let(:cluster) do
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index fd82150c12a..8fd54e0bf1d 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -126,6 +126,126 @@ describe ProjectPolicy do
end
end
end
+
+ describe 'read_wiki' do
+ subject { described_class.new(user, project) }
+
+ member_roles = %i[guest developer]
+ stranger_roles = %i[anonymous non_member]
+
+ user_roles = stranger_roles + member_roles
+
+ # When a user is anonymous, their `current_user == nil`
+ let(:user) { create(:user) unless user_role == :anonymous }
+
+ before do
+ project.visibility = project_visibility
+ project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
+ project.add_user(user, user_role) if member_roles.include?(user_role)
+ end
+
+ title = ->(project_visibility, wiki_access_level, user_role) do
+ [
+ "project is #{Gitlab::VisibilityLevel.level_name project_visibility}",
+ "wiki is #{ProjectFeature.str_from_access_level wiki_access_level}",
+ "user is #{user_role}"
+ ].join(', ')
+ end
+
+ describe 'Situations where :read_wiki is always false' do
+ where(case_names: title,
+ project_visibility: Gitlab::VisibilityLevel.options.values,
+ wiki_access_level: [ProjectFeature::DISABLED],
+ user_role: user_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+
+ describe 'Situations where :read_wiki is always true' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: user_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ describe 'Situations where :read_wiki requires project membership' do
+ context 'the wiki is private, and the user is a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::PRIVATE],
+ user_role: member_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is private, and the user is not member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::PRIVATE],
+ user_role: stranger_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is enabled, and the user is a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: member_roles)
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the wiki is enabled, and the user is not a member' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::PRIVATE],
+ wiki_access_level: [ProjectFeature::ENABLED],
+ user_role: stranger_roles)
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+ end
+
+ describe 'Situations where :read_wiki prohibits anonymous access' do
+ context 'the user is not anonymous' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
+ user_role: user_roles.reject { |u| u == :anonymous })
+
+ with_them do
+ it { is_expected.to be_allowed(:read_wiki) }
+ end
+ end
+
+ context 'the user is not anonymous' do
+ where(case_names: title,
+ project_visibility: [Gitlab::VisibilityLevel::INTERNAL],
+ wiki_access_level: [ProjectFeature::ENABLED, ProjectFeature::PUBLIC],
+ user_role: %i[anonymous])
+
+ with_them do
+ it { is_expected.to be_disallowed(:read_wiki) }
+ end
+ end
+ end
+ end
end
context 'issues feature' do
diff --git a/spec/presenters/blobs/unfold_presenter_spec.rb b/spec/presenters/blobs/unfold_presenter_spec.rb
index 7ece5f623ce..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,25 @@ 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
+ end
+
+ context 'when to is -1' do
+ let(:params) { { full: false, since: 2, to: -1, bottom: true, offset: 1, indent: 1 } }
+
+ it 'sets other attributes' do
+ 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
@@ -54,8 +82,10 @@ describe Blobs::UnfoldPresenter do
expect(lines.size).to eq(total_lines)
lines.each.with_index do |line, index|
- expect(line.text).to include("LC#{index + 1}")
- expect(line.text).to eq(line.rich_text)
+ line_number = index + 1
+
+ expect(line.text).to eq(line_number.to_s)
+ expect(line.rich_text).to include("LC#{line_number}")
expect(line.type).to be_nil
end
end
@@ -81,8 +111,9 @@ describe Blobs::UnfoldPresenter do
end
end
- context 'when since is greater than 1' do
- let(:params) { { since: 5, to: 10, offset: 10 } }
+ context 'when "since" is greater than 1' do
+ let(:default_params) { { since: 5, to: 10, offset: 10 } }
+ let(:params) { default_params }
it 'adds top match line' do
line = subject.diff_lines.first
@@ -91,6 +122,38 @@ describe Blobs::UnfoldPresenter do
expect(line.old_pos).to eq(5)
expect(line.new_pos).to eq(5)
end
+
+ context '"to" is higher than blob size' do
+ let(:params) { default_params.merge(to: total_lines + 10, bottom: true) }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+ end
+
+ context '"to" is equal to blob size' do
+ let(:params) { default_params.merge(to: total_lines, bottom: true) }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+ end
+
+ context '"to" is less than blob size' do
+ let(:params) { default_params.merge(to: total_lines - 3, bottom: true) }
+
+ it 'adds bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to eq('match')
+ expect(line.old_pos).to eq(total_lines - 3 - params[:offset])
+ expect(line.new_pos).to eq(total_lines - 3)
+ end
+ end
end
context 'when "to" is less than blob size' do
@@ -114,6 +177,22 @@ describe Blobs::UnfoldPresenter do
expect(line.type).to be_nil
end
end
+
+ context 'when "to" is "-1"' do
+ let(:params) { { since: 10, to: -1, offset: 10, bottom: true } }
+
+ it 'does not add bottom match line' do
+ line = subject.diff_lines.last
+
+ expect(line.type).to be_nil
+ end
+
+ it 'last line is the latest blob line' do
+ line = subject.diff_lines.last
+
+ expect(line.text).to eq(total_lines.to_s)
+ end
+ end
end
describe '#lines' do
diff --git a/spec/presenters/clusters/cluster_presenter_spec.rb b/spec/presenters/clusters/cluster_presenter_spec.rb
index 7054a70e2ed..6b988e2645b 100644
--- a/spec/presenters/clusters/cluster_presenter_spec.rb
+++ b/spec/presenters/clusters/cluster_presenter_spec.rb
@@ -43,7 +43,7 @@ describe Clusters::ClusterPresenter do
end
shared_examples 'ancestor clusters' do
- context 'ancestor clusters', :nested_groups do
+ context 'ancestor clusters' do
let(:root_group) { create(:group, name: 'Root Group') }
let(:parent) { create(:group, name: 'parent', parent: root_group) }
let(:child) { create(:group, name: 'child', parent: parent) }
diff --git a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
index 001545bb5df..b4bf39f3cdb 100644
--- a/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
+++ b/spec/presenters/projects/settings/deploy_keys_presenter_spec.rb
@@ -29,10 +29,6 @@ describe Projects::Settings::DeployKeysPresenter do
it 'returns the enabled_keys size' do
expect(presenter.enabled_keys_size).to eq(1)
end
-
- it 'returns true if there is any enabled_keys' do
- expect(presenter.any_keys_enabled?).to eq(true)
- end
end
describe '#available_keys/#available_project_keys' do
@@ -54,9 +50,5 @@ describe Projects::Settings::DeployKeysPresenter do
it 'returns the available_project_keys size' do
expect(presenter.available_project_keys_size).to eq(1)
end
-
- it 'shows if there is an available key' do
- expect(presenter.key_available?(deploy_key)).to eq(false)
- end
end
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/boards_spec.rb b/spec/requests/api/boards_spec.rb
index de79e8c4c5c..0b9c0c2ebe9 100644
--- a/spec/requests/api/boards_spec.rb
+++ b/spec/requests/api/boards_spec.rb
@@ -63,7 +63,7 @@ describe API::Boards do
end
end
- describe "POST /groups/:id/boards/lists", :nested_groups do
+ describe "POST /groups/:id/boards/lists" do
set(:group) { create(:group) }
set(:board_parent) { create(:group, parent: group ) }
let(:url) { "/groups/#{board_parent.id}/boards/#{board.id}/lists" }
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index b5e45f99109..1be8883bd3c 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -8,10 +8,6 @@ describe API::CommitStatuses do
let(:developer) { create_user(:developer) }
let(:sha) { commit.id }
- let(:commit_status) do
- create(:commit_status, status: :pending, pipeline: pipeline)
- end
-
describe "GET /projects/:id/repository/commits/:sha/statuses" do
let(:get_url) { "/projects/#{project.id}/repository/commits/#{sha}/statuses" }
@@ -239,6 +235,26 @@ describe API::CommitStatuses do
expect(CommitStatus.count).to eq 1
end
end
+
+ context 'when a pipeline id is specified' do
+ let!(:first_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
+ let!(:other_pipeline) { project.ci_pipelines.create(source: :push, sha: commit.id, ref: 'master', status: 'created') }
+
+ subject do
+ post api(post_url, developer), params: {
+ pipeline_id: other_pipeline.id,
+ state: 'success',
+ ref: 'master'
+ }
+ end
+
+ it 'update the correct pipeline' do
+ subject
+
+ expect(first_pipeline.reload.status).to eq('created')
+ expect(other_pipeline.reload.status).to eq('success')
+ end
+ end
end
context 'when retrying a commit status' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 204e378f7be..5e6ff40e8cf 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -126,6 +126,12 @@ describe API::Commits do
end
end
+ context "with empty ref_name parameter" do
+ let(:route) { "/projects/#{project_id}/repository/commits?ref_name=" }
+
+ it_behaves_like 'project commits'
+ end
+
context "path optional parameter" do
it "returns project commits matching provided path parameter" do
path = 'files/ruby/popen.rb'
@@ -281,7 +287,7 @@ describe API::Commits do
end
it 'does not increment the usage counters using access token authentication' do
- expect(::Gitlab::UsageDataCounters::WebIdeCommitsCounter).not_to receive(:increment)
+ expect(::Gitlab::UsageDataCounters::WebIdeCounter).not_to receive(:increment_commits_count)
post api(url, user), params: valid_c_params
end
@@ -320,67 +326,132 @@ describe API::Commits do
end
end
- context 'when the API user is a guest' do
+ context 'when committing to a new branch' do
def last_commit_id(project, branch_name)
project.repository.find_branch(branch_name)&.dereferenced_target&.id
end
- let(:public_project) { create(:project, :public, :repository) }
- let!(:url) { "/projects/#{public_project.id}/repository/commits" }
- let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
+ before do
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ context 'when the API user is a guest' do
+ let(:public_project) { create(:project, :public, :repository) }
+ let(:url) { "/projects/#{public_project.id}/repository/commits" }
+ let(:guest) { create(:user).tap { |u| public_project.add_guest(u) } }
- expect(response).to have_gitlab_http_status(403)
- end
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- context 'when start_project is provided' do
- context 'when posting to a forked project the user owns' do
- let!(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ expect(response).to have_gitlab_http_status(403)
+ end
- before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- end
+ context 'when start_project is provided' do
+ context 'when posting to a forked project the user owns' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
+
+ context 'identified by Integer (id)' do
+ before do
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
+ end
- context 'identified by Integer (id)' do
- before do
- valid_c_params[:start_project] = public_project.id
+ context 'identified by String (full_path)' do
+ before do
+ valid_c_params[:start_project] = public_project.full_path
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ end
+ end
+
+ context 'when branch already exists' do
+ before do
+ valid_c_params.delete(:start_branch)
+ valid_c_params[:branch] = 'master'
+ valid_c_params[:start_project] = public_project.id
+ end
+
+ it 'returns a 400' do
+ post api(url, guest), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ context 'when force is set to true' do
+ before do
+ valid_c_params[:force] = true
+ end
+
+ it 'adds a new commit to forked_project and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(public_project, valid_c_params[:branch]) }
+ end
+ end
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ context 'when start_sha is also provided' do
+ let(:forked_project) { fork_project(public_project, guest, namespace: guest.namespace, repository: false) }
+ let(:start_sha) { public_project.repository.commit.parent.sha }
- expect(response).to have_gitlab_http_status(201)
+ before do
+ # initialize an empty repository to force fetching from the original project
+ forked_project.repository.create_if_not_exists
+
+ valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
+ end
+
+ it 'fetches the start_sha from the original project to use as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, guest), params: valid_c_params }
+ .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(forked_project, 'master') }
+
+ last_commit = forked_project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
- context 'identified by String (full_path)' do
+ context 'when the target project is not part of the fork network of start_project' do
+ let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
+ let(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+
before do
- valid_c_params[:start_project] = public_project.full_path
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
+ valid_c_params[:start_project] = public_project.id
end
- it 'adds a new commit to forked_project and returns a 201' do
- expect { post api(url, guest), params: valid_c_params }
- .to change { last_commit_id(forked_project, valid_c_params[:branch]) }
- .and not_change { last_commit_id(public_project, valid_c_params[:start_branch]) }
+ it 'returns a 403' do
+ post api(url, guest), params: valid_c_params
- expect(response).to have_gitlab_http_status(201)
+ expect(response).to have_gitlab_http_status(403)
end
end
end
- context 'when the target project is not part of the fork network of start_project' do
- let(:unrelated_project) { create(:project, :public, :repository, creator: guest) }
- let!(:url) { "/projects/#{unrelated_project.id}/repository/commits" }
+ context 'when posting to a forked project the user does not have write access' do
+ let(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
+ let(:url) { "/projects/#{forked_project.id}/repository/commits" }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
+ valid_c_params[:start_branch] = 'master'
+ valid_c_params[:branch] = 'patch'
valid_c_params[:start_project] = public_project.id
end
@@ -392,20 +463,68 @@ describe API::Commits do
end
end
- context 'when posting to a forked project the user does not have write access' do
- let!(:forked_project) { fork_project(public_project, user, namespace: user.namespace, repository: true) }
- let!(:url) { "/projects/#{forked_project.id}/repository/commits" }
+ context 'when start_sha is provided' do
+ let(:start_sha) { project.repository.commit.parent.sha }
before do
- valid_c_params[:start_branch] = "master"
- valid_c_params[:branch] = "patch"
- valid_c_params[:start_project] = public_project.id
+ valid_c_params[:start_sha] = start_sha
+ valid_c_params.delete(:start_branch)
end
- it 'returns a 403' do
- post api(url, guest), params: valid_c_params
+ it 'returns a 400 if start_branch is also provided' do
+ valid_c_params[:start_branch] = 'master'
+ post api(url, user), params: valid_c_params
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('start_branch, start_sha are mutually exclusive')
+ end
+
+ it 'returns a 400 if branch already exists' do
+ valid_c_params[:branch] = 'master'
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("A branch called 'master' already exists. Switch to that branch in order to make changes")
+ end
+
+ it 'returns a 400 if start_sha does not exist' do
+ valid_c_params[:start_sha] = '1' * 40
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Cannot find start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'returns a 400 if start_sha is not a full SHA' do
+ valid_c_params[:start_sha] = start_sha.slice(0, 7)
+ post api(url, user), params: valid_c_params
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['message']).to eq("Invalid start_sha '#{valid_c_params[:start_sha]}'")
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+ .and not_change { last_commit_id(project, 'master') }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
+
+ context 'when force is set to true and branch already exists' do
+ before do
+ valid_c_params[:force] = true
+ valid_c_params[:branch] = 'master'
+ end
+
+ it 'uses the start_sha as parent commit and returns a 201' do
+ expect_request_with_status(201) { post api(url, user), params: valid_c_params }
+ .to change { last_commit_id(project, valid_c_params[:branch]) }
+
+ last_commit = project.repository.find_branch(valid_c_params[:branch]).dereferenced_target
+ expect(last_commit.parent_id).to eq(start_sha)
+ end
end
end
end
diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/container_registry_spec.rb
deleted file mode 100644
index b64f3ea1081..00000000000
--- a/spec/requests/api/container_registry_spec.rb
+++ /dev/null
@@ -1,245 +0,0 @@
-require 'spec_helper'
-
-describe API::ContainerRegistry do
- include ExclusiveLeaseHelpers
-
- set(:project) { create(:project, :private) }
- set(:maintainer) { create(:user) }
- set(:developer) { create(:user) }
- set(:reporter) { create(:user) }
- set(:guest) { create(:user) }
-
- let(:root_repository) { create(:container_repository, :root, project: project) }
- let(:test_repository) { create(:container_repository, project: project) }
-
- let(:api_user) { maintainer }
-
- before do
- project.add_maintainer(maintainer)
- project.add_developer(developer)
- project.add_reporter(reporter)
- project.add_guest(guest)
-
- stub_feature_flags(container_registry_api: true)
- stub_container_registry_config(enabled: true)
-
- root_repository
- test_repository
- end
-
- shared_examples 'being disallowed' do |param|
- context "for #{param}" do
- let(:api_user) { public_send(param) }
-
- it 'returns access denied' do
- subject
-
- expect(response).to have_gitlab_http_status(:forbidden)
- end
- end
-
- context "for anonymous" do
- let(:api_user) { nil }
-
- it 'returns not found' do
- subject
-
- expect(response).to have_gitlab_http_status(:not_found)
- end
- end
- end
-
- describe 'GET /projects/:id/registry/repositories' do
- subject { get api("/projects/#{project.id}/registry/repositories", api_user) }
-
- it_behaves_like 'being disallowed', :guest
-
- context 'for reporter' do
- let(:api_user) { reporter }
-
- it 'returns a list of repositories' do
- subject
-
- expect(json_response.length).to eq(2)
- expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
- root_repository.id, test_repository.id)
- end
-
- it 'returns a matching schema' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('registry/repositories')
- end
- end
- end
-
- describe 'DELETE /projects/:id/registry/repositories/:repository_id' do
- subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) }
-
- it_behaves_like 'being disallowed', :developer
-
- context 'for maintainer' do
- let(:api_user) { maintainer }
-
- it 'schedules removal of repository' do
- expect(DeleteContainerRepositoryWorker).to receive(:perform_async)
- .with(maintainer.id, root_repository.id)
-
- subject
-
- expect(response).to have_gitlab_http_status(:accepted)
- end
- end
- end
-
- describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do
- subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) }
-
- it_behaves_like 'being disallowed', :guest
-
- context 'for reporter' do
- let(:api_user) { reporter }
-
- before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest))
- end
-
- it 'returns a list of tags' do
- subject
-
- expect(json_response.length).to eq(2)
- expect(json_response.map { |repository| repository['name'] }).to eq %w(latest rootA)
- end
-
- it 'returns a matching schema' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('registry/tags')
- end
- end
- end
-
- describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do
- subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params }
-
- it_behaves_like 'being disallowed', :developer do
- let(:params) do
- { name_regex: 'v10.*' }
- end
- end
-
- context 'for maintainer' do
- let(:api_user) { maintainer }
-
- context 'without required parameters' do
- let(:params) { }
-
- it 'returns bad request' do
- subject
-
- expect(response).to have_gitlab_http_status(:bad_request)
- end
- end
-
- context 'passes all declared parameters' do
- let(:params) do
- { name_regex: 'v10.*',
- keep_n: 100,
- older_than: '1 day',
- other: 'some value' }
- end
-
- let(:worker_params) do
- { name_regex: 'v10.*',
- keep_n: 100,
- older_than: '1 day' }
- end
-
- let(:lease_key) { "container_repository:cleanup_tags:#{root_repository.id}" }
-
- it 'schedules cleanup of tags repository' do
- stub_exclusive_lease(lease_key, timeout: 1.hour)
- expect(CleanupContainerRepositoryWorker).to receive(:perform_async)
- .with(maintainer.id, root_repository.id, worker_params)
-
- subject
-
- expect(response).to have_gitlab_http_status(:accepted)
- end
-
- context 'called multiple times in one hour' do
- it 'returns 400 with an error message' do
- stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
- subject
-
- expect(response).to have_gitlab_http_status(400)
- expect(response.body).to include('This request has already been made.')
- end
-
- it 'executes service only for the first time' do
- expect(CleanupContainerRepositoryWorker).to receive(:perform_async).once
-
- 2.times { subject }
- end
- end
- end
- end
- end
-
- describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
- subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
-
- it_behaves_like 'being disallowed', :guest
-
- context 'for reporter' do
- let(:api_user) { reporter }
-
- before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
- end
-
- it 'returns a details of tag' do
- subject
-
- expect(json_response).to include(
- 'name' => 'rootA',
- 'digest' => 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15',
- 'revision' => 'd7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac',
- 'total_size' => 2319870)
- end
-
- it 'returns a matching schema' do
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(response).to match_response_schema('registry/tag')
- end
- end
- end
-
- describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
- subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
-
- it_behaves_like 'being disallowed', :reporter
-
- context 'for developer' do
- let(:api_user) { developer }
-
- before do
- stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
- end
-
- it 'properly removes tag' do
- expect_any_instance_of(ContainerRegistry::Client)
- .to receive(:delete_repository_tag).with(root_repository.path,
- 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15')
-
- subject
-
- expect(response).to have_gitlab_http_status(:ok)
- end
- end
- end
-end
diff --git a/spec/requests/api/discussions_spec.rb b/spec/requests/api/discussions_spec.rb
index ca1ffe3c524..ef09c6effbb 100644
--- a/spec/requests/api/discussions_spec.rb
+++ b/spec/requests/api/discussions_spec.rb
@@ -9,6 +9,61 @@ 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) }
+
+ let(:url) { "/projects/#{project.id}/merge_requests/#{merge_request.iid}/discussions" }
+
+ 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
+ end
+
context 'when noteable is an Issue' do
let!(:issue) { create(:issue, project: project, author: user) }
let!(:issue_note) { create(:discussion_note_on_issue, noteable: issue, project: project, author: user) }
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 1ad536258ba..21b67357543 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -186,6 +186,14 @@ describe API::Files do
expect(headers[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
end
+ it 'returns blame file info' do
+ url = route(file_path) + '/blame'
+
+ get api(url, current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it 'sets inline content disposition by default' do
url = route(file_path) + "/raw"
@@ -252,6 +260,160 @@ describe API::Files do
end
end
+ describe 'GET /projects/:id/repository/files/:file_path/blame' do
+ shared_examples_for 'repository blame files' do
+ let(:expected_blame_range_sizes) do
+ [3, 2, 1, 2, 1, 1, 1, 1, 8, 1, 3, 1, 2, 1, 4, 1, 2, 2]
+ end
+
+ let(:expected_blame_range_commit_ids) do
+ %w[
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 570e7b2abdd848b95f2f578043fc23bd6f6fd24d
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ 874797c3a73b60d2187ed6e2fcabd289ff75171e
+ 913c66a37b4a45b9769037c55c2d238bd0942d2e
+ ]
+ end
+
+ it 'returns file attributes in headers' do
+ head api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.headers['X-Gitlab-File-Path']).to eq(CGI.unescape(file_path))
+ expect(response.headers['X-Gitlab-File-Name']).to eq('popen.rb')
+ expect(response.headers['X-Gitlab-Last-Commit-Id']).to eq('570e7b2abdd848b95f2f578043fc23bd6f6fd24d')
+ expect(response.headers['X-Gitlab-Content-Sha256'])
+ .to eq('c440cd09bae50c4632cc58638ad33c6aa375b6109d811e76a9cc3a613c1e8887')
+ end
+
+ it 'returns blame file attributes as json' do
+ get api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.map { |x| x['lines'].size }).to eq(expected_blame_range_sizes)
+ expect(json_response.map { |x| x['commit']['id'] }).to eq(expected_blame_range_commit_ids)
+ range = json_response[0]
+ expect(range['lines']).to eq(["require 'fileutils'", "require 'open3'", ''])
+ expect(range['commit']['id']).to eq('913c66a37b4a45b9769037c55c2d238bd0942d2e')
+ expect(range['commit']['parent_ids']).to eq(['cfe32cf61b73a0d5e9f13e774abde7ff789b1660'])
+ expect(range['commit']['message'])
+ .to eq("Files, encoding and much more\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n")
+
+ expect(range['commit']['authored_date']).to eq('2014-02-27T08:14:56.000Z')
+ expect(range['commit']['author_name']).to eq('Dmitriy Zaporozhets')
+ expect(range['commit']['author_email']).to eq('dmitriy.zaporozhets@gmail.com')
+
+ expect(range['commit']['committed_date']).to eq('2014-02-27T08:14:56.000Z')
+ expect(range['commit']['committer_name']).to eq('Dmitriy Zaporozhets')
+ expect(range['commit']['committer_email']).to eq('dmitriy.zaporozhets@gmail.com')
+ end
+
+ it 'returns blame file info for files with dots' do
+ url = route('.gitignore') + '/blame'
+
+ get api(url, current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns file by commit sha' do
+ # This file is deleted on HEAD
+ file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee'
+ params[:ref] = '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+
+ get api(route(file_path) + '/blame', current_user), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ context 'when mandatory params are not given' do
+ it_behaves_like '400 response' do
+ let(:request) { get api(route('any%2Ffile/blame'), current_user) }
+ end
+ end
+
+ context 'when file_path does not exist' do
+ let(:params) { { ref: 'master' } }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route('app%2Fmodels%2Fapplication%2Erb/blame'), current_user), params: params }
+ let(:message) { '404 File Not Found' }
+ end
+ end
+
+ context 'when commit does not exist' do
+ let(:params) { { ref: '1111111111111111111111111111111111111111' } }
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(route(file_path + '/blame'), current_user), params: params }
+ let(:message) { '404 Commit Not Found' }
+ end
+ end
+
+ context 'when repository is disabled' do
+ include_context 'disabled repository'
+
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path + '/blame'), current_user), params: params }
+ end
+ end
+ end
+
+ context 'when unauthenticated', 'and project is public' do
+ it_behaves_like 'repository blame files' do
+ let(:project) { create(:project, :public, :repository) }
+ let(:current_user) { nil }
+ end
+ end
+
+ context 'when unauthenticated', 'and project is private' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(route(file_path)), params: params }
+ let(:message) { '404 Project Not Found' }
+ end
+ end
+
+ context 'when authenticated', 'as a developer' do
+ it_behaves_like 'repository blame files' do
+ let(:current_user) { user }
+ end
+ end
+
+ context 'when authenticated', 'as a guest' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(route(file_path) + '/blame', guest), params: params }
+ end
+ end
+
+ context 'when PATs are used' do
+ it 'returns blame file by commit sha' do
+ token = create(:personal_access_token, scopes: ['read_repository'], user: user)
+
+ # This file is deleted on HEAD
+ file_path = 'files%2Fjs%2Fcommit%2Ejs%2Ecoffee'
+ params[:ref] = '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
+
+ get api(route(file_path) + '/blame', personal_access_token: token), params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+ end
+
describe "GET /projects/:id/repository/files/:file_path/raw" do
shared_examples_for 'repository raw files' do
it 'returns raw file info' 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/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 63fa16c79ca..815e9531ecf 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe 'getting projects', :nested_groups do
+describe 'getting projects' do
include GraphqlHelpers
let(:group) { create(:group) }
diff --git a/spec/requests/api/group_container_repositories_spec.rb b/spec/requests/api/group_container_repositories_spec.rb
new file mode 100644
index 00000000000..0a41e455d01
--- /dev/null
+++ b/spec/requests/api/group_container_repositories_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupContainerRepositories do
+ set(:group) { create(:group, :private) }
+ set(:project) { create(:project, :private, group: group) }
+ let(:reporter) { create(:user) }
+ let(:guest) { create(:user) }
+
+ let(:root_repository) { create(:container_repository, :root, project: project) }
+ let(:test_repository) { create(:container_repository, project: project) }
+
+ let(:users) do
+ {
+ anonymous: nil,
+ guest: guest,
+ reporter: reporter
+ }
+ end
+
+ let(:api_user) { reporter }
+
+ before do
+ group.add_reporter(reporter)
+ group.add_guest(guest)
+
+ stub_feature_flags(container_registry_api: true)
+ stub_container_registry_config(enabled: true)
+
+ root_repository
+ test_repository
+ end
+
+ describe 'GET /groups/:id/registry/repositories' do
+ let(:url) { "/groups/#{group.id}/registry/repositories" }
+
+ subject { get api(url, api_user) }
+
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ it_behaves_like 'returns repositories for allowed users', :reporter, 'group' do
+ let(:object) { group }
+ end
+
+ context 'with invalid group id' do
+ let(:url) { '/groups/123412341234/registry/repositories' }
+
+ it 'returns not found' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
index 3769f8b78e4..0be4e2e9121 100644
--- a/spec/requests/api/group_labels_spec.rb
+++ b/spec/requests/api/group_labels_spec.rb
@@ -14,12 +14,25 @@ describe API::GroupLabels do
get api("/groups/#{group.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/group_labels')
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label'))
expect(json_response.size).to eq(2)
expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
end
+
+ context 'when the with_counts parameter is set' do
+ it 'includes counts in the response' do
+ get api("/groups/#{group.id}/labels", user), params: { with_counts: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/label_with_counts'))
+ expect(json_response.size).to eq(2)
+ expect(json_response.map { |r| r['open_issues_count'] }).to contain_exactly(0, 0)
+ end
+ end
end
describe 'POST /groups/:id/labels' do
@@ -94,7 +107,7 @@ describe API::GroupLabels do
expect(response).to have_gitlab_http_status(400)
end
- it "does not delete parent's group labels", :nested_groups do
+ it "does not delete parent's group labels" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
@@ -127,7 +140,7 @@ describe API::GroupLabels do
expect(json_response['description']).to eq('test')
end
- it "does not update parent's group label", :nested_groups do
+ it "does not update parent's group label" do
subgroup = create(:group, parent: group)
subgroup_label = create(:group_label, title: 'feature', group: subgroup)
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index c41408fba65..50f36141aed 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -530,7 +530,7 @@ describe API::Groups do
expect(json_response.length).to eq(2)
end
- it "returns projects including those in subgroups", :nested_groups do
+ it "returns projects including those in subgroups" do
subgroup = create(:group, parent: group1)
create(:project, group: subgroup)
create(:project, group: subgroup)
@@ -642,7 +642,7 @@ describe API::Groups do
end
end
- describe 'GET /groups/:id/subgroups', :nested_groups do
+ describe 'GET /groups/:id/subgroups' do
let!(:subgroup1) { create(:group, parent: group1) }
let!(:subgroup2) { create(:group, :private, parent: group1) }
let!(:subgroup3) { create(:group, :private, parent: group2) }
@@ -786,7 +786,7 @@ describe API::Groups do
expect(response).to have_gitlab_http_status(403)
end
- context 'as owner', :nested_groups do
+ context 'as owner' do
before do
group2.add_owner(user1)
end
@@ -798,15 +798,15 @@ describe API::Groups do
end
end
- context 'as maintainer', :nested_groups do
+ context 'as maintainer' do
before do
group2.add_maintainer(user1)
end
- it 'cannot create subgroups' do
+ it 'can create subgroups' do
post api("/groups", user1), params: { parent_id: group2.id, name: 'foo', path: 'foo' }
- expect(response).to have_gitlab_http_status(403)
+ expect(response).to have_gitlab_http_status(201)
end
end
end
@@ -825,7 +825,7 @@ describe API::Groups do
expect(json_response["visibility"]).to eq(Gitlab::VisibilityLevel.string_level(Gitlab::CurrentSettings.current_application_settings.default_group_visibility))
end
- it "creates a nested group", :nested_groups do
+ it "creates a nested group" do
parent = create(:group)
parent.add_owner(user3)
group = attributes_for(:group, { parent_id: parent.id })
diff --git a/spec/requests/api/issues/get_group_issues_spec.rb b/spec/requests/api/issues/get_group_issues_spec.rb
index 9a41d790945..c487471e4a1 100644
--- a/spec/requests/api/issues/get_group_issues_spec.rb
+++ b/spec/requests/api/issues/get_group_issues_spec.rb
@@ -82,7 +82,7 @@ describe API::Issues do
end
end
- context 'when group has subgroups', :nested_groups do
+ context 'when group has subgroups' do
let(:subgroup_1) { create(:group, parent: group) }
let(:subgroup_2) { create(:group, parent: subgroup_1) }
@@ -342,6 +342,14 @@ describe API::Issues do
group_project.add_reporter(user)
end
+ it 'exposes known attributes' do
+ get api(base_url, admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response.last.keys).to include(*%w(id iid project_id title description))
+ expect(json_response.last).not_to have_key('subscribed')
+ end
+
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
diff --git a/spec/requests/api/issues/get_project_issues_spec.rb b/spec/requests/api/issues/get_project_issues_spec.rb
index f7ca6fd1e0a..521d6b88734 100644
--- a/spec/requests/api/issues/get_project_issues_spec.rb
+++ b/spec/requests/api/issues/get_project_issues_spec.rb
@@ -575,6 +575,7 @@ describe API::Issues do
expect(json_response['assignee']).to be_a Hash
expect(json_response['author']).to be_a Hash
expect(json_response['confidential']).to be_falsy
+ expect(json_response['subscribed']).to be_truthy
end
it 'exposes the closed_at attribute' do
diff --git a/spec/requests/api/issues/issues_spec.rb b/spec/requests/api/issues/issues_spec.rb
index d195f54be11..27cf66629fe 100644
--- a/spec/requests/api/issues/issues_spec.rb
+++ b/spec/requests/api/issues/issues_spec.rb
@@ -216,6 +216,10 @@ describe API::Issues do
expect_paginated_array_response([issue.id, closed_issue.id])
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
+ # Calculating the value of subscribed field triggers Markdown
+ # processing. We can't do that for multiple issues / merge
+ # requests in a single API request.
+ expect(json_response.last).not_to have_key('subscribed')
end
it 'returns an array of closed issues' do
diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb
index 518181e4d93..ad0974f55a3 100644
--- a/spec/requests/api/labels_spec.rb
+++ b/spec/requests/api/labels_spec.rb
@@ -11,65 +11,76 @@ describe API::Labels do
end
describe 'GET /projects/:id/labels' do
- it 'returns all available labels to the project' do
- group = create(:group)
- group_label = create(:group_label, title: 'feature', group: group)
- project.update(group: group)
- create(:labeled_issue, project: project, labels: [group_label], author: user)
- create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
- create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+ let(:group) { create(:group) }
+ let!(:group_label) { create(:group_label, title: 'feature', group: group) }
- expected_keys = %w(
- id name color text_color description
- open_issues_count closed_issues_count open_merge_requests_count
- subscribed priority is_project_label
- )
+ before do
+ project.update!(group: group)
+ end
+ it 'returns all available labels to the project' do
get api("/projects/#{project.id}/labels", user)
expect(response).to have_gitlab_http_status(200)
expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
+ expect(json_response).to all(match_schema('public_api/v4/labels/project_label'))
expect(json_response.size).to eq(3)
- expect(json_response.first.keys).to match_array expected_keys
expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+ end
- label1_response = json_response.find { |l| l['name'] == label1.title }
- group_label_response = json_response.find { |l| l['name'] == group_label.title }
- priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
-
- expect(label1_response['open_issues_count']).to eq(0)
- expect(label1_response['closed_issues_count']).to eq(1)
- expect(label1_response['open_merge_requests_count']).to eq(0)
- expect(label1_response['name']).to eq(label1.name)
- expect(label1_response['color']).to be_present
- expect(label1_response['text_color']).to be_present
- expect(label1_response['description']).to be_nil
- expect(label1_response['priority']).to be_nil
- expect(label1_response['subscribed']).to be_falsey
- expect(label1_response['is_project_label']).to be_truthy
-
- expect(group_label_response['open_issues_count']).to eq(1)
- expect(group_label_response['closed_issues_count']).to eq(0)
- expect(group_label_response['open_merge_requests_count']).to eq(0)
- expect(group_label_response['name']).to eq(group_label.name)
- expect(group_label_response['color']).to be_present
- expect(group_label_response['text_color']).to be_present
- expect(group_label_response['description']).to be_nil
- expect(group_label_response['priority']).to be_nil
- expect(group_label_response['subscribed']).to be_falsey
- expect(group_label_response['is_project_label']).to be_falsey
-
- expect(priority_label_response['open_issues_count']).to eq(0)
- expect(priority_label_response['closed_issues_count']).to eq(0)
- expect(priority_label_response['open_merge_requests_count']).to eq(1)
- expect(priority_label_response['name']).to eq(priority_label.name)
- expect(priority_label_response['color']).to be_present
- expect(priority_label_response['text_color']).to be_present
- expect(priority_label_response['description']).to be_nil
- expect(priority_label_response['priority']).to eq(3)
- expect(priority_label_response['subscribed']).to be_falsey
- expect(priority_label_response['is_project_label']).to be_truthy
+ context 'when the with_counts parameter is set' do
+ before do
+ create(:labeled_issue, project: project, labels: [group_label], author: user)
+ create(:labeled_issue, project: project, labels: [label1], author: user, state: :closed)
+ create(:labeled_merge_request, labels: [priority_label], author: user, source_project: project )
+ end
+
+ it 'includes counts in the response' do
+ get api("/projects/#{project.id}/labels", user), params: { with_counts: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to all(match_schema('public_api/v4/labels/project_label_with_counts'))
+ expect(json_response.size).to eq(3)
+ expect(json_response.map { |l| l['name'] }).to match_array([group_label.name, priority_label.name, label1.name])
+
+ label1_response = json_response.find { |l| l['name'] == label1.title }
+ group_label_response = json_response.find { |l| l['name'] == group_label.title }
+ priority_label_response = json_response.find { |l| l['name'] == priority_label.title }
+
+ expect(label1_response).to include('open_issues_count' => 0,
+ 'closed_issues_count' => 1,
+ 'open_merge_requests_count' => 0,
+ 'name' => label1.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => nil,
+ 'subscribed' => false,
+ 'is_project_label' => true)
+
+ expect(group_label_response).to include('open_issues_count' => 1,
+ 'closed_issues_count' => 0,
+ 'open_merge_requests_count' => 0,
+ 'name' => group_label.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => nil,
+ 'subscribed' => false,
+ 'is_project_label' => false)
+
+ expect(priority_label_response).to include('open_issues_count' => 0,
+ 'closed_issues_count' => 0,
+ 'open_merge_requests_count' => 1,
+ 'name' => priority_label.name,
+ 'description' => nil,
+ 'color' => a_string_matching(/^#\h{6}$/),
+ 'text_color' => a_string_matching(/^#\h{6}$/),
+ 'priority' => 3,
+ 'subscribed' => false,
+ 'is_project_label' => true)
+ end
end
end
diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb
index 55f38079b1f..26f6e705528 100644
--- a/spec/requests/api/members_spec.rb
+++ b/spec/requests/api/members_spec.rb
@@ -99,7 +99,7 @@ describe API::Members do
end
end
- describe 'GET /:source_type/:id/members/all', :nested_groups do
+ describe 'GET /:source_type/:id/members/all' do
let(:nested_user) { create(:user) }
let(:project_user) { create(:user) }
let(:linked_group_user) { create(:user) }
@@ -238,7 +238,7 @@ describe API::Members do
end
context 'access levels' do
- it 'does not create the member if group level is higher', :nested_groups do
+ it 'does not create the member if group level is higher' do
parent = create(:group)
group.update(parent: parent)
@@ -252,7 +252,7 @@ describe API::Members do
expect(json_response['message']['access_level']).to eq(["should be greater than or equal to Developer inherited membership from group #{parent.name}"])
end
- it 'creates the member if group level is lower', :nested_groups do
+ it 'creates the member if group level is lower' do
parent = create(:group)
group.update(parent: parent)
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index ced853caab4..15d6db42760 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -723,7 +723,7 @@ describe API::MergeRequests do
it_behaves_like 'merge requests list'
- context 'when have subgroups', :nested_groups do
+ context 'when have subgroups' do
let!(:group) { create(:group, :public) }
let!(:subgroup) { create(:group, parent: group) }
let!(:project) { create(:project, :public, :repository, creator: user, namespace: subgroup, only_allow_merge_if_pipeline_succeeds: false) }
@@ -1571,7 +1571,7 @@ describe API::MergeRequests do
end
end
- describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref" do
+ describe "GET /projects/:id/merge_requests/:merge_request_iid/merge_ref", :clean_gitlab_redis_shared_state do
before do
merge_request.mark_as_unchecked!
end
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index e8ed016db69..a7b919de2ef 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -336,7 +336,6 @@ describe API::ProjectClusters do
it 'does not update cluster attributes' do
expect(cluster.domain).not_to eq('new_domain.com')
expect(cluster.platform_kubernetes.namespace).not_to eq('invalid_namespace')
- expect(cluster.kubernetes_namespace_for(project)).not_to eq('invalid_namespace')
end
it 'returns validation errors' do
diff --git a/spec/requests/api/project_container_repositories_spec.rb b/spec/requests/api/project_container_repositories_spec.rb
new file mode 100644
index 00000000000..f1dc4e6f0b2
--- /dev/null
+++ b/spec/requests/api/project_container_repositories_spec.rb
@@ -0,0 +1,228 @@
+require 'spec_helper'
+
+describe API::ProjectContainerRepositories do
+ include ExclusiveLeaseHelpers
+
+ set(:project) { create(:project, :private) }
+ set(:maintainer) { create(:user) }
+ set(:developer) { create(:user) }
+ set(:reporter) { create(:user) }
+ set(:guest) { create(:user) }
+
+ let(:root_repository) { create(:container_repository, :root, project: project) }
+ let(:test_repository) { create(:container_repository, project: project) }
+
+ let(:users) do
+ {
+ anonymous: nil,
+ developer: developer,
+ guest: guest,
+ maintainer: maintainer,
+ reporter: reporter
+ }
+ end
+
+ let(:api_user) { maintainer }
+
+ before do
+ project.add_maintainer(maintainer)
+ project.add_developer(developer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+
+ stub_feature_flags(container_registry_api: true)
+ stub_container_registry_config(enabled: true)
+
+ root_repository
+ test_repository
+ end
+
+ describe 'GET /projects/:id/registry/repositories' do
+ let(:url) { "/projects/#{project.id}/registry/repositories" }
+
+ subject { get api(url, api_user) }
+
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ it_behaves_like 'returns repositories for allowed users', :reporter, 'project' do
+ let(:object) { project }
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}", api_user) }
+
+ it_behaves_like 'rejected container repository access', :developer, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ context 'for maintainer' do
+ let(:api_user) { maintainer }
+
+ it 'schedules removal of repository' do
+ expect(DeleteContainerRepositoryWorker).to receive(:perform_async)
+ .with(maintainer.id, root_repository.id)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/registry/repositories/:repository_id/tags' do
+ subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user) }
+
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ context 'for reporter' do
+ let(:api_user) { reporter }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest))
+ end
+
+ it 'returns a list of tags' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['name'] }).to eq %w(latest rootA)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/tags')
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags", api_user), params: params }
+
+ context 'disallowed' do
+ let(:params) do
+ { name_regex: 'v10.*' }
+ end
+
+ it_behaves_like 'rejected container repository access', :developer, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+ end
+
+ context 'for maintainer' do
+ let(:api_user) { maintainer }
+
+ context 'without required parameters' do
+ let(:params) { }
+
+ it 'returns bad request' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'passes all declared parameters' do
+ let(:params) do
+ { name_regex: 'v10.*',
+ keep_n: 100,
+ older_than: '1 day',
+ other: 'some value' }
+ end
+
+ let(:worker_params) do
+ { name_regex: 'v10.*',
+ keep_n: 100,
+ older_than: '1 day' }
+ end
+
+ let(:lease_key) { "container_repository:cleanup_tags:#{root_repository.id}" }
+
+ it 'schedules cleanup of tags repository' do
+ stub_exclusive_lease(lease_key, timeout: 1.hour)
+ expect(CleanupContainerRepositoryWorker).to receive(:perform_async)
+ .with(maintainer.id, root_repository.id, worker_params)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:accepted)
+ end
+
+ context 'called multiple times in one hour' do
+ it 'returns 400 with an error message' do
+ stub_exclusive_lease_taken(lease_key, timeout: 1.hour)
+ subject
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(response.body).to include('This request has already been made.')
+ end
+
+ it 'executes service only for the first time' do
+ expect(CleanupContainerRepositoryWorker).to receive(:perform_async).once
+
+ 2.times { subject }
+ end
+ end
+ end
+ end
+ end
+
+ describe 'GET /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
+ subject { get api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
+
+ it_behaves_like 'rejected container repository access', :guest, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ context 'for reporter' do
+ let(:api_user) { reporter }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ end
+
+ it 'returns a details of tag' do
+ subject
+
+ expect(json_response).to include(
+ 'name' => 'rootA',
+ 'digest' => 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15',
+ 'revision' => 'd7a513a663c1a6dcdba9ed832ca53c02ac2af0c333322cd6ca92936d1d9917ac',
+ 'total_size' => 2319870)
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/tag')
+ end
+ end
+ end
+
+ describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do
+ subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) }
+
+ it_behaves_like 'rejected container repository access', :reporter, :forbidden
+ it_behaves_like 'rejected container repository access', :anonymous, :not_found
+
+ context 'for developer' do
+ let(:api_user) { developer }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true)
+ end
+
+ it 'properly removes tag' do
+ expect_any_instance_of(ContainerRegistry::Client)
+ .to receive(:delete_repository_tag).with(root_repository.path,
+ 'sha256:4c8e63ca4cb663ce6c688cb06f1c372b088dac5b6d7ad7d49cd620d85cf72a15')
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fee300e9d7a..1d7ca85cdd2 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -838,6 +838,28 @@ describe API::Projects do
end
end
+ describe 'GET /users/:user_id/starred_projects/' do
+ before do
+ user3.update(starred_projects: [project, project2, project3])
+ end
+
+ it 'returns error when user not found' do
+ get api('/users/9999/starred_projects/')
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+
+ it 'returns projects filtered by user' do
+ get api("/users/#{user3.id}/starred_projects/", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |project| project['id'] }).to contain_exactly(project.id, project2.id, project3.id)
+ end
+ end
+
describe 'POST /projects/user/:id' do
it 'creates new project without path but with name and return 201' do
expect { post api("/projects/user/#{user.id}", admin), params: { name: 'Foo Project' } }.to change { Project.count }.by(1)
@@ -1357,7 +1379,7 @@ describe API::Projects do
end
end
- context 'nested group project', :nested_groups do
+ context 'nested group project' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:project2) { create(:project, group: nested_group) }
@@ -2148,6 +2170,85 @@ describe API::Projects do
end
end
+ describe 'GET /projects/:id/starrers' do
+ shared_examples_for 'project starrers response' do
+ it 'returns an array of starrers' do
+ get api("/projects/#{public_project.id}/starrers", current_user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response[0]['starred_since']).to be_present
+ expect(json_response[0]['user']).to be_present
+ end
+
+ it 'returns the proper security headers' do
+ get api('/projects/1/starrers', current_user)
+
+ expect(response).to include_security_headers
+ end
+ end
+
+ let(:public_project) { create(:project, :public) }
+ let(:private_user) { create(:user, private_profile: true) }
+
+ before do
+ user.update(starred_projects: [public_project])
+ private_user.update(starred_projects: [public_project])
+ end
+
+ it 'returns not_found(404) for not existing project' do
+ get api("/projects/9999999999/starrers", user)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ context 'public project without user' do
+ it_behaves_like 'project starrers response' do
+ let(:current_user) { nil }
+ end
+
+ it 'returns only starrers with a public profile' do
+ get api("/projects/#{public_project.id}/starrers", nil)
+
+ user_ids = json_response.map { |s| s['user']['id'] }
+ expect(user_ids).to include(user.id)
+ expect(user_ids).not_to include(private_user.id)
+ end
+ end
+
+ context 'public project with user with private profile' do
+ it_behaves_like 'project starrers response' do
+ let(:current_user) { private_user }
+ end
+
+ it 'returns current user with a private profile' do
+ get api("/projects/#{public_project.id}/starrers", private_user)
+
+ user_ids = json_response.map { |s| s['user']['id'] }
+ expect(user_ids).to include(user.id, private_user.id)
+ end
+ end
+
+ context 'private project' do
+ context 'with unauthorized user' do
+ it 'returns not_found for existing but unauthorized project' do
+ get api("/projects/#{project3.id}/starrers", user3)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'without user' do
+ it 'returns not_found for existing but unauthorized project' do
+ get api("/projects/#{project3.id}/starrers", nil)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+ end
+
describe 'GET /projects/:id/languages' do
context 'with an authorized user' do
it_behaves_like 'languages and percentages JSON response' do
diff --git a/spec/requests/api/resource_label_events_spec.rb b/spec/requests/api/resource_label_events_spec.rb
index 37b46eaeb86..25bea627b0c 100644
--- a/spec/requests/api/resource_label_events_spec.rb
+++ b/spec/requests/api/resource_label_events_spec.rb
@@ -4,55 +4,12 @@ require 'spec_helper'
describe API::ResourceLabelEvents do
set(:user) { create(:user) }
- set(:project) { create(:project, :public, :repository, namespace: user.namespace) }
- set(:private_user) { create(:user) }
+ set(:project) { create(:project, :public, namespace: user.namespace) }
before do
project.add_developer(user)
end
- shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name|
- describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do
- it "returns an array of resource label events" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.first['id']).to eq(event.id)
- end
-
- it "returns a 404 error when eventable id not found" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
-
- it "returns 404 when not authorized" do
- parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
-
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
-
- describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do
- it "returns a resource label event by id" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['id']).to eq(event.id)
- end
-
- it "returns a 404 error if resource label event not found" do
- get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
-
context 'when eventable is an Issue' do
let(:issue) { create(:issue, project: project, author: user) }
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 91cb8760a04..76a70ab6e9e 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -10,10 +10,7 @@ describe API::Services do
end
Service.available_services_names.each do |service|
- # TODO: Remove below `if: (service != "kubernetes")` in the next release
- # KubernetesService was deprecated and it can't be updated. Right now it's
- # only readable. It should be completely removed in the next iteration.
- describe "PUT /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do
+ describe "PUT /projects/:id/services/#{service.dasherize}" do
include_context service
it "updates #{service} settings" do
@@ -62,10 +59,7 @@ describe API::Services do
end
end
- # TODO: Remove below `if: (service != "kubernetes")` in the next release
- # KubernetesService was deprecated and it can't be updated. Right now it's
- # only readable. It should be completely removed in the next iteration.
- describe "DELETE /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do
+ describe "DELETE /projects/:id/services/#{service.dasherize}" do
include_context service
before do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 8a60980fe80..590107d5161 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -25,6 +25,9 @@ describe API::Settings, 'Settings' do
expect(json_response['ed25519_key_restriction']).to eq(0)
expect(json_response['performance_bar_allowed_group_id']).to be_nil
expect(json_response['instance_statistics_visibility_private']).to be(false)
+ expect(json_response['allow_local_requests_from_hooks_and_services']).to be(false)
+ expect(json_response['allow_local_requests_from_web_hooks_and_services']).to be(false)
+ expect(json_response['allow_local_requests_from_system_hooks']).to be(true)
expect(json_response).not_to have_key('performance_bar_allowed_group_path')
expect(json_response).not_to have_key('performance_bar_enabled')
end
@@ -67,7 +70,9 @@ describe API::Settings, 'Settings' do
instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000,
default_branch_protection: ::Gitlab::Access::PROTECTION_DEV_CAN_MERGE,
- local_markdown_version: 3
+ local_markdown_version: 3,
+ allow_local_requests_from_web_hooks_and_services: true,
+ allow_local_requests_from_system_hooks: false
}
expect(response).to have_gitlab_http_status(200)
@@ -95,6 +100,8 @@ describe API::Settings, 'Settings' do
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
expect(json_response['local_markdown_version']).to eq(3)
+ expect(json_response['allow_local_requests_from_web_hooks_and_services']).to eq(true)
+ expect(json_response['allow_local_requests_from_system_hooks']).to eq(false)
end
end
@@ -117,6 +124,14 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to be_nil
end
+ it 'supports legacy allow_local_requests_from_hooks_and_services' do
+ put api("/application/settings", admin),
+ params: { allow_local_requests_from_hooks_and_services: true }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['allow_local_requests_from_hooks_and_services']).to eq(true)
+ end
+
context 'external policy classification settings' do
let(:settings) do
{
@@ -129,6 +144,7 @@ describe API::Settings, 'Settings' do
external_auth_client_key_pass: "5iveL!fe"
}
end
+
let(:attribute_names) { settings.keys.map(&:to_s) }
it 'includes the attributes in the API' do
@@ -150,6 +166,56 @@ describe API::Settings, 'Settings' do
end
end
+ context "snowplow tracking settings" do
+ let(:settings) do
+ {
+ snowplow_collector_hostname: "snowplow.example.com",
+ snowplow_cookie_domain: ".example.com",
+ snowplow_enabled: true,
+ snowplow_site_id: "site_id"
+ }
+ end
+
+ let(:attribute_names) { settings.keys.map(&:to_s) }
+
+ it "includes the attributes in the API" do
+ get api("/application/settings", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ attribute_names.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+
+ it "allows updating the settings" do
+ put api("/application/settings", admin), params: settings
+
+ expect(response).to have_gitlab_http_status(200)
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+
+ context "missing snowplow_collector_hostname value when snowplow_enabled is true" do
+ it "returns a blank parameter error message" do
+ put api("/application/settings", admin), params: { snowplow_enabled: true }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to eq("snowplow_collector_hostname is missing")
+ end
+
+ it "handles validation errors" do
+ put api("/application/settings", admin), params: settings.merge({
+ snowplow_collector_hostname: nil
+ })
+
+ expect(response).to have_gitlab_http_status(400)
+ message = json_response["message"]
+ expect(message["snowplow_collector_hostname"]).to include("can't be blank")
+ end
+ end
+ end
+
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { plantuml_enabled: true }
diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb
index f0f01e97f1d..8ea3d16a41f 100644
--- a/spec/requests/api/triggers_spec.rb
+++ b/spec/requests/api/triggers_spec.rb
@@ -270,34 +270,6 @@ describe API::Triggers do
end
end
- describe 'POST /projects/:id/triggers/:trigger_id/take_ownership' do
- context 'authenticated user with valid permissions' do
- it 'updates owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to include('owner')
- expect(trigger.reload.owner).to eq(user)
- end
- end
-
- context 'authenticated user with invalid permissions' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership", user2)
-
- expect(response).to have_gitlab_http_status(403)
- end
- end
-
- context 'unauthenticated user' do
- it 'does not update owner' do
- post api("/projects/#{project.id}/triggers/#{trigger.id}/take_ownership")
-
- expect(response).to have_gitlab_http_status(401)
- end
- end
- end
-
describe 'DELETE /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do
it 'deletes trigger' do
diff --git a/spec/requests/api/variables_spec.rb b/spec/requests/api/variables_spec.rb
index 55b1419a004..69f105b71a8 100644
--- a/spec/requests/api/variables_spec.rb
+++ b/spec/requests/api/variables_spec.rb
@@ -106,6 +106,30 @@ describe API::Variables do
expect(response).to have_gitlab_http_status(400)
end
+
+ it 'creates variable with a specific environment scope' do
+ expect do
+ post api("/projects/#{project.id}/variables", user), params: { key: 'TEST_VARIABLE_2', value: 'VALUE_2', environment_scope: 'review/*' }
+ end.to change { project.variables.reload.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['key']).to eq('TEST_VARIABLE_2')
+ expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['environment_scope']).to eq('review/*')
+ end
+
+ it 'allows duplicated variable key given different environment scopes' do
+ variable = create(:ci_variable, project: project)
+
+ expect do
+ post api("/projects/#{project.id}/variables", user), params: { key: variable.key, value: 'VALUE_2', environment_scope: 'review/*' }
+ end.to change { project.variables.reload.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['key']).to eq(variable.key)
+ expect(json_response['value']).to eq('VALUE_2')
+ expect(json_response['environment_scope']).to eq('review/*')
+ end
end
context 'authorized user with invalid permissions' do
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 86e41cbdf00..025568d8bea 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -104,7 +104,7 @@ describe 'OpenID Connect requests' do
expect(json_response).to match(id_token_claims.merge(user_info_claims))
expected_groups = [group1.full_path, group3.full_path]
- expected_groups << group4.full_path if Group.supports_nested_objects?
+ expected_groups << group4.full_path
expect(json_response['groups']).to match_array(expected_groups)
end
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/requests/request_profiler_spec.rb b/spec/requests/request_profiler_spec.rb
index 75b22b1879b..851affbcf88 100644
--- a/spec/requests/request_profiler_spec.rb
+++ b/spec/requests/request_profiler_spec.rb
@@ -3,13 +3,18 @@ require 'spec_helper'
describe 'Request Profiler' do
let(:user) { create(:user) }
- shared_examples 'profiling a request' do
+ shared_examples 'profiling a request' do |profile_type, extension|
before do
allow(Rails).to receive(:cache).and_return(ActiveSupport::Cache::MemoryStore.new)
allow(RubyProf::Profile).to receive(:profile) do |&blk|
blk.call
RubyProf::Profile.new
end
+ allow(MemoryProfiler).to receive(:report) do |&blk|
+ blk.call
+ MemoryProfiler.start
+ MemoryProfiler.stop
+ end
end
it 'creates a profile of the request' do
@@ -18,10 +23,11 @@ describe 'Request Profiler' do
path = "/#{project.full_path}"
Timecop.freeze(time) do
- get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token }
+ get path, params: {}, headers: { 'X-Profile-Token' => Gitlab::RequestProfiler.profile_token, 'X-Profile-Mode' => profile_type }
end
- profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}.html"
+ profile_type = 'execution' if profile_type.nil?
+ profile_path = "#{Gitlab.config.shared.path}/tmp/requests_profiles/#{path.tr('/', '|')}_#{time.to_i}_#{profile_type}.#{extension}"
expect(File.exist?(profile_path)).to be true
end
@@ -35,10 +41,14 @@ describe 'Request Profiler' do
login_as(user)
end
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
context "when user is not logged-in" do
- include_examples 'profiling a request'
+ include_examples 'profiling a request', 'execution', 'html'
+ include_examples 'profiling a request', nil, 'html'
+ include_examples 'profiling a request', 'memory', 'txt'
end
end
diff --git a/spec/routing/git_http_routing_spec.rb b/spec/routing/git_http_routing_spec.rb
new file mode 100644
index 00000000000..af14e5f81cb
--- /dev/null
+++ b/spec/routing/git_http_routing_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'git_http routing' do
+ include RSpec::Rails::RequestExampleGroup
+
+ describe 'wiki.git routing', 'routing' do
+ let(:wiki_path) { '/gitlab/gitlabhq/wikis' }
+
+ it 'redirects namespace/project.wiki.git to the project wiki' do
+ expect(get('/gitlab/gitlabhq.wiki.git')).to redirect_to(wiki_path)
+ end
+
+ it 'preserves query parameters' do
+ expect(get('/gitlab/gitlabhq.wiki.git?foo=bar&baz=qux')).to redirect_to("#{wiki_path}?foo=bar&baz=qux")
+ end
+
+ it 'only redirects when the format is .git' do
+ expect(get('/gitlab/gitlabhq.wiki')).not_to redirect_to(wiki_path)
+ expect(get('/gitlab/gitlabhq.wiki.json')).not_to redirect_to(wiki_path)
+ end
+ end
+end
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 0ff777388e5..27df42c0aee 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -10,91 +10,91 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
subject(:cop) { described_class.new }
- it 'flags the use of `prepend EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ it 'does not flag the use of `prepend_if_ee EEFoo` in the middle of a file' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend EEFoo
+ prepend_if_ee 'EEFoo'
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar
- ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee(EE::Foo::Bar)` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend(EE::Foo::Bar)
- ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee('EE::Foo::Bar')
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee EE::Foo::Bar::Baz` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend EE::Foo::Bar::Baz
- ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee 'EE::Foo::Bar::Baz'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `prepend ::EE` in the middle of a file' do
+ it 'flags the use of `prepend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- prepend ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ prepend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include EE::Foo
- ^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `include ::EE` in the middle of a file' do
+ it 'flags the use of `include_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- include ::EE::Foo
- ^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ include_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend EE::Foo
- ^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee 'EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
- it 'flags the use of `extend ::EE` in the middle of a file' do
+ it 'flags the use of `extend_if_ee ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
- extend ::EE::Foo
- ^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ extend_if_ee '::EE::Foo'
+ ^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
end
SOURCE
end
@@ -102,7 +102,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag prepending of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- prepend Foo
+ prepend_if_ee 'Foo'
end
SOURCE
end
@@ -110,7 +110,7 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag including of regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- include Foo
+ include_if_ee 'Foo'
end
SOURCE
end
@@ -118,51 +118,111 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
it 'does not flag extending using regular modules' do
expect_no_offenses(<<~SOURCE)
class Foo
- extend Foo
+ extend_if_ee 'Foo'
end
SOURCE
end
- it 'does not flag the use of `prepend EE` on the last line' do
+ it 'does not flag the use of `prepend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.prepend(EE::Foo)
+ Foo.prepend_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `include EE` on the last line' do
+ it 'does not flag the use of `include_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.include(EE::Foo)
+ Foo.include_if_ee('EE::Foo')
SOURCE
end
- it 'does not flag the use of `extend EE` on the last line' do
+ it 'does not flag the use of `extend_if_ee EE` on the last line' do
expect_no_offenses(<<~SOURCE)
class Foo
end
- Foo.extend(EE::Foo)
+ Foo.extend_if_ee('EE::Foo')
SOURCE
end
it 'autocorrects offenses by just disabling the Cop' do
source = <<~SOURCE
class Foo
- prepend EE::Foo
- include Bar
+ prepend_if_ee 'EE::Foo'
+ include_if_ee 'Bar'
end
SOURCE
expect(autocorrect_source(source)).to eq(<<~SOURCE)
class Foo
- prepend EE::Foo # rubocop: disable Cop/InjectEnterpriseEditionModule
- include Bar
+ prepend_if_ee 'EE::Foo' # rubocop: disable Cop/InjectEnterpriseEditionModule
+ include_if_ee 'Bar'
+ end
+ SOURCE
+ end
+
+ it 'disallows the use of prepend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of extend to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
end
+
+ Foo.extend(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of include to inject an EE module' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include(EE::Foo)
+ ^^^^^^^^^^^^^^^^^^^^ EE modules must be injected using `include_if_ee`, `extend_if_ee`, or `prepend_if_ee`
+ SOURCE
+ end
+
+ it 'disallows the use of prepend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.prepend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of include_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.include_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
+ SOURCE
+ end
+
+ it 'disallows the use of extend_if_ee without a String' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ end
+
+ Foo.extend_if_ee(EE::Foo)
+ ^^^^^^^ EE modules to inject must be specified as a String
SOURCE
end
end
diff --git a/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
new file mode 100644
index 00000000000..258144d4000
--- /dev/null
+++ b/spec/rubocop/cop/rspec/top_level_describe_path_spec.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/top_level_describe_path'
+
+describe RuboCop::Cop::RSpec::TopLevelDescribePath do
+ include RuboCop::RSpec::ExpectOffense
+ include CopHelper
+
+ subject(:cop) { described_class.new }
+
+ context 'when the file ends in _spec.rb' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo_spec.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the file is a frontend fixture' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/frontend/fixtures/foo.rb')
+ describe 'Foo' do
+ end
+ SOURCE
+ end
+ end
+
+ context 'when the describe is in a shared example' do
+ context 'with shared_examples' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+
+ context 'with shared_examples_for' do
+ it 'registers no offenses' do
+ expect_no_offenses(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ shared_examples_for 'Foo' do
+ describe '#bar' do
+ end
+ end
+ SOURCE
+ end
+ end
+ end
+
+ context 'when the describe is at the top level' do
+ it 'marks the describe as offending' do
+ expect_offense(<<~SOURCE.strip_indent, 'spec/foo.rb')
+ describe 'Foo' do
+ ^^^^^^^^^^^^^^ #{described_class::MESSAGE}
+ end
+ SOURCE
+ end
+ end
+end
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 89588b4df2b..c5b03bdd8c1 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -9,12 +9,14 @@ describe AnalyticsIssueEntity do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
- let(:project) { create(:project) }
- let(:request) { EntityRequest.new(project: project, entity: :merge_request) }
+ let(:project) { create(:project, name: 'my project') }
+ let(:request) { EntityRequest.new(entity: :merge_request) }
let(:entity) do
described_class.new(entity_hash, request: request, project: project)
diff --git a/spec/serializers/analytics_issue_serializer_spec.rb b/spec/serializers/analytics_issue_serializer_spec.rb
index 5befc28f4fa..9cb2ce13d12 100644
--- a/spec/serializers/analytics_issue_serializer_spec.rb
+++ b/spec/serializers/analytics_issue_serializer_spec.rb
@@ -3,12 +3,12 @@ require 'spec_helper'
describe AnalyticsIssueSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, name: 'my project') }
let(:resource) do
{
total_time: "172802.724419",
@@ -16,7 +16,9 @@ describe AnalyticsIssueSerializer do
iid: "1",
id: "1",
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
diff --git a/spec/serializers/analytics_merge_request_serializer_spec.rb b/spec/serializers/analytics_merge_request_serializer_spec.rb
index 62067cc0ef2..a864051b2a3 100644
--- a/spec/serializers/analytics_merge_request_serializer_spec.rb
+++ b/spec/serializers/analytics_merge_request_serializer_spec.rb
@@ -3,12 +3,12 @@ require 'spec_helper'
describe AnalyticsMergeRequestSerializer do
subject do
described_class
- .new(project: project, entity: :merge_request)
+ .new(entity: :merge_request)
.represent(resource)
end
let(:user) { create(:user) }
- let(:project) { create(:project) }
+ let(:project) { create(:project, name: 'my project') }
let(:resource) do
{
total_time: "172802.724419",
@@ -17,7 +17,9 @@ describe AnalyticsMergeRequestSerializer do
id: "1",
state: 'open',
created_at: "2016-11-12 15:04:02.948604",
- author: user
+ author: user,
+ project_path: project.path,
+ namespace_path: project.namespace.route.path
}
end
diff --git a/spec/serializers/analytics_stage_serializer_spec.rb b/spec/serializers/analytics_stage_serializer_spec.rb
index 5b05c2f2ef3..86a796a2d94 100644
--- a/spec/serializers/analytics_stage_serializer_spec.rb
+++ b/spec/serializers/analytics_stage_serializer_spec.rb
@@ -6,11 +6,11 @@ describe AnalyticsStageSerializer do
end
let(:resource) do
- Gitlab::CycleAnalytics::CodeStage.new(project: double, options: {})
+ Gitlab::CycleAnalytics::CodeStage.new(options: { project: double })
end
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(1.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(1.12)
allow_any_instance_of(Gitlab::CycleAnalytics::BaseEventFetcher).to receive(:event_result).and_return({})
end
@@ -24,7 +24,7 @@ describe AnalyticsStageSerializer do
context 'when median is equal 0' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0)
end
it 'sets the value to nil' do
@@ -34,7 +34,7 @@ describe AnalyticsStageSerializer do
context 'when median is below 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(0.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(0.12)
end
it 'sets the value to equal to median' do
@@ -44,7 +44,7 @@ describe AnalyticsStageSerializer do
context 'when median is above 1' do
before do
- allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:median).and_return(60.12)
+ allow_any_instance_of(Gitlab::CycleAnalytics::BaseStage).to receive(:project_median).and_return(60.12)
end
it 'sets the value to equal to median' do
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index f38a18fcf59..76ecca06522 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -22,7 +22,7 @@ describe ClusterApplicationEntity do
end
it 'has can_uninstall' do
- expect(subject[:can_uninstall]).to be_falsey
+ expect(subject[:can_uninstall]).to be_truthy
end
context 'non-helm application' do
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index 76ad2aee5c5..1b19eac9a97 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -32,6 +32,10 @@ describe DeploymentEntity do
expect(subject).to include(:created_at)
end
+ it 'exposes deployed_at' do
+ expect(subject).to include(:deployed_at)
+ end
+
context 'when the pipeline has another manual action' do
let(:other_build) { create(:ci_build, :manual, name: 'another deploy', pipeline: pipeline) }
let!(:other_deployment) { create(:deployment, deployable: other_build) }
diff --git a/spec/serializers/group_child_entity_spec.rb b/spec/serializers/group_child_entity_spec.rb
index b58d95ccb43..00e2f931549 100644
--- a/spec/serializers/group_child_entity_spec.rb
+++ b/spec/serializers/group_child_entity_spec.rb
@@ -62,7 +62,7 @@ describe GroupChildEntity do
it_behaves_like 'group child json'
end
- describe 'for a group', :nested_groups do
+ describe 'for a group' do
let(:description) { 'Awesomeness' }
let(:object) do
create(:group, :nested, :with_avatar,
diff --git a/spec/serializers/group_child_serializer_spec.rb b/spec/serializers/group_child_serializer_spec.rb
index 5541ada3750..c9e8535585b 100644
--- a/spec/serializers/group_child_serializer_spec.rb
+++ b/spec/serializers/group_child_serializer_spec.rb
@@ -16,7 +16,7 @@ describe GroupChildSerializer do
end
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
@@ -75,7 +75,7 @@ describe GroupChildSerializer do
expect(serializer.represent(build_list(:project, 2))).to be_kind_of(Array)
end
- context 'with a hierarchy', :nested_groups do
+ context 'with a hierarchy' do
let(:parent) { create(:group) }
subject(:serializer) do
diff --git a/spec/serializers/issue_entity_spec.rb b/spec/serializers/issue_entity_spec.rb
index caa3e41402b..0e05b3c84f4 100644
--- a/spec/serializers/issue_entity_spec.rb
+++ b/spec/serializers/issue_entity_spec.rb
@@ -17,4 +17,37 @@ describe IssueEntity do
it 'has time estimation attributes' do
expect(subject).to include(:time_estimate, :total_time_spent, :human_time_estimate, :human_total_time_spent)
end
+
+ context 'when issue got moved' do
+ let(:public_project) { create(:project, :public) }
+ let(:member) { create(:user) }
+ let(:non_member) { create(:user) }
+ let(:issue) { create(:issue, project: public_project) }
+
+ before do
+ project.add_developer(member)
+ public_project.add_developer(member)
+ Issues::MoveService.new(public_project, member).execute(issue, project)
+ end
+
+ context 'when user cannot read target project' do
+ it 'does not return moved_to_id' do
+ request = double('request', current_user: non_member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to be_nil
+ end
+ end
+
+ context 'when user can read target project' do
+ it 'returns moved moved_to_id' do
+ request = double('request', current_user: member)
+
+ response = described_class.new(issue, request: request).as_json
+
+ expect(response[:moved_to_id]).to eq(issue.moved_to_id)
+ end
+ end
+ end
end
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/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 54e6abc2d3a..7f9827329b3 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -126,7 +126,7 @@ describe PipelineSerializer do
expect(subject.all? { |entry| entry[:merge_request].present? }).to be_truthy
end
- it 'preloads related merge requests', :postgresql do
+ it 'preloads related merge requests' do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.log)
diff --git a/spec/serializers/user_serializer_spec.rb b/spec/serializers/user_serializer_spec.rb
new file mode 100644
index 00000000000..2e4a8c644fe
--- /dev/null
+++ b/spec/serializers/user_serializer_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe UserSerializer do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+
+ context 'serializer with merge request context' do
+ let(:merge_request) { create(:merge_request) }
+ let(:project) { merge_request.project }
+ let(:serializer) { described_class.new(merge_request_iid: merge_request.iid) }
+
+ before do
+ allow(project).to(
+ receive_message_chain(:merge_requests, :find_by_iid!)
+ .with(merge_request.iid).and_return(merge_request)
+ )
+
+ project.add_maintainer(user1)
+ end
+
+ it 'returns a user with can_merge option' do
+ serialized_user1, serialized_user2 = serializer.represent([user1, user2], project: project).as_json
+
+ expect(serialized_user1).to include("id" => user1.id, "can_merge" => true)
+ expect(serialized_user2).to include("id" => user2.id, "can_merge" => false)
+ end
+ end
+end
diff --git a/spec/serializers/variable_entity_spec.rb b/spec/serializers/variable_entity_spec.rb
index effc0022633..10664ff66ec 100644
--- a/spec/serializers/variable_entity_spec.rb
+++ b/spec/serializers/variable_entity_spec.rb
@@ -8,7 +8,7 @@ describe VariableEntity do
subject { entity.as_json }
it 'contains required fields' do
- expect(subject).to include(:id, :key, :value, :protected)
+ expect(subject).to include(:id, :key, :value, :protected, :environment_scope)
end
end
end
diff --git a/spec/services/application_settings/update_service_spec.rb b/spec/services/application_settings/update_service_spec.rb
index a641828faa5..adb5219d691 100644
--- a/spec/services/application_settings/update_service_spec.rb
+++ b/spec/services/application_settings/update_service_spec.rb
@@ -62,6 +62,54 @@ describe ApplicationSettings::UpdateService do
end
end
+ describe 'updating outbound_local_requests_whitelist' do
+ context 'when params is blank' do
+ let(:params) { {} }
+
+ it 'does not add to whitelist' do
+ expect { subject.execute }.not_to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+ end
+ end
+
+ context 'when param add_to_outbound_local_requests_whitelist contains values' do
+ before do
+ application_settings.outbound_local_requests_whitelist = ['127.0.0.1']
+ end
+
+ let(:params) { { add_to_outbound_local_requests_whitelist: ['example.com', ''] } }
+
+ it 'adds to whitelist' do
+ expect { subject.execute }.to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+
+ expect(application_settings.outbound_local_requests_whitelist).to contain_exactly(
+ '127.0.0.1', 'example.com'
+ )
+ end
+ end
+
+ context 'when param outbound_local_requests_whitelist_raw is passed' do
+ before do
+ application_settings.outbound_local_requests_whitelist = ['127.0.0.1']
+ end
+
+ let(:params) { { outbound_local_requests_whitelist_raw: 'example.com;gitlab.com' } }
+
+ it 'overwrites the existing whitelist' do
+ expect { subject.execute }.to change {
+ application_settings.outbound_local_requests_whitelist
+ }
+
+ expect(application_settings.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'gitlab.com'
+ )
+ end
+ end
+ end
+
describe 'performance bar settings' do
using RSpec::Parameterized::TableSyntax
@@ -180,4 +228,20 @@ describe ApplicationSettings::UpdateService do
described_class.new(application_settings, admin, { home_page_url: 'http://foo.bar' }).execute
end
end
+
+ context 'when raw_blob_request_limit is passsed' do
+ let(:params) do
+ {
+ raw_blob_request_limit: 600
+ }
+ end
+
+ it 'updates raw_blob_request_limit value' do
+ subject.execute
+
+ application_settings.reload
+
+ expect(application_settings.raw_blob_request_limit).to eq(600)
+ end
+ end
end
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 4f4776bbb27..3ca389ba25b 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -145,6 +145,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ describe '#pull_access_token' do
+ let(:project) { create(:project) }
+ let(:token) { described_class.pull_access_token(project.full_path) }
+
+ subject { { token: token } }
+
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['pull'] }
+ end
+
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'user authorization' do
let(:current_user) { create(:user) }
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/services/award_emojis/collect_user_emoji_service_spec.rb b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
new file mode 100644
index 00000000000..a0dea31b403
--- /dev/null
+++ b/spec/services/award_emojis/collect_user_emoji_service_spec.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe AwardEmojis::CollectUserEmojiService do
+ describe '#execute' do
+ it 'returns an Array containing the awarded emoji names' do
+ user = create(:user)
+
+ create(:award_emoji, user: user, name: 'thumbsup')
+ create(:award_emoji, user: user, name: 'thumbsup')
+ create(:award_emoji, user: user, name: 'thumbsdown')
+
+ awarded = described_class.new(user).execute
+
+ expect(awarded).to eq([{ name: 'thumbsup' }, { name: 'thumbsdown' }])
+ end
+
+ it 'returns an empty Array when no user is given' do
+ awarded = described_class.new.execute
+
+ expect(awarded).to be_empty
+ end
+ end
+end
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/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb
index 40878e24cb4..931b67b2950 100644
--- a/spec/services/boards/issues/list_service_spec.rb
+++ b/spec/services/boards/issues/list_service_spec.rb
@@ -112,7 +112,7 @@ describe Boards::Issues::ListService do
it_behaves_like 'issues list service'
end
- context 'and group is an ancestor', :nested_groups do
+ context 'and group is an ancestor' do
let(:parent) { create(:group) }
let(:group) { create(:group, parent: parent) }
let!(:backlog) { create(:backlog_list, board: board) }
diff --git a/spec/services/ci/archive_trace_service_spec.rb b/spec/services/ci/archive_trace_service_spec.rb
index 44a77c29086..454db3d5a48 100644
--- a/spec/services/ci/archive_trace_service_spec.rb
+++ b/spec/services/ci/archive_trace_service_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe Ci::ArchiveTraceService, '#execute' do
- subject { described_class.new.execute(job) }
+ subject { described_class.new.execute(job, worker_name: ArchiveTraceWorker.name) }
context 'when job is finished' do
let(:job) { create(:ci_build, :success, :trace_live) }
@@ -25,6 +25,34 @@ describe Ci::ArchiveTraceService, '#execute' do
expect { subject }.not_to change { Ci::JobArtifact.trace.count }
end
end
+
+ context 'when job does not have trace' do
+ let(:job) { create(:ci_build, :success) }
+
+ it 'leaves a warning message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: 'The job does not have live trace but going to be archived.',
+ job_id: job.id)
+
+ subject
+ end
+ end
+
+ context 'when job failed to archive trace but did not raise an exception' do
+ before do
+ allow_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!) {}
+ end
+
+ it 'leaves a warning message in sidekiq log' do
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: 'The job does not have archived trace after archiving.',
+ job_id: job.id)
+
+ subject
+ end
+ end
end
context 'when job is running' do
@@ -37,10 +65,10 @@ describe Ci::ArchiveTraceService, '#execute' do
issue_url: 'https://gitlab.com/gitlab-org/gitlab-ce/issues/51502',
extra: { job_id: job.id } ).once
- expect(Rails.logger)
- .to receive(:error)
- .with("Failed to archive trace. id: #{job.id} message: Job is not finished yet")
- .and_call_original
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: ArchiveTraceWorker.name,
+ message: "Failed to archive trace. message: Job is not finished yet.",
+ job_id: job.id).and_call_original
expect(Gitlab::Metrics)
.to receive(:counter)
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index d9b61dfe503..deb68899309 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1099,6 +1099,63 @@ describe Ci::CreatePipelineService do
end
end
end
+
+ context 'when needs is used' do
+ let(:pipeline) { execute_service }
+
+ let(:config) do
+ {
+ build_a: {
+ stage: "build",
+ script: "ls",
+ only: %w[master]
+ },
+ test_a: {
+ stage: "test",
+ script: "ls",
+ only: %w[master feature],
+ needs: %w[build_a]
+ },
+ deploy: {
+ stage: "deploy",
+ script: "ls",
+ only: %w[tags]
+ }
+ }
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ context 'when pipeline on master is created' do
+ let(:ref_name) { 'refs/heads/master' }
+
+ it 'creates a pipeline with build_a and test_a' do
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.map(&:name)).to contain_exactly("build_a", "test_a")
+ end
+ end
+
+ context 'when pipeline on feature is created' do
+ let(:ref_name) { 'refs/heads/feature' }
+
+ it 'does not create a pipeline as test_a depends on build_a' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.builds).to be_empty
+ expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
+ end
+ end
+
+ context 'when pipeline on v1.0.0 is created' do
+ let(:ref_name) { 'refs/tags/v1.0.0' }
+
+ it 'does create a pipeline only with deploy' do
+ expect(pipeline).to be_persisted
+ expect(pipeline.builds.map(&:name)).to contain_exactly("deploy")
+ end
+ end
+ end
end
describe '#execute!' do
diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb
index 1e68b7956ea..cf39f3da4fe 100644
--- a/spec/services/ci/play_build_service_spec.rb
+++ b/spec/services/ci/play_build_service_spec.rb
@@ -60,6 +60,19 @@ describe Ci::PlayBuildService, '#execute' do
expect(build.reload.user).to eq user
end
+
+ context 'when variables are supplied' do
+ let(:job_variables) do
+ [{ key: 'first', secret_value: 'first' },
+ { key: 'second', secret_value: 'second' }]
+ end
+
+ it 'assigns the variables to the build' do
+ service.execute(build, job_variables)
+
+ expect(build.reload.job_variables.map(&:key)).to contain_exactly('first', 'second')
+ end
+ end
end
context 'when build is not a playable manual action' do
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index cadb519ccee..1b28d2d4d02 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -700,6 +700,138 @@ describe Ci::ProcessPipelineService, '#execute' do
end
end
+ context 'when pipeline with needs is created' do
+ let!(:linux_build) { create_build('linux:build', stage: 'build', stage_idx: 0) }
+ let!(:mac_build) { create_build('mac:build', stage: 'build', stage_idx: 0) }
+ let!(:linux_rspec) { create_build('linux:rspec', stage: 'test', stage_idx: 1) }
+ let!(:linux_rubocop) { create_build('linux:rubocop', stage: 'test', stage_idx: 1) }
+ let!(:mac_rspec) { create_build('mac:rspec', stage: 'test', stage_idx: 1) }
+ let!(:mac_rubocop) { create_build('mac:rubocop', stage: 'test', stage_idx: 1) }
+ let!(:deploy) { create_build('deploy', stage: 'deploy', stage_idx: 2) }
+
+ let!(:linux_rspec_on_build) { create(:ci_build_need, build: linux_rspec, name: 'linux:build') }
+ let!(:linux_rubocop_on_build) { create(:ci_build_need, build: linux_rubocop, name: 'linux:build') }
+
+ let!(:mac_rspec_on_build) { create(:ci_build_need, build: mac_rspec, name: 'mac:build') }
+ let!(:mac_rubocop_on_build) { create(:ci_build_need, build: mac_rubocop, name: 'mac:build') }
+
+ it 'when linux:* finishes first it runs it out of order' do
+ expect(process_pipeline).to be_truthy
+
+ expect(stages).to eq(%w(pending created created))
+ expect(builds.pending).to contain_exactly(linux_build, mac_build)
+
+ # we follow the single path of linux
+ linux_build.reset.success!
+
+ expect(stages).to eq(%w(running pending created))
+ expect(builds.success).to contain_exactly(linux_build)
+ expect(builds.pending).to contain_exactly(mac_build, linux_rspec, linux_rubocop)
+
+ linux_rspec.reset.success!
+
+ expect(stages).to eq(%w(running running created))
+ expect(builds.success).to contain_exactly(linux_build, linux_rspec)
+ expect(builds.pending).to contain_exactly(mac_build, linux_rubocop)
+
+ linux_rubocop.reset.success!
+
+ expect(stages).to eq(%w(running running created))
+ expect(builds.success).to contain_exactly(linux_build, linux_rspec, linux_rubocop)
+ expect(builds.pending).to contain_exactly(mac_build)
+
+ mac_build.reset.success!
+ mac_rspec.reset.success!
+ mac_rubocop.reset.success!
+
+ expect(stages).to eq(%w(success success pending))
+ expect(builds.success).to contain_exactly(
+ linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop)
+ expect(builds.pending).to contain_exactly(deploy)
+ end
+
+ context 'when feature ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it 'when linux:build finishes first it follows stages' do
+ expect(process_pipeline).to be_truthy
+
+ expect(stages).to eq(%w(pending created created))
+ expect(builds.pending).to contain_exactly(linux_build, mac_build)
+
+ # we follow the single path of linux
+ linux_build.reset.success!
+
+ expect(stages).to eq(%w(running created created))
+ expect(builds.success).to contain_exactly(linux_build)
+ expect(builds.pending).to contain_exactly(mac_build)
+
+ mac_build.reset.success!
+
+ expect(stages).to eq(%w(success pending created))
+ expect(builds.success).to contain_exactly(linux_build, mac_build)
+ expect(builds.pending).to contain_exactly(
+ linux_rspec, linux_rubocop, mac_rspec, mac_rubocop)
+
+ linux_rspec.reset.success!
+ linux_rubocop.reset.success!
+ mac_rspec.reset.success!
+ mac_rubocop.reset.success!
+
+ expect(stages).to eq(%w(success success pending))
+ expect(builds.success).to contain_exactly(
+ linux_build, linux_rspec, linux_rubocop, mac_build, mac_rspec, mac_rubocop)
+ expect(builds.pending).to contain_exactly(deploy)
+ end
+ end
+
+ context 'when one of the jobs is run on a failure' do
+ let!(:linux_notify) { create_build('linux:notify', stage: 'deploy', stage_idx: 2, when: 'on_failure') }
+
+ let!(:linux_notify_on_build) { create(:ci_build_need, build: linux_notify, name: 'linux:build') }
+
+ context 'when another job in build phase fails first' do
+ context 'when ci_dag_support is enabled' do
+ it 'does skip linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ mac_build.reset.drop!
+ linux_build.reset.success!
+
+ expect(linux_notify.reset).to be_skipped
+ end
+ end
+
+ context 'when ci_dag_support is disabled' do
+ before do
+ stub_feature_flags(ci_dag_support: false)
+ end
+
+ it 'does run linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ mac_build.reset.drop!
+ linux_build.reset.success!
+
+ expect(linux_notify.reset).to be_pending
+ end
+ end
+ end
+
+ context 'when linux:build job fails first' do
+ it 'does run linux:notify' do
+ expect(process_pipeline).to be_truthy
+
+ linux_build.reset.drop!
+
+ expect(linux_notify.reset).to be_pending
+ end
+ end
+ end
+ end
+
def process_pipeline
described_class.new(pipeline.project, user).execute(pipeline)
end
@@ -712,6 +844,10 @@ describe Ci::ProcessPipelineService, '#execute' do
all_builds.where.not(status: [:created, :skipped])
end
+ def stages
+ pipeline.reset.stages.map(&:status)
+ end
+
def builds_names
builds.pluck(:name)
end
diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb
index 11b06ef5019..fe7c6fe4700 100644
--- a/spec/services/ci/retry_build_service_spec.rb
+++ b/spec/services/ci/retry_build_service_spec.rb
@@ -30,7 +30,8 @@ describe Ci::RetryBuildService do
job_artifacts_sast job_artifacts_dependency_scanning
job_artifacts_container_scanning job_artifacts_dast
job_artifacts_license_management job_artifacts_performance
- job_artifacts_codequality job_artifacts_metrics scheduled_at].freeze
+ job_artifacts_codequality job_artifacts_metrics scheduled_at
+ job_variables].freeze
IGNORE_ACCESSORS =
%i[type lock_version target_url base_tags trace_sections
@@ -65,6 +66,9 @@ describe Ci::RetryBuildService do
file_type: file_type, job: build, expire_at: build.artifacts_expire_at)
end
+ create(:ci_job_variable, job: build)
+ create(:ci_build_need, build: build)
+
build.reload
end
diff --git a/spec/services/clusters/build_kubernetes_namespace_service_spec.rb b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
new file mode 100644
index 00000000000..36c05469542
--- /dev/null
+++ b/spec/services/clusters/build_kubernetes_namespace_service_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::BuildKubernetesNamespaceService do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:environment) { create(:environment) }
+ let(:project) { environment.project }
+
+ let(:namespace_generator) { double(from_environment_slug: namespace) }
+ let(:namespace) { 'namespace' }
+
+ subject { described_class.new(cluster, environment: environment).execute }
+
+ before do
+ allow(Gitlab::Kubernetes::DefaultNamespace).to receive(:new).and_return(namespace_generator)
+ end
+
+ shared_examples 'shared attributes' do
+ it 'initializes a new namespace and sets default values' do
+ expect(subject).to be_new_record
+ expect(subject.cluster).to eq cluster
+ expect(subject.project).to eq project
+ expect(subject.namespace).to eq namespace
+ expect(subject.service_account_name).to eq "#{namespace}-service-account"
+ end
+ end
+
+ include_examples 'shared attributes'
+
+ it 'sets cluster_project and environment' do
+ expect(subject.cluster_project).to eq cluster.cluster_project
+ expect(subject.environment).to eq environment
+ end
+
+ context 'namespace per environment is disabled' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp, :namespace_per_environment_disabled) }
+
+ include_examples 'shared attributes'
+
+ it 'does not set environment' do
+ expect(subject.cluster_project).to eq cluster.cluster_project
+ expect(subject.environment).to be_nil
+ end
+ end
+
+ context 'group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+
+ include_examples 'shared attributes'
+
+ it 'does not set cluster_project' do
+ expect(subject.cluster_project).to be_nil
+ expect(subject.environment).to eq environment
+ end
+ end
+end
diff --git a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
index 44407ae2793..e44cc3f5a78 100644
--- a/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
+++ b/spec/services/clusters/gcp/kubernetes/create_or_update_namespace_service_spec.rb
@@ -9,8 +9,9 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
let(:platform) { cluster.platform }
let(:api_url) { 'https://kubernetes.example.com' }
let(:project) { cluster.project }
+ let(:environment) { create(:environment, project: project) }
let(:cluster_project) { cluster.cluster_project }
- let(:namespace) { "#{project.path}-#{project.id}" }
+ let(:namespace) { "#{project.name}-#{project.id}-#{environment.slug}" }
subject do
described_class.new(
@@ -79,7 +80,8 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
let(:kubernetes_namespace) do
build(:cluster_kubernetes_namespace,
cluster: cluster,
- project: project)
+ project: project,
+ environment: environment)
end
it_behaves_like 'successful creation of kubernetes namespace'
@@ -92,20 +94,22 @@ describe Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService, '#execute' d
build(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
- cluster_project: cluster_project)
+ cluster_project: cluster_project,
+ environment: environment)
end
it_behaves_like 'successful creation of kubernetes namespace'
end
context 'when there is a Kubernetes Namespace associated' do
- let(:namespace) { 'new-namespace' }
+ let(:namespace) { "new-namespace-#{environment.slug}" }
let(:kubernetes_namespace) do
create(:cluster_kubernetes_namespace,
cluster: cluster,
project: cluster_project.project,
- cluster_project: cluster_project)
+ cluster_project: cluster_project,
+ environment: environment)
end
before do
diff --git a/spec/services/clusters/refresh_service_spec.rb b/spec/services/clusters/refresh_service_spec.rb
deleted file mode 100644
index 5bc8a709941..00000000000
--- a/spec/services/clusters/refresh_service_spec.rb
+++ /dev/null
@@ -1,113 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe Clusters::RefreshService do
- shared_examples 'creates a kubernetes namespace' do
- let(:token) { 'aaaaaa' }
- let(:service_account_creator) { double(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService, execute: true) }
- let(:secrets_fetcher) { double(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService, execute: token) }
-
- it 'creates a kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
-
- expect { subject }.to change(project.kubernetes_namespaces, :count)
-
- kubernetes_namespace = cluster.kubernetes_namespaces.first
- expect(kubernetes_namespace).to be_present
- expect(kubernetes_namespace.project).to eq(project)
- end
- end
-
- shared_examples 'does not create a kubernetes namespace' do
- it 'does not create a new kubernetes namespace' do
- expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).not_to receive(:namespace_creator)
- expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).not_to receive(:new)
-
- expect { subject }.not_to change(Clusters::KubernetesNamespace, :count)
- end
- end
-
- describe '.create_or_update_namespaces_for_cluster' do
- let(:cluster) { create(:cluster, :provided_by_user, :project) }
- let(:project) { cluster.project }
-
- subject { described_class.create_or_update_namespaces_for_cluster(cluster) }
-
- context 'cluster is project level' do
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'cluster is group level' do
- let(:cluster) { create(:cluster, :provided_by_user, :group) }
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
-
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
- end
-
- describe '.create_or_update_namespaces_for_project' do
- let(:project) { create(:project) }
-
- subject { described_class.create_or_update_namespaces_for_project(project) }
-
- it 'creates no kubernetes namespaces' do
- expect { subject }.not_to change(project.kubernetes_namespaces, :count)
- end
-
- context 'project has a project cluster' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, cluster_type: :project_type, projects: [project]) }
-
- include_examples 'creates a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'project belongs to a group cluster' do
- let!(:cluster) { create(:cluster, :provided_by_gcp, :group) }
-
- let(:group) { cluster.group }
- let(:project) { create(:project, group: group) }
-
- include_examples 'does not create a kubernetes namespace'
-
- context 'when project already has kubernetes namespace' do
- before do
- create(:cluster_kubernetes_namespace, project: project, cluster: cluster)
- end
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-
- context 'cluster is not managed' do
- let!(:cluster) { create(:cluster, :project, :not_managed, projects: [project]) }
-
- include_examples 'does not create a kubernetes namespace'
- end
- end
-end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index f6b6989b955..9b83f65a17e 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -36,6 +36,22 @@ describe CreateSnippetService do
end
end
+ describe 'usage counter' do
+ let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
+
+ it 'increments count' do
+ expect do
+ create_snippet(nil, @admin, @opts)
+ end.to change { counter.read(:create) }.by 1
+ end
+
+ it 'does not increment count if create fails' do
+ expect do
+ create_snippet(nil, @admin, {})
+ end.not_to change { counter.read(:create) }
+ end
+ end
+
def create_snippet(project, user, opts)
CreateSnippetService.new(project, user, opts).execute
end
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 4a2ec769116..874df9a68cd 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -14,6 +14,78 @@ describe Git::BaseHooksService do
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' }
+ describe '#execute_project_hooks' do
+ class TestService < described_class
+ def hook_name
+ :push_hooks
+ end
+
+ def commits
+ []
+ end
+ end
+
+ let(:project) { create(:project, :repository) }
+
+ subject { TestService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ context '#execute_hooks' do
+ before do
+ expect(project).to receive(:has_active_hooks?).and_return(active)
+ end
+
+ context 'active hooks' do
+ let(:active) { true }
+
+ it 'executes the hooks' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive hooks' do
+ let(:active) { false }
+
+ it 'does not execute the hooks' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+ end
+
+ context '#execute_services' do
+ before do
+ expect(project).to receive(:has_active_services?).and_return(active)
+ end
+
+ context 'active services' do
+ let(:active) { true }
+
+ it 'executes the services' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive services' do
+ let(:active) { false }
+
+ it 'does not execute the services' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
describe 'with remote mirrors' do
class TestService < described_class
def commits
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 23be400059e..2bf7dc32436 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Git::BranchHooksService do
include RepoHelpers
+ include ProjectForksHelper
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
@@ -25,7 +26,7 @@ describe Git::BranchHooksService do
end
describe "Git Push Data" do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
@@ -109,6 +110,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to eq(oldrev)
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Change some files')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to eq(1)
end
@@ -124,6 +126,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to be_nil
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Initial commit')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to be > 1
end
@@ -156,9 +159,13 @@ describe Git::BranchHooksService do
let(:blank_sha) { Gitlab::Git::BLANK_SHA }
def clears_cache(extended: [])
- expect(ProjectCacheWorker)
- .to receive(:perform_async)
- .with(project.id, extended, %i[commit_count repository_size])
+ expect(service).to receive(:invalidated_file_types).and_return(extended)
+
+ if extended.present?
+ expect(ProjectCacheWorker)
+ .to receive(:perform_async)
+ .with(project.id, extended, [], false)
+ end
service.execute
end
@@ -266,10 +273,10 @@ describe Git::BranchHooksService do
end
describe 'Processing commit messages' do
- # Create 4 commits, 2 of which have references. Limiting to 2 commits, we
- # expect to see one commit message processor enqueued.
- let(:commit_ids) do
- Array.new(4) do |i|
+ # Create 6 commits, 3 of which have references. Limiting to 4 commits, we
+ # expect to see two commit message processors enqueued.
+ let!(:commit_ids) do
+ Array.new(6) do |i|
message = "Issue #{'#' if i.even?}#{i}"
project.repository.update_file(
user, 'README.md', '', message: message, branch_name: branch
@@ -277,18 +284,18 @@ describe Git::BranchHooksService do
end
end
- let(:oldrev) { commit_ids.first }
+ let(:oldrev) { project.commit(commit_ids.first).parent_id }
let(:newrev) { commit_ids.last }
before do
- stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 2)
+ stub_const("::Git::BaseHooksService::PROCESS_COMMIT_LIMIT", 4)
end
context 'creating the default branch' do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -296,7 +303,7 @@ describe Git::BranchHooksService do
context 'updating the default branch' do
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -317,7 +324,7 @@ describe Git::BranchHooksService do
let(:oldrev) { Gitlab::Git::BLANK_SHA }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -327,7 +334,7 @@ describe Git::BranchHooksService do
let(:branch) { 'fix' }
it 'processes a limited number of commit messages' do
- expect(ProcessCommitWorker).to receive(:perform_async).once
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
service.execute
end
@@ -343,6 +350,55 @@ describe Git::BranchHooksService do
service.execute
end
end
+
+ context 'when the project is forked' do
+ let(:upstream_project) { project }
+ let(:forked_project) { fork_project(upstream_project, user, repository: true) }
+
+ let!(:forked_service) do
+ described_class.new(forked_project, user, oldrev: oldrev, newrev: newrev, ref: ref)
+ end
+
+ context 'when commits already exists in the upstream project' do
+ it 'does not process commit messages' do
+ expect(ProcessCommitWorker).not_to receive(:perform_async)
+
+ forked_service.execute
+ end
+ end
+
+ context 'when a commit does not exist in the upstream repo' do
+ # On top of the existing 6 commits, 3 of which have references,
+ # create 2 more, 1 of which has a reference. Limiting to 4 commits, we
+ # expect to see one commit message processor enqueued.
+ let!(:forked_commit_ids) do
+ Array.new(2) do |i|
+ message = "Issue #{'#' if i.even?}#{i}"
+ forked_project.repository.update_file(
+ user, 'README.md', '', message: message, branch_name: branch
+ )
+ end
+ end
+
+ let(:newrev) { forked_commit_ids.last }
+
+ it 'processes the commit message' do
+ expect(ProcessCommitWorker).to receive(:perform_async).once
+
+ forked_service.execute
+ end
+ end
+
+ context 'when the upstream project no longer exists' do
+ it 'processes the commit messages' do
+ upstream_project.destroy!
+
+ expect(ProcessCommitWorker).to receive(:perform_async).twice
+
+ forked_service.execute
+ end
+ end
+ end
end
describe 'New branch detection' do
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 6e39fa6b3c0..d9e607cd251 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -76,9 +76,28 @@ 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 }
- expect(Ci::Pipeline.last).to be_push
+
+ pipeline = Ci::Pipeline.last
+ expect(pipeline).to be_push
+ expect(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref).to eq(ref)
end
end
@@ -123,6 +142,10 @@ describe Git::BranchPushService, services: true do
describe "Webhooks" do
context "execute webhooks" do
+ before do
+ create(:project_hook, push_events: true, project: project)
+ end
+
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index f5938a5c708..e362577d289 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -26,7 +26,8 @@ describe Git::TagHooksService, :service do
describe 'System hooks' do
it 'Executes system hooks' do
- push_data = service.execute
+ push_data = service.send(:push_data)
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect_next_instance_of(SystemHooksService) do |system_hooks_service|
expect(system_hooks_service)
@@ -40,6 +41,7 @@ describe Git::TagHooksService, :service do
describe "Webhooks" do
it "executes hooks on the project" do
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect(project).to receive(:execute_hooks)
service.execute
@@ -61,7 +63,7 @@ describe Git::TagHooksService, :service do
describe 'Push data' do
shared_examples_for 'tag push data expectations' do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
object_kind: 'tag_push',
diff --git a/spec/services/git/tag_push_service_spec.rb b/spec/services/git/tag_push_service_spec.rb
index 418952b52da..7e008637182 100644
--- a/spec/services/git/tag_push_service_spec.rb
+++ b/spec/services/git/tag_push_service_spec.rb
@@ -26,8 +26,8 @@ describe Git::TagPushService do
subject
end
- it 'flushes the tags cache' do
- expect(project.repository).to receive(:expire_tags_cache)
+ it 'does not flush the tags cache' do
+ expect(project.repository).not_to receive(:expire_tags_cache)
subject
end
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
index 7f8ab517cef..7591b2f6f12 100644
--- a/spec/services/groups/auto_devops_service_spec.rb
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -47,7 +47,7 @@ describe Groups::AutoDevopsService, '#execute' do
expect(subgroup_1.auto_devops_enabled?).to eq(false)
end
- context 'when subgroups have projects', :nested_groups do
+ context 'when subgroups have projects' do
it 'reflects changes on projects' do
subgroup_1 = create(:group, parent: group)
project_1 = create(:project, namespace: subgroup_1)
diff --git a/spec/services/groups/create_service_spec.rb b/spec/services/groups/create_service_spec.rb
index c5ff6cdbacd..0f9f20de586 100644
--- a/spec/services/groups/create_service_spec.rb
+++ b/spec/services/groups/create_service_spec.rb
@@ -44,7 +44,7 @@ describe Groups::CreateService, '#execute' do
end
end
- describe 'creating subgroup', :nested_groups do
+ describe 'creating subgroup' do
let!(:group) { create(:group) }
let!(:service) { described_class.new(user, group_params.merge(parent_id: group.id)) }
@@ -54,39 +54,31 @@ describe Groups::CreateService, '#execute' do
end
it { is_expected.to be_persisted }
+ end
- context 'when nested groups feature is disabled' do
- it 'does not save group and returns an error' do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
+ context 'as guest' do
+ it 'does not save group and returns an error' do
+ is_expected.not_to be_persisted
- is_expected.not_to be_persisted
- expect(subject.errors[:parent_id]).to include('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
+ expect(subject.parent_id).to be_nil
end
end
- context 'when nested groups feature is enabled' do
+ context 'as owner' do
before do
- allow(Group).to receive(:supports_nested_objects?).and_return(true)
+ group.add_owner(user)
end
- context 'as guest' do
- it 'does not save group and returns an error' do
- is_expected.not_to be_persisted
+ it { is_expected.to be_persisted }
+ end
- expect(subject.errors[:parent_id].first).to eq('You don’t have permission to create a subgroup in this group.')
- expect(subject.parent_id).to be_nil
- end
+ context 'as maintainer' do
+ before do
+ group.add_maintainer(user)
end
- context 'as owner' do
- before do
- group.add_owner(user)
- end
-
- it { is_expected.to be_persisted }
- end
+ it { is_expected.to be_persisted }
end
end
diff --git a/spec/services/groups/nested_create_service_spec.rb b/spec/services/groups/nested_create_service_spec.rb
index 13acf9e055b..b30392c1b12 100644
--- a/spec/services/groups/nested_create_service_spec.rb
+++ b/spec/services/groups/nested_create_service_spec.rb
@@ -28,35 +28,7 @@ describe Groups::NestedCreateService do
end
end
- describe 'without subgroups' do
- let(:params) { { group_path: 'a-group' } }
-
- before do
- allow(Group).to receive(:supports_nested_objects?) { false }
- end
-
- it 'creates the group' do
- group = service.execute
-
- expect(group).to be_persisted
- end
-
- it 'returns the group if it already existed' do
- existing_group = create(:group, path: 'a-group')
-
- expect(service.execute).to eq(existing_group)
- end
-
- it 'raises an error when tring to create a subgroup' do
- service = described_class.new(user, group_path: 'a-group/a-sub-group')
-
- expect { service.execute }.to raise_error('Nested groups are not supported on MySQL')
- end
-
- it_behaves_like 'with a visibility level'
- end
-
- describe 'with subgroups', :nested_groups do
+ describe 'with subgroups' do
let(:params) { { group_path: 'a-group/a-sub-group' } }
describe "#execute" do
diff --git a/spec/services/groups/transfer_service_spec.rb b/spec/services/groups/transfer_service_spec.rb
index b5708ebba76..f3af8cf5f3b 100644
--- a/spec/services/groups/transfer_service_spec.rb
+++ b/spec/services/groups/transfer_service_spec.rb
@@ -2,28 +2,13 @@
require 'rails_helper'
-describe Groups::TransferService, :postgresql do
+describe Groups::TransferService do
let(:user) { create(:user) }
let(:new_parent_group) { create(:group, :public) }
let!(:group_member) { create(:group_member, :owner, group: group, user: user) }
let(:transfer_service) { described_class.new(group, user) }
shared_examples 'ensuring allowed transfer for a group' do
- context 'with other database than PostgreSQL' do
- before do
- allow(Group).to receive(:supports_nested_objects?).and_return(false)
- end
-
- it 'returns false' do
- expect(transfer_service.execute(new_parent_group)).to be_falsy
- end
-
- it 'adds an error on group' do
- transfer_service.execute(new_parent_group)
- expect(transfer_service.error).to eq('Transfer failed: Database is not supported.')
- end
- end
-
context "when there's an exception on GitLab shell directories" do
let(:new_parent_group) { create(:group, :public) }
diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb
index d081c20f669..12e9c2b2f3a 100644
--- a/spec/services/groups/update_service_spec.rb
+++ b/spec/services/groups/update_service_spec.rb
@@ -86,6 +86,7 @@ describe Groups::UpdateService do
context "unauthorized visibility_level validation" do
let!(:service) { described_class.new(internal_group, user, visibility_level: 99) }
+
before do
internal_group.add_user(user, Gitlab::Access::MAINTAINER)
end
@@ -96,6 +97,20 @@ describe Groups::UpdateService do
end
end
+ context 'when updating #emails_disabled' do
+ let(:service) { described_class.new(internal_group, user, emails_disabled: true) }
+
+ it 'updates the attribute' do
+ internal_group.add_user(user, Gitlab::Access::OWNER)
+
+ expect { service.execute }.to change { internal_group.emails_disabled }.to(true)
+ end
+
+ it 'does not update when not group owner' do
+ expect { service.execute }.not_to change { internal_group.emails_disabled }
+ end
+ end
+
context 'rename group' do
let!(:service) { described_class.new(internal_group, user, path: SecureRandom.hex) }
@@ -133,7 +148,7 @@ describe Groups::UpdateService do
end
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let(:subgroup) { create(:group, :private, parent: private_group) }
context 'when the parent group share_with_group_lock is enabled' do
diff --git a/spec/services/issuable/bulk_update_service_spec.rb b/spec/services/issuable/bulk_update_service_spec.rb
index b0bcd7a36ba..e3a728f2566 100644
--- a/spec/services/issuable/bulk_update_service_spec.rb
+++ b/spec/services/issuable/bulk_update_service_spec.rb
@@ -16,22 +16,174 @@ describe Issuable::BulkUpdateService do
shared_examples 'updates milestones' do
it 'succeeds' do
- result = bulk_update(issues, milestone_id: milestone.id)
+ result = bulk_update(issuables, milestone_id: milestone.id)
expect(result[:success]).to be_truthy
- expect(result[:count]).to eq(issues.count)
+ expect(result[:count]).to eq(issuables.count)
end
- it 'updates the issues milestone' do
- bulk_update(issues, milestone_id: milestone.id)
+ it 'updates the issuables milestone' do
+ bulk_update(issuables, milestone_id: milestone.id)
- issues.each do |issue|
- expect(issue.reload.milestone).to eq(milestone)
+ issuables.each do |issuable|
+ expect(issuable.reload.milestone).to eq(milestone)
end
end
end
- context 'with project issues' do
+ shared_examples 'updating labels' do
+ def create_issue_with_labels(labels)
+ create(:labeled_issue, project: project, labels: labels)
+ end
+
+ let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
+ let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
+ let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
+ let(:issue_no_labels) { create(:issue, project: project) }
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
+
+ let(:labels) { [] }
+ let(:add_labels) { [] }
+ let(:remove_labels) { [] }
+
+ let(:bulk_update_params) do
+ {
+ label_ids: labels.map(&:id),
+ add_label_ids: add_labels.map(&:id),
+ remove_label_ids: remove_labels.map(&:id)
+ }
+ end
+
+ before do
+ bulk_update(issues, bulk_update_params)
+ end
+
+ context 'when label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_no_labels] }
+ let(:labels) { [bug, regression] }
+
+ it 'updates the labels of all issues passed to the labels passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+
+ context 'when those label IDs are empty' do
+ let(:labels) { [] }
+
+ it 'updates the issues passed to have no labels' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+ end
+ end
+
+ context 'when add_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug, regression, merge_requests] }
+
+ it 'adds those label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:remove_labels) { [bug, regression, merge_requests] }
+
+ it 'removes those label IDs from all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when add_label_ids and remove_label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+
+ context 'when add_label_ids and label_ids are passed' do
+ let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
+ let(:labels) { [merge_requests] }
+ let(:add_labels) { [regression] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_no_labels.label_ids).to be_empty
+ end
+ end
+
+ context 'when remove_label_ids and label_ids are passed' do
+ let(:issues) { [issue_no_labels, issue_bug_and_regression] }
+ let(:labels) { [merge_requests] }
+ let(:remove_labels) { [regression] }
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id)
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
+ end
+ end
+
+ context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
+ let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
+ let(:labels) { [regression] }
+ let(:add_labels) { [bug] }
+ let(:remove_labels) { [merge_requests] }
+
+ it 'adds the label IDs to all issues passed' do
+ expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
+ end
+
+ it 'removes the label IDs from all issues passed' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(merge_requests.id)
+ end
+
+ it 'ignores the label IDs parameter' do
+ expect(issues.map(&:reload).flat_map(&:label_ids)).not_to include(regression.id)
+ end
+
+ it 'does not update issues not passed in' do
+ expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
+ end
+ end
+ end
+
+ context 'with issuables at a project level' do
describe 'close issues' do
let(:issues) { create_list(:issue, 2, project: project) }
@@ -171,166 +323,18 @@ describe Issuable::BulkUpdateService do
end
describe 'updating milestones' do
- let(:issues) { [create(:issue, project: project)] }
+ let(:issuables) { [create(:issue, project: project)] }
let(:milestone) { create(:milestone, project: project) }
it_behaves_like 'updates milestones'
end
describe 'updating labels' do
- def create_issue_with_labels(labels)
- create(:labeled_issue, project: project, labels: labels)
- end
-
let(:bug) { create(:label, project: project) }
let(:regression) { create(:label, project: project) }
let(:merge_requests) { create(:label, project: project) }
- let(:issue_all_labels) { create_issue_with_labels([bug, regression, merge_requests]) }
- let(:issue_bug_and_regression) { create_issue_with_labels([bug, regression]) }
- let(:issue_bug_and_merge_requests) { create_issue_with_labels([bug, merge_requests]) }
- let(:issue_no_labels) { create(:issue, project: project) }
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests, issue_no_labels] }
-
- let(:labels) { [] }
- let(:add_labels) { [] }
- let(:remove_labels) { [] }
-
- let(:bulk_update_params) do
- {
- label_ids: labels.map(&:id),
- add_label_ids: add_labels.map(&:id),
- remove_label_ids: remove_labels.map(&:id)
- }
- end
-
- before do
- bulk_update(issues, bulk_update_params)
- end
-
- context 'when label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_no_labels] }
- let(:labels) { [bug, regression] }
-
- it 'updates the labels of all issues passed to the labels passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(match_array(labels.map(&:id)))
- end
-
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
-
- context 'when those label IDs are empty' do
- let(:labels) { [] }
-
- it 'updates the issues passed to have no labels' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
- end
- end
- end
-
- context 'when add_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug, regression, merge_requests] }
-
- it 'adds those label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(*add_labels.map(&:id)))
- end
-
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
-
- context 'when remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:remove_labels) { [bug, regression, merge_requests] }
-
- it 'removes those label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(be_empty)
- end
-
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
-
- context 'when add_label_ids and remove_label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_merge_requests, issue_no_labels] }
- let(:add_labels) { [bug] }
- let(:remove_labels) { [merge_requests] }
-
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
-
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
- end
-
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
-
- context 'when add_label_ids and label_ids are passed' do
- let(:issues) { [issue_all_labels, issue_bug_and_regression, issue_bug_and_merge_requests] }
- let(:labels) { [merge_requests] }
- let(:add_labels) { [regression] }
-
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(regression.id))
- end
-
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
-
- it 'does not update issues not passed in' do
- expect(issue_no_labels.label_ids).to be_empty
- end
- end
-
- context 'when remove_label_ids and label_ids are passed' do
- let(:issues) { [issue_no_labels, issue_bug_and_regression] }
- let(:labels) { [merge_requests] }
- let(:remove_labels) { [regression] }
-
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
- end
-
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
- end
-
- it 'does not update issues not passed in' do
- expect(issue_all_labels.label_ids).to contain_exactly(bug.id, regression.id, merge_requests.id)
- end
- end
-
- context 'when add_label_ids, remove_label_ids, and label_ids are passed' do
- let(:issues) { [issue_bug_and_merge_requests, issue_no_labels] }
- let(:labels) { [regression] }
- let(:add_labels) { [bug] }
- let(:remove_labels) { [merge_requests] }
-
- it 'adds the label IDs to all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids)).to all(include(bug.id))
- end
-
- it 'removes the label IDs from all issues passed' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(merge_requests.id)
- end
-
- it 'ignores the label IDs parameter' do
- expect(issues.map(&:reload).map(&:label_ids).flatten).not_to include(regression.id)
- end
-
- it 'does not update issues not passed in' do
- expect(issue_bug_and_regression.label_ids).to contain_exactly(bug.id, regression.id)
- end
- end
+ it_behaves_like 'updating labels'
end
describe 'subscribe to issues' do
@@ -360,22 +364,45 @@ describe Issuable::BulkUpdateService do
end
end
- context 'with group issues' do
+ context 'with issuables at a group level' do
let(:group) { create(:group) }
- context 'updating milestone' do
+ describe 'updating milestones' do
let(:milestone) { create(:milestone, group: group) }
- let(:project1) { create(:project, :repository, group: group) }
- let(:project2) { create(:project, :repository, group: group) }
- let(:issue1) { create(:issue, project: project1) }
- let(:issue2) { create(:issue, project: project2) }
- let(:issues) { [issue1, issue2] }
+ let(:project) { create(:project, :repository, group: group) }
before do
group.add_maintainer(user)
end
- it_behaves_like 'updates milestones'
+ context 'when issues' do
+ let(:issue1) { create(:issue, project: project) }
+ let(:issue2) { create(:issue, project: project) }
+ let(:issuables) { [issue1, issue2] }
+
+ it_behaves_like 'updates milestones'
+ end
+
+ context 'when merge requests' do
+ let(:merge_request1) { create(:merge_request, source_project: project, source_branch: 'branch-1') }
+ let(:merge_request2) { create(:merge_request, source_project: project, source_branch: 'branch-2') }
+ let(:issuables) { [merge_request1, merge_request2] }
+
+ it_behaves_like 'updates milestones'
+ end
+ end
+
+ describe 'updating labels' do
+ let(:project) { create(:project, :repository, group: group) }
+ let(:bug) { create(:group_label, group: group) }
+ let(:regression) { create(:group_label, group: group) }
+ let(:merge_requests) { create(:group_label, group: group) }
+
+ before do
+ group.add_reporter(user)
+ end
+
+ it_behaves_like 'updating labels'
end
end
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 468e7c286d5..fd9a63b79cc 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -116,7 +116,7 @@ describe Issues::UpdateService, :mailer do
expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
end
- context 'when moving issue between issues from different projects', :nested_groups do
+ context 'when moving issue between issues from different projects' do
let(:group) { create(:group) }
let(:subgroup) { create(:group, parent: group) }
@@ -179,7 +179,7 @@ describe Issues::UpdateService, :mailer do
it 'sends email to user2 about assign of new issue and email to user3 about issue unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
- recipients = deliveries.last(2).map(&:to).flatten
+ recipients = deliveries.last(2).flat_map(&:to)
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(issue.title)
end
@@ -226,6 +226,15 @@ describe Issues::UpdateService, :mailer do
end
end
+ 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('added a Zoom call')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq('added a Zoom call to this issue')
+ end
+
context 'when issue turns confidential' do
let(:opts) do
{
@@ -703,7 +712,7 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when moving an issue ', :nested_groups do
+ context 'when moving an issue ' do
it 'raises an error for invalid move ids within a project' do
opts = { move_between_ids: [9000, 9999] }
diff --git a/spec/services/members/destroy_service_spec.rb b/spec/services/members/destroy_service_spec.rb
index 52f9a305d8f..7dce7f035d4 100644
--- a/spec/services/members/destroy_service_spec.rb
+++ b/spec/services/members/destroy_service_spec.rb
@@ -267,15 +267,15 @@ describe Members::DestroyService do
expect(group.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subgroup membership', :postgresql do
+ it 'removes the subgroup membership' do
expect(subgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubgroup membership', :postgresql do
+ it 'removes the subsubgroup membership' do
expect(subsubgroup.members.map(&:user)).not_to include(member_user)
end
- it 'removes the subsubproject membership', :postgresql do
+ it 'removes the subsubproject membership' do
expect(subsubproject.members.map(&:user)).not_to include(member_user)
end
diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb
index 5c3b209086c..f18239f6d39 100644
--- a/spec/services/merge_requests/build_service_spec.rb
+++ b/spec/services/merge_requests/build_service_spec.rb
@@ -1,5 +1,4 @@
# frozen_string_literal: true
-
require 'spec_helper'
describe MergeRequests::BuildService do
@@ -225,6 +224,11 @@ describe MergeRequests::BuildService do
let(:label_ids) { [label2.id] }
let(:milestone_id) { milestone2.id }
+ before do
+ # Guests are not able to assign labels or milestones to an issue
+ project.add_developer(user)
+ end
+
it 'assigns milestone_id and label_ids instead of issue labels and milestone' do
expect(merge_request.milestone).to eq(milestone2)
expect(merge_request.labels).to match_array([label2])
@@ -479,4 +483,35 @@ describe MergeRequests::BuildService do
end
end
end
+
+ context 'when assigning labels' do
+ let(:label_ids) { [create(:label, project: project).id] }
+
+ context 'for members with less than developer access' do
+ it 'is not allowed' do
+ expect(merge_request.label_ids).to be_empty
+ end
+ end
+
+ context 'for users allowed to assign labels' do
+ before do
+ project.add_developer(user)
+ end
+
+ context 'for labels in the project' do
+ it 'is allowed for developers' do
+ expect(merge_request.label_ids).to contain_exactly(*label_ids)
+ end
+ end
+
+ context 'for unrelated labels' do
+ let(:project_label) { create(:label, project: project) }
+ let(:label_ids) { [create(:label).id, project_label.id] }
+
+ it 'only assigns related labels' do
+ expect(merge_request.label_ids).to contain_exactly(project_label.id)
+ end
+ end
+ end
+ end
end
diff --git a/spec/services/merge_requests/mergeability_check_service_spec.rb b/spec/services/merge_requests/mergeability_check_service_spec.rb
index 6e827f2ea5b..a864da0a6fb 100644
--- a/spec/services/merge_requests/mergeability_check_service_spec.rb
+++ b/spec/services/merge_requests/mergeability_check_service_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-describe MergeRequests::MergeabilityCheckService do
+describe MergeRequests::MergeabilityCheckService, :clean_gitlab_redis_shared_state do
shared_examples_for 'unmergeable merge request' do
it 'updates or keeps merge status as cannot_be_merged' do
subject
@@ -60,24 +60,69 @@ describe MergeRequests::MergeabilityCheckService do
subject { described_class.new(merge_request).execute }
+ def execute_within_threads(amount:, retry_lease: true)
+ threads = []
+
+ amount.times do
+ # Let's use a different object for each thread to get closer
+ # to a real world scenario.
+ mr = MergeRequest.find(merge_request.id)
+
+ threads << Thread.new do
+ described_class.new(mr).execute(retry_lease: retry_lease)
+ end
+ end
+
+ threads.each(&:join)
+ threads
+ end
+
before do
project.add_developer(merge_request.author)
end
it_behaves_like 'mergeable merge request'
- context 'when multiple calls to the service' do
- it 'returns success' do
- subject
- result = subject
+ context 'when lock is disabled' do
+ before do
+ stub_feature_flags(merge_ref_auto_sync_lock: false)
+ end
- expect(result).to be_a(ServiceResponse)
- expect(result.success?).to be(true)
+ it_behaves_like 'mergeable merge request'
+ end
+
+ context 'when concurrent calls' do
+ it 'waits first lock and returns "cached" result in subsequent calls' do
+ threads = execute_within_threads(amount: 3)
+ results = threads.map { |t| t.value.status }
+
+ expect(results).to contain_exactly(:success, :success, :success)
+ end
+
+ it 'writes the merge-ref once' do
+ service = instance_double(MergeRequests::MergeToRefService)
+
+ expect(MergeRequests::MergeToRefService).to receive(:new).once { service }
+ expect(service).to receive(:execute).once.and_return(success: true)
+
+ execute_within_threads(amount: 3)
end
- it 'second call does not change the merge-ref' do
- expect { subject }.to change(merge_request, :merge_ref_head).from(nil)
- expect { subject }.not_to change(merge_request.merge_ref_head, :id)
+ it 'resets one merge request upon execution' do
+ expect_any_instance_of(MergeRequest).to receive(:reset).once
+
+ execute_within_threads(amount: 2)
+ end
+
+ context 'when retry_lease flag is false' do
+ it 'the first call succeeds, subsequent concurrent calls get a lock error response' do
+ threads = execute_within_threads(amount: 3, retry_lease: false)
+ results = threads.map { |t| [t.value.status, t.value.message] }
+
+ expect(results).to contain_exactly([:error, 'Failed to obtain a lock'],
+ [:error, 'Failed to obtain a lock'],
+ [:success, nil])
+ end
end
end
@@ -102,8 +147,7 @@ describe MergeRequests::MergeabilityCheckService do
context 'when broken' do
before do
- allow(merge_request).to receive(:broken?) { true }
- allow(project.repository).to receive(:can_be_merged?) { false }
+ expect(merge_request).to receive(:broken?) { true }
end
it_behaves_like 'unmergeable merge request'
@@ -117,10 +161,13 @@ describe MergeRequests::MergeabilityCheckService do
end
end
- context 'when it has conflicts' do
- before do
- allow(merge_request).to receive(:broken?) { false }
- allow(project.repository).to receive(:can_be_merged?) { false }
+ context 'when it cannot be merged on git' do
+ let(:merge_request) do
+ create(:merge_request,
+ merge_status: :unchecked,
+ source_branch: 'conflict-resolvable',
+ source_project: project,
+ target_branch: 'conflict-start')
end
it_behaves_like 'unmergeable merge request'
diff --git a/spec/services/merge_requests/push_options_handler_service_spec.rb b/spec/services/merge_requests/push_options_handler_service_spec.rb
index 54b9c6dae38..a27fea0c90f 100644
--- a/spec/services/merge_requests/push_options_handler_service_spec.rb
+++ b/spec/services/merge_requests/push_options_handler_service_spec.rb
@@ -11,6 +11,8 @@ describe MergeRequests::PushOptionsHandlerService do
let(:service) { described_class.new(project, user, changes, push_options) }
let(:source_branch) { 'fix' }
let(:target_branch) { 'feature' }
+ let(:title) { 'my title' }
+ let(:description) { 'my description' }
let(:new_branch_changes) { "#{Gitlab::Git::BLANK_SHA} 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:existing_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/#{source_branch}" }
let(:deleted_branch_changes) { "d14d6c0abdd253381df51a723d58691b2ee1ab08 #{Gitlab::Git::BLANK_SHA} refs/heads/#{source_branch}" }
@@ -73,6 +75,26 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can set the title of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the title' do
+ service.execute
+
+ expect(last_mr.title).to eq(title)
+ end
+ end
+
+ shared_examples_for 'a service that can set the description of a merge request' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'sets the description' do
+ service.execute
+
+ expect(last_mr.description).to eq(description)
+ end
+ end
+
shared_examples_for 'a service that can set the merge request to merge when pipeline succeeds' do
subject(:last_mr) { MergeRequest.last }
@@ -90,6 +112,16 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ shared_examples_for 'a service that can remove the source branch when it is merged' do
+ subject(:last_mr) { MergeRequest.last }
+
+ it 'returns true to force_remove_source_branch?' do
+ service.execute
+
+ expect(last_mr.force_remove_source_branch?).to eq(true)
+ end
+ end
+
shared_examples_for 'a service that does not create a merge request' do
it do
expect { service.execute }.not_to change { MergeRequest.count }
@@ -208,6 +240,72 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`remove_source_branch` push option' do
+ let(:push_options) { { remove_source_branch: true } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, remove_source_branch: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, remove_source_branch: true } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can remove the source branch when it is merged'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe '`target` push option' do
let(:push_options) { { target: target_branch } }
@@ -274,6 +372,138 @@ describe MergeRequests::PushOptionsHandlerService do
end
end
+ describe '`title` push option' do
+ let(:push_options) { { title: title } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, title: title } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the title of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
+ describe '`description` push option' do
+ let(:push_options) { { description: description } }
+
+ context 'with a new branch' do
+ let(:changes) { new_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch but no open MR' do
+ let(:changes) { existing_branch_changes }
+
+ it_behaves_like 'a service that does not create a merge request'
+
+ it 'adds an error to the service' do
+ error = "A merge_request.create push option is required to create a merge request for branch #{source_branch}"
+
+ service.execute
+
+ expect(service.errors).to include(error)
+ end
+
+ context 'when coupled with the `create` push option' do
+ let(:push_options) { { create: true, description: description } }
+
+ it_behaves_like 'a service that can create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+ end
+
+ context 'with an existing branch that has a merge request open' do
+ let(:changes) { existing_branch_changes }
+ let!(:merge_request) { create(:merge_request, source_project: project, source_branch: source_branch)}
+
+ it_behaves_like 'a service that does not create a merge request'
+ it_behaves_like 'a service that can set the description of a merge request'
+ end
+
+ context 'with a deleted branch' do
+ let(:changes) { deleted_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+
+ context 'with the project default branch' do
+ let(:changes) { default_branch_changes }
+
+ it_behaves_like 'a service that does nothing'
+ end
+ end
+
describe 'multiple pushed branches' do
let(:push_options) { { create: true } }
let(:changes) do
diff --git a/spec/services/merge_requests/rebase_service_spec.rb b/spec/services/merge_requests/rebase_service_spec.rb
index ee9caaf2f47..7b8c94c86fe 100644
--- a/spec/services/merge_requests/rebase_service_spec.rb
+++ b/spec/services/merge_requests/rebase_service_spec.rb
@@ -25,7 +25,7 @@ describe MergeRequests::RebaseService do
describe '#execute' do
context 'when another rebase is already in progress' do
before do
- allow(merge_request).to receive(:gitaly_rebase_in_progress?).and_return(true)
+ allow(repository).to receive(:rebase_in_progress?).with(merge_request.id).and_return(true)
end
it 'saves the error message' do
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 7e5837a4798..9688e02d6ac 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -91,7 +91,8 @@ describe MergeRequests::UpdateService, :mailer do
labels: [],
mentioned_users: [user2],
assignees: [user3],
- total_time_spent: 0
+ total_time_spent: 0,
+ description: "FYI #{user2.to_reference}"
}
)
end
@@ -99,7 +100,7 @@ describe MergeRequests::UpdateService, :mailer do
it 'sends email to user2 about assign of new merge request and email to user3 about merge request unassignment' do
deliveries = ActionMailer::Base.deliveries
email = deliveries.last
- recipients = deliveries.last(2).map(&:to).flatten
+ recipients = deliveries.last(2).flat_map(&:to)
expect(recipients).to include(user2.email, user3.email)
expect(email.subject).to include(merge_request.title)
end
diff --git a/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
new file mode 100644
index 00000000000..53b7497ae21
--- /dev/null
+++ b/spec/services/metrics/dashboard/custom_metric_embed_service_spec.rb
@@ -0,0 +1,145 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::CustomMetricEmbedService do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ let(:dashboard_path) { system_dashboard_path }
+ let(:group) { business_metric_title }
+ let(:title) { 'title' }
+ let(:y_label) { 'y_label' }
+
+ describe '.valid_params?' do
+ let(:valid_params) do
+ {
+ embedded: true,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ end
+
+ subject { described_class.valid_params?(params) }
+
+ let(:params) { valid_params }
+
+ it { is_expected.to be_truthy }
+
+ context 'not embedded' do
+ let(:params) { valid_params.except(:embedded) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'non-system dashboard' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined dashboard' do
+ let(:params) { valid_params.except(:dashboard_path) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'non-custom metric group' do
+ let(:group) { 'Different Group' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing group' do
+ let(:group) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing title' do
+ let(:title) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined y-axis label' do
+ let(:params) { valid_params.except(:y_label) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ embedded: true,
+ environment: environment,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ ]
+ end
+
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ context 'the custom metric exists' do
+ let!(:metric) { create(:prometheus_metric, project: project) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+
+ it 'does not cache the unprocessed dashboard' do
+ expect(Gitlab::Metrics::Dashboard::Cache).not_to receive(:fetch)
+
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'multiple metrics meet criteria' do
+ let!(:metric_2) { create(:prometheus_metric, project: project, query: 'avg(metric_2)') }
+
+ it_behaves_like 'valid embedded dashboard service response'
+
+ it 'includes both metrics' do
+ result = service_call
+ included_queries = all_queries(result[:dashboard])
+
+ expect(included_queries).to include('avg(metric_2)', 'avg(metric)')
+ end
+ end
+ end
+
+ context 'when the metric exists in another project' do
+ let!(:metric) { create(:prometheus_metric, project: create(:project)) }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+ end
+
+ private
+
+ def all_queries(dashboard)
+ dashboard[:panel_groups].flat_map do |group|
+ group[:panels].flat_map do |panel|
+ panel[:metrics].map do |metric|
+ metric[:query_range]
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/default_embed_service_spec.rb b/spec/services/metrics/dashboard/default_embed_service_spec.rb
new file mode 100644
index 00000000000..803b9a93be7
--- /dev/null
+++ b/spec/services/metrics/dashboard/default_embed_service_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::DefaultEmbedService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) { [project, user, { environment: environment }] }
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ system_service = Metrics::Dashboard::SystemDashboardService
+
+ expect(system_service).to receive(:new).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'when called with a non-system dashboard' do
+ let(:dashboard_path) { 'garbage/dashboard/path' }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
new file mode 100644
index 00000000000..a0f7315f750
--- /dev/null
+++ b/spec/services/metrics/dashboard/dynamic_embed_service_spec.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::DynamicEmbedService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:project) { build(:project) }
+ set(:user) { create(:user) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:group) { 'Group A' }
+ let(:title) { 'Super Chart A1' }
+ let(:y_label) { 'y_label' }
+
+ describe '.valid_params?' do
+ let(:valid_params) do
+ {
+ embedded: true,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ end
+
+ subject { described_class.valid_params?(params) }
+
+ let(:params) { valid_params }
+
+ it { is_expected.to be_truthy }
+
+ context 'not embedded' do
+ let(:params) { valid_params.except(:embedded) }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined dashboard' do
+ let(:params) { valid_params.except(:dashboard_path) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing dashboard' do
+ let(:dashboard) { '' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'missing group' do
+ let(:group) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'missing title' do
+ let(:title) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'undefined y-axis label' do
+ let(:params) { valid_params.except(:y_label) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#get_dashboard' do
+ let(:service_params) do
+ [
+ project,
+ user,
+ {
+ environment: environment,
+ dashboard_path: dashboard_path,
+ group: group,
+ title: title,
+ y_label: y_label
+ }
+ ]
+ end
+
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ context 'when the dashboard does not exist' do
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the dashboard is exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid embedded dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect(YAML).to receive(:safe_load).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'when the specified group is not present on the dashboard' do
+ let(:group) { 'Group Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the specified title is not present on the dashboard' do
+ let(:title) { 'Title Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ context 'when the specified y-axis label is not present on the dashboard' do
+ let(:y_label) { 'Y-Axis Not Found' }
+
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+ end
+
+ shared_examples 'uses system dashboard' do
+ it 'uses the default dashboard' do
+ expect(Gitlab::Metrics::Dashboard::Finder)
+ .to receive(:find_raw)
+ .with(project, dashboard_path: system_dashboard_path)
+ .once
+
+ service_call
+ end
+ end
+
+ context 'when the dashboard is nil' do
+ let(:dashboard_path) { nil }
+
+ it_behaves_like 'uses system dashboard'
+ end
+
+ context 'when the dashboard is not present' do
+ let(:dashboard_path) { '' }
+
+ it_behaves_like 'uses system dashboard'
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/project_dashboard_service_spec.rb b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
new file mode 100644
index 00000000000..1357914be2a
--- /dev/null
+++ b/spec/services/metrics/dashboard/project_dashboard_service_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe Metrics::Dashboard::ProjectDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'get_dashboard' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ context 'when the dashboard does not exist' do
+ it_behaves_like 'misconfigured dashboard service response', :not_found
+ end
+
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ context 'when the dashboard exists' do
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it_behaves_like 'valid dashboard service response'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect_any_instance_of(described_class)
+ .to receive(:get_raw_dashboard)
+ .once
+ .and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'and the dashboard is then deleted' do
+ it 'does not return the previously cached dashboard' do
+ described_class.new(*service_params).get_dashboard
+
+ delete_project_dashboard(project, user, dashboard_path)
+
+ expect_any_instance_of(described_class)
+ .to receive(:get_raw_dashboard)
+ .once
+ .and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ end
+ end
+ end
+
+ context 'when the dashboard is configured incorrectly' do
+ let(:project) { project_with_dashboard(dashboard_path, {}) }
+
+ it_behaves_like 'misconfigured dashboard service response', :unprocessable_entity
+ end
+ end
+
+ describe '::all_dashboard_paths' do
+ let(:all_dashboards) { described_class.all_dashboard_paths(project) }
+
+ context 'when there are no project dashboards' do
+ it 'returns an empty array' do
+ expect(all_dashboards).to be_empty
+ end
+ end
+
+ context 'when there are project dashboards available' do
+ let(:dashboard_path) { '.gitlab/dashboards/test.yml' }
+ let(:project) { project_with_dashboard(dashboard_path) }
+
+ it 'returns the dashboard attributes' do
+ expect(all_dashboards).to eq(
+ [{
+ path: dashboard_path,
+ display_name: 'test.yml',
+ default: false
+ }]
+ )
+ end
+ end
+ end
+end
diff --git a/spec/services/metrics/dashboard/system_dashboard_service_spec.rb b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
new file mode 100644
index 00000000000..8be3e7f6064
--- /dev/null
+++ b/spec/services/metrics/dashboard/system_dashboard_service_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Metrics::Dashboard::SystemDashboardService, :use_clean_rails_memory_store_caching do
+ include MetricsDashboardHelpers
+
+ set(:user) { create(:user) }
+ set(:project) { create(:project) }
+ set(:environment) { create(:environment, project: project) }
+
+ before do
+ project.add_maintainer(user)
+ end
+
+ describe 'get_dashboard' do
+ let(:dashboard_path) { described_class::SYSTEM_DASHBOARD_PATH }
+ let(:service_params) { [project, user, { environment: environment, dashboard_path: dashboard_path }] }
+ let(:service_call) { described_class.new(*service_params).get_dashboard }
+
+ it_behaves_like 'valid dashboard service response'
+ it_behaves_like 'raises error for users with insufficient permissions'
+
+ it 'caches the unprocessed dashboard for subsequent calls' do
+ expect(YAML).to receive(:safe_load).once.and_call_original
+
+ described_class.new(*service_params).get_dashboard
+ described_class.new(*service_params).get_dashboard
+ end
+
+ context 'when called with a non-system dashboard' do
+ let(:dashboard_path) { 'garbage/dashboard/path' }
+
+ # We want to alwaus return the system dashboard.
+ it_behaves_like 'valid dashboard service response'
+ end
+ end
+
+ describe '::all_dashboard_paths' do
+ it 'returns the dashboard attributes' do
+ all_dashboards = described_class.all_dashboard_paths(project)
+
+ expect(all_dashboards).to eq(
+ [{
+ path: described_class::SYSTEM_DASHBOARD_PATH,
+ display_name: described_class::SYSTEM_DASHBOARD_NAME,
+ default: true
+ }]
+ )
+ end
+ end
+end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 46abd8f356a..cd4ea9c401d 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -365,5 +365,43 @@ describe Notes::CreateService do
.and change { existing_note.updated_at }
end
end
+
+ describe "usage counter" do
+ let(:counter) { Gitlab::UsageDataCounters::NoteCounter }
+
+ context 'snippet note' do
+ let(:snippet) { create(:project_snippet, project: project) }
+ let(:opts) { { note: 'reply', noteable_type: 'Snippet', noteable_id: snippet.id, project: project } }
+
+ it 'increments usage counter' do
+ expect do
+ note = described_class.new(project, user, opts).execute
+
+ expect(note).to be_valid
+ end.to change { counter.read(:create, opts[:noteable_type]) }.by 1
+ end
+
+ it 'does not increment usage counter when creation fails' do
+ expect do
+ note = described_class.new(project, user, { note: '' }).execute
+
+ expect(note).to be_invalid
+ end.not_to change { counter.read(:create, opts[:noteable_type]) }
+ end
+ end
+
+ context 'issue note' do
+ let(:issue) { create(:issue, project: project) }
+ let(:opts) { { note: 'reply', noteable_type: 'Issue', noteable_id: issue.id, project: project } }
+
+ it 'does not increment usage counter' do
+ expect do
+ note = described_class.new(project, user, opts).execute
+
+ expect(note).to be_valid
+ end.not_to change { counter.read(:create, opts[:noteable_type]) }
+ end
+ end
+ end
end
end
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 3e3de051732..d925aa2b6c3 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -111,7 +111,7 @@ describe NotificationService, :mailer do
should_email(participant)
end
- context 'for subgroups', :nested_groups do
+ context 'for subgroups' do
before do
build_group(project)
end
@@ -240,45 +240,50 @@ describe NotificationService, :mailer do
end
describe '#new_note' do
- it do
- add_users(project)
- add_user_subscriptions(issue)
- reset_delivered_emails!
+ context do
+ before do
+ add_users(project)
+ add_user_subscriptions(issue)
+ reset_delivered_emails!
+ end
- expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
+ it do
+ expect(SentNotification).to receive(:record).with(issue, any_args).exactly(10).times
- notification.new_note(note)
+ notification.new_note(note)
- should_email(@u_watcher)
- should_email(note.noteable.author)
- should_email(note.noteable.assignees.first)
- should_email(@u_custom_global)
- should_email(@u_mentioned)
- should_email(@subscriber)
- should_email(@watcher_and_subscriber)
- should_email(@subscribed_participant)
- should_email(@u_custom_off)
- should_email(@unsubscribed_mentioned)
- should_not_email(@u_guest_custom)
- should_not_email(@u_guest_watcher)
- should_not_email(note.author)
- should_not_email(@u_participating)
- should_not_email(@u_disabled)
- should_not_email(@unsubscriber)
- should_not_email(@u_outsider_mentioned)
- should_not_email(@u_lazy_participant)
- end
+ should_email(@u_watcher)
+ should_email(note.noteable.author)
+ should_email(note.noteable.assignees.first)
+ should_email(@u_custom_global)
+ should_email(@u_mentioned)
+ should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
+ should_email(@subscribed_participant)
+ should_email(@u_custom_off)
+ should_email(@unsubscribed_mentioned)
+ should_not_email(@u_guest_custom)
+ should_not_email(@u_guest_watcher)
+ should_not_email(note.author)
+ should_not_email(@u_participating)
+ should_not_email(@u_disabled)
+ should_not_email(@unsubscriber)
+ should_not_email(@u_outsider_mentioned)
+ should_not_email(@u_lazy_participant)
+ end
- it "emails the note author if they've opted into notifications about their activity" do
- add_users(project)
- add_user_subscriptions(issue)
- reset_delivered_emails!
+ it "emails the note author if they've opted into notifications about their activity" do
+ note.author.notified_of_own_activity = true
- note.author.notified_of_own_activity = true
+ notification.new_note(note)
- notification.new_note(note)
+ should_email(note.author)
+ end
- should_email(note.author)
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
it 'filters out "mentioned in" notes' do
@@ -337,7 +342,12 @@ describe NotificationService, :mailer do
it_behaves_like 'new note notifications'
- context 'which is a subgroup', :nested_groups do
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
+
+ context 'which is a subgroup' do
let!(:parent) { create(:group) }
let!(:group) { create(:group, parent: parent) }
@@ -388,7 +398,7 @@ describe NotificationService, :mailer do
should_email(admin)
end
- context 'on project that belongs to subgroup', :nested_groups do
+ context 'on project that belongs to subgroup' do
let(:group_reporter) { create(:user) }
let(:group_guest) { create(:user) }
let(:parent_group) { create(:group) }
@@ -458,7 +468,7 @@ describe NotificationService, :mailer do
should_not_email_nested_group_user(@pg_disabled)
end
- it 'notifies parent group members with mention level', :nested_groups do
+ it 'notifies parent group members with mention level' do
note = create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: "@#{@pg_mention.username}")
notification.new_note(note)
@@ -472,6 +482,11 @@ describe NotificationService, :mailer do
expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
@@ -619,6 +634,11 @@ describe NotificationService, :mailer do
notification.new_note(note)
should_not_email(@u_committer)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
@@ -645,6 +665,11 @@ describe NotificationService, :mailer do
.to contain_exactly(*merge_request.assignees.pluck(:id), merge_request.author.id, @u_watcher.id)
expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { note }
+ let(:notification_trigger) { notification.new_note(note) }
+ end
end
end
end
@@ -819,6 +844,11 @@ describe NotificationService, :mailer do
should_email(user_4)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.new_issue(issue, @u_disabled) }
+ end
+
context 'confidential issues' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
@@ -861,6 +891,11 @@ describe NotificationService, :mailer do
let(:mentionable) { issue }
include_examples 'notifications for new mentions'
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned) }
+ end
end
describe '#reassigned_issue' do
@@ -969,6 +1004,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled, [assignee]) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled, [assignee]) }
+ end
end
describe '#relabeled_issue' do
@@ -1028,6 +1068,11 @@ describe NotificationService, :mailer do
should_email(subscriber_to_both)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.relabeled_issue(issue, [group_label_2, label_2], @u_disabled) }
+ end
+
context 'confidential issues' do
let(:author) { create(:user) }
let(:assignee) { create(:user) }
@@ -1065,12 +1110,19 @@ describe NotificationService, :mailer do
end
describe '#removed_milestone_issue' do
- it_behaves_like 'altered milestone notification on issue' do
+ context do
let(:milestone) { create(:milestone, project: project, issues: [issue]) }
let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
- before do
- notification.removed_milestone_issue(issue, issue.author)
+ it_behaves_like 'altered milestone notification on issue' do
+ before do
+ notification.removed_milestone_issue(issue, issue.author)
+ end
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.removed_milestone_issue(issue, issue.author) }
end
end
@@ -1110,12 +1162,19 @@ describe NotificationService, :mailer do
end
describe '#changed_milestone_issue' do
- it_behaves_like 'altered milestone notification on issue' do
+ context do
let(:new_milestone) { create(:milestone, project: project, issues: [issue]) }
let!(:subscriber_to_new_milestone) { create(:user) { |u| issue.toggle_subscription(u, project) } }
- before do
- notification.changed_milestone_issue(issue, new_milestone, issue.author)
+ it_behaves_like 'altered milestone notification on issue' do
+ before do
+ notification.changed_milestone_issue(issue, new_milestone, issue.author)
+ end
+ end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.changed_milestone_issue(issue, new_milestone, issue.author) }
end
end
@@ -1183,6 +1242,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.close_issue(issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.close_issue(issue, @u_disabled) }
+ end
end
describe '#reopen_issue' do
@@ -1214,6 +1278,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) }
+ end
end
describe '#issue_moved' do
@@ -1240,6 +1309,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.issue_moved(issue, new_issue, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.issue_moved(issue, new_issue, @u_disabled) }
+ end
end
describe '#issue_due' do
@@ -1280,6 +1354,11 @@ describe NotificationService, :mailer do
let(:issuable) { issue }
let(:notification_trigger) { notification.issue_due(issue) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { issue }
+ let(:notification_trigger) { notification.issue_due(issue) }
+ end
end
end
@@ -1374,6 +1453,11 @@ describe NotificationService, :mailer do
should_email(user_4)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) }
+ end
+
context 'participating' do
it_should_behave_like 'participating by assignee notification' do
let(:participant) { create(:user, username: 'user-participant')}
@@ -1406,6 +1490,11 @@ describe NotificationService, :mailer do
let(:mentionable) { merge_request }
include_examples 'notifications for new mentions'
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { send_notifications(@u_watcher, @u_participant_mentioned, @u_custom_global, @u_mentioned) }
+ end
end
describe '#reassigned_merge_request' do
@@ -1449,6 +1538,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.reassigned_merge_request(merge_request, current_user, [assignee]) }
+ end
end
describe '#push_to_merge_request' do
@@ -1479,6 +1573,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.push_to_merge_request(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.push_to_merge_request(merge_request, @u_disabled) }
+ end
end
describe '#relabel_merge_request' do
@@ -1512,28 +1611,43 @@ describe NotificationService, :mailer do
should_not_email(@u_participating)
should_not_email(@u_lazy_participant)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.relabeled_merge_request(merge_request, [group_label_2, label_2], @u_disabled) }
+ end
end
describe '#removed_milestone_merge_request' do
- it_behaves_like 'altered milestone notification on merge request' do
- let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
- let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ let(:milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ it_behaves_like 'altered milestone notification on merge request' do
before do
notification.removed_milestone_merge_request(merge_request, merge_request.author)
end
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.removed_milestone_merge_request(merge_request, merge_request.author) }
+ end
end
describe '#changed_milestone_merge_request' do
- it_behaves_like 'altered milestone notification on merge request' do
- let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
- let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ let(:new_milestone) { create(:milestone, project: project, merge_requests: [merge_request]) }
+ let!(:subscriber_to_new_milestone) { create(:user) { |u| merge_request.toggle_subscription(u, project) } }
+ it_behaves_like 'altered milestone notification on merge request' do
before do
notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author)
end
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.changed_milestone_merge_request(merge_request, new_milestone, merge_request.author) }
+ end
end
describe '#merge_request_unmergeable' do
@@ -1544,6 +1658,11 @@ describe NotificationService, :mailer do
expect(email_recipients.size).to eq(1)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_request_unmergeable(merge_request) }
+ end
+
describe 'when merge_when_pipeline_succeeds is true' do
before do
merge_request.update(
@@ -1590,6 +1709,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) }
+ end
end
describe '#merged_merge_request' do
@@ -1642,6 +1766,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) }
+ end
end
describe '#reopen_merge_request' do
@@ -1672,6 +1801,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) }
+ end
end
describe "#resolve_all_discussions" do
@@ -1695,6 +1829,11 @@ describe NotificationService, :mailer do
let(:issuable) { merge_request }
let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { merge_request }
+ let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) }
+ end
end
end
@@ -1719,6 +1858,11 @@ describe NotificationService, :mailer do
should_not_email(@u_disabled)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_was_moved(project, "gitlab/gitlab") }
+ end
+
context 'users not having access to the new location' do
it 'does not send email' do
old_user = create(:user)
@@ -1762,6 +1906,11 @@ describe NotificationService, :mailer do
should_only_email(@u_participating)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_exported(project, @u_participating) }
+ end
end
describe '#project_not_exported' do
@@ -1770,6 +1919,11 @@ describe NotificationService, :mailer do
should_only_email(@u_participating)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.project_not_exported(project, @u_participating, ['error']) }
+ end
end
end
end
@@ -1800,6 +1954,11 @@ describe NotificationService, :mailer do
should_email(maintainer)
should_not_email(developer)
end
+
+ it_behaves_like 'group emails are disabled' do
+ let(:notification_target) { group }
+ let(:notification_trigger) { group.request_access(added_user) }
+ end
end
describe '#decline_group_invite' do
@@ -1839,6 +1998,11 @@ describe NotificationService, :mailer do
should_not_email_anyone
end
end
+
+ it_behaves_like 'group emails are disabled' do
+ let(:notification_target) { group }
+ let(:notification_trigger) { group.add_guest(added_user) }
+ end
end
end
@@ -1859,6 +2023,11 @@ describe NotificationService, :mailer do
should_only_email(project.owner)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { project.request_access(added_user) }
+ end
end
context 'for a project in a group' do
@@ -1878,7 +2047,7 @@ describe NotificationService, :mailer do
end
end
- describe '#decline_group_invite' do
+ describe '#decline_project_invite' do
let(:member) { create(:user) }
before do
@@ -1900,6 +2069,11 @@ describe NotificationService, :mailer do
should_only_email(added_user)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { create_member! }
+ end
+
context 'when notifications are disabled' do
before do
create_global_setting_for(added_user, :disabled)
@@ -2063,27 +2237,69 @@ describe NotificationService, :mailer do
end
context 'when the creator has custom notifications enabled' do
- before do
- pipeline = create_pipeline(u_custom_notification_enabled, :success)
- notification.pipeline_finished(pipeline)
- end
+ let(:pipeline) { create_pipeline(u_custom_notification_enabled, :success) }
it 'emails only the creator' do
+ notification.pipeline_finished(pipeline)
+
should_only_email(u_custom_notification_enabled, kind: :bcc)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { pipeline }
+ let(:notification_trigger) { notification.pipeline_finished(pipeline) }
+ end
+
+ context 'when the creator has group notification email set' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ before do
+ group = create(:group)
+
+ project.update(group: group)
+ create(:notification_setting, user: u_custom_notification_enabled, source: group, notification_email: group_notification_email)
+ end
+
+ it 'sends to group notification email' do
+ notification.pipeline_finished(pipeline)
+
+ expect(email_recipients(kind: :bcc).first).to eq(group_notification_email)
+ end
+ end
end
end
context 'with a failed pipeline' do
context 'when the creator has no custom notification set' do
- before do
- pipeline = create_pipeline(u_member, :failed)
- notification.pipeline_finished(pipeline)
- end
+ let(:pipeline) { create_pipeline(u_member, :failed) }
it 'emails only the creator' do
+ notification.pipeline_finished(pipeline)
+
should_only_email(u_member, kind: :bcc)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { pipeline }
+ let(:notification_trigger) { notification.pipeline_finished(pipeline) }
+ end
+
+ context 'when the creator has group notification email set' do
+ let(:group_notification_email) { 'user+group@example.com' }
+
+ before do
+ group = create(:group)
+
+ project.update(group: group)
+ create(:notification_setting, user: u_member, source: group, notification_email: group_notification_email)
+ end
+
+ it 'sends to group notification email' do
+ notification.pipeline_finished(pipeline)
+
+ expect(email_recipients(kind: :bcc).first).to eq(group_notification_email)
+ end
+ end
end
context 'when the creator has watch set' do
@@ -2183,6 +2399,11 @@ describe NotificationService, :mailer do
should_only_email(u_maintainer1, u_maintainer2, u_owner)
end
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { domain }
+ let(:notification_trigger) { notify! }
+ end
+
it 'emails nobody if the project is missing' do
domain.project = nil
@@ -2192,30 +2413,6 @@ describe NotificationService, :mailer do
end
end
end
-
- describe '#pages_domain_verification_failed' do
- it 'emails current watching maintainers' do
- notification.pages_domain_verification_failed(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
-
- describe '#pages_domain_enabled' do
- it 'emails current watching maintainers' do
- notification.pages_domain_enabled(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
-
- describe '#pages_domain_disabled' do
- it 'emails current watching maintainers' do
- notification.pages_domain_disabled(domain)
-
- should_only_email(u_maintainer1, u_maintainer2, u_owner)
- end
- end
end
context 'Auto DevOps notifications' do
@@ -2234,6 +2431,11 @@ describe NotificationService, :mailer do
should_email(owner, times: 1) # Once for the disable pipeline.
should_email(pipeline_user, times: 2) # Once for the new permission, once for the disable.
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.autodevops_disabled(pipeline, [owner.email, pipeline_user.email]) }
+ end
end
end
@@ -2247,6 +2449,11 @@ describe NotificationService, :mailer do
should_email(user)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.repository_cleanup_success(project, user) }
+ end
end
describe '#repository_cleanup_failure' do
@@ -2255,6 +2462,11 @@ describe NotificationService, :mailer do
should_email(user)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.repository_cleanup_failure(project, user, 'Some error') }
+ end
end
end
@@ -2288,6 +2500,11 @@ describe NotificationService, :mailer do
should_only_email(u_maintainer1, u_maintainer2, u_owner)
end
+
+ it_behaves_like 'project emails are disabled' do
+ let(:notification_target) { project }
+ let(:notification_trigger) { notification.remote_mirror_update_failed(remote_mirror) }
+ end
end
end
@@ -2378,52 +2595,40 @@ describe NotificationService, :mailer do
group
end
- # Creates a nested group only if supported
- # to avoid errors on MySQL
def create_nested_group(visibility)
- if Group.supports_nested_objects?
- parent_group = create(:group, visibility)
- child_group = create(:group, visibility, parent: parent_group)
+ parent_group = create(:group, visibility)
+ child_group = create(:group, visibility, parent: parent_group)
- # Parent group member: global=disabled, parent_group=watch, child_group=global
- @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
- @pg_watcher.notification_settings_for(nil).disabled!
+ # Parent group member: global=disabled, parent_group=watch, child_group=global
+ @pg_watcher ||= create_user_with_notification(:watch, 'parent_group_watcher', parent_group)
+ @pg_watcher.notification_settings_for(nil).disabled!
- # Parent group member: global=global, parent_group=disabled, child_group=global
- @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
- @pg_disabled.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=disabled, child_group=global
+ @pg_disabled ||= create_user_with_notification(:disabled, 'parent_group_disabled', parent_group)
+ @pg_disabled.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=mention, child_group=global
- @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=mention, child_group=global
+ @pg_mention ||= create_user_with_notification(:mention, 'parent_group_mention', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- # Parent group member: global=global, parent_group=participating, child_group=global
- @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
- @pg_mention.notification_settings_for(nil).global!
+ # Parent group member: global=global, parent_group=participating, child_group=global
+ @pg_participant ||= create_user_with_notification(:participating, 'parent_group_participant', parent_group)
+ @pg_mention.notification_settings_for(nil).global!
- child_group
- else
- create(:group, visibility)
- end
+ child_group
end
def add_member_for_parent_group(user, project)
- return unless Group.supports_nested_objects?
-
project.reload
project.group.parent.add_maintainer(user)
end
def should_email_nested_group_user(user, times: 1, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_email(user, times: 1, recipients: email_recipients)
end
def should_not_email_nested_group_user(user, recipients: email_recipients)
- return unless Group.supports_nested_objects?
-
should_not_email(user, recipients: email_recipients)
end
diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb
index 2f70c8ea94d..b625653bc77 100644
--- a/spec/services/projects/autocomplete_service_spec.rb
+++ b/spec/services/projects/autocomplete_service_spec.rb
@@ -118,7 +118,7 @@ describe Projects::AutocompleteService do
expect(milestone_titles).to eq([group_milestone2.title, group_milestone1.title])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :public, parent: group) }
let!(:subgroup_milestone) { create(:milestone, group: subgroup) }
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/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index 3af7ee3ad50..9a6f64b825a 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -121,7 +121,22 @@ describe Projects::DestroyService do
it { expect(Dir.exist?(remove_path)).to be_truthy }
end
- context 'when flushing caches fail' do
+ context 'when flushing caches fail due to Git errors' do
+ before do
+ allow(project.repository).to receive(:before_delete).and_raise(::Gitlab::Git::CommandError)
+ allow(Gitlab::GitLogger).to receive(:warn).with(
+ class: described_class.name,
+ project_id: project.id,
+ disk_path: project.disk_path,
+ message: 'Gitlab::Git::CommandError').and_call_original
+
+ perform_enqueued_jobs { destroy_project(project, user, {}) }
+ end
+
+ it_behaves_like 'deleting the project'
+ end
+
+ context 'when flushing caches fail due to Redis' do
before do
new_user = create(:user)
project.team.add_user(new_user, Gitlab::Access::DEVELOPER)
@@ -226,6 +241,18 @@ describe Projects::DestroyService do
expect(destroy_project(project, user)).to be false
end
end
+
+ context 'when registry is disabled' do
+ before do
+ stub_container_registry_config(enabled: false)
+ end
+
+ it 'does not attempting to remove any tags' do
+ expect(Projects::ContainerRepository::DestroyService).not_to receive(:new)
+
+ destroy_project(project, user)
+ end
+ end
end
context 'when there are tags for legacy root repository' do
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index f25233ceeb1..06efc2ff825 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -20,13 +20,8 @@ describe Projects::DownloadService do
context 'for URLs that are on the whitelist' do
before do
- sham_rack_app = ShamRack.at('mycompany.fogbugz.com').stub
- sham_rack_app.register_resource('/rails_sample.jpg', File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'), 'image/jpg')
- sham_rack_app.register_resource('/doc_sample.txt', File.read(Rails.root + 'spec/fixtures/doc_sample.txt'), 'text/plain')
- end
-
- after do
- ShamRack.unmount_all
+ stub_request(:get, 'http://mycompany.fogbugz.com/rails_sample.jpg').to_return(body: File.read(Rails.root + 'spec/fixtures/rails_sample.jpg'))
+ stub_request(:get, 'http://mycompany.fogbugz.com/doc_sample.txt').to_return(body: File.read(Rails.root + 'spec/fixtures/doc_sample.txt'))
end
context 'an image file' do
diff --git a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
index 80debcd3a7a..dabfd61d3f5 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb
@@ -33,7 +33,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
before do
allow(project).to receive(:lfs_enabled?).and_return(true)
- response = instance_double(HTTParty::Response)
+ response = instance_double(Gitlab::HTTP::Response)
allow(response).to receive(:body).and_return(objects_response.to_json)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
@@ -95,7 +95,7 @@ describe Projects::LfsPointers::LfsDownloadLinkListService do
shared_examples 'JSON parse errors' do |body|
it 'raises error' do
- response = instance_double(HTTParty::Response)
+ response = instance_double(Gitlab::HTTP::Response)
allow(response).to receive(:body).and_return(body)
allow(response).to receive(:success?).and_return(true)
allow(Gitlab::HTTP).to receive(:post).and_return(response)
diff --git a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
index 75d534c59bf..970e82e7107 100644
--- a/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
+++ b/spec/services/projects/lfs_pointers/lfs_download_service_spec.rb
@@ -17,7 +17,7 @@ describe Projects::LfsPointers::LfsDownloadService do
before do
ApplicationSetting.create_from_defaults
- stub_application_setting(allow_local_requests_from_hooks_and_services: local_request_setting)
+ stub_application_setting(allow_local_requests_from_web_hooks_and_services: local_request_setting)
allow(project).to receive(:lfs_enabled?).and_return(true)
end
diff --git a/spec/services/projects/update_remote_mirror_service_spec.rb b/spec/services/projects/update_remote_mirror_service_spec.rb
index be2811ab1e7..4396ccab584 100644
--- a/spec/services/projects/update_remote_mirror_service_spec.rb
+++ b/spec/services/projects/update_remote_mirror_service_spec.rb
@@ -10,49 +10,91 @@ describe Projects::UpdateRemoteMirrorService do
subject(:service) { described_class.new(project, project.creator) }
- describe "#execute" do
+ describe '#execute' do
+ subject(:execute!) { service.execute(remote_mirror, 0) }
+
before do
project.repository.add_branch(project.owner, 'existing-branch', 'master')
allow(remote_mirror).to receive(:update_repository).and_return(true)
end
- it "ensures the remote exists" do
+ it 'ensures the remote exists' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
expect(remote_mirror).to receive(:ensure_remote!)
- service.execute(remote_mirror)
+ execute!
end
- it "fetches the remote repository" do
+ it 'fetches the remote repository' do
expect(project.repository)
.to receive(:fetch_remote)
- .with(remote_mirror.remote_name, no_tags: true, ssh_auth: remote_mirror)
+ .with(remote_mirror.remote_name, no_tags: true, ssh_auth: remote_mirror)
- service.execute(remote_mirror)
+ execute!
end
- it "returns success when updated succeeds" do
+ it 'marks the mirror as started when beginning' do
+ expect(remote_mirror).to receive(:update_start!).and_call_original
+
+ execute!
+ end
+
+ it 'marks the mirror as successfully finished' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
- result = service.execute(remote_mirror)
+ result = execute!
expect(result[:status]).to eq(:success)
+ expect(remote_mirror).to be_finished
+ end
+
+ it 'marks the mirror as failed and raises the error when an unexpected error occurs' do
+ allow(project.repository).to receive(:fetch_remote).and_raise('Badly broken')
+
+ expect { execute! }.to raise_error /Badly broken/
+
+ expect(remote_mirror).to be_failed
+ expect(remote_mirror.last_error).to include('Badly broken')
+ end
+
+ context 'when the update fails because of a `Gitlab::Git::CommandError`' do
+ before do
+ allow(project.repository).to receive(:fetch_remote).and_raise(Gitlab::Git::CommandError.new('fetch failed'))
+ end
+
+ it 'wraps `Gitlab::Git::CommandError`s in a service error' do
+ expect(execute!).to eq(status: :error, message: 'fetch failed')
+ end
+
+ it 'marks the mirror as to be retried' do
+ execute!
+
+ expect(remote_mirror).to be_to_retry
+ expect(remote_mirror.last_error).to include('fetch failed')
+ end
+
+ it "marks the mirror as failed after #{described_class::MAX_TRIES} tries" do
+ service.execute(remote_mirror, described_class::MAX_TRIES)
+
+ expect(remote_mirror).to be_failed
+ expect(remote_mirror.last_error).to include('fetch failed')
+ end
end
context 'when syncing all branches' do
- it "push all the branches the first time" do
+ it 'push all the branches the first time' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
expect(remote_mirror).to receive(:update_repository).with({})
- service.execute(remote_mirror)
+ execute!
end
end
context 'when only syncing protected branches' do
- it "sync updated protected branches" do
+ it 'sync updated protected branches' do
stub_fetch_remote(project, remote_name: remote_name, ssh_auth: remote_mirror)
protected_branch = create_protected_branch(project)
remote_mirror.only_protected_branches = true
@@ -61,7 +103,7 @@ describe Projects::UpdateRemoteMirrorService do
.to receive(:update_repository)
.with(only_branches_matching: [protected_branch.name])
- service.execute(remote_mirror)
+ execute!
end
def create_protected_branch(project)
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 6bbaa410d56..31bd0f0f836 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -186,7 +186,10 @@ describe Projects::UpdateService do
expect_any_instance_of(ProjectWiki).to receive(:wiki).and_raise(ProjectWiki::CouldNotCreateWikiError)
expect_any_instance_of(described_class).to receive(:log_error).with("Could not create wiki for #{project.full_name}")
- expect(Gitlab::Metrics).to receive(:counter)
+
+ counter = double(:counter)
+ expect(Gitlab::Metrics).to receive(:counter).with(:wiki_can_not_be_created_total, 'Counts the times we failed to create a wiki').and_return(counter)
+ expect(counter).to receive(:increment)
update_project(project, user, project_feature_attributes: { wiki_access_level: ProjectFeature::ENABLED })
end
@@ -366,9 +369,28 @@ describe Projects::UpdateService do
end
end
+ context 'when updating #emails_disabled' do
+ it 'updates the attribute for the project owner' do
+ expect { update_project(project, user, emails_disabled: true) }
+ .to change { project.emails_disabled }
+ .to(true)
+ end
+
+ it 'does not update when not project owner' do
+ maintainer = create(:user)
+ project.add_user(maintainer, :maintainer)
+
+ expect { update_project(project, maintainer, emails_disabled: true) }
+ .not_to change { project.emails_disabled }
+ end
+ end
+
context 'with external authorization enabled' do
before do
enable_external_authorization_service_check
+
+ allow(::Gitlab::ExternalAuthorization)
+ .to receive(:access_allowed?).with(user, 'default_label', project.full_path).and_call_original
end
it 'does not save the project with an error if the service denies access' do
@@ -399,8 +421,7 @@ describe Projects::UpdateService do
end
it 'does not check the label when it does not change' do
- expect(::Gitlab::ExternalAuthorization)
- .not_to receive(:access_allowed?)
+ expect(::Gitlab::ExternalAuthorization).to receive(:access_allowed?).once
update_project(project, user, { name: 'New name' })
end
diff --git a/spec/services/prometheus/proxy_service_spec.rb b/spec/services/prometheus/proxy_service_spec.rb
index 4bdb20de4c9..03bda94e9c6 100644
--- a/spec/services/prometheus/proxy_service_spec.rb
+++ b/spec/services/prometheus/proxy_service_spec.rb
@@ -131,7 +131,7 @@ describe Prometheus::ProxyService do
allow(environment).to receive(:prometheus_adapter)
.and_return(prometheus_adapter)
allow(prometheus_adapter).to receive(:can_query?).and_return(true)
- allow(prometheus_adapter).to receive(:prometheus_client_wrapper)
+ allow(prometheus_adapter).to receive(:prometheus_client)
.and_return(prometheus_client)
end
diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb
index 95a131e8c86..c9714964fc9 100644
--- a/spec/services/quick_actions/interpret_service_spec.rb
+++ b/spec/services/quick_actions/interpret_service_spec.rb
@@ -28,61 +28,108 @@ describe QuickActions::InterpretService do
shared_examples 'reopen command' do
it 'returns state_event: "reopen" if content contains /reopen' do
issuable.close!
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'reopen')
end
+
+ it 'returns the reopen message' do
+ issuable.close!
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Reopened this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'close command' do
it 'returns state_event: "close" if content contains /close' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(state_event: 'close')
end
+
+ it 'returns the close message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Closed this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'title command' do
it 'populates title: "A brand new title" if content contains /title A brand new title' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(title: 'A brand new title')
end
+
+ it 'returns the title message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq(%{Changed the title to "A brand new title".})
+ end
end
shared_examples 'milestone command' do
it 'fetches milestone and populates milestone_id if content contains /milestone' do
milestone # populate the milestone
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: milestone.id)
end
+
+ it 'returns the milestone message' do
+ milestone # populate the milestone
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the milestone to #{milestone.to_reference}.")
+ end
+
+ it 'returns empty milestone message when milestone is wrong' do
+ _, _, message = service.execute('/milestone %wrong-milestone', issuable)
+
+ expect(message).to be_empty
+ end
end
shared_examples 'remove_milestone command' do
it 'populates milestone_id: nil if content contains /remove_milestone' do
issuable.update!(milestone_id: milestone.id)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(milestone_id: nil)
end
+
+ it 'returns removed milestone message' do
+ issuable.update!(milestone_id: milestone.id)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{milestone.to_reference} milestone.")
+ end
end
shared_examples 'label command' do
it 'fetches label ids and populates add_label_ids if content contains /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [bug.id, inprogress.id])
end
+
+ it 'returns the label message' do
+ bug # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Added #{bug.to_reference(format: :name)} #{inprogress.to_reference(format: :name)} labels.")
+ end
end
shared_examples 'multiple label command' do
it 'fetches label ids and populates add_label_ids if content contains multiple /label' do
bug # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id, bug.id])
end
@@ -91,7 +138,7 @@ describe QuickActions::InterpretService do
shared_examples 'multiple label with same argument' do
it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(add_label_ids: [inprogress.id])
end
@@ -120,16 +167,23 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id])
end
+
+ it 'returns the unlabel message' do
+ issuable.update!(label_ids: [inprogress.id]) # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Removed #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'multiple unlabel command' do
it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do
issuable.update!(label_ids: [inprogress.id, bug.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id])
end
@@ -138,7 +192,7 @@ describe QuickActions::InterpretService do
shared_examples 'unlabel command with no argument' do
it 'populates label_ids: [] if content contains /unlabel with no arguments' do
issuable.update!(label_ids: [inprogress.id]) # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [])
end
@@ -148,91 +202,161 @@ describe QuickActions::InterpretService do
it 'populates label_ids: [] if content contains /relabel' do
issuable.update!(label_ids: [bug.id]) # populate the label
inprogress # populate the label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(label_ids: [inprogress.id])
end
+
+ it 'returns the relabel message' do
+ issuable.update!(label_ids: [bug.id]) # populate the label
+ inprogress # populate the label
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Replaced all labels with #{inprogress.to_reference(format: :name)} label.")
+ end
end
shared_examples 'todo command' do
it 'populates todo_event: "add" if content contains /todo' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'add')
end
+
+ it 'returns the todo message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Added a To Do.')
+ end
end
shared_examples 'done command' do
it 'populates todo_event: "done" if content contains /done' do
TodoService.new.mark_todo(issuable, developer)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(todo_event: 'done')
end
+
+ it 'returns the done message' do
+ TodoService.new.mark_todo(issuable, developer)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Marked To Do as done.')
+ end
end
shared_examples 'subscribe command' do
it 'populates subscription_event: "subscribe" if content contains /subscribe' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'subscribe')
end
+
+ it 'returns the subscribe message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Subscribed to this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'unsubscribe command' do
it 'populates subscription_event: "unsubscribe" if content contains /unsubscribe' do
issuable.subscribe(developer, project)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(subscription_event: 'unsubscribe')
end
+
+ it 'returns the unsubscribe message' do
+ issuable.subscribe(developer, project)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unsubscribed from this #{issuable.to_ability_name.humanize(capitalize: false)}.")
+ end
end
shared_examples 'due command' do
+ let(:expected_date) { Date.new(2016, 8, 28) }
+
it 'populates due_date: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
+
+ expect(updates).to eq(due_date: expected_date)
+ end
- expect(updates).to eq(due_date: defined?(expected_date) ? expected_date : Date.new(2016, 8, 28))
+ it 'returns due_date message: Date.new(2016, 8, 28) if content contains /due 2016-08-28' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Set the due date to #{expected_date.to_s(:medium)}.")
end
end
shared_examples 'remove_due_date command' do
- it 'populates due_date: nil if content contains /remove_due_date' do
+ before do
issuable.update!(due_date: Date.today)
- _, updates = service.execute(content, issuable)
+ end
+
+ it 'populates due_date: nil if content contains /remove_due_date' do
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(due_date: nil)
end
+
+ it 'returns Removed the due date' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed the due date.')
+ end
end
shared_examples 'wip command' do
it 'returns wip_event: "wip" if content contains /wip' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'wip')
end
+
+ it 'returns the wip message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'unwip command' do
it 'returns wip_event: "unwip" if content contains /wip' do
issuable.update!(title: issuable.wip_title)
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(wip_event: 'unwip')
end
+
+ it 'returns the unwip message' do
+ issuable.update!(title: issuable.wip_title)
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Unmarked this #{issuable.to_ability_name.humanize(capitalize: false)} as Work In Progress.")
+ end
end
shared_examples 'estimate command' do
it 'populates time_estimate: 3600 if content contains /estimate 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 3600)
end
+
+ it 'returns the time_estimate formatted message' do
+ _, _, message = service.execute('/estimate 79d', issuable)
+
+ expect(message).to eq('Set time estimate to 3mo 3w 4d.')
+ end
end
shared_examples 'spend command' do
it 'populates spend_time: 3600 if content contains /spend 1h' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 3600,
@@ -240,11 +364,17 @@ describe QuickActions::InterpretService do
spent_at: DateTime.now.to_date
})
end
+
+ it 'returns the spend_time message including the formatted duration and verb' do
+ _, _, message = service.execute('/spend -120m', issuable)
+
+ expect(message).to eq('Subtracted 2h spent time.')
+ end
end
shared_examples 'spend command with negative time' do
it 'populates spend_time: -1800 if content contains /spend -30m' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: -1800,
@@ -256,7 +386,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with valid date' do
it 'populates spend time: 1800 with date in date type format' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: {
duration: 1800,
@@ -268,7 +398,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with invalid date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -276,7 +406,7 @@ describe QuickActions::InterpretService do
shared_examples 'spend command with future date' do
it 'will not create any note and timelog' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq({})
end
@@ -284,18 +414,30 @@ describe QuickActions::InterpretService do
shared_examples 'remove_estimate command' do
it 'populates time_estimate: 0 if content contains /remove_estimate' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(time_estimate: 0)
end
+
+ it 'returns the remove_estimate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed time estimate.')
+ end
end
shared_examples 'remove_time_spent command' do
it 'populates spend_time: :reset if content contains /remove_time_spent' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(spend_time: { duration: :reset, user_id: developer.id })
end
+
+ it 'returns the remove_time_spent message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Removed spent time.')
+ end
end
shared_examples 'lock command' do
@@ -303,10 +445,16 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: false) }
it 'returns discussion_locked: true if content contains /lock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: true)
end
+
+ it 'returns the lock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Locked the discussion.')
+ end
end
shared_examples 'unlock command' do
@@ -314,45 +462,79 @@ describe QuickActions::InterpretService do
let(:merge_request) { create(:merge_request, source_project: project, discussion_locked: true) }
it 'returns discussion_locked: true if content contains /unlock' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(discussion_locked: false)
end
+
+ it 'returns the unlock discussion message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Unlocked the discussion.')
+ end
end
- shared_examples 'empty command' do
+ shared_examples 'empty command' do |error_msg|
it 'populates {} if content contains an unsupported command' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to be_empty
end
+
+ it "returns #{error_msg || 'an empty'} message" do
+ _, _, message = service.execute(content, issuable)
+
+ if error_msg
+ expect(message).to eq(error_msg)
+ else
+ expect(message).to be_empty
+ end
+ end
end
shared_examples 'merge command' do
let(:project) { create(:project, :repository) }
it 'runs merge command if content contains /merge' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(merge: merge_request.diff_head_sha)
end
+
+ it 'returns them merge message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Scheduled to merge this merge request when the pipeline succeeds.')
+ end
end
shared_examples 'award command' do
it 'toggle award 100 emoji if content contains /award :100:' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(emoji_award: "100")
end
+
+ it 'returns the award message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Toggled :100: emoji award.')
+ end
end
shared_examples 'duplicate command' do
it 'fetches issue and populates canonical_issue_id if content contains /duplicate issue_reference' do
issue_duplicate # populate the issue
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(canonical_issue_id: issue_duplicate.id)
end
+
+ it 'returns the duplicate message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Marked this issue as a duplicate of #{issue_duplicate.to_reference(project)}.")
+ end
end
shared_examples 'copy_metadata command' do
@@ -360,7 +542,7 @@ describe QuickActions::InterpretService do
source_issuable # populate the issue
todo_label # populate this label
inreview_label # populate this label
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates[:add_label_ids]).to match_array([inreview_label.id, todo_label.id])
@@ -370,19 +552,45 @@ describe QuickActions::InterpretService do
expect(updates).not_to have_key(:milestone_id)
end
end
+
+ it 'returns the copy metadata message' do
+ _, _, message = service.execute("/copy_metadata #{source_issuable.to_reference}", issuable)
+
+ expect(message).to eq("Copied labels and milestone from #{source_issuable.to_reference}.")
+ end
+ end
+
+ describe 'move issue command' do
+ it 'returns the move issue message' do
+ _, _, message = service.execute("/move #{project.full_path}", issue)
+
+ expect(message).to eq("Moved this issue to #{project.full_path}.")
+ end
+
+ it 'returns move issue failure message when the referenced issue is not found' do
+ _, _, message = service.execute('/move invalid', issue)
+
+ expect(message).to eq("Failed to move this issue because target project doesn't exist.")
+ end
end
shared_examples 'confidential command' do
it 'marks issue as confidential if content contains /confidential' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(confidential: true)
end
+
+ it 'returns the confidential message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq('Made this issue confidential.')
+ end
end
shared_examples 'shrug command' do
it 'appends ¯\_(ツ)_/¯ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::SHRUG)
end
@@ -390,7 +598,7 @@ describe QuickActions::InterpretService do
shared_examples 'tableflip command' do
it 'appends (╯°□°)╯︵ ┻━┻ to the comment' do
- new_content, _ = service.execute(content, issuable)
+ new_content, _, _ = service.execute(content, issuable)
expect(new_content).to end_with(described_class::TABLEFLIP)
end
@@ -398,18 +606,34 @@ describe QuickActions::InterpretService do
shared_examples 'tag command' do
it 'tags a commit' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(tag_name: tag_name, tag_message: tag_message)
end
+
+ it 'returns the tag message' do
+ _, _, message = service.execute(content, issuable)
+
+ if tag_message.present?
+ expect(message).to eq(%{Tagged this commit to #{tag_name} with "#{tag_message}".})
+ else
+ expect(message).to eq("Tagged this commit to #{tag_name}.")
+ end
+ end
end
shared_examples 'assign command' do
it 'assigns to a single user' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(assignee_ids: [developer.id])
end
+
+ it 'returns the assign message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Assigned #{developer.to_reference}.")
+ end
end
it_behaves_like 'reopen command' do
@@ -463,7 +687,7 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(project, developer, {}) }
it 'precheck passes and returns merge command' do
- _, updates = service.execute('/merge', merge_request)
+ _, updates, _ = service.execute('/merge', merge_request)
expect(updates).to eq(merge: nil)
end
@@ -559,7 +783,7 @@ describe QuickActions::InterpretService do
end
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', "Failed to assign a user because no user was found." do
let(:content) { '/assign @abcd1234' }
let(:issuable) { issue }
end
@@ -574,20 +798,34 @@ describe QuickActions::InterpretService do
context 'Issue' do
it 'populates assignee_ids: [] if content contains /unassign' do
- issue.update(assignee_ids: [developer.id])
- _, updates = service.execute(content, issue)
+ issue.update!(assignee_ids: [developer.id])
+ _, updates, _ = service.execute(content, issue)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ issue.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
context 'Merge Request' do
it 'populates assignee_ids: [] if content contains /unassign' do
- merge_request.update(assignee_ids: [developer.id])
- _, updates = service.execute(content, merge_request)
+ merge_request.update!(assignee_ids: [developer.id])
+ _, updates, _ = service.execute(content, merge_request)
expect(updates).to eq(assignee_ids: [])
end
+
+ it 'returns the unassign message for all the assignee if content contains /unassign' do
+ merge_request.update(assignee_ids: [developer.id, developer2.id])
+ _, _, message = service.execute(content, merge_request)
+
+ expect(message).to eq("Removed assignees #{developer.to_reference} and #{developer2.to_reference}.")
+ end
end
end
@@ -979,12 +1217,12 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:content) { "/duplicate imaginary#1234" }
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:other_project) { create(:project, :private) }
let(:issue_duplicate) { create(:issue, project: other_project) }
@@ -1049,7 +1287,7 @@ describe QuickActions::InterpretService do
let(:issuable) { issue }
end
- it_behaves_like 'empty command' do
+ it_behaves_like 'empty command', 'Failed to mark this issue as a duplicate because referenced issue was not found.' do
let(:content) { '/duplicate #{issue.to_reference}' }
let(:issuable) { issue }
end
@@ -1132,13 +1370,13 @@ describe QuickActions::InterpretService do
let(:service) { described_class.new(non_empty_project, developer)}
it 'updates target_branch if /target_branch command is executed' do
- _, updates = service.execute('/target_branch merge-test', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
it 'handles blanks around param' do
- _, updates = service.execute('/target_branch merge-test ', merge_request)
+ _, updates, _ = service.execute('/target_branch merge-test ', merge_request)
expect(updates).to eq(target_branch: 'merge-test')
end
@@ -1156,6 +1394,12 @@ describe QuickActions::InterpretService do
let(:issuable) { another_merge_request }
end
end
+
+ it 'returns the target_branch message' do
+ _, _, message = service.execute('/target_branch merge-test', merge_request)
+
+ expect(message).to eq('Set target branch to merge-test.')
+ end
end
context '/board_move command' do
@@ -1171,13 +1415,13 @@ describe QuickActions::InterpretService do
it 'populates remove_label_ids for all current board columns' do
issue.update!(label_ids: [todo.id, inprogress.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id, inprogress.id])
end
it 'populates add_label_ids with the id of the given label' do
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:add_label_ids]).to eq([inreview.id])
end
@@ -1185,7 +1429,7 @@ describe QuickActions::InterpretService do
it 'does not include the given label id in remove_label_ids' do
issue.update!(label_ids: [todo.id, inreview.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
@@ -1193,11 +1437,19 @@ describe QuickActions::InterpretService do
it 'does not remove label ids that are not lists on the board' do
issue.update!(label_ids: [todo.id, bug.id])
- _, updates = service.execute(content, issue)
+ _, updates, _ = service.execute(content, issue)
expect(updates[:remove_label_ids]).to match_array([todo.id])
end
+ it 'returns board_move message' do
+ issue.update!(label_ids: [todo.id, inprogress.id])
+
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Moved issue to ~#{inreview.id} column in the board.")
+ end
+
context 'if the project has multiple boards' do
let(:issuable) { issue }
@@ -1211,19 +1463,19 @@ describe QuickActions::InterpretService do
context 'if the given label does not exist' do
let(:issuable) { issue }
let(:content) { '/board_move ~"Fake Label"' }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
end
context 'if multiple labels are given' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{inreview.title}" ~"#{todo.title}"} }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because only a single label can be provided.'
end
context 'if the given label is not a list on the board' do
let(:issuable) { issue }
let(:content) { %{/board_move ~"#{bug.title}"} }
- it_behaves_like 'empty command'
+ it_behaves_like 'empty command', 'Failed to move this issue because label was not found.'
end
context 'if issuable is not an Issue' do
@@ -1292,10 +1544,16 @@ describe QuickActions::InterpretService do
end
it 'populates create_merge_request with branch_name and issue iid' do
- _, updates = service.execute(content, issuable)
+ _, updates, _ = service.execute(content, issuable)
expect(updates).to eq(create_merge_request: { branch_name: branch_name, issue_iid: issuable.iid })
end
+
+ it 'returns the create_merge_request message' do
+ _, _, message = service.execute(content, issuable)
+
+ expect(message).to eq("Created branch '#{branch_name}' and a merge request to resolve this issue.")
+ end
end
end
@@ -1556,7 +1814,13 @@ describe QuickActions::InterpretService do
it 'uses the default branch name' do
_, explanations = service.explain(content, issue)
- expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue'])
+ expect(explanations).to eq(['Creates a branch and a merge request to resolve this issue.'])
+ end
+
+ it 'returns the execution message using the default branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq('Created a branch and a merge request to resolve this issue.')
end
end
@@ -1566,7 +1830,13 @@ describe QuickActions::InterpretService do
it 'uses the given branch name' do
_, explanations = service.explain(content, issue)
- expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue"])
+ expect(explanations).to eq(["Creates branch 'foo' and a merge request to resolve this issue."])
+ end
+
+ it 'returns the execution message using the given branch name' do
+ _, _, message = service.execute(content, issue)
+
+ expect(message).to eq("Created branch 'foo' and a merge request to resolve this issue.")
end
end
end
diff --git a/spec/services/self_monitoring/project/create_service_spec.rb b/spec/services/self_monitoring/project/create_service_spec.rb
index d11e27c6d52..def20448bd9 100644
--- a/spec/services/self_monitoring/project/create_service_spec.rb
+++ b/spec/services/self_monitoring/project/create_service_spec.rb
@@ -30,15 +30,13 @@ describe SelfMonitoring::Project::CreateService do
context 'with admin users' do
let(:project) { result[:project] }
+ let(:group) { result[:group] }
+ let(:application_setting) { Gitlab::CurrentSettings.current_application_settings }
let!(:user) { create(:user, :admin) }
before do
- allow(ApplicationSetting)
- .to receive(:current)
- .and_return(
- ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true)
- )
+ application_setting.allow_local_requests_from_web_hooks_and_services = true
end
shared_examples 'has prometheus service' do |listen_address|
@@ -55,6 +53,15 @@ describe SelfMonitoring::Project::CreateService do
it_behaves_like 'has prometheus service', 'http://localhost:9090'
+ 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.path.split('-').last.length).to eq(8)
+ expect(group.visibility_level).to eq(described_class::VISIBILITY_LEVEL)
+ end
+
it 'creates project with internal visibility' do
expect(result[:status]).to eq(:success)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
@@ -62,7 +69,7 @@ describe SelfMonitoring::Project::CreateService do
end
it 'creates project with internal visibility even when internal visibility is restricted' do
- stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::INTERNAL])
+ application_setting.restricted_visibility_levels = [Gitlab::VisibilityLevel::INTERNAL]
expect(result[:status]).to eq(:success)
expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::INTERNAL)
@@ -71,8 +78,8 @@ describe SelfMonitoring::Project::CreateService do
it 'creates project with correct name and description' do
expect(result[:status]).to eq(:success)
- expect(project.name).to eq(described_class::DEFAULT_NAME)
- expect(project.description).to eq(described_class::DEFAULT_DESCRIPTION)
+ expect(project.name).to eq(described_class::PROJECT_NAME)
+ expect(project.description).to eq(described_class::PROJECT_DESCRIPTION)
end
it 'adds all admins as maintainers' do
@@ -81,28 +88,54 @@ describe SelfMonitoring::Project::CreateService do
create(:user)
expect(result[:status]).to eq(:success)
- expect(project.owner).to eq(user)
- expect(project.members.collect(&:user)).to contain_exactly(user, admin1, admin2)
- expect(project.members.collect(&:access_level)).to contain_exactly(
- Gitlab::Access::MAINTAINER,
+ expect(project.owner).to eq(group)
+ expect(group.members.collect(&:user)).to contain_exactly(user, admin1, admin2)
+ expect(group.members.collect(&:access_level)).to contain_exactly(
+ Gitlab::Access::OWNER,
Gitlab::Access::MAINTAINER,
Gitlab::Access::MAINTAINER
)
end
- # This should pass when https://gitlab.com/gitlab-org/gitlab-ce/issues/44496
- # is complete and the prometheus listen address is added to the whitelist.
- # context 'when local requests from hooks and services are not allowed' do
- # before do
- # allow(ApplicationSetting)
- # .to receive(:current)
- # .and_return(
- # ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: false)
- # )
- # end
+ it 'saves the project id' do
+ expect(result[:status]).to eq(:success)
+ expect(application_setting.instance_administration_project_id).to eq(project.id)
+ end
+
+ 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')
+ end
+
+ it 'does not fail when a project already exists' do
+ expect(result[:status]).to eq(:success)
+
+ second_result = subject.execute
+
+ expect(second_result[:status]).to eq(:success)
+ expect(second_result[:project]).to eq(project)
+ expect(second_result[:group]).to eq(group)
+ end
+
+ context 'when local requests from hooks and services are not allowed' do
+ before do
+ 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.outbound_local_requests_whitelist = ['example.com']
- # it_behaves_like 'has prometheus service', 'http://localhost:9090'
- # end
+ expect(result[:status]).to eq(:success)
+ expect(application_setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com', 'localhost'
+ )
+ end
+ end
context 'with non default prometheus address' do
before do
@@ -177,7 +210,7 @@ describe SelfMonitoring::Project::CreateService do
expect(result).to eq({
status: :error,
message: 'Could not add admins as members',
- failed_step: :add_project_members
+ failed_step: :add_group_members
})
end
end
diff --git a/spec/services/submodules/update_service_spec.rb b/spec/services/submodules/update_service_spec.rb
index cf92350c1b2..47b31d4bcbf 100644
--- a/spec/services/submodules/update_service_spec.rb
+++ b/spec/services/submodules/update_service_spec.rb
@@ -142,7 +142,7 @@ describe Submodules::UpdateService do
let(:branch_name) { nil }
it_behaves_like 'returns error result' do
- let(:error_message) { 'You can only create or edit files when you are on a branch' }
+ let(:error_message) { 'Invalid parameters' }
end
end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 157cfc46e69..f46f9633c1c 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -513,6 +513,30 @@ describe SystemNoteService do
end
end
+ describe '.zoom_link_added' do
+ subject { described_class.zoom_link_added(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link added note text' do
+ expect(subject.note).to eq('added a Zoom call to this issue')
+ end
+ end
+
+ describe '.zoom_link_removed' do
+ subject { described_class.zoom_link_removed(issue, project, author) }
+
+ it_behaves_like 'a system note' do
+ let(:action) { 'pinned_embed' }
+ end
+
+ it 'sets the zoom link removed note text' do
+ expect(subject.note).to eq('removed a Zoom call from this issue')
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index 9adaee6481b..a309951bbcb 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -114,6 +114,23 @@ describe TaskListToggleService do
expect(toggler.execute).to be_falsey
end
+ it 'properly handles tasks in a blockquote' do
+ markdown =
+ <<-EOT.strip_heredoc
+ > > * [ ] Task 1
+ > * [x] Task 2
+ EOT
+
+ markdown_html = Banzai::Pipeline::FullPipeline.call(markdown, project: nil)[:output].to_html
+ toggler = described_class.new(markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '> > * [ ] Task 1', line_number: 1)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[0]).to eq "> > * [x] Task 1\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> Task 1')
+ end
+
it 'properly handles a GitLab blockquote' do
markdown =
<<-EOT.strip_heredoc
diff --git a/spec/services/todos/destroy/entity_leave_service_spec.rb b/spec/services/todos/destroy/entity_leave_service_spec.rb
index 2a553e18807..ce809bbf6c5 100644
--- a/spec/services/todos/destroy/entity_leave_service_spec.rb
+++ b/spec/services/todos/destroy/entity_leave_service_spec.rb
@@ -176,7 +176,7 @@ describe Todos::Destroy::EntityLeaveService do
end
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:subgroup) { create(:group, :private, parent: group) }
let(:subgroup2) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/todos/destroy/group_private_service_spec.rb b/spec/services/todos/destroy/group_private_service_spec.rb
index a1798686d7c..7dd495847b3 100644
--- a/spec/services/todos/destroy/group_private_service_spec.rb
+++ b/spec/services/todos/destroy/group_private_service_spec.rb
@@ -35,7 +35,7 @@ describe Todos::Destroy::GroupPrivateService do
expect(project_member.todos).to match_array([todo_project_member])
end
- context 'with nested groups', :nested_groups do
+ context 'with nested groups' do
let(:parent_group) { create(:group) }
let(:subgroup) { create(:group, :private, parent: group) }
let(:subproject) { create(:project, group: subgroup) }
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 23ea4e003f8..0678f54c195 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -40,6 +40,23 @@ describe UpdateSnippetService do
end
end
+ describe 'usage counter' do
+ let(:counter) { Gitlab::UsageDataCounters::SnippetCounter }
+ let(:snippet) { create_snippet(nil, @user, @opts) }
+
+ it 'increments count' do
+ expect do
+ update_snippet(nil, @admin, snippet, @opts)
+ end.to change { counter.read(:update) }.by 1
+ end
+
+ it 'does not increment count if create fails' do
+ expect do
+ update_snippet(nil, @admin, snippet, { title: '' })
+ end.not_to change { counter.read(:update) }
+ end
+ end
+
def create_snippet(project, user, opts)
CreateSnippetService.new(project, user, opts).execute
end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 0287a24808d..f5a914bb482 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -135,7 +135,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects of subgroups of groups the user is a member of', :nested_groups do
+ context 'projects of subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let!(:other_project) { create(:project, group: nested_group) }
@@ -163,7 +163,7 @@ describe Users::RefreshAuthorizedProjectsService do
end
end
- context 'projects shared with subgroups of groups the user is a member of', :nested_groups do
+ context 'projects shared with subgroups of groups the user is a member of' do
let(:group) { create(:group) }
let(:nested_group) { create(:group, parent: group) }
let(:other_project) { create(:project) }
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 37bafc0c002..50167a2e059 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -19,17 +19,37 @@ describe WebHookService do
let(:service_instance) { described_class.new(project_hook, data, :push_hooks) }
describe '#initialize' do
- it 'allow_local_requests is true if hook is a SystemHook' do
- instance = described_class.new(build(:system_hook), data, :system_hook)
- expect(instance.request_options[:allow_local_requests]).to be_truthy
+ before do
+ stub_application_setting(setting_name => setting)
end
- it 'allow_local_requests is false if hook is not a SystemHook' do
- %i(project_hook service_hook web_hook_log).each do |hook|
- instance = described_class.new(build(hook), data, hook)
- expect(instance.request_options[:allow_local_requests]).to be_falsey
+ shared_examples_for 'respects outbound network setting' do
+ context 'when local requests are allowed' do
+ let(:setting) { true }
+
+ it { expect(hook.request_options[:allow_local_requests]).to be_truthy }
+ end
+
+ context 'when local requests are not allowed' do
+ let(:setting) { false }
+
+ it { expect(hook.request_options[:allow_local_requests]).to be_falsey }
end
end
+
+ context 'when SystemHook' do
+ let(:setting_name) { :allow_local_requests_from_system_hooks }
+ let(:hook) { described_class.new(build(:system_hook), data, :system_hook) }
+
+ include_examples 'respects outbound network setting'
+ end
+
+ context 'when ProjectHook' do
+ let(:setting_name) { :allow_local_requests_from_web_hooks_and_services }
+ let(:hook) { described_class.new(build(:project_hook), data, :project_hook) }
+
+ include_examples 'respects outbound network setting'
+ end
end
describe '#execute' do
diff --git a/spec/services/wiki_pages/base_service_spec.rb b/spec/services/wiki_pages/base_service_spec.rb
new file mode 100644
index 00000000000..2e70246c6f2
--- /dev/null
+++ b/spec/services/wiki_pages/base_service_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe WikiPages::BaseService do
+ let(:project) { double('project') }
+ let(:user) { double('user') }
+
+ subject(:service) { described_class.new(project, user, {}) }
+
+ describe '#increment_usage' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+ error = counter::UnknownEvent
+
+ it 'raises an error on unknown events' do
+ expect { subject.send(:increment_usage, :bad_event) }.to raise_error error
+ end
+
+ context 'the event is valid' do
+ counter::KNOWN_EVENTS.each do |e|
+ it "updates the #{e} counter" do
+ expect { subject.send(:increment_usage, e) }.to change { counter.read(e) }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/wiki_pages/create_service_spec.rb b/spec/services/wiki_pages/create_service_spec.rb
index 84510dcf700..ef03a2e9788 100644
--- a/spec/services/wiki_pages/create_service_spec.rb
+++ b/spec/services/wiki_pages/create_service_spec.rb
@@ -14,6 +14,10 @@ describe WikiPages::CreateService do
}
end
+ let(:bad_opts) do
+ { title: '' }
+ end
+
subject(:service) { described_class.new(project, user, opts) }
before do
@@ -36,5 +40,26 @@ describe WikiPages::CreateService do
service.execute
end
+
+ it 'counts wiki page creation' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute }.to change { counter.read(:create) }.by 1
+ end
+
+ context 'when the options are bad' do
+ subject(:service) { described_class.new(project, user, bad_opts) }
+
+ it 'does not count a creation event' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute }.not_to change { counter.read(:create) }
+ end
+
+ it 'reports the error' do
+ expect(service.execute).to be_invalid
+ .and have_attributes(errors: be_present)
+ end
+ end
end
end
diff --git a/spec/services/wiki_pages/destroy_service_spec.rb b/spec/services/wiki_pages/destroy_service_spec.rb
index c74eac4dad6..350a7eb123b 100644
--- a/spec/services/wiki_pages/destroy_service_spec.rb
+++ b/spec/services/wiki_pages/destroy_service_spec.rb
@@ -20,5 +20,17 @@ describe WikiPages::DestroyService do
service.execute(page)
end
+
+ it 'increments the delete count' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute(page) }.to change { counter.read(:delete) }.by 1
+ end
+
+ it 'does not increment the delete count if the deletion failed' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute(nil) }.not_to change { counter.read(:delete) }
+ end
end
end
diff --git a/spec/services/wiki_pages/update_service_spec.rb b/spec/services/wiki_pages/update_service_spec.rb
index 19866bd3bfc..d5f46e7b2db 100644
--- a/spec/services/wiki_pages/update_service_spec.rb
+++ b/spec/services/wiki_pages/update_service_spec.rb
@@ -16,6 +16,10 @@ describe WikiPages::UpdateService do
}
end
+ let(:bad_opts) do
+ { title: '' }
+ end
+
subject(:service) { described_class.new(project, user, opts) }
before do
@@ -39,5 +43,26 @@ describe WikiPages::UpdateService do
service.execute(page)
end
+
+ it 'counts edit events' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute page }.to change { counter.read(:update) }.by 1
+ end
+
+ context 'when the options are bad' do
+ subject(:service) { described_class.new(project, user, bad_opts) }
+
+ it 'does not count an edit event' do
+ counter = Gitlab::UsageDataCounters::WikiPageCounter
+
+ expect { service.execute page }.not_to change { counter.read(:update) }
+ end
+
+ it 'reports the error' do
+ expect(service.execute page).to be_invalid
+ .and have_attributes(errors: be_present)
+ end
+ end
end
end
diff --git a/spec/services/zoom_notes_service_spec.rb b/spec/services/zoom_notes_service_spec.rb
new file mode 100644
index 00000000000..419ecf3f374
--- /dev/null
+++ b/spec/services/zoom_notes_service_spec.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ZoomNotesService do
+ describe '#execute' do
+ let(:issue) { OpenStruct.new(description: description) }
+ let(:project) { Object.new }
+ let(:user) { Object.new }
+ let(:description) { 'an issue description' }
+ let(:old_description) { nil }
+
+ subject { described_class.new(issue, project, user, old_description: old_description) }
+
+ shared_examples 'no notifications' do
+ it "doesn't create notifications" do
+ expect(SystemNoteService).not_to receive(:zoom_link_added)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ it_behaves_like 'no notifications'
+
+ context 'when the zoom link exists in both description and old_description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context "when the zoom link doesn't exist in both description and old_description" do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when description == old_description' do
+ let(:old_description) { 'an issue description' }
+
+ it_behaves_like 'no notifications'
+ end
+
+ context 'when the description contains a zoom link and old_description is nil' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been added to the description' do
+ let(:description) { 'a changed issue description https://zoom.us/j/123' }
+ let(:old_description) { 'an issue description' }
+
+ it 'creates a zoom_link_added notification' do
+ expect(SystemNoteService).to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).not_to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+
+ context 'when the zoom link has been removed from the description' do
+ let(:description) { 'a changed issue description' }
+ let(:old_description) { 'an issue description https://zoom.us/j/123' }
+
+ it 'creates a zoom_link_removed notification' do
+ expect(SystemNoteService).not_to receive(:zoom_link_added).with(issue, project, user)
+ expect(SystemNoteService).to receive(:zoom_link_removed)
+
+ subject.execute
+ end
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 95e0d8858b9..bd504f1553b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -3,6 +3,7 @@ SimpleCovEnv.start!
ENV["RAILS_ENV"] = 'test'
ENV["IN_MEMORY_APPLICATION_SETTINGS"] = 'true'
+ENV["RSPEC_ALLOW_INVALID_URLS"] = 'true'
require File.expand_path('../config/environment', __dir__)
require 'rspec/rails'
@@ -47,6 +48,9 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
quality_level = Quality::TestLevel.new
RSpec.configure do |config|
+ config.filter_run focus: true
+ config.run_all_when_everything_filtered = true
+
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
@@ -104,6 +108,7 @@ RSpec.configure do |config|
config.include Rails.application.routes.url_helpers, type: :routing
config.include PolicyHelpers, type: :policy
config.include MemoryUsageHelper
+ config.include ExpectRequestWithStatus, type: :request
if ENV['CI']
# This includes the first try, i.e. tests will be run 4 times before failing.
@@ -134,6 +139,8 @@ RSpec.configure do |config|
allow(Feature).to receive(:enabled?).with(flag).and_return(enabled)
end
+ allow(Gitlab::GitalyClient).to receive(:can_use_disk?).and_return(enabled)
+
# The following can be removed when we remove the staged rollout strategy
# and we can just enable it using instance wide settings
# (ie. ApplicationSetting#auto_devops_enabled)
@@ -144,9 +151,9 @@ RSpec.configure do |config|
Gitlab::ThreadMemoryCache.cache_backend.clear
end
- config.around(:example, :quarantine) do
+ config.around(:example, :quarantine) do |example|
# Skip tests in quarantine unless we explicitly focus on them.
- skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ example.run if config.inclusion_filter[:quarantine]
end
config.before(:example, :request_store) do
@@ -255,18 +262,6 @@ RSpec.configure do |config|
Gitlab::CurrentSettings.clear_in_memory_application_settings!
end
- config.around(:each, :nested_groups) do |example|
- example.run if Group.supports_nested_objects?
- end
-
- config.around(:each, :postgresql) do |example|
- example.run if Gitlab::Database.postgresql?
- end
-
- config.around(:each, :mysql) do |example|
- example.run if Gitlab::Database.mysql?
- end
-
# This makes sure the `ApplicationController#can?` method is stubbed with the
# original implementation for all view specs.
config.before(:each, type: :view) do
diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb
index 3abb5096a7a..b7aff32460d 100644
--- a/spec/support/api/boards_shared_examples.rb
+++ b/spec/support/api/boards_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project boards' do |route_definition, ee = false|
let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) }
diff --git a/spec/support/api/issues_resolving_discussions_shared_examples.rb b/spec/support/api/issues_resolving_discussions_shared_examples.rb
index d2d6260dfa8..4c44f1bd103 100644
--- a/spec/support/api/issues_resolving_discussions_shared_examples.rb
+++ b/spec/support/api/issues_resolving_discussions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'creating an issue resolving discussions through the API' do
it 'creates a new project issue' do
expect(response).to have_gitlab_http_status(:created)
diff --git a/spec/support/api/members_shared_examples.rb b/spec/support/api/members_shared_examples.rb
index 8d910e52eda..603efd4fc75 100644
--- a/spec/support/api/members_shared_examples.rb
+++ b/spec/support/api/members_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'a 404 response when source is private' do
before do
source.update_column(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
diff --git a/spec/support/api/milestones_shared_examples.rb b/spec/support/api/milestones_shared_examples.rb
index 63b719be03e..d6439f77408 100644
--- a/spec/support/api/milestones_shared_examples.rb
+++ b/spec/support/api/milestones_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'group and project milestones' do |route_definition|
let(:resource_route) { "#{route}/#{milestone.id}" }
let(:label_1) { create(:label, title: 'label_1', project: project, priority: 1) }
diff --git a/spec/support/api/repositories_shared_context.rb b/spec/support/api/repositories_shared_context.rb
index f1341804e56..346015106e3 100644
--- a/spec/support/api/repositories_shared_context.rb
+++ b/spec/support/api/repositories_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'disabled repository' do
before do
project.project_feature.update!(
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index 4cf34d43117..ebbd57c8115 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SchemaPath
def self.expand(schema, dir = nil)
if Gitlab.ee? && dir.nil?
diff --git a/spec/support/api/scopes/read_user_shared_examples.rb b/spec/support/api/scopes/read_user_shared_examples.rb
index 683234264a8..3786a8012f9 100644
--- a/spec/support/api/scopes/read_user_shared_examples.rb
+++ b/spec/support/api/scopes/read_user_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'allows the "read_user" scope' do |api_version|
let(:version) { api_version || 'v4' }
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index 15037222630..3bd1b145433 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'an unauthorized API user' do
it { is_expected.to eq(403) }
end
diff --git a/spec/support/banzai/reference_filter_shared_examples.rb b/spec/support/banzai/reference_filter_shared_examples.rb
index 476d80f3a93..27765652f28 100644
--- a/spec/support/banzai/reference_filter_shared_examples.rb
+++ b/spec/support/banzai/reference_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for reference links containing HTML.
#
# Requires a reference:
diff --git a/spec/support/batch_loader.rb b/spec/support/batch_loader.rb
index bb790e660a6..0eb8f279d29 100644
--- a/spec/support/batch_loader.rb
+++ b/spec/support/batch_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.after do
BatchLoader::Executor.clear_current
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 56ac208a025..8accc5c1df5 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Style/GlobalVars
require 'capybara/rails'
require 'capybara/rspec'
@@ -58,6 +60,7 @@ Capybara.javascript_driver = :chrome
Capybara.default_max_wait_time = timeout
Capybara.ignore_hidden_elements = true
Capybara.default_normalize_ws = true
+Capybara.enable_aria_label = true
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
diff --git a/spec/support/carrierwave.rb b/spec/support/carrierwave.rb
index b376822d530..8da55514c78 100644
--- a/spec/support/carrierwave.rb
+++ b/spec/support/carrierwave.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
CarrierWave.root = File.expand_path('tmp/tests/public', Rails.root)
RSpec.configure do |config|
diff --git a/spec/support/chunked_io/chunked_io_helpers.rb b/spec/support/chunked_io/chunked_io_helpers.rb
index fec1f951563..278f577f3cb 100644
--- a/spec/support/chunked_io/chunked_io_helpers.rb
+++ b/spec/support/chunked_io/chunked_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ChunkedIOHelpers
def sample_trace_raw
@sample_trace_raw ||= File.read(expand_fixture_path('trace/sample_trace'))
diff --git a/spec/support/commit_trailers_spec_helper.rb b/spec/support/commit_trailers_spec_helper.rb
index efa317fd2f9..a958e259368 100644
--- a/spec/support/commit_trailers_spec_helper.rb
+++ b/spec/support/commit_trailers_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CommitTrailersSpecHelper
extend ActiveSupport::Concern
diff --git a/spec/support/controllers/githubish_import_controller_shared_context.rb b/spec/support/controllers/githubish_import_controller_shared_context.rb
index e71994edec6..3706178ee34 100644
--- a/spec/support/controllers/githubish_import_controller_shared_context.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'a GitHub-ish import controller' do
let(:user) { create(:user) }
let(:token) { "asdasd12345" }
diff --git a/spec/support/controllers/githubish_import_controller_shared_examples.rb b/spec/support/controllers/githubish_import_controller_shared_examples.rb
index 5bb1269a19d..718d9857b18 100644
--- a/spec/support/controllers/githubish_import_controller_shared_examples.rb
+++ b/spec/support/controllers/githubish_import_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
@@ -321,7 +323,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen an existing nested namespace and name for the project', :postgresql do
+ context 'user has chosen an existing nested namespace and name for the project' do
let(:parent_namespace) { create(:group, name: 'foo') }
let(:nested_namespace) { create(:group, name: 'bar', parent: parent_namespace) }
let(:test_name) { 'test_name' }
@@ -340,7 +342,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen a non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen a non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
it 'takes the selected namespace and name' do
@@ -371,7 +373,7 @@ shared_examples 'a GitHub-ish import controller: POST create' do
end
end
- context 'user has chosen existent and non-existent nested namespaces and name for the project', :postgresql do
+ context 'user has chosen existent and non-existent nested namespaces and name for the project' do
let(:test_name) { 'test_name' }
let!(:parent_namespace) { create(:group, name: 'foo') }
diff --git a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
index a0c77eecb61..d636c1cf6cd 100644
--- a/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
+++ b/spec/support/controllers/ldap_omniauth_callbacks_controller_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_context 'Ldap::OmniauthCallbacksController' do
diff --git a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
index 355555d9d19..b5149a0fcb1 100644
--- a/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
+++ b/spec/support/controllers/sessionless_auth_controller_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'authenticates sessionless user' do |path, format, params|
params ||= {}
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 19b32c84d81..c57abbd96c6 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# rubocop:disable Metrics/AbcSize
# Note: The ABC size is large here because we have a method generating test cases with
@@ -50,7 +52,7 @@ module CycleAnalyticsHelpers
end
median_time_difference = time_differences.sort[2]
- expect(subject[phase].median).to be_within(5).of(median_time_difference)
+ expect(subject[phase].project_median).to be_within(5).of(median_time_difference)
end
context "when the data belongs to another project" do
@@ -80,7 +82,7 @@ module CycleAnalyticsHelpers
# Turn off the stub before checking assertions
allow(self).to receive(:project).and_call_original
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
@@ -103,7 +105,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -121,7 +123,7 @@ module CycleAnalyticsHelpers
Timecop.freeze(end_time + 1.day) { post_fn[self, data] } if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -138,7 +140,7 @@ module CycleAnalyticsHelpers
post_fn[self, data] if post_fn
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
@@ -146,7 +148,7 @@ module CycleAnalyticsHelpers
context "when none of the start / end conditions are matched" do
it "returns nil" do
- expect(subject[phase].median).to be_nil
+ expect(subject[phase].project_median).to be_nil
end
end
end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index 08622dff6d9..041ffa25535 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DbCleaner
def delete_from_all_tables!(except: nil)
DatabaseCleaner.clean_with(:deletion, cache_tables: false, except: except)
diff --git a/spec/support/external_authorization_service_helpers.rb b/spec/support/external_authorization_service_helpers.rb
index 79dd9a3d58e..f4214d800cf 100644
--- a/spec/support/external_authorization_service_helpers.rb
+++ b/spec/support/external_authorization_service_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExternalAuthorizationServiceHelpers
def enable_external_authorization_service_check
stub_application_setting(external_authorization_service_enabled: true)
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index 7c8e57702ae..5590bf0fb7e 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'thread comments' do |resource_name|
let(:form_selector) { '.js-main-target-form' }
let(:dropdown_selector) { "#{form_selector} .comment-type-dropdown" }
diff --git a/spec/support/features/reportable_note_shared_examples.rb b/spec/support/features/reportable_note_shared_examples.rb
index 5d5a0a7b5d2..2f9208e6ed5 100644
--- a/spec/support/features/reportable_note_shared_examples.rb
+++ b/spec/support/features/reportable_note_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'reportable note' do |type|
diff --git a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
index 8d0e03134d0..d4f8a87d0d8 100644
--- a/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
+++ b/spec/support/features/resolving_discussions_in_issues_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'creating an issue for a thread' do
it 'shows an issue with the title filled in' do
title_field = page.find_field('issue[title]')
diff --git a/spec/support/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 0de92aedba5..c97eeba87db 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "an autodiscoverable RSS feed with current_user's feed token" do
it "has an RSS autodiscovery link tag with current_user's feed token" do
expect(page).to have_css("link[type*='atom+xml'][href*='feed_token=#{user.feed_token}']", visible: false)
diff --git a/spec/support/features/variable_list_shared_examples.rb b/spec/support/features/variable_list_shared_examples.rb
index 01531864c1f..0f8ad2c6536 100644
--- a/spec/support/features/variable_list_shared_examples.rb
+++ b/spec/support/features/variable_list_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'variable list' do
it 'shows list of variables' do
page.within('.js-ci-variable-list-section') do
diff --git a/spec/support/forgery_protection.rb b/spec/support/forgery_protection.rb
index fa87d5fa881..1d6ea013292 100644
--- a/spec/support/forgery_protection.rb
+++ b/spec/support/forgery_protection.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ForgeryProtection
def with_forgery_protection
ActionController::Base.allow_forgery_protection = true
diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb
index 2fdbddd40c2..a1328ef0d13 100644
--- a/spec/support/google_api/cloud_platform_helpers.rb
+++ b/spec/support/google_api/cloud_platform_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GoogleApi
module CloudPlatformHelpers
def stub_google_api_validate_token
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index 4a9ce9beb78..aff0f87b6e4 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ApiHelpers
# Public: Prepend a request path with the path to the API
#
@@ -30,11 +32,12 @@ module ApiHelpers
end
if query_string
- full_path << (path.index('?') ? '&' : '?')
- full_path << query_string
- end
+ separator = path.index('?') ? '&' : '?'
- full_path
+ full_path + separator + query_string
+ else
+ full_path
+ end
end
def expect_paginated_array_response(items)
diff --git a/spec/support/helpers/assets_helpers.rb b/spec/support/helpers/assets_helpers.rb
index 09bbf451671..fa24ad9ad2a 100644
--- a/spec/support/helpers/assets_helpers.rb
+++ b/spec/support/helpers/assets_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module AssetsHelpers
# In a CI environment the assets are not compiled, as there is a CI job
# `compile-assets` that compiles them in the prepare stage for all following
diff --git a/spec/support/helpers/bare_repo_operations.rb b/spec/support/helpers/bare_repo_operations.rb
index 3f4a4243cb6..099610f087d 100644
--- a/spec/support/helpers/bare_repo_operations.rb
+++ b/spec/support/helpers/bare_repo_operations.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'zlib'
class BareRepoOperations
diff --git a/spec/support/helpers/board_helpers.rb b/spec/support/helpers/board_helpers.rb
index b85fde222ea..683ee3e4bf2 100644
--- a/spec/support/helpers/board_helpers.rb
+++ b/spec/support/helpers/board_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module BoardHelpers
def click_card(card)
within card do
diff --git a/spec/support/helpers/capybara_helpers.rb b/spec/support/helpers/capybara_helpers.rb
index bcc2df44708..5abbc1e2951 100644
--- a/spec/support/helpers/capybara_helpers.rb
+++ b/spec/support/helpers/capybara_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CapybaraHelpers
# Execute a block a certain number of times before considering it a failure
#
diff --git a/spec/support/helpers/ci_artifact_metadata_generator.rb b/spec/support/helpers/ci_artifact_metadata_generator.rb
index ef638d59d2d..e02501565a9 100644
--- a/spec/support/helpers/ci_artifact_metadata_generator.rb
+++ b/spec/support/helpers/ci_artifact_metadata_generator.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# frozen_sting_literal: true
# This generates fake CI metadata .gz for testing
diff --git a/spec/support/helpers/cookie_helper.rb b/spec/support/helpers/cookie_helper.rb
index 5ff7b0b68c9..ea4be12355b 100644
--- a/spec/support/helpers/cookie_helper.rb
+++ b/spec/support/helpers/cookie_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper for setting cookies in Selenium/WebDriver
#
module CookieHelper
diff --git a/spec/support/helpers/cycle_analytics_helpers.rb b/spec/support/helpers/cycle_analytics_helpers.rb
index 100e439ef44..575b2e779c5 100644
--- a/spec/support/helpers/cycle_analytics_helpers.rb
+++ b/spec/support/helpers/cycle_analytics_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module CycleAnalyticsHelpers
include GitHelpers
diff --git a/spec/support/helpers/database_connection_helpers.rb b/spec/support/helpers/database_connection_helpers.rb
index 763329499f0..10ea7b5de91 100644
--- a/spec/support/helpers/database_connection_helpers.rb
+++ b/spec/support/helpers/database_connection_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DatabaseConnectionHelpers
def run_with_new_database_connection
pool = ActiveRecord::Base.connection_pool
diff --git a/spec/support/helpers/devise_helpers.rb b/spec/support/helpers/devise_helpers.rb
index fb2a110422a..70fc6a48414 100644
--- a/spec/support/helpers/devise_helpers.rb
+++ b/spec/support/helpers/devise_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DeviseHelpers
# explicitly tells Devise which mapping to use
# this is needed when we are testing a Devise controller bypassing the router
diff --git a/spec/support/helpers/drag_to_helper.rb b/spec/support/helpers/drag_to_helper.rb
index 6d53ad0b602..6099f87323f 100644
--- a/spec/support/helpers/drag_to_helper.rb
+++ b/spec/support/helpers/drag_to_helper.rb
@@ -1,3 +1,5 @@
+# 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}}});")
diff --git a/spec/support/helpers/dropzone_helper.rb b/spec/support/helpers/dropzone_helper.rb
index fe72d320fcf..a0f261b312e 100644
--- a/spec/support/helpers/dropzone_helper.rb
+++ b/spec/support/helpers/dropzone_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module DropzoneHelper
# Provides a way to perform `attach_file` for a Dropzone-based file input
#
diff --git a/spec/support/helpers/email_helpers.rb b/spec/support/helpers/email_helpers.rb
index ed049daba80..024340310a1 100644
--- a/spec/support/helpers/email_helpers.rb
+++ b/spec/support/helpers/email_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module EmailHelpers
def sent_to_user(user, recipients: email_recipients)
recipients.count { |to| to == user.notification_email }
@@ -29,6 +31,10 @@ module EmailHelpers
expect(ActionMailer::Base.deliveries).to be_empty
end
+ def should_email_anyone
+ expect(ActionMailer::Base.deliveries).not_to be_empty
+ end
+
def email_recipients(kind: :to)
ActionMailer::Base.deliveries.flat_map(&kind)
end
diff --git a/spec/support/helpers/exclusive_lease_helpers.rb b/spec/support/helpers/exclusive_lease_helpers.rb
index 383cc7dee81..77703e20602 100644
--- a/spec/support/helpers/exclusive_lease_helpers.rb
+++ b/spec/support/helpers/exclusive_lease_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExclusiveLeaseHelpers
def stub_exclusive_lease(key = nil, uuid = 'uuid', renew: false, timeout: nil)
key ||= instance_of(String)
diff --git a/spec/support/helpers/expect_next_instance_of.rb b/spec/support/helpers/expect_next_instance_of.rb
index b95046b2b42..749d2cb2a56 100644
--- a/spec/support/helpers/expect_next_instance_of.rb
+++ b/spec/support/helpers/expect_next_instance_of.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExpectNextInstanceOf
def expect_next_instance_of(klass, *new_args)
receive_new = receive(:new)
diff --git a/spec/support/helpers/expect_offense.rb b/spec/support/helpers/expect_offense.rb
index 35718ba90c5..76301fe19ff 100644
--- a/spec/support/helpers/expect_offense.rb
+++ b/spec/support/helpers/expect_offense.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rubocop/rspec/support'
# https://github.com/backus/rubocop-rspec/blob/master/spec/support/expect_offense.rb
diff --git a/spec/support/helpers/expect_request_with_status.rb b/spec/support/helpers/expect_request_with_status.rb
new file mode 100644
index 00000000000..0469a94e336
--- /dev/null
+++ b/spec/support/helpers/expect_request_with_status.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module ExpectRequestWithStatus
+ def expect_request_with_status(status)
+ expect do
+ yield
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+end
diff --git a/spec/support/helpers/fake_blob_helpers.rb b/spec/support/helpers/fake_blob_helpers.rb
index 801ca8b7412..ef4740638ff 100644
--- a/spec/support/helpers/fake_blob_helpers.rb
+++ b/spec/support/helpers/fake_blob_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FakeBlobHelpers
class FakeBlob
include BlobLike
diff --git a/spec/support/helpers/fake_migration_classes.rb b/spec/support/helpers/fake_migration_classes.rb
index c7766df7a52..6c066b3b199 100644
--- a/spec/support/helpers/fake_migration_classes.rb
+++ b/spec/support/helpers/fake_migration_classes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeRenameReservedPathMigrationV1 < ActiveRecord::Migration[4.2]
include Gitlab::Database::RenameReservedPathsMigration::V1
diff --git a/spec/support/helpers/fake_u2f_device.rb b/spec/support/helpers/fake_u2f_device.rb
index 22cd8152d77..f765b277175 100644
--- a/spec/support/helpers/fake_u2f_device.rb
+++ b/spec/support/helpers/fake_u2f_device.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class FakeU2fDevice
attr_reader :name
diff --git a/spec/support/helpers/features/branches_helpers.rb b/spec/support/helpers/features/branches_helpers.rb
index df88fd425c9..2a50b41cb4e 100644
--- a/spec/support/helpers/features/branches_helpers.rb
+++ b/spec/support/helpers/features/branches_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/features/notes_helpers.rb b/spec/support/helpers/features/notes_helpers.rb
index 8a139fafac2..8c27f81930d 100644
--- a/spec/support/helpers/features/notes_helpers.rb
+++ b/spec/support/helpers/features/notes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with notes.
#
# Usage:
@@ -21,6 +23,14 @@ module Spec
end
end
+ def edit_note(note_text_to_edit, new_note_text)
+ page.within('#notes-list li.note', text: note_text_to_edit) do
+ find('.js-note-edit').click
+ fill_in('note[note]', with: new_note_text)
+ find('.js-comment-button').click
+ end
+ end
+
def preview_note(text)
page.within('.js-main-target-form') do
filled_text = fill_in('note[note]', with: text)
diff --git a/spec/support/helpers/features/sorting_helpers.rb b/spec/support/helpers/features/sorting_helpers.rb
index 003ecb251fe..a6428bf8573 100644
--- a/spec/support/helpers/features/sorting_helpers.rb
+++ b/spec/support/helpers/features/sorting_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These helpers allow you to manipulate with sorting features.
#
# Usage:
diff --git a/spec/support/helpers/filter_spec_helper.rb b/spec/support/helpers/filter_spec_helper.rb
index 721d359c2ee..95c24d76dcd 100644
--- a/spec/support/helpers/filter_spec_helper.rb
+++ b/spec/support/helpers/filter_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper methods for Banzai filter specs
#
# Must be included into specs manually
diff --git a/spec/support/helpers/filtered_search_helpers.rb b/spec/support/helpers/filtered_search_helpers.rb
index 34ef185ea27..39c818b1763 100644
--- a/spec/support/helpers/filtered_search_helpers.rb
+++ b/spec/support/helpers/filtered_search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FilteredSearchHelpers
def filtered_search
page.find('.filtered-search')
diff --git a/spec/support/helpers/fixture_helpers.rb b/spec/support/helpers/fixture_helpers.rb
index 611d19f36a0..7b3b8ae5f7a 100644
--- a/spec/support/helpers/fixture_helpers.rb
+++ b/spec/support/helpers/fixture_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module FixtureHelpers
def fixture_file(filename, dir: '')
return '' if filename.blank?
diff --git a/spec/support/helpers/git_http_helpers.rb b/spec/support/helpers/git_http_helpers.rb
index c83860d7b51..de8bb9ac8e3 100644
--- a/spec/support/helpers/git_http_helpers.rb
+++ b/spec/support/helpers/git_http_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'workhorse_helpers'
module GitHttpHelpers
diff --git a/spec/support/helpers/gitlab_verify_helpers.rb b/spec/support/helpers/gitlab_verify_helpers.rb
index 5df4bf24ec2..9901ce374ed 100644
--- a/spec/support/helpers/gitlab_verify_helpers.rb
+++ b/spec/support/helpers/gitlab_verify_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module GitlabVerifyHelpers
def collect_ranges(args = {})
verifier = described_class.new(args.merge(batch_size: 1))
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index ec3c460cd37..d86371d70b9 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -1,6 +1,10 @@
+# frozen_string_literal: true
+
module GraphqlHelpers
MutationDefinition = Struct.new(:query, :variables)
+ NoData = Class.new(StandardError)
+
# makes an underscored string look like a fieldname
# "merge_request" => "mergeRequest"
def self.fieldnamerize(underscored_field_name)
@@ -156,8 +160,9 @@ module GraphqlHelpers
post_graphql(mutation.query, current_user: current_user, variables: mutation.variables)
end
+ # Raises an error if no data is found
def graphql_data
- json_response['data']
+ json_response['data'] || (raise NoData, graphql_errors)
end
def graphql_errors
@@ -171,8 +176,9 @@ module GraphqlHelpers
end
end
+ # Raises an error if no response is found
def graphql_mutation_response(mutation_name)
- graphql_data[GraphqlHelpers.fieldnamerize(mutation_name)]
+ graphql_data.fetch(GraphqlHelpers.fieldnamerize(mutation_name))
end
def nested_fields?(field)
diff --git a/spec/support/helpers/import_spec_helper.rb b/spec/support/helpers/import_spec_helper.rb
index d4eced724fa..d8fb2ba08af 100644
--- a/spec/support/helpers/import_spec_helper.rb
+++ b/spec/support/helpers/import_spec_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'ostruct'
# Helper methods for controller specs in the Import namespace
diff --git a/spec/support/helpers/input_helper.rb b/spec/support/helpers/input_helper.rb
index acbb42274ec..5136f8e9cb8 100644
--- a/spec/support/helpers/input_helper.rb
+++ b/spec/support/helpers/input_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# see app/assets/javascripts/test_utils/simulate_input.js
module InputHelper
diff --git a/spec/support/helpers/inspect_requests.rb b/spec/support/helpers/inspect_requests.rb
index 88ddc5c7f6c..4a1d9cb8539 100644
--- a/spec/support/helpers/inspect_requests.rb
+++ b/spec/support/helpers/inspect_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative './wait_for_requests'
module InspectRequests
diff --git a/spec/support/helpers/issue_helpers.rb b/spec/support/helpers/issue_helpers.rb
index ffd72515f37..82b954a92e2 100644
--- a/spec/support/helpers/issue_helpers.rb
+++ b/spec/support/helpers/issue_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module IssueHelpers
def visit_issues(project, opts = {})
visit project_issues_path project, opts
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index cdd7724cc13..7ec65318ec5 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'action_dispatch/testing/test_request'
require 'fileutils'
@@ -19,7 +21,7 @@ module JavaScriptFixturesHelpers
end
def fixture_root_path
- (Gitlab.ee? ? 'ee/' : '') + 'spec/javascripts/fixtures'
+ 'tmp/tests/frontend/fixtures' + (Gitlab.ee? ? '-ee' : '')
end
# Public: Removes all fixture files from given directory
diff --git a/spec/support/helpers/jira_service_helper.rb b/spec/support/helpers/jira_service_helper.rb
index 7e955f3d593..57c33c81ea3 100644
--- a/spec/support/helpers/jira_service_helper.rb
+++ b/spec/support/helpers/jira_service_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module JiraServiceHelper
JIRA_URL = "http://jira.example.net".freeze
JIRA_API = JIRA_URL + "/rest/api/2"
diff --git a/spec/support/helpers/key_generator_helper.rb b/spec/support/helpers/key_generator_helper.rb
index d55d8312c65..59c8eeb3692 100644
--- a/spec/support/helpers/key_generator_helper.rb
+++ b/spec/support/helpers/key_generator_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Spec
module Support
module Helpers
@@ -33,7 +35,7 @@ module Spec
# Packs string components into an openssh-encoded pubkey.
def pack_pubkey_components(strings)
- (strings.map { |s| [s.length].pack('N') }).zip(strings).flatten.join
+ (strings.flat_map { |s| [s.length].pack('N') }).zip(strings).join
end
end
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index 278264f3df5..538a5b8ef3c 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module KubernetesHelpers
include Gitlab::Kubernetes
diff --git a/spec/support/helpers/ldap_helpers.rb b/spec/support/helpers/ldap_helpers.rb
index 66ca5d7f0a3..dce8a3803f5 100644
--- a/spec/support/helpers/ldap_helpers.rb
+++ b/spec/support/helpers/ldap_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module LdapHelpers
def ldap_adapter(provider = 'ldapmain', ldap = double(:ldap))
::Gitlab::Auth::LDAP::Adapter.new(provider, ldap)
diff --git a/spec/support/helpers/live_debugger.rb b/spec/support/helpers/live_debugger.rb
index 911eb48a8ca..d6091035b59 100644
--- a/spec/support/helpers/live_debugger.rb
+++ b/spec/support/helpers/live_debugger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'io/console'
module LiveDebugger
diff --git a/spec/support/helpers/login_helpers.rb b/spec/support/helpers/login_helpers.rb
index 0cb99b4e087..2b508ee6f2c 100644
--- a/spec/support/helpers/login_helpers.rb
+++ b/spec/support/helpers/login_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'devise_helpers'
module LoginHelpers
diff --git a/spec/support/helpers/markdown_feature.rb b/spec/support/helpers/markdown_feature.rb
index 96401379cf0..eea03fb9325 100644
--- a/spec/support/helpers/markdown_feature.rb
+++ b/spec/support/helpers/markdown_feature.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This is a helper class used by the GitLab Markdown feature spec
#
# Because the feature spec only cares about the output of the Markdown, and the
diff --git a/spec/support/helpers/merge_request_diff_helpers.rb b/spec/support/helpers/merge_request_diff_helpers.rb
index 3b49d0b3319..49beecc6d4b 100644
--- a/spec/support/helpers/merge_request_diff_helpers.rb
+++ b/spec/support/helpers/merge_request_diff_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestDiffHelpers
def click_diff_line(line_holder, diff_side = nil)
line = get_line_components(line_holder, diff_side)
diff --git a/spec/support/helpers/merge_request_helpers.rb b/spec/support/helpers/merge_request_helpers.rb
index 772adff4626..3c359bc9353 100644
--- a/spec/support/helpers/merge_request_helpers.rb
+++ b/spec/support/helpers/merge_request_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MergeRequestHelpers
def visit_merge_requests(project, opts = {})
visit project_merge_requests_path project, opts
diff --git a/spec/support/helpers/metrics_dashboard_helpers.rb b/spec/support/helpers/metrics_dashboard_helpers.rb
index 1511a2f6b49..0e86b6dfda7 100644
--- a/spec/support/helpers/metrics_dashboard_helpers.rb
+++ b/spec/support/helpers/metrics_dashboard_helpers.rb
@@ -18,6 +18,14 @@ module MetricsDashboardHelpers
project.repository.refresh_method_caches([:metrics_dashboard])
end
+ def system_dashboard_path
+ Metrics::Dashboard::SystemDashboardService::SYSTEM_DASHBOARD_PATH
+ end
+
+ def business_metric_title
+ PrometheusMetricEnums.group_details[:business][:group_title]
+ end
+
shared_examples_for 'misconfigured dashboard service response' do |status_code|
it 'returns an appropriate message and status code' do
result = service_call
diff --git a/spec/support/helpers/migrations_helpers.rb b/spec/support/helpers/migrations_helpers.rb
index 272b24f7541..2727ab7fb1e 100644
--- a/spec/support/helpers/migrations_helpers.rb
+++ b/spec/support/helpers/migrations_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
def active_record_base
ActiveRecord::Base
diff --git a/spec/support/helpers/mobile_helpers.rb b/spec/support/helpers/mobile_helpers.rb
index 4230d315d9b..94dbd2fb1b7 100644
--- a/spec/support/helpers/mobile_helpers.rb
+++ b/spec/support/helpers/mobile_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MobileHelpers
def resize_screen_xs
resize_window(575, 768)
diff --git a/spec/support/helpers/note_interaction_helpers.rb b/spec/support/helpers/note_interaction_helpers.rb
index 79a0aa174b1..a4322618cd3 100644
--- a/spec/support/helpers/note_interaction_helpers.rb
+++ b/spec/support/helpers/note_interaction_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NoteInteractionHelpers
def open_more_actions_dropdown(note)
note_element = find("#note_#{note.id}")
diff --git a/spec/support/helpers/notification_helpers.rb b/spec/support/helpers/notification_helpers.rb
index 44c2051598c..16ecb338f6e 100644
--- a/spec/support/helpers/notification_helpers.rb
+++ b/spec/support/helpers/notification_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module NotificationHelpers
extend self
diff --git a/spec/support/helpers/project_forks_helper.rb b/spec/support/helpers/project_forks_helper.rb
index bcb11a09b36..b2d22853e4c 100644
--- a/spec/support/helpers/project_forks_helper.rb
+++ b/spec/support/helpers/project_forks_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ProjectForksHelper
def fork_project(project, user = nil, params = {})
Gitlab::GitalyClient.allow_n_plus_1_calls do
diff --git a/spec/support/helpers/prometheus_helpers.rb b/spec/support/helpers/prometheus_helpers.rb
index db662836013..7c03746a395 100644
--- a/spec/support/helpers/prometheus_helpers.rb
+++ b/spec/support/helpers/prometheus_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module PrometheusHelpers
def prometheus_memory_query(environment_slug)
%{avg(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / 2^20}
@@ -74,6 +76,14 @@ module PrometheusHelpers
WebMock.stub_request(:any, /prometheus.example.com/)
end
+ def stub_any_prometheus_request_with_response(status: 200, headers: {}, body: nil)
+ stub_any_prometheus_request.to_return({
+ status: status,
+ headers: { 'Content-Type' => 'application/json' }.merge(headers),
+ body: body || prometheus_values_body.to_json
+ })
+ end
+
def stub_all_prometheus_requests(environment_slug, body: nil, status: 200)
stub_prometheus_request(
prometheus_query_with_time_url(prometheus_memory_query(environment_slug), Time.now.utc),
diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb
index f77b43391dd..d936dc6de41 100644
--- a/spec/support/helpers/query_recorder.rb
+++ b/spec/support/helpers/query_recorder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ActiveRecord
class QueryRecorder
attr_reader :log, :skip_cached, :cached
diff --git a/spec/support/helpers/quick_actions_helpers.rb b/spec/support/helpers/quick_actions_helpers.rb
index 361190aa352..cb853f5363f 100644
--- a/spec/support/helpers/quick_actions_helpers.rb
+++ b/spec/support/helpers/quick_actions_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module QuickActionsHelpers
def write_note(text)
Sidekiq::Testing.fake! do
diff --git a/spec/support/helpers/rake_helpers.rb b/spec/support/helpers/rake_helpers.rb
index 7d8d7750bf3..d8f354a69da 100644
--- a/spec/support/helpers/rake_helpers.rb
+++ b/spec/support/helpers/rake_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RakeHelpers
def run_rake_task(task_name, *args)
Rake::Task[task_name].reenable
diff --git a/spec/support/helpers/reactive_caching_helpers.rb b/spec/support/helpers/reactive_caching_helpers.rb
index 528da37e8cf..aa9d3b3a199 100644
--- a/spec/support/helpers/reactive_caching_helpers.rb
+++ b/spec/support/helpers/reactive_caching_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReactiveCachingHelpers
def reactive_cache_key(subject, *qualifiers)
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
diff --git a/spec/support/helpers/redis_without_keys.rb b/spec/support/helpers/redis_without_keys.rb
index 6220167dee6..e030f1028f7 100644
--- a/spec/support/helpers/redis_without_keys.rb
+++ b/spec/support/helpers/redis_without_keys.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
class Redis
ForbiddenCommand = Class.new(StandardError)
diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb
index 9f27502aa52..f96a01d15b5 100644
--- a/spec/support/helpers/reference_parser_helpers.rb
+++ b/spec/support/helpers/reference_parser_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ReferenceParserHelpers
def empty_html_link
Nokogiri::HTML.fragment('<a></a>').children[0]
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 44d95a029af..ca4d2acbf2c 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RepoHelpers
extend self
diff --git a/spec/support/helpers/routes_helpers.rb b/spec/support/helpers/routes_helpers.rb
index c4129606418..2a7cd81cbe3 100644
--- a/spec/support/helpers/routes_helpers.rb
+++ b/spec/support/helpers/routes_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RoutesHelpers
def fake_routes(&block)
@routes = @routes.dup
diff --git a/spec/support/helpers/search_helpers.rb b/spec/support/helpers/search_helpers.rb
index abbbb636d66..815337f8615 100644
--- a/spec/support/helpers/search_helpers.rb
+++ b/spec/support/helpers/search_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SearchHelpers
def select_filter(name)
find(:xpath, "//ul[contains(@class, 'search-filter')]//a[contains(.,'#{name}')]").click
diff --git a/spec/support/helpers/seed_repo.rb b/spec/support/helpers/seed_repo.rb
index 71f1a86b0c1..20738b45129 100644
--- a/spec/support/helpers/seed_repo.rb
+++ b/spec/support/helpers/seed_repo.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This file is generated by generate-seed-repo-rb. Do not edit this file manually.
#
# Seed repo:
diff --git a/spec/support/helpers/select2_helper.rb b/spec/support/helpers/select2_helper.rb
index 87672c8896d..9c42c2b0d8b 100644
--- a/spec/support/helpers/select2_helper.rb
+++ b/spec/support/helpers/select2_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative 'wait_for_requests'
# Select2 ajax programmatic helper
diff --git a/spec/support/helpers/selection_helper.rb b/spec/support/helpers/selection_helper.rb
index b4725b137b2..a5f9ca76f6e 100644
--- a/spec/support/helpers/selection_helper.rb
+++ b/spec/support/helpers/selection_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module SelectionHelper
def select_element(selector)
find(selector)
diff --git a/spec/support/helpers/sorting_helper.rb b/spec/support/helpers/sorting_helper.rb
index e505a6b7258..3801d25fb63 100644
--- a/spec/support/helpers/sorting_helper.rb
+++ b/spec/support/helpers/sorting_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Helper allows you to sort items
#
# Params
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index 049702be1f6..c8b2bf040e6 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'active_support/core_ext/hash/transform_values'
require 'active_support/hash_with_indifferent_access'
require 'active_support/dependencies'
diff --git a/spec/support/helpers/stub_env.rb b/spec/support/helpers/stub_env.rb
index 1c2f474a015..8107ffc939f 100644
--- a/spec/support/helpers/stub_env.rb
+++ b/spec/support/helpers/stub_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubENV
def stub_env(key_or_hash, value = nil)
diff --git a/spec/support/helpers/stub_feature_flags.rb b/spec/support/helpers/stub_feature_flags.rb
index 48258692304..6c3efff7262 100644
--- a/spec/support/helpers/stub_feature_flags.rb
+++ b/spec/support/helpers/stub_feature_flags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubFeatureFlags
# Stub Feature flags with `flag_name: true/false`
#
diff --git a/spec/support/helpers/stub_gitlab_calls.rb b/spec/support/helpers/stub_gitlab_calls.rb
index 4cb3b18df85..7d10cffe920 100644
--- a/spec/support/helpers/stub_gitlab_calls.rb
+++ b/spec/support/helpers/stub_gitlab_calls.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabCalls
def stub_gitlab_calls
stub_user
@@ -20,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/helpers/stub_gitlab_data.rb b/spec/support/helpers/stub_gitlab_data.rb
index fa402f35b95..ed518393c03 100644
--- a/spec/support/helpers/stub_gitlab_data.rb
+++ b/spec/support/helpers/stub_gitlab_data.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubGitlabData
def gitlab_ci_yaml
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
diff --git a/spec/support/helpers/stub_metrics.rb b/spec/support/helpers/stub_metrics.rb
index 64983fdf222..e347955efbb 100644
--- a/spec/support/helpers/stub_metrics.rb
+++ b/spec/support/helpers/stub_metrics.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubMetrics
def authentication_metrics
Gitlab::Auth::Activity
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index d31f9908714..e5b8bb712bb 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubObjectStorage
def stub_object_storage_uploader(
config:,
diff --git a/spec/support/helpers/stub_requests.rb b/spec/support/helpers/stub_requests.rb
index 5cad35282c0..473f07dd413 100644
--- a/spec/support/helpers/stub_requests.rb
+++ b/spec/support/helpers/stub_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module StubRequests
IP_ADDRESS_STUB = '8.8.8.9'.freeze
@@ -26,6 +28,19 @@ module StubRequests
.and_return([addr])
end
+ def stub_all_dns(url, ip_address:)
+ url = URI(url)
+ port = 80 # arbitarily chosen, does not matter as we are not going to connect
+ socket = Socket.sockaddr_in(port, ip_address)
+ addr = Addrinfo.new(socket)
+
+ # See Gitlab::UrlBlocker
+ allow(Addrinfo).to receive(:getaddrinfo).and_call_original
+ allow(Addrinfo).to receive(:getaddrinfo)
+ .with(url.hostname, anything, nil, :STREAM)
+ .and_return([addr])
+ end
+
def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
url = parse_url(url)
url.hostname = hostname
diff --git a/spec/support/helpers/stub_worker.rb b/spec/support/helpers/stub_worker.rb
index 58b7ee93dff..cac839ed5fe 100644
--- a/spec/support/helpers/stub_worker.rb
+++ b/spec/support/helpers/stub_worker.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Inspired by https://github.com/ljkbennett/stub_env/blob/master/lib/stub_env/helpers.rb
module StubWorker
def stub_worker(queue:)
diff --git a/spec/support/helpers/terms_helper.rb b/spec/support/helpers/terms_helper.rb
index a00ec14138b..a61bae18f9a 100644
--- a/spec/support/helpers/terms_helper.rb
+++ b/spec/support/helpers/terms_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TermsHelper
def enforce_terms
stub_env('IN_MEMORY_APPLICATION_SETTINGS', 'false')
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 893b10ea752..a4acf76e1a3 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rspec/mocks'
require 'toml-rb'
@@ -130,7 +132,7 @@ module TestEnv
# Keeps gitlab-shell and gitlab-test
def clean_test_path
Dir[TMP_TEST_PATH].each do |entry|
- unless File.basename(entry) =~ /\A(gitaly|gitlab-(shell|test|test_bare|test-fork|test-fork_bare))\z/
+ unless test_dirs.include?(File.basename(entry))
FileUtils.rm_rf(entry)
end
end
@@ -141,14 +143,6 @@ module TestEnv
FileUtils.mkdir_p(artifacts_path)
end
- def clean_gitlab_test_path
- Dir[TMP_TEST_PATH].each do |entry|
- unless test_dirs.include?(File.basename(entry))
- FileUtils.rm_rf(entry)
- end
- end
- end
-
def setup_gitlab_shell
component_timed_setup('GitLab Shell',
install_dir: Gitlab.config.gitlab_shell.path,
@@ -324,6 +318,7 @@ module TestEnv
# These are directories that should be preserved at cleanup time
def test_dirs
@test_dirs ||= %w[
+ frontend
gitaly
gitlab-shell
gitlab-test
@@ -368,10 +363,7 @@ module TestEnv
# Try to reset without fetching to avoid using the network.
unless reset.call
raise 'Could not fetch test seed repository.' unless system(*%W(#{Gitlab.config.git.bin_path} -C #{repo_path} fetch origin))
-
- # Before we used Git clone's --mirror option, bare repos could end up
- # with missing refs, clearing them and retrying should fix the issue.
- clean_gitlab_test_path && init unless reset.call
+ raise "Could not update test seed repository, please delete #{repo_path} and try again" unless reset.call
end
end
diff --git a/spec/support/helpers/upload_helpers.rb b/spec/support/helpers/upload_helpers.rb
index 5eead80c935..60e14a8673b 100644
--- a/spec/support/helpers/upload_helpers.rb
+++ b/spec/support/helpers/upload_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'fileutils'
module UploadHelpers
diff --git a/spec/support/helpers/wait_for_requests.rb b/spec/support/helpers/wait_for_requests.rb
index 45b9faa0fea..3bb2f7c5b51 100644
--- a/spec/support/helpers/wait_for_requests.rb
+++ b/spec/support/helpers/wait_for_requests.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitForRequests
extend self
diff --git a/spec/support/helpers/wait_helpers.rb b/spec/support/helpers/wait_helpers.rb
index 7e8e25798e8..a8c4408db59 100644
--- a/spec/support/helpers/wait_helpers.rb
+++ b/spec/support/helpers/wait_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WaitHelpers
extend self
diff --git a/spec/support/helpers/wiki_helpers.rb b/spec/support/helpers/wiki_helpers.rb
index 8165403cb60..06cea728b42 100644
--- a/spec/support/helpers/wiki_helpers.rb
+++ b/spec/support/helpers/wiki_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WikiHelpers
extend self
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index ef1f9f68671..4488e5f227e 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module WorkhorseHelpers
extend self
diff --git a/spec/support/http_io/http_io_helpers.rb b/spec/support/http_io/http_io_helpers.rb
index 42144870eb5..0193db81fa9 100644
--- a/spec/support/http_io/http_io_helpers.rb
+++ b/spec/support/http_io/http_io_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module HttpIOHelpers
def stub_remote_url_206(url, file_path)
WebMock.stub_request(:get, url)
diff --git a/spec/support/import_export/common_util.rb b/spec/support/import_export/common_util.rb
index 2542a59bb00..ac6840dbcfc 100644
--- a/spec/support/import_export/common_util.rb
+++ b/spec/support/import_export/common_util.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ImportExport
module CommonUtil
def setup_symlink(tmpdir, symlink_name)
diff --git a/spec/support/import_export/configuration_helper.rb b/spec/support/import_export/configuration_helper.rb
index b4164cff922..122df7f27f0 100644
--- a/spec/support/import_export/configuration_helper.rb
+++ b/spec/support/import_export/configuration_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ConfigurationHelper
# Returns a list of models from hashes/arrays contained in +project_tree+
def names_from_tree(project_tree)
diff --git a/spec/support/import_export/export_file_helper.rb b/spec/support/import_export/export_file_helper.rb
index 388b88f0331..f862a9bc1a4 100644
--- a/spec/support/import_export/export_file_helper.rb
+++ b/spec/support/import_export/export_file_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require './spec/support/import_export/configuration_helper'
module ExportFileHelper
diff --git a/spec/support/inspect_squelch.rb b/spec/support/inspect_squelch.rb
index 8ee6732370b..90475204889 100644
--- a/spec/support/inspect_squelch.rb
+++ b/spec/support/inspect_squelch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This class can generate a lot of output if it fails,
# so squelch the instance variable output.
class ActiveSupport::Cache::NullStore
diff --git a/spec/support/issuables_requiring_filter_shared_examples.rb b/spec/support/issuables_requiring_filter_shared_examples.rb
index 71bcc82ee58..ee25df00dfb 100644
--- a/spec/support/issuables_requiring_filter_shared_examples.rb
+++ b/spec/support/issuables_requiring_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables requiring filter' do |action|
it "doesn't load any issuables if no filter is set" do
expect_any_instance_of(described_class).not_to receive(:issuables_collection)
diff --git a/spec/support/json_response.rb b/spec/support/json_response.rb
index 43d8ab73dde..55bdce0cfe9 100644
--- a/spec/support/json_response.rb
+++ b/spec/support/json_response.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include_context 'JSON response', type: :controller
config.include_context 'JSON response', type: :request
diff --git a/spec/support/matchers/abort_matcher.rb b/spec/support/matchers/abort_matcher.rb
index ce1dd140210..64fed2ca069 100644
--- a/spec/support/matchers/abort_matcher.rb
+++ b/spec/support/matchers/abort_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :abort_execution do
match do |code_block|
@captured_stderr = StringIO.new
diff --git a/spec/support/matchers/access_matchers.rb b/spec/support/matchers/access_matchers.rb
index e6899e2d23c..c9ff777f604 100644
--- a/spec/support/matchers/access_matchers.rb
+++ b/spec/support/matchers/access_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchers
#
# The custom matchers contained in this module are used to test a user's access
diff --git a/spec/support/matchers/access_matchers_for_controller.rb b/spec/support/matchers/access_matchers_for_controller.rb
index 429401a5da8..401bf6c196e 100644
--- a/spec/support/matchers/access_matchers_for_controller.rb
+++ b/spec/support/matchers/access_matchers_for_controller.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# AccessMatchersForController
#
# For testing authorize_xxx in controller.
diff --git a/spec/support/matchers/background_migrations_matchers.rb b/spec/support/matchers/background_migrations_matchers.rb
index f4127efc6ae..c38aa7ad6a6 100644
--- a/spec/support/matchers/background_migrations_matchers.rb
+++ b/spec/support/matchers/background_migrations_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_scheduled_delayed_migration do |delay, *expected|
match do |migration|
BackgroundMigrationWorker.jobs.any? do |job|
diff --git a/spec/support/matchers/be_a_binary_string.rb b/spec/support/matchers/be_a_binary_string.rb
index f041ae76167..6195c6c7554 100644
--- a/spec/support/matchers/be_a_binary_string.rb
+++ b/spec/support/matchers/be_a_binary_string.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_a_binary_string do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('ASCII-8BIT')
diff --git a/spec/support/matchers/be_like_time.rb b/spec/support/matchers/be_like_time.rb
index 1f27390eab7..b449f7a7ffb 100644
--- a/spec/support/matchers/be_like_time.rb
+++ b/spec/support/matchers/be_like_time.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_like_time do |expected|
match do |actual|
expect(actual).to be_within(1.second).of(expected)
diff --git a/spec/support/matchers/be_url.rb b/spec/support/matchers/be_url.rb
index f8096af1b22..69171f53891 100644
--- a/spec/support/matchers/be_url.rb
+++ b/spec/support/matchers/be_url.rb
@@ -1,5 +1,11 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_url do |_|
match do |actual|
URI.parse(actual) rescue false
end
end
+
+# looks better when used like:
+# expect(thing).to receive(:method).with(a_valid_url)
+RSpec::Matchers.alias_matcher :a_valid_url, :be_url
diff --git a/spec/support/matchers/be_utf8.rb b/spec/support/matchers/be_utf8.rb
index ea806352422..4fa55539a49 100644
--- a/spec/support/matchers/be_utf8.rb
+++ b/spec/support/matchers/be_utf8.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_utf8 do |_|
match do |actual|
actual.is_a?(String) && actual.encoding == Encoding.find('UTF-8')
diff --git a/spec/support/matchers/be_valid_commit.rb b/spec/support/matchers/be_valid_commit.rb
index 3696e4d5f03..b59339de622 100644
--- a/spec/support/matchers/be_valid_commit.rb
+++ b/spec/support/matchers/be_valid_commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :be_valid_commit do
match do |actual|
actual &&
diff --git a/spec/support/matchers/disallow_request_matchers.rb b/spec/support/matchers/disallow_request_matchers.rb
index db4d90e4fd0..a161e3660cd 100644
--- a/spec/support/matchers/disallow_request_matchers.rb
+++ b/spec/support/matchers/disallow_request_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :disallow_request do
match do |middleware|
alert = middleware.env['rack.session'].to_hash
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
index cd042401f3a..40cf85eb8e5 100644
--- a/spec/support/matchers/exceed_query_limit.rb
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module ExceedQueryLimitHelpers
def with_threshold(threshold)
@threshold = threshold
diff --git a/spec/support/matchers/execute_check.rb b/spec/support/matchers/execute_check.rb
index 7232fad52fb..d3c0751f0dc 100644
--- a/spec/support/matchers/execute_check.rb
+++ b/spec/support/matchers/execute_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :execute_check do |expected|
match do |actual|
expect(actual).to eq(SystemCheck)
diff --git a/spec/support/matchers/gitaly_matchers.rb b/spec/support/matchers/gitaly_matchers.rb
index 933ed22b5d0..c2b3ebe3422 100644
--- a/spec/support/matchers/gitaly_matchers.rb
+++ b/spec/support/matchers/gitaly_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitaly_request_with_path do |storage_name, relative_path|
match do |actual|
repository = actual.repository
diff --git a/spec/support/matchers/gitlab_git_matchers.rb b/spec/support/matchers/gitlab_git_matchers.rb
index c840cd4bf2d..aea1603db05 100644
--- a/spec/support/matchers/gitlab_git_matchers.rb
+++ b/spec/support/matchers/gitlab_git_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :gitlab_git_repository_with do |values|
match do |actual|
actual.is_a?(Gitlab::Git::Repository) &&
diff --git a/spec/support/matchers/graphql_matchers.rb b/spec/support/matchers/graphql_matchers.rb
index 7894484f590..4d48b4b5389 100644
--- a/spec/support/matchers/graphql_matchers.rb
+++ b/spec/support/matchers/graphql_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :require_graphql_authorizations do |*expected|
match do |field|
expect(field.metadata[:authorize]).to eq(*expected)
diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb
index 23fb8e9c1c4..273bd0b7f40 100644
--- a/spec/support/matchers/have_emoji.rb
+++ b/spec/support/matchers/have_emoji.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_emoji do |emoji_name|
match do |actual|
expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
diff --git a/spec/support/matchers/have_gitlab_http_status.rb b/spec/support/matchers/have_gitlab_http_status.rb
index e7e418cdde4..13a64a58218 100644
--- a/spec/support/matchers/have_gitlab_http_status.rb
+++ b/spec/support/matchers/have_gitlab_http_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_gitlab_http_status do |expected|
match do |actual|
expect(actual).to have_http_status(expected)
diff --git a/spec/support/matchers/have_issuable_counts.rb b/spec/support/matchers/have_issuable_counts.rb
index 92cf3de5448..049cfc022fb 100644
--- a/spec/support/matchers/have_issuable_counts.rb
+++ b/spec/support/matchers/have_issuable_counts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_issuable_counts do |opts|
expected_counts = opts.map do |state, count|
"#{state.to_s.humanize} #{count}"
diff --git a/spec/support/matchers/include_module.rb b/spec/support/matchers/include_module.rb
index 0a78af1e90e..9b6970cf061 100644
--- a/spec/support/matchers/include_module.rb
+++ b/spec/support/matchers/include_module.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_module do |expected|
match do
described_class.included_modules.include?(expected)
diff --git a/spec/support/matchers/issuable_matchers.rb b/spec/support/matchers/issuable_matchers.rb
index 62f510b0fbd..743f0b8c932 100644
--- a/spec/support/matchers/issuable_matchers.rb
+++ b/spec/support/matchers/issuable_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_header_with_correct_id_and_link do |level, text, id, parent = ".md"|
match do |actual|
node = find("#{parent} h#{level} a#user-content-#{id}")
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index ec4ec6f4038..12e8fa83a60 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# MarkdownMatchers
#
# Custom matchers for our custom HTML::Pipeline filters. These are used to test
diff --git a/spec/support/matchers/match_file.rb b/spec/support/matchers/match_file.rb
index d1888b3376a..4e522b52912 100644
--- a/spec/support/matchers/match_file.rb
+++ b/spec/support/matchers/match_file.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_file do |expected|
match do |actual|
expect(Digest::MD5.hexdigest(actual)).to eq(Digest::MD5.hexdigest(File.read(expected)))
diff --git a/spec/support/matchers/match_ids.rb b/spec/support/matchers/match_ids.rb
index 1cb6b74acac..7bc41949937 100644
--- a/spec/support/matchers/match_ids.rb
+++ b/spec/support/matchers/match_ids.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :match_ids do |*expected|
match do |actual|
actual_ids = map_ids(actual)
diff --git a/spec/support/matchers/metric_counter_matcher.rb b/spec/support/matchers/metric_counter_matcher.rb
index 22d5cd17e3f..f0d52b9b149 100644
--- a/spec/support/matchers/metric_counter_matcher.rb
+++ b/spec/support/matchers/metric_counter_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :increment do |counter|
match do |adapter|
expect(adapter.send(counter))
diff --git a/spec/support/matchers/navigation_matcher.rb b/spec/support/matchers/navigation_matcher.rb
index 63f59b9654c..ad73c96031e 100644
--- a/spec/support/matchers/navigation_matcher.rb
+++ b/spec/support/matchers/navigation_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :have_active_navigation do |expected|
match do |page|
expect(page).to have_selector('.sidebar-top-level-items > li.active', count: 1)
diff --git a/spec/support/matchers/pagination_matcher.rb b/spec/support/matchers/pagination_matcher.rb
index 9a7697e2bfc..a3e9c3b8474 100644
--- a/spec/support/matchers/pagination_matcher.rb
+++ b/spec/support/matchers/pagination_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_pagination_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Total', 'X-Total-Pages', 'X-Per-Page', 'X-Page', 'X-Next-Page', 'X-Prev-Page', 'Link')
diff --git a/spec/support/matchers/query_matcher.rb b/spec/support/matchers/query_matcher.rb
index bb0feca7c43..3e47fe241bc 100644
--- a/spec/support/matchers/query_matcher.rb
+++ b/spec/support/matchers/query_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
supports_block_expectations
diff --git a/spec/support/matchers/satisfy_matchers.rb b/spec/support/matchers/satisfy_matchers.rb
index 585915bac93..dd1920ee2b2 100644
--- a/spec/support/matchers/satisfy_matchers.rb
+++ b/spec/support/matchers/satisfy_matchers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These matchers are a syntactic hack to provide more readable expectations for
# an Enumerable object.
#
diff --git a/spec/support/matchers/security_header_matcher.rb b/spec/support/matchers/security_header_matcher.rb
index f8518d13ebb..760b1fddd06 100644
--- a/spec/support/matchers/security_header_matcher.rb
+++ b/spec/support/matchers/security_header_matcher.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec::Matchers.define :include_security_headers do |expected|
match do |actual|
expect(actual.headers).to include('X-Content-Type-Options')
diff --git a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
index 016bcfa9b1b..656be3b6d4d 100644
--- a/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
+++ b/spec/support/migrations_helpers/track_untracked_uploads_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module MigrationsHelpers
module TrackUntrackedUploadsHelpers
PUBLIC_DIR = File.join(Rails.root, 'tmp', 'tests', 'public')
diff --git a/spec/support/omni_auth.rb b/spec/support/omni_auth.rb
index 0b1af4052ff..64aa3855a6f 100644
--- a/spec/support/omni_auth.rb
+++ b/spec/support/omni_auth.rb
@@ -1 +1,3 @@
+# frozen_string_literal: true
+
OmniAuth.config.test_mode = true
diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb
index de21e808932..4e006edb7da 100644
--- a/spec/support/prometheus/additional_metrics_shared_examples.rb
+++ b/spec/support/prometheus/additional_metrics_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'additional metrics query' do
include Prometheus::MetricBuilders
@@ -48,7 +50,7 @@ RSpec.shared_examples 'additional metrics query' do
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:project) { cluster.project }
let(:environment) { create(:environment, slug: 'environment-slug', project: project) }
- let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) }
+ let(:kube_namespace) { environment.deployment_namespace }
it_behaves_like 'query context containing environment slug and filter'
diff --git a/spec/support/prometheus/metric_builders.rb b/spec/support/prometheus/metric_builders.rb
index c8d056d3fc8..512e32a44d0 100644
--- a/spec/support/prometheus/metric_builders.rb
+++ b/spec/support/prometheus/metric_builders.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Prometheus
module MetricBuilders
def simple_query(suffix = 'a', **opts)
diff --git a/spec/support/protected_tags/access_control_ce_shared_examples.rb b/spec/support/protected_tags/access_control_ce_shared_examples.rb
index 71eec9f3217..8666c19481c 100644
--- a/spec/support/protected_tags/access_control_ce_shared_examples.rb
+++ b/spec/support/protected_tags/access_control_ce_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "protected tags > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected tags that #{access_type_name} can create" do
diff --git a/spec/support/redis/redis_helpers.rb b/spec/support/redis/redis_helpers.rb
index 0457e8487d8..7c571738a01 100644
--- a/spec/support/redis/redis_helpers.rb
+++ b/spec/support/redis/redis_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module RedisHelpers
# config/README.md
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 6aa59960092..7e47cdae866 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "redis_shared_examples" do
include StubENV
diff --git a/spec/support/rspec.rb b/spec/support/rspec.rb
index b38c5dfe60b..1c9f9e5161e 100644
--- a/spec/support/rspec.rb
+++ b/spec/support/rspec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require_relative "helpers/stub_configuration"
require_relative "helpers/stub_metrics"
require_relative "helpers/stub_object_storage"
diff --git a/spec/support/seed.rb b/spec/support/seed.rb
index bea2e9c3044..36cb819763b 100644
--- a/spec/support/seed.rb
+++ b/spec/support/seed.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.include SeedHelper, :seed_helper
diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb
index b0bf942aa09..27f6d0570b6 100644
--- a/spec/support/services/clusters/create_service_shared.rb
+++ b/spec/support/services/clusters/create_service_shared.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'valid cluster create params' do
let(:params) do
{
@@ -30,23 +32,56 @@ shared_context 'invalid cluster create params' do
end
shared_examples 'create cluster service success' do
- it 'creates a cluster object and performs a worker' do
- expect(ClusterProvisionWorker).to receive(:perform_async)
-
- expect { subject }
- .to change { Clusters::Cluster.count }.by(1)
- .and change { Clusters::Providers::Gcp.count }.by(1)
-
- expect(subject.name).to eq('test-cluster')
- expect(subject.user).to eq(user)
- expect(subject.project).to eq(project)
- expect(subject.provider.gcp_project_id).to eq('gcp-project')
- expect(subject.provider.zone).to eq('us-central1-a')
- expect(subject.provider.num_nodes).to eq(1)
- expect(subject.provider.machine_type).to eq('machine_type-a')
- expect(subject.provider.access_token).to eq(access_token)
- expect(subject.provider).to be_legacy_abac
- expect(subject.platform).to be_nil
+ context 'namespace per environment feature is enabled' do
+ before do
+ stub_feature_flags(kubernetes_namespace_per_environment: true)
+ end
+
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ expect(subject.namespace_per_environment).to eq true
+ end
+ end
+
+ context 'namespace per environment feature is disabled' do
+ before do
+ stub_feature_flags(kubernetes_namespace_per_environment: false)
+ end
+
+ it 'creates a cluster object and performs a worker' do
+ expect(ClusterProvisionWorker).to receive(:perform_async)
+
+ expect { subject }
+ .to change { Clusters::Cluster.count }.by(1)
+ .and change { Clusters::Providers::Gcp.count }.by(1)
+
+ expect(subject.name).to eq('test-cluster')
+ expect(subject.user).to eq(user)
+ expect(subject.project).to eq(project)
+ expect(subject.provider.gcp_project_id).to eq('gcp-project')
+ expect(subject.provider.zone).to eq('us-central1-a')
+ expect(subject.provider.num_nodes).to eq(1)
+ expect(subject.provider.machine_type).to eq('machine_type-a')
+ expect(subject.provider.access_token).to eq(access_token)
+ expect(subject.provider).to be_legacy_abac
+ expect(subject.platform).to be_nil
+ expect(subject.namespace_per_environment).to eq false
+ end
end
end
diff --git a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
index 8b4cffaac19..4c3644e6724 100644
--- a/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
+++ b/spec/support/services/issuable_create_service_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with executable attributes.
# It can take a `default_params`.
diff --git a/spec/support/services/issuable_update_service_shared_examples.rb b/spec/support/services/issuable_update_service_shared_examples.rb
index ffbce6c42bf..5e5acd0e40a 100644
--- a/spec/support/services/issuable_update_service_shared_examples.rb
+++ b/spec/support/services/issuable_update_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable update service' do
def update_issuable(opts)
described_class.new(project, user, opts).execute(open_issuable)
diff --git a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
index 1284415da1f..65236f13e27 100644
--- a/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
+++ b/spec/support/services/migrate_to_ghost_user_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples "migrating a deleted user's associated records to the ghost user" do |record_class, fields|
diff --git a/spec/support/setup_builds_storage.rb b/spec/support/setup_builds_storage.rb
index 1d2a4856724..bbe442f07b0 100644
--- a/spec/support/setup_builds_storage.rb
+++ b/spec/support/setup_builds_storage.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
def builds_path
Rails.root.join('tmp/tests/builds')
diff --git a/spec/support/shared_contexts/email_shared_context.rb b/spec/support/shared_contexts/email_shared_context.rb
index 4f5d53f9317..b4d061a8215 100644
--- a/spec/support/shared_contexts/email_shared_context.rb
+++ b/spec/support/shared_contexts/email_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context :email_shared_context do
let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" }
let(:receiver) { Gitlab::Email::Receiver.new(email_raw) }
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
index a0d994c4d8d..38f6011646e 100644
--- a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'GroupProjectsFinder context' do
diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
index b8a9554f55f..26ab6fbd400 100644
--- a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'IssuesFinder context' do
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
index ab6687f1d07..ef1e65d2577 100644
--- a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
index 9e1f89ee0ed..d6404b2ee4b 100644
--- a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
RSpec.shared_context 'UsersFinder#execute filter by project context' do
diff --git a/spec/support/shared_contexts/json_response_shared_context.rb b/spec/support/shared_contexts/json_response_shared_context.rb
index df5fc288089..bd37c97ed35 100644
--- a/spec/support/shared_contexts/json_response_shared_context.rb
+++ b/spec/support/shared_contexts/json_response_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'JSON response' do
let(:json_response) { JSON.parse(response.body) }
end
diff --git a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
index 05424d08b9d..276ebf973c8 100644
--- a/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
+++ b/spec/support/shared_contexts/merge_requests_allowing_collaboration.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'merge request allowing collaboration' do
include ProjectForksHelper
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 b4808ac0068..fd24c443288 100644
--- a/spec/support/shared_contexts/policies/group_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/group_policy_shared_context.rb
@@ -7,7 +7,7 @@ RSpec.shared_context 'GroupPolicy context' do
let(:maintainer) { create(:user) }
let(:owner) { create(:user) }
let(:admin) { create(:admin) }
- let(:group) { create(:group, :private) }
+ let(:group) { create(:group, :private, :owner_subgroup_creation_only) }
let(:guest_permissions) do
%i[
@@ -16,7 +16,7 @@ RSpec.shared_context 'GroupPolicy context' do
read_group_merge_requests
]
end
- let(:reporter_permissions) { [:admin_label] }
+ let(:reporter_permissions) { %i[admin_label read_container_image] }
let(:developer_permissions) { [:admin_milestone] }
let(:maintainer_permissions) do
%i[
@@ -31,7 +31,7 @@ RSpec.shared_context 'GroupPolicy context' do
:admin_group_member,
:change_visibility_level,
:set_note_created_at,
- (Gitlab::Database.postgresql? ? :create_subgroup : nil)
+ :create_subgroup
].compact
end
diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb
index 0c3a24d206f..4d176ab5fca 100644
--- a/spec/support/shared_contexts/services_shared_context.rb
+++ b/spec/support/shared_contexts/services_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Service.available_services_names.each do |service|
shared_context service do
let(:dashed_service) { service.dasherize }
diff --git a/spec/support/shared_contexts/url_shared_context.rb b/spec/support/shared_contexts/url_shared_context.rb
index 1b1f67daac3..560cd500ecd 100644
--- a/spec/support/shared_contexts/url_shared_context.rb
+++ b/spec/support/shared_contexts/url_shared_context.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'invalid urls' do
let(:urls_with_CRLF) do
["http://127.0.0.1:333/pa\rth",
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb
index e7ec24c5b7e..a43d2a75082 100644
--- a/spec/support/shared_examples/application_setting_examples.rb
+++ b/spec/support/shared_examples/application_setting_examples.rb
@@ -1,62 +1,150 @@
# frozen_string_literal: true
+RSpec.shared_examples 'string of domains' do |attribute|
+ it 'sets single domain' do
+ setting.method("#{attribute}_raw=").call('example.com')
+ expect(setting.method(attribute).call).to eq(['example.com'])
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.method("#{attribute}_raw=").call('example.com *.example.com')
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.method("#{attribute}_raw=").call("example.com\n *.example.com")
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.method("#{attribute}_raw=").call("example.com, *.example.com")
+ expect(setting.method(attribute).call).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with semicolon' do
+ setting.method("#{attribute}_raw=").call("example.com; *.example.com")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with mixture of everything' do
+ setting.method("#{attribute}_raw=").call("example.com; *.example.com\n test.com\sblock.com yes.com")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ end
+
+ it 'removes duplicates' do
+ setting.method("#{attribute}_raw=").call("example.com; example.com; 127.0.0.1; 127.0.0.1")
+ expect(setting.method(attribute).call).to contain_exactly('example.com', '127.0.0.1')
+ end
+
+ it 'does not fail with garbage values' do
+ setting.method("#{attribute}_raw=").call("example;34543:garbage:fdh5654;")
+ expect(setting.method(attribute).call).to contain_exactly('example', '34543:garbage:fdh5654')
+ end
+
+ it 'does not raise error with nil' do
+ setting.method("#{attribute}_raw=").call(nil)
+ expect(setting.method(attribute).call).to eq([])
+ end
+end
+
RSpec.shared_examples 'application settings examples' do
context 'restricted signup domains' do
- it 'sets single domain' do
- setting.domain_whitelist_raw = 'example.com'
- expect(setting.domain_whitelist).to eq(['example.com'])
- end
+ it_behaves_like 'string of domains', :domain_whitelist
+ end
- it 'sets multiple domains with spaces' do
- setting.domain_whitelist_raw = 'example.com *.example.com'
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
+ context 'blacklisted signup domains' do
+ it_behaves_like 'string of domains', :domain_blacklist
- it 'sets multiple domains with newlines and a space' do
- setting.domain_whitelist_raw = "example.com\n *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ it 'sets multiple domain with file' do
+ setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
+ expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
end
+ end
+
+ context 'outbound_local_requests_whitelist' do
+ it_behaves_like 'string of domains', :outbound_local_requests_whitelist
- it 'sets multiple domains with commas' do
- setting.domain_whitelist_raw = "example.com, *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ it 'clears outbound_local_requests_whitelist_arrays memoization' do
+ setting.outbound_local_requests_whitelist_raw = 'example.com'
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['example.com']
+ )
+
+ setting.outbound_local_requests_whitelist_raw = 'gitlab.com'
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['gitlab.com']
+ )
end
end
- context 'blacklisted signup domains' do
- it 'sets single domain' do
- setting.domain_blacklist_raw = 'example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com')
+ context 'outbound_local_requests_whitelist_arrays' do
+ it 'separates the IPs and domains' do
+ setting.outbound_local_requests_whitelist = [
+ '192.168.1.1', '127.0.0.0/28', 'www.example.com', 'example.com',
+ '::ffff:a00:2', '1:0:0:0:0:0:0:0/124', 'subdomain.example.com'
+ ]
+
+ ip_whitelist = [
+ IPAddr.new('192.168.1.1'), IPAddr.new('127.0.0.0/8'),
+ IPAddr.new('::ffff:a00:2'), IPAddr.new('1:0:0:0:0:0:0:0/124')
+ ]
+ domain_whitelist = ['www.example.com', 'example.com', 'subdomain.example.com']
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ ip_whitelist, domain_whitelist
+ )
end
+ end
- it 'sets multiple domains with spaces' do
- setting.domain_blacklist_raw = 'example.com *.example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
+ context 'add_to_outbound_local_requests_whitelist' do
+ it 'adds entry to outbound_local_requests_whitelist' do
+ setting.outbound_local_requests_whitelist = ['example.com']
- it 'sets multiple domains with newlines and a space' do
- setting.domain_blacklist_raw = "example.com\n *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
+ setting.add_to_outbound_local_requests_whitelist(
+ ['example.com', '127.0.0.1', 'gitlab.com']
+ )
- it 'sets multiple domains with commas' do
- setting.domain_blacklist_raw = "example.com, *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ expect(setting.outbound_local_requests_whitelist).to contain_exactly(
+ 'example.com',
+ '127.0.0.1',
+ 'gitlab.com'
+ )
end
- it 'sets multiple domains with semicolon' do
- setting.domain_blacklist_raw = "example.com; *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ it 'clears outbound_local_requests_whitelist_arrays memoization' do
+ setting.outbound_local_requests_whitelist = ['example.com']
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [],
+ ['example.com']
+ )
+
+ setting.add_to_outbound_local_requests_whitelist(
+ ['example.com', 'gitlab.com']
+ )
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [],
+ ['example.com', 'gitlab.com']
+ )
end
- it 'sets multiple domains with mixture of everything' do
- setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ it 'does not raise error with nil' do
+ setting.outbound_local_requests_whitelist = nil
+
+ setting.add_to_outbound_local_requests_whitelist(['gitlab.com'])
+
+ expect(setting.outbound_local_requests_whitelist).to contain_exactly('gitlab.com')
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly(
+ [], ['gitlab.com']
+ )
end
- it 'sets multiple domain with file' do
- setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
- expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
+ it 'does not raise error with nil' do
+ setting.outbound_local_requests_whitelist = nil
+
+ expect(setting.outbound_local_requests_whitelist_arrays).to contain_exactly([], [])
end
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/boards/multiple_issue_boards_shared_examples.rb b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
new file mode 100644
index 00000000000..76d82649c5f
--- /dev/null
+++ b/spec/support/shared_examples/boards/multiple_issue_boards_shared_examples.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+shared_examples_for 'multiple issue boards' do
+ dropdown_selector = '.js-boards-selector .dropdown-menu'
+
+ context 'authorized user' do
+ before do
+ parent.add_maintainer(user)
+
+ login_as(user)
+
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'shows current board name' do
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board.name)
+ end
+ end
+
+ it 'shows a list of boards' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'switches current board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'creates new board without detailed configuration' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_button 'Create new board'
+ end
+
+ fill_in 'board-new-name', with: 'This is a new board'
+ click_button 'Create board'
+ wait_for_requests
+
+ expect(page).to have_button('This is a new board')
+ end
+
+ it 'deletes board' do
+ click_button board.name
+
+ wait_for_requests
+
+ page.within(dropdown_selector) do
+ click_button 'Delete board'
+ end
+
+ expect(page).to have_content('Are you sure you want to delete this board?')
+ click_button 'Delete'
+
+ click_button board2.name
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content(board.name)
+ expect(page).to have_content(board2.name)
+ end
+ end
+
+ it 'adds a list to the none default board' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ click_link board2.name
+ end
+
+ wait_for_requests
+
+ page.within('.boards-switcher') do
+ expect(page).to have_content(board2.name)
+ end
+
+ click_button 'Add list'
+
+ wait_for_requests
+
+ page.within '.dropdown-menu-issues-board-new' do
+ click_link planning.title
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 3)
+
+ click_button board2.name
+
+ page.within(dropdown_selector) do
+ click_link board.name
+ end
+
+ wait_for_requests
+
+ expect(page).to have_selector('.board', count: 2)
+ end
+
+ it 'maintains sidebar state over board switch' do
+ assert_boards_nav_active
+
+ find('.boards-switcher').click
+ wait_for_requests
+ click_link board2.name
+
+ assert_boards_nav_active
+ end
+ end
+
+ context 'unauthorized user' do
+ before do
+ visit boards_path
+ wait_for_requests
+ end
+
+ it 'does not show action links' do
+ click_button board.name
+
+ page.within(dropdown_selector) do
+ expect(page).not_to have_content('Create new board')
+ expect(page).not_to have_content('Delete board')
+ end
+ end
+ end
+
+ def assert_boards_nav_active
+ expect(find('.nav-sidebar .active .active')).to have_selector('a', text: 'Boards')
+ end
+end
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index dc97a39f051..dcc92dda950 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'chat slash commands service' do
describe "Associations" do
it { is_expected.to respond_to :token }
@@ -91,6 +93,19 @@ RSpec.shared_examples 'chat slash commands service' do
subject.trigger(params)
end
+
+ context 'when user is blocked' do
+ before do
+ chat_name.user.block
+ end
+
+ it 'blocks command execution' do
+ expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
+
+ result = subject.trigger(params)
+ expect(result).to include(text: /^Whoops! This action is not allowed/)
+ end
+ end
end
end
end
diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb
index ab0550e2613..e2b4b50d41d 100644
--- a/spec/support/shared_examples/ci_trace_shared_examples.rb
+++ b/spec/support/shared_examples/ci_trace_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'common trace features' do
describe '#html' do
before do
@@ -5,11 +7,11 @@ shared_examples_for 'common trace features' do
end
it "returns formatted html" do
- expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>")
+ expect(trace.html).to eq("<span>12<br/>34</span>")
end
it "returns last line of formatted html" do
- expect(trace.html(last_lines: 1)).to eq("<span class=\"\">34</span>")
+ expect(trace.html(last_lines: 1)).to eq("<span>34</span>")
end
end
@@ -720,6 +722,58 @@ shared_examples_for 'trace with enabled live trace feature' do
end
end
+ describe '#archived_trace_exist?' do
+ subject { trace.archived_trace_exist? }
+
+ context 'when trace does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when archived trace exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when live trace exists' do
+ before do
+ Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream|
+ stream.write('abc')
+ end
+ end
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '#live_trace_exist?' do
+ subject { trace.live_trace_exist? }
+
+ context 'when trace does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when archived trace exists' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when live trace exists' do
+ before do
+ Gitlab::Ci::Trace::ChunkedIO.new(build) do |stream|
+ stream.write('abc')
+ end
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
describe '#archive!' do
subject { trace.archive! }
diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb
index da5a4f3e319..75f93a32d78 100644
--- a/spec/support/shared_examples/common_system_notes_examples.rb
+++ b/spec/support/shared_examples/common_system_notes_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'system note creation' do |update_params, note_text|
subject { described_class.new(project, user).execute(issuable, old_labels: []) }
diff --git a/spec/support/shared_examples/container_repositories_shared_examples.rb b/spec/support/shared_examples/container_repositories_shared_examples.rb
new file mode 100644
index 00000000000..946b130fca2
--- /dev/null
+++ b/spec/support/shared_examples/container_repositories_shared_examples.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+shared_examples 'rejected container repository access' do |user_type, status|
+ context "for #{user_type}" do
+ let(:api_user) { users[user_type] }
+
+ it "returns #{status}" do
+ subject
+
+ expect(response).to have_gitlab_http_status(status)
+ end
+ end
+end
+
+shared_examples 'returns repositories for allowed users' do |user_type, scope|
+ context "for #{user_type}" do
+ it 'returns a list of repositories' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
+ root_repository.id, test_repository.id)
+ expect(response.body).not_to include('tags')
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ end
+
+ context 'with tags param' do
+ let(:url) { "/#{scope}s/#{object.id}/registry/repositories?tags=true" }
+
+ before do
+ stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA latest), with_manifest: true)
+ stub_container_registry_tags(repository: test_repository.path, tags: %w(rootA latest), with_manifest: true)
+ end
+
+ it 'returns a list of repositories and their tags' do
+ subject
+
+ expect(json_response.length).to eq(2)
+ expect(json_response.map { |repository| repository['id'] }).to contain_exactly(
+ root_repository.id, test_repository.id)
+ expect(response.body).to include('tags')
+ end
+
+ it 'returns a matching schema' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('registry/repositories')
+ 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 8dd78fd0a25..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
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'disabled when using an external authorization service' do
@@ -6,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/issuable_notes_filter_shared_examples.rb b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
index f4b02dc5350..26ed86bfe26 100644
--- a/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/issuable_notes_filter_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable notes filter' do
let(:params) do
if issuable_parent.is_a?(Project)
@@ -39,7 +41,15 @@ shared_examples 'issuable notes filter' do
get :discussions, params: params.merge(notes_filter: notes_filter)
- expect(user.reload.notes_filter_for(issuable)).to eq(0)
+ expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes])
+ end
+
+ it 'does not set notes filter when persist_filter param is false' do
+ notes_filter = UserPreference::NOTES_FILTERS[:only_comments]
+
+ get :discussions, params: params.merge(notes_filter: notes_filter, persist_filter: false)
+
+ expect(user.reload.notes_filter_for(issuable)).to eq(UserPreference::NOTES_FILTERS[:all_notes])
end
it 'returns only user comments' 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 eb051166a69..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
@@ -1,13 +1,15 @@
+# frozen_string_literal: true
+
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
@@ -17,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/controllers/todos_shared_examples.rb b/spec/support/shared_examples/controllers/todos_shared_examples.rb
index bafd9bac8d0..f3f9abb7da2 100644
--- a/spec/support/shared_examples/controllers/todos_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/todos_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'todos actions' do
context 'when authorized' do
before do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 59708173716..39d13cccb13 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'handle uploads' do
let(:user) { create(:user) }
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
@@ -74,6 +76,16 @@ shared_examples 'handle uploads' do
UploadService.new(model, jpg, uploader_class).execute
end
+ context 'when accessing a specific upload via different model' do
+ it 'responds with status 404' do
+ params.merge!(other_params)
+
+ show_upload
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
context "when the model is public" do
before do
model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/support/shared_examples/controllers/variables_shared_examples.rb b/spec/support/shared_examples/controllers/variables_shared_examples.rb
index e80722857ec..78666e677ef 100644
--- a/spec/support/shared_examples/controllers/variables_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/variables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'GET #show lists all variables' do
it 'renders the variables as json' do
subject
diff --git a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
index 4e45e2921e7..60c8899d349 100644
--- a/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
+++ b/spec/support/shared_examples/dirty_submit_form_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'dirty submit form' do |selector_args|
selectors = selector_args.is_a?(Array) ? selector_args : [selector_args]
diff --git a/spec/support/shared_examples/discussions_provider_shared_examples.rb b/spec/support/shared_examples/discussions_provider_shared_examples.rb
new file mode 100644
index 00000000000..77cf1ac3f51
--- /dev/null
+++ b/spec/support/shared_examples/discussions_provider_shared_examples.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+shared_examples 'discussions provider' do
+ it 'returns the expected discussions' do
+ get :discussions, params: { namespace_id: project.namespace, project_id: project, id: requested_iid }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('entities/discussions')
+
+ expect(json_response.size).to eq(expected_discussion_count)
+ expect(json_response.pluck('id')).to eq(expected_discussion_ids)
+ end
+end
diff --git a/spec/support/shared_examples/email_format_shared_examples.rb b/spec/support/shared_examples/email_format_shared_examples.rb
index b924a208e71..22d6c2b38e3 100644
--- a/spec/support/shared_examples/email_format_shared_examples.rb
+++ b/spec/support/shared_examples/email_format_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all objects with an email attribute.
# Takes a list of email-format attributes and requires:
# - subject { "the object with a attribute= setter" }
diff --git a/spec/support/shared_examples/fast_destroy_all.rb b/spec/support/shared_examples/fast_destroy_all.rb
index a8079b6d864..a64259c03f2 100644
--- a/spec/support/shared_examples/fast_destroy_all.rb
+++ b/spec/support/shared_examples/fast_destroy_all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'fast destroyable' do
describe 'Forbid #destroy and #destroy_all' do
it 'does not delete database rows and associted external data' do
diff --git a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
index 2b36955a3c4..f24e47f4638 100644
--- a/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
+++ b/spec/support/shared_examples/features/comments_on_merge_request_files_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'comment on merge request file' do
it 'adds a comment' do
click_diff_line(find("[id='#{sample_commit.line_code}']"))
diff --git a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
index ec1b1754cf0..c0db4cdde72 100644
--- a/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/creatable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'a creatable merge request' do
include WaitForRequests
diff --git a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
index a6121fcc50a..964c80007b0 100644
--- a/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
+++ b/spec/support/shared_examples/features/editable_merge_request_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'an editable merge request' do
it 'updates merge request', :js do
find('.js-assignee-search').click
diff --git a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
index 96c821b26f7..09a48533ee3 100644
--- a/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuable_sidebar_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issue sidebar stays collapsed on mobile' do
before do
resize_screen_xs
diff --git a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
index c92c7f603d6..63ed37cde03 100644
--- a/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
+++ b/spec/support/shared_examples/features/issuables_user_dropdown_behaviors_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable user dropdown behaviors' do
include FilteredSearchHelpers
diff --git a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
index d87e5fcaa88..8e1d24c4be2 100644
--- a/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
+++ b/spec/support/shared_examples/features/master_manages_access_requests_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Maintainer manages access requests' do
let(:user) { create(:user) }
let(:maintainer) { create(:user) }
diff --git a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
index 64c3b80136d..51559c0b110 100644
--- a/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
+++ b/spec/support/shared_examples/features/project_features_apply_to_issuables_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project features apply to issuables' do |klass|
let(:described_class) { klass }
diff --git a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
index a8f2c2e7a5a..db83d6f0793 100644
--- a/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
+++ b/spec/support/shared_examples/features/protected_branches_access_control_ce.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "protected branches > access control > CE" do
ProtectedRefAccess::HUMAN_ACCESS_LEVELS.each do |(access_type_id, access_type_name)|
it "allows creating protected branches that #{access_type_name} can push to" do
@@ -6,7 +8,7 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -32,13 +34,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -78,7 +80,7 @@ shared_examples "protected branches > access control > CE" do
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
@@ -95,13 +97,13 @@ shared_examples "protected branches > access control > CE" do
set_protected_branch_name('master')
find(".js-allowed-to-merge").click
- within('.qa-allowed-to-merge-dropdown') do
+ within('.rspec-allowed-to-merge-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
find(".js-allowed-to-push").click
- within('.qa-allowed-to-push-dropdown') do
+ within('.rspec-allowed-to-push-dropdown') do
expect(first("li")).to have_content("Roles")
find(:link, 'No one').click
end
diff --git a/spec/support/shared_examples/features/search_shared_examples.rb b/spec/support/shared_examples/features/search_shared_examples.rb
index 25ebbf011d5..e27d6700cbf 100644
--- a/spec/support/shared_examples/features/search_shared_examples.rb
+++ b/spec/support/shared_examples/features/search_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'top right search form' do
it 'does not show top right search form' do
expect(page).not_to have_selector('.search')
diff --git a/spec/support/shared_examples/file_finder.rb b/spec/support/shared_examples/file_finder.rb
index 0dc351b5149..984a06ccd1a 100644
--- a/spec/support/shared_examples/file_finder.rb
+++ b/spec/support/shared_examples/file_finder.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'file finder' do
let(:query) { 'files' }
let(:search_results) { subject.find(query) }
diff --git a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
index d7e17cc0b70..b8b0079e36d 100644
--- a/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
+++ b/spec/support/shared_examples/finders/finder_with_external_authorization_enabled.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a finder with external authorization service' do
diff --git a/spec/support/shared_examples/gitlab_verify.rb b/spec/support/shared_examples/gitlab_verify.rb
index 560913ca92f..721ea3b4c88 100644
--- a/spec/support/shared_examples/gitlab_verify.rb
+++ b/spec/support/shared_examples/gitlab_verify.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do
describe 'batching' do
let(:first_batch) { objects[0].id..objects[0].id }
diff --git a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
index 713f0a879c1..145c476c7f7 100644
--- a/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
+++ b/spec/support/shared_examples/graphql/issuable_state_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issuable state' do
it 'exposes all the existing issuable states' do
expect(described_class.values.keys).to include(*%w[opened closed locked])
diff --git a/spec/support/shared_examples/group_members_shared_example.rb b/spec/support/shared_examples/group_members_shared_example.rb
index 547c83c7955..4f7d496741d 100644
--- a/spec/support/shared_examples/group_members_shared_example.rb
+++ b/spec/support/shared_examples/group_members_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members and requesters associations' do
describe '#members_and_requesters' do
it 'includes members and requesters' do
diff --git a/spec/support/shared_examples/helm_generated_script.rb b/spec/support/shared_examples/helm_generated_script.rb
index 01bee603274..17f495ebe46 100644
--- a/spec/support/shared_examples/helm_generated_script.rb
+++ b/spec/support/shared_examples/helm_generated_script.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'helm commands' do
describe '#generate_script' do
let(:helm_setup) do
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index d97b21f71cd..3460a8ba297 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cache counters invalidator' do
it 'invalidates counter cache for assignees' do
expect_any_instance_of(User).to receive(:invalidate_merge_request_cache_counts)
diff --git a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
index 244f4766a84..52d90b5f183 100644
--- a/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
+++ b/spec/support/shared_examples/issuables_list_metadata_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuables list meta-data' do |issuable_type, action = nil|
include ProjectForksHelper
diff --git a/spec/support/shared_examples/issue_tracker_service_shared_example.rb b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
index a6ab03cb808..0a483fd30ba 100644
--- a/spec/support/shared_examples/issue_tracker_service_shared_example.rb
+++ b/spec/support/shared_examples/issue_tracker_service_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
it { is_expected.to allow_value('https://example.com').for(url_attr) }
diff --git a/spec/support/shared_examples/ldap_shared_examples.rb b/spec/support/shared_examples/ldap_shared_examples.rb
index 52c34e78965..0a70ce7ea0c 100644
--- a/spec/support/shared_examples/ldap_shared_examples.rb
+++ b/spec/support/shared_examples/ldap_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'normalizes a DN' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
index f326e502092..22e5698825d 100644
--- a/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
+++ b/spec/support/shared_examples/legacy_path_redirect_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'redirecting a legacy path' do |source, target|
include RSpec::Rails::RequestExampleGroup
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index dcf7c1a90c2..2cbc0c2bdf2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'backfill migration for project repositories' do |storage|
describe '#perform' do
let(:storage_versions) { storage == :legacy ? [nil, 0] : [1, 2] }
diff --git a/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
new file mode 100644
index 00000000000..91bf804978d
--- /dev/null
+++ b/spec/support/shared_examples/lib/gitlab/usage_data_counters/a_redis_counter.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+shared_examples 'a redis usage counter' do |thing, event|
+ describe ".count(#{event})", :clean_gitlab_redis_shared_state do
+ it "increments the #{thing} #{event} counter by 1" do
+ expect do
+ described_class.count(event)
+ end.to change { described_class.read(event) }.by 1
+ end
+ end
+
+ describe ".read(#{event})", :clean_gitlab_redis_shared_state do
+ event_count = 5
+
+ it "returns the total number of #{event} events" do
+ event_count.times do
+ described_class.count(event)
+ end
+
+ expect(described_class.read(event)).to eq(event_count)
+ end
+ end
+end
+
+shared_examples 'a redis usage counter with totals' do |prefix, events|
+ describe 'totals', :clean_gitlab_redis_shared_state do
+ before do
+ events.each do |k, n|
+ n.times do
+ described_class.count(k)
+ end
+ end
+ end
+
+ let(:expected_totals) do
+ events.transform_keys { |k| "#{prefix}_#{k}".to_sym }
+ end
+
+ it 'can report all totals' do
+ expect(described_class.totals).to include(expected_totals)
+ end
+ end
+
+ # Override these let-bindings to adjust the unknown events tests
+ let(:unknown_event) { described_class::UnknownEvent }
+ let(:bad_event) { :wibble }
+
+ describe 'unknown events' do
+ it 'cannot increment' do
+ expect { described_class.count(bad_event) }.to raise_error unknown_event
+ end
+
+ it 'cannot read' do
+ expect { described_class.read(bad_event) }.to raise_error unknown_event
+ end
+ end
+end
diff --git a/spec/support/shared_examples/malicious_regexp_shared_examples.rb b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
index a86050e2cf2..96c02260d53 100644
--- a/spec/support/shared_examples/malicious_regexp_shared_examples.rb
+++ b/spec/support/shared_examples/malicious_regexp_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'timeout'
shared_examples 'malicious regexp' do
diff --git a/spec/support/shared_examples/mentionable_shared_examples.rb b/spec/support/shared_examples/mentionable_shared_examples.rb
index fea52c2eeb2..93a8c4709a6 100644
--- a/spec/support/shared_examples/mentionable_shared_examples.rb
+++ b/spec/support/shared_examples/mentionable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specifications for behavior common to all Mentionable implementations.
# Requires a shared context containing:
# - subject { "the mentionable implementation" }
diff --git a/spec/support/shared_examples/milestone_tabs_examples.rb b/spec/support/shared_examples/milestone_tabs_examples.rb
index 8b757586941..bda4b978737 100644
--- a/spec/support/shared_examples/milestone_tabs_examples.rb
+++ b/spec/support/shared_examples/milestone_tabs_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'milestone tabs' do
def go(path, extra_params = {})
params =
diff --git a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
index a248f60d23e..b837ca87256 100644
--- a/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
+++ b/spec/support/shared_examples/models/atomic_internal_id_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'AtomicInternalId' do |validate_presence: true|
diff --git a/spec/support/shared_examples/models/chat_service_shared_examples.rb b/spec/support/shared_examples/models/chat_service_shared_examples.rb
index 0a302e7d030..b6a3d50d14a 100644
--- a/spec/support/shared_examples/models/chat_service_shared_examples.rb
+++ b/spec/support/shared_examples/models/chat_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require "spec_helper"
shared_examples_for "chat service" do |service_name|
@@ -220,7 +222,8 @@ shared_examples_for "chat service" do |service_name|
context "with not default branch" do
let(:pipeline) do
- create(:ci_pipeline, project: project, status: "failed", ref: "not-the-default-branch")
+ create(:ci_pipeline, :failed, project: project,
+ sha: project.commit.sha, ref: "not-the-default-branch")
end
context "when notify_only_default_branch enabled" do
diff --git a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
index d6490a808ce..8e58cc7ba22 100644
--- a/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_core_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application core specs' do |application_name|
it { is_expected.to belong_to(:cluster) }
it { is_expected.to validate_presence_of(:cluster) }
diff --git a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
index bd3661471f8..7ddb3b11c85 100644
--- a/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_helm_cert_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application helm specs' do |application_name|
let(:application) { create(application_name) }
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 4525c03837f..5341aacb445 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'cluster application status specs' do |application_name|
describe '#status' do
let(:cluster) { create(:cluster, :provided_by_gcp) }
diff --git a/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
new file mode 100644
index 00000000000..8b298c5c974
--- /dev/null
+++ b/spec/support/shared_examples/models/diff_positionable_note_shared_examples.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+shared_examples_for 'a valid diff positionable note' do |factory_on_commit|
+ context 'for commit' do
+ let(:project) { create(:project, :repository) }
+ let(:commit) { project.commit(sample_commit.id) }
+ let(:commit_id) { commit.id }
+ let(:diff_refs) { commit.diff_refs }
+
+ let(:position) do
+ Gitlab::Diff::Position.new(
+ old_path: "files/ruby/popen.rb",
+ new_path: "files/ruby/popen.rb",
+ old_line: nil,
+ new_line: 14,
+ diff_refs: diff_refs
+ )
+ end
+
+ subject { build(factory_on_commit, commit_id: commit_id, position: position) }
+
+ context 'position diff refs matches commit diff refs' do
+ it 'is valid' do
+ expect(subject).to be_valid
+ expect(subject.errors).not_to have_key(:commit_id)
+ end
+ end
+
+ context 'position diff refs does not match commit diff refs' do
+ let(:diff_refs) do
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: "not_existing_sha",
+ head_sha: "existing_sha"
+ )
+ end
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+
+ context 'commit does not exist' do
+ let(:commit_id) { 'non-existing' }
+
+ it 'is invalid' do
+ expect(subject).to be_invalid
+ expect(subject.errors).to have_key(:commit_id)
+ end
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
index a4762b68858..7ea2bb265cc 100644
--- a/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/issuable_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# This shared example requires a `builder` and `user` variable
shared_examples 'issuable hook data' do |kind|
let(:data) { builder.build(user: user) }
diff --git a/spec/support/shared_examples/models/members_notifications_shared_example.rb b/spec/support/shared_examples/models/members_notifications_shared_example.rb
index ef5cea3f2a5..050d710f1de 100644
--- a/spec/support/shared_examples/models/members_notifications_shared_example.rb
+++ b/spec/support/shared_examples/models/members_notifications_shared_example.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'members notifications' do |entity_type|
let(:notification_service) { double('NotificationService').as_null_object }
diff --git a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
index f0264878811..03d10c10e3c 100644
--- a/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
+++ b/spec/support/shared_examples/models/project_hook_data_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'project hook data with deprecateds' do |project_key: :project|
it 'contains project data' do
expect(data[project_key][:name]).to eq(project.name)
diff --git a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
index aad63982e7a..e03435cafe8 100644
--- a/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
+++ b/spec/support/shared_examples/models/update_project_statistics_shared_examples.rb
@@ -32,19 +32,6 @@ shared_examples_for 'UpdateProjectStatistics' do
subject.save!
end
-
- context 'when feature flag is disabled for the namespace' do
- it 'does not schedules a namespace statistics worker' do
- namespace = subject.project.root_ancestor
-
- stub_feature_flags(update_statistics_namespace: false, namespace: namespace)
-
- expect(Namespaces::ScheduleAggregationWorker)
- .not_to receive(:perform_async)
-
- subject.save!
- end
- end
end
context 'when updating' do
@@ -87,20 +74,6 @@ shared_examples_for 'UpdateProjectStatistics' do
subject.save!
end.not_to exceed_query_limit(control_count)
end
-
- context 'when the feature flag is disabled for the namespace' do
- it 'does not schedule a namespace statistics worker' do
- namespace = subject.project.root_ancestor
-
- stub_feature_flags(update_statistics_namespace: false, namespace: namespace)
-
- expect(Namespaces::ScheduleAggregationWorker)
- .not_to receive(:perform_async)
-
- subject.write_attribute(statistic_attribute, read_attribute + delta)
- subject.save!
- end
- end
end
context 'when destroying' do
@@ -144,18 +117,5 @@ shared_examples_for 'UpdateProjectStatistics' do
project.destroy!
end
end
-
- context 'when feature flag is disabled for the namespace' do
- it 'does not schedule a namespace statistics worker' do
- namespace = subject.project.root_ancestor
-
- stub_feature_flags(update_statistics_namespace: false, namespace: namespace)
-
- expect(Namespaces::ScheduleAggregationWorker)
- .not_to receive(:perform_async)
-
- subject.destroy!
- end
- end
end
end
diff --git a/spec/support/shared_examples/models/with_uploads_shared_examples.rb b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
index 43033a2d256..eb1ade03017 100644
--- a/spec/support/shared_examples/models/with_uploads_shared_examples.rb
+++ b/spec/support/shared_examples/models/with_uploads_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples_for 'model with uploads' do |supports_fileuploads|
diff --git a/spec/support/shared_examples/notify_shared_examples.rb b/spec/support/shared_examples/notify_shared_examples.rb
index e64c7e37a0c..ca031df000e 100644
--- a/spec/support/shared_examples/notify_shared_examples.rb
+++ b/spec/support/shared_examples/notify_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'gitlab email notification' do
set(:group) { create(:group) }
set(:subgroup) { create(:group, parent: group) }
@@ -42,42 +44,17 @@ shared_examples 'an email sent from GitLab' do
end
shared_examples 'an email sent to a user' do
- let(:group_notification_email) { 'user+group@example.com' }
-
it 'is sent to user\'s global notification email address' do
expect(subject).to deliver_to(recipient.notification_email)
end
- context 'that is part of a project\'s group' do
- it 'is sent to user\'s group notification email address when set' do
- create(:notification_setting, user: recipient, source: project.group, notification_email: group_notification_email)
- expect(subject).to deliver_to(group_notification_email)
- end
-
- it 'is sent to user\'s global notification email address when no group email set' do
- create(:notification_setting, user: recipient, source: project.group, notification_email: '')
- expect(subject).to deliver_to(recipient.notification_email)
- end
- end
+ context 'with group notification email' do
+ it 'is sent to user\'s group notification email' do
+ group_notification_email = 'user+group@example.com'
- context 'when project is in a sub-group', :nested_groups do
- before do
- project.update!(group: subgroup)
- end
-
- it 'is sent to user\'s subgroup notification email address when set' do
- # Set top-level group notification email address to make sure it doesn't get selected
create(:notification_setting, user: recipient, source: group, notification_email: group_notification_email)
- subgroup_notification_email = 'user+subgroup@example.com'
- create(:notification_setting, user: recipient, source: subgroup, notification_email: subgroup_notification_email)
-
- expect(subject).to deliver_to(subgroup_notification_email)
- end
-
- it 'is sent to user\'s group notification email address when set and subgroup email address not set' do
- create(:notification_setting, user: recipient, source: subgroup, notification_email: '')
- expect(subject).to deliver_to(recipient.notification_email)
+ expect(subject).to deliver_to(group_notification_email)
end
end
end
diff --git a/spec/support/shared_examples/policies/clusterable_shared_examples.rb b/spec/support/shared_examples/policies/clusterable_shared_examples.rb
index 4f9873d53e4..0b427c23256 100644
--- a/spec/support/shared_examples/policies/clusterable_shared_examples.rb
+++ b/spec/support/shared_examples/policies/clusterable_shared_examples.rb
@@ -13,7 +13,11 @@ shared_examples 'clusterable policies' do
clusterable.add_developer(current_user)
end
+ it { expect_disallowed(:read_cluster) }
it { expect_disallowed(:add_cluster) }
+ it { expect_disallowed(:create_cluster) }
+ it { expect_disallowed(:update_cluster) }
+ it { expect_disallowed(:admin_cluster) }
end
context 'with a maintainer' do
@@ -22,7 +26,11 @@ shared_examples 'clusterable policies' do
end
context 'with no clusters' do
+ it { expect_allowed(:read_cluster) }
it { expect_allowed(:add_cluster) }
+ it { expect_allowed(:create_cluster) }
+ it { expect_allowed(:update_cluster) }
+ it { expect_allowed(:admin_cluster) }
end
end
end
diff --git a/spec/support/shared_examples/position_formatters.rb b/spec/support/shared_examples/position_formatters.rb
index ffc9456dbc7..30b6b8d24f0 100644
--- a/spec/support/shared_examples/position_formatters.rb
+++ b/spec/support/shared_examples/position_formatters.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for "position formatter" do
let(:formatter) { described_class.new(attrs) }
diff --git a/spec/support/shared_examples/project_latest_successful_build_for_examples.rb b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
new file mode 100644
index 00000000000..a9bd23e9fc9
--- /dev/null
+++ b/spec/support/shared_examples/project_latest_successful_build_for_examples.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+shared_examples 'latest successful build for sha or ref' do
+ context 'with many builds' do
+ let(:other_pipeline) { create_pipeline(project) }
+ let(:other_build) { create_build(other_pipeline, 'test') }
+ let(:build_name) { other_build.name }
+
+ before do
+ pipeline1 = create_pipeline(project)
+ pipeline2 = create_pipeline(project)
+ create_build(pipeline1, 'test')
+ create_build(pipeline1, 'test2')
+ create_build(pipeline2, 'test2')
+ end
+
+ it 'gives the latest builds from latest pipeline' do
+ expect(subject).to eq(other_build)
+ end
+ end
+
+ context 'with succeeded pipeline' do
+ let!(:build) { create_build }
+ let(:build_name) { build.name }
+
+ context 'standalone pipeline' do
+ it 'returns builds for ref for default_branch' do
+ expect(subject).to eq(build)
+ end
+
+ context 'with nonexistent build' do
+ let(:build_name) { 'TAIL' }
+
+ it 'returns empty relation if the build cannot be found' do
+ expect(subject).to be_nil
+ end
+ end
+ end
+
+ context 'with some pending pipeline' do
+ before do
+ create_build(create_pipeline(project, 'pending'))
+ end
+
+ it 'gives the latest build from latest pipeline' do
+ expect(subject).to eq(build)
+ end
+ end
+ end
+
+ context 'with pending pipeline' do
+ let!(:pending_build) { create_build(pipeline) }
+ let(:build_name) { pending_build.name }
+
+ before do
+ pipeline.update(status: 'pending')
+ end
+
+ it 'returns empty relation' do
+ expect(subject).to be_nil
+ end
+ end
+end
diff --git a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
index b337a1c18d8..f5a86e4dc2c 100644
--- a/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/commit/tag_quick_action_shared_examples.rb
@@ -5,7 +5,7 @@ shared_examples 'tag quick action' do
it 'tags this commit' do
add_note("/tag #{tag_name} #{tag_message}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content %{Tagged this commit to #{tag_name} with "#{tag_message}".}
expect(page).to have_content "tagged commit #{truncated_commit_sha}"
expect(page).to have_content tag_name
diff --git a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
index a79a61bc708..6e7eb78261a 100644
--- a/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issuable/close_quick_action_shared_examples.rb
@@ -68,7 +68,7 @@ shared_examples 'close quick action' do |issuable_type|
it "does not close the #{issuable_type}" do
add_note('/close')
- expect(page).not_to have_content 'Commands applied'
+ expect(page).not_to have_content "Closed this #{issuable.to_ability_name.humanize(capitalize: false)}."
expect(issuable).to be_open
end
end
diff --git a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
index 34dba5dbc31..3e9ee9a633f 100644
--- a/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/create_merge_request_quick_action_shared_examples.rb
@@ -2,8 +2,14 @@
shared_examples 'create_merge_request quick action' do
context 'create a merge request starting from an issue' do
- def expect_mr_quickaction(success)
- expect(page).to have_content 'Commands applied'
+ def expect_mr_quickaction(success, branch_name = nil)
+ command_message = if branch_name
+ "Created branch '#{branch_name}' and a merge request to resolve this issue"
+ else
+ "Created a branch and a merge request to resolve this issue"
+ end
+
+ expect(page).to have_content command_message
if success
expect(page).to have_content 'created merge request'
@@ -13,19 +19,21 @@ shared_examples 'create_merge_request quick action' do
end
it "doesn't create a merge request when the branch name is invalid" do
- add_note("/create_merge_request invalid branch name")
+ branch_name = 'invalid branch name'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it "doesn't create a merge request when a branch with that name already exists" do
- add_note("/create_merge_request feature")
+ branch_name = 'feature'
+ add_note("/create_merge_request #{branch_name}")
wait_for_requests
- expect_mr_quickaction(false)
+ expect_mr_quickaction(false, branch_name)
end
it 'creates a new merge request using issue iid and title as branch name when the branch name is empty' do
@@ -46,7 +54,7 @@ shared_examples 'create_merge_request quick action' do
branch_name = '1-feature'
add_note("/create_merge_request #{branch_name}")
- expect_mr_quickaction(true)
+ expect_mr_quickaction(true, branch_name)
created_mr = project.merge_requests.last
expect(created_mr.source_branch).to eq(branch_name)
diff --git a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
index 633c7135fbc..3834b8b2b87 100644
--- a/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/duplicate_quick_action_shared_examples.rb
@@ -9,7 +9,6 @@ shared_examples 'duplicate quick action' do
add_note("/duplicate ##{original_issue.to_reference}")
expect(page).not_to have_content "/duplicate #{original_issue.to_reference}"
- expect(page).to have_content 'Commands applied'
expect(page).to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_closed
@@ -28,7 +27,6 @@ shared_examples 'duplicate quick action' do
it 'does not create a note, and does not mark the issue as a duplicate' do
add_note("/duplicate ##{original_issue.to_reference}")
- expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
expect(issue.reload).to be_open
diff --git a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
index a0b0d888769..a37b2392d52 100644
--- a/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/issue/move_quick_action_shared_examples.rb
@@ -12,7 +12,7 @@ shared_examples 'move quick action' do
it 'moves the issue' do
add_note("/move #{target_project.full_path}")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
@@ -29,7 +29,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{project_unauthorized.full_path}."
expect(issue.reload).to be_open
end
end
@@ -40,7 +40,7 @@ shared_examples 'move quick action' do
wait_for_requests
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Failed to move this issue because target project doesn't exist."
expect(issue.reload).to be_open
end
end
@@ -56,7 +56,7 @@ shared_examples 'move quick action' do
shared_examples 'applies the commands to issues in both projects, target and source' do
it "applies quick actions" do
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content "Moved this issue to #{target_project.full_path}."
expect(issue.reload).to be_closed
visit project_issue_path(target_project, issue)
diff --git a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
index c454ddc4bba..ac7c17915de 100644
--- a/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
+++ b/spec/support/shared_examples/quick_actions/merge_request/merge_quick_action_shared_examples.rb
@@ -10,7 +10,7 @@ shared_examples 'merge quick action' do
it 'merges the MR' do
add_note("/merge")
- expect(page).to have_content 'Commands applied'
+ expect(page).to have_content 'Scheduled to merge this merge request when the pipeline succeeds.'
expect(merge_request.reload).to be_merged
end
diff --git a/spec/support/shared_examples/reference_parser_shared_examples.rb b/spec/support/shared_examples/reference_parser_shared_examples.rb
index baf8bcc04b8..d903c0f10e0 100644
--- a/spec/support/shared_examples/reference_parser_shared_examples.rb
+++ b/spec/support/shared_examples/reference_parser_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples "referenced feature visibility" do |*related_features|
let(:feature_fields) do
related_features.map { |feature| (feature + "_access_level").to_sym }
diff --git a/spec/support/shared_examples/relative_positioning_shared_examples.rb b/spec/support/shared_examples/relative_positioning_shared_examples.rb
new file mode 100644
index 00000000000..b7382cea93c
--- /dev/null
+++ b/spec/support/shared_examples/relative_positioning_shared_examples.rb
@@ -0,0 +1,283 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'a class that supports relative positioning' do
+ let(:item1) { create(factory, default_params) }
+ let(:item2) { create(factory, default_params) }
+ let(:new_item) { create(factory, default_params) }
+
+ def create_item(params)
+ create(factory, params.merge(default_params))
+ end
+
+ def create_items_with_positions(positions)
+ positions.map do |position|
+ create_item(relative_position: position)
+ end
+ end
+
+ describe '.move_nulls_to_end' do
+ it 'moves items with null relative_position to the end' do
+ item1.update!(relative_position: nil)
+ item2.update!(relative_position: nil)
+
+ described_class.move_nulls_to_end([item1, item2])
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ expect(item1.prev_relative_position).to eq nil
+ expect(item2.next_relative_position).to eq nil
+ end
+
+ it 'moves the item near the start position when there are no existing positions' do
+ item1.update!(relative_position: nil)
+
+ described_class.move_nulls_to_end([item1])
+
+ expect(item1.relative_position).to eq(described_class::START_POSITION + described_class::IDEAL_DISTANCE)
+ end
+
+ it 'does not perform any moves if all items have their relative_position set' do
+ item1.update!(relative_position: 1)
+
+ expect(item1).not_to receive(:save)
+
+ described_class.move_nulls_to_end([item1])
+ end
+ end
+
+ describe '#max_relative_position' do
+ it 'returns maximum position' do
+ expect(item1.max_relative_position).to eq item2.relative_position
+ end
+ end
+
+ describe '#prev_relative_position' do
+ it 'returns previous position if there is an item above' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item2.prev_relative_position).to eq item1.relative_position
+ end
+
+ it 'returns nil if there is no item above' do
+ expect(item1.prev_relative_position).to eq nil
+ end
+ end
+
+ describe '#next_relative_position' do
+ it 'returns next position if there is an item below' do
+ item1.update(relative_position: 5)
+ item2.update(relative_position: 15)
+
+ expect(item1.next_relative_position).to eq item2.relative_position
+ end
+
+ it 'returns nil if there is no item below' do
+ expect(item2.next_relative_position).to eq nil
+ end
+ end
+
+ describe '#move_before' do
+ it 'moves item before' do
+ [item2, item1].each(&:move_to_end)
+
+ item1.move_before(item2)
+
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+ end
+
+ describe '#move_after' do
+ it 'moves item after' do
+ [item1, item2].each(&:move_to_end)
+
+ item1.move_after(item2)
+
+ expect(item1.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#move_to_end' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'moves item to the end' do
+ new_item.move_to_end
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+ end
+
+ describe '#move_between' do
+ before do
+ [item1, item2].each do |item1|
+ item1.move_to_end && item1.save
+ end
+ end
+
+ it 'positions item between two other' do
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(new_item.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item between on top' do
+ new_item.move_between(nil, item1)
+
+ expect(new_item.relative_position).to be < item1.relative_position
+ end
+
+ it 'positions item between to end' do
+ new_item.move_between(item2, nil)
+
+ expect(new_item.relative_position).to be > item2.relative_position
+ end
+
+ it 'positions items even when after and before positions are the same' do
+ item2.update relative_position: item1.relative_position
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions items between other two if distance is 1' do
+ item2.update relative_position: item1.relative_position + 1
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be > item1.relative_position
+ expect(item1.relative_position).to be < item2.relative_position
+ end
+
+ it 'positions item in the middle of other two if distance is big enough' do
+ item1.update relative_position: 6000
+ item2.update relative_position: 10000
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(8000)
+ end
+
+ it 'positions item closer to the middle if we are at the very top' do
+ item2.update relative_position: 6000
+
+ new_item.move_between(nil, item2)
+
+ expect(new_item.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item closer to the middle if we are at the very bottom' do
+ new_item.update relative_position: 1
+ item1.update relative_position: 6000
+ item2.destroy
+
+ new_item.move_between(item1, nil)
+
+ expect(new_item.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
+ end
+
+ it 'positions item in the middle of other two if distance is not big enough' do
+ item1.update relative_position: 100
+ item2.update relative_position: 400
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to eq(250)
+ end
+
+ it 'positions item in the middle of other two is there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+
+ new_item.move_between(item1, item2)
+
+ expect(new_item.relative_position).to be_between(item1.relative_position, item2.relative_position)
+ end
+
+ it 'uses rebalancing if there is no place' do
+ item1.update relative_position: 100
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item2, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be_between(item2.relative_position, item3.relative_position)
+ expect(item1.reload.relative_position).not_to eq(100)
+ end
+
+ it 'positions item right if we pass none-sequential parameters' do
+ item1.update relative_position: 99
+ item2.update relative_position: 101
+ item3 = create_item(relative_position: 102)
+ new_item.update relative_position: 103
+
+ new_item.move_between(item1, item3)
+ new_item.save!
+
+ expect(new_item.relative_position).to be(100)
+ end
+
+ it 'avoids N+1 queries when rebalancing other items' do
+ items = create_items_with_positions([100, 101, 102])
+
+ count = ActiveRecord::QueryRecorder.new do
+ new_item.move_between(items[-2], items[-1])
+ end
+
+ items = create_items_with_positions([150, 151, 152, 153, 154])
+
+ expect { new_item.move_between(items[-2], items[-1]) }.not_to exceed_query_limit(count)
+ end
+ end
+
+ describe '#move_sequence_before' do
+ it 'moves the whole sequence of items to the middle of the nearest gap' do
+ items = create_items_with_positions([90, 100, 101, 102])
+
+ items.last.move_sequence_before
+ items.last.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([90, 95, 96, 102])
+ end
+
+ it 'finds a gap if there are unused positions' do
+ items = create_items_with_positions([100, 101, 102])
+
+ items.last.move_sequence_before
+ items.last.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([50, 51, 102])
+ end
+ end
+
+ describe '#move_sequence_after' do
+ it 'moves the whole sequence of items to the middle of the nearest gap' do
+ items = create_items_with_positions([100, 101, 102, 110])
+
+ items.first.move_sequence_after
+ items.first.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([100, 105, 106, 110])
+ end
+
+ it 'finds a gap if there are unused positions' do
+ items = create_items_with_positions([100, 101, 102])
+
+ items.first.move_sequence_after
+ items.first.save!
+
+ positions = items.map { |item| item.reload.relative_position }
+ expect(positions).to eq([100, 601, 602])
+ end
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
index 8a7fcf856a1..776a0bdd29e 100644
--- a/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/custom_attributes_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'custom attributes endpoints' do |attributable_name|
let!(:custom_attribute1) { attributable.custom_attributes.create key: 'foo', value: 'foo' }
let!(:custom_attribute2) { attributable.custom_attributes.create key: 'bar', value: 'bar' }
diff --git a/spec/support/shared_examples/requests/api/diff_discussions.rb b/spec/support/shared_examples/requests/api/diff_discussions.rb
index 366c2955359..76c6c93964a 100644
--- a/spec/support/shared_examples/requests/api/diff_discussions.rb
+++ b/spec/support/shared_examples/requests/api/diff_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'diff discussions API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions" do
it "includes diff discussions" do
diff --git a/spec/support/shared_examples/requests/api/discussions.rb b/spec/support/shared_examples/requests/api/discussions.rb
index c3132c41f5b..fc72287f265 100644
--- a/spec/support/shared_examples/requests/api/discussions.rb
+++ b/spec/support/shared_examples/requests/api/discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
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/issuable_participants_examples.rb b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
index 96d59e0c472..9fe6288d53f 100644
--- a/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
+++ b/spec/support/shared_examples/requests/api/issuable_participants_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issuable participants endpoint' do
let(:area) { entity.class.name.underscore.pluralize }
diff --git a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
index 5f4e178f2e5..90c1ed8d09b 100644
--- a/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/issues/merge_requests_count_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
def get_issue
json_response.is_a?(Array) ? json_response.detect {|issue| issue['id'] == target_issue.id} : json_response
end
diff --git a/spec/support/shared_examples/requests/api/notes.rb b/spec/support/shared_examples/requests/api/notes.rb
index 57eefd5ef01..354ae7288b1 100644
--- a/spec/support/shared_examples/requests/api/notes.rb
+++ b/spec/support/shared_examples/requests/api/notes.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'noteable API' do |parent_type, noteable_type, id_name|
describe "GET /#{parent_type}/:id/#{noteable_type}/:noteable_id/notes" do
context 'sorting' do
diff --git a/spec/support/shared_examples/requests/api/resolvable_discussions.rb b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
index 7e2416b23f3..42054a273f3 100644
--- a/spec/support/shared_examples/requests/api/resolvable_discussions.rb
+++ b/spec/support/shared_examples/requests/api/resolvable_discussions.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'resolvable discussions API' do |parent_type, noteable_type, id_name|
describe "PUT /#{parent_type}/:id/#{noteable_type}/:noteable_id/discussions/:discussion_id" do
it "resolves discussion if resolved is true" do
diff --git a/spec/support/shared_examples/requests/api/status_shared_examples.rb b/spec/support/shared_examples/requests/api/status_shared_examples.rb
index ebfc5fed3bb..eebed7e42c1 100644
--- a/spec/support/shared_examples/requests/api/status_shared_examples.rb
+++ b/spec/support/shared_examples/requests/api/status_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for status checking.
#
# Requires an API request:
diff --git a/spec/support/shared_examples/requests/graphql_shared_examples.rb b/spec/support/shared_examples/requests/graphql_shared_examples.rb
index 04140cad3f0..2a38d56141a 100644
--- a/spec/support/shared_examples/requests/graphql_shared_examples.rb
+++ b/spec/support/shared_examples/requests/graphql_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
shared_examples 'a working graphql query' do
diff --git a/spec/support/shared_examples/resource_label_events_api.rb b/spec/support/shared_examples/resource_label_events_api.rb
new file mode 100644
index 00000000000..945cb8d9f2c
--- /dev/null
+++ b/spec/support/shared_examples/resource_label_events_api.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+shared_examples 'resource_label_events API' do |parent_type, eventable_type, id_name|
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events" do
+ it "returns an array of resource label events" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.first['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error when eventable id not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/12345/resource_label_events", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it "returns 404 when not authorized" do
+ parent.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ private_user = create(:user)
+
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events", private_user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+
+ describe "GET /#{parent_type}/:id/#{eventable_type}/:noteable_id/resource_label_events/:event_id" do
+ it "returns a resource label event by id" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/#{event.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(event.id)
+ end
+
+ it "returns a 404 error if resource label event not found" do
+ get api("/#{parent_type}/#{parent.id}/#{eventable_type}/#{eventable[id_name]}/resource_label_events/12345", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/serializers/note_entity_examples.rb b/spec/support/shared_examples/serializers/note_entity_examples.rb
index ec208aba2a9..bfcaa2f1bd5 100644
--- a/spec/support/shared_examples/serializers/note_entity_examples.rb
+++ b/spec/support/shared_examples/serializers/note_entity_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'note entity' do
subject { entity.as_json }
diff --git a/spec/support/shared_examples/services/boards/boards_create_service.rb b/spec/support/shared_examples/services/boards/boards_create_service.rb
index 5bdc04f660f..19818a6091b 100644
--- a/spec/support/shared_examples/services/boards/boards_create_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_create_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards create service' do
context 'when parent does not have a board' do
it 'creates a new board' do
diff --git a/spec/support/shared_examples/services/boards/boards_list_service.rb b/spec/support/shared_examples/services/boards/boards_list_service.rb
index e0d5a7c61f2..566e5050f8e 100644
--- a/spec/support/shared_examples/services/boards/boards_list_service.rb
+++ b/spec/support/shared_examples/services/boards/boards_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'boards list service' do
context 'when parent does not have a board' do
it 'creates a new parent board' do
diff --git a/spec/support/shared_examples/services/boards/issues_list_service.rb b/spec/support/shared_examples/services/boards/issues_list_service.rb
index 8b879cef084..75733c774ef 100644
--- a/spec/support/shared_examples/services/boards/issues_list_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues list service' do
it 'delegates search to IssuesFinder' do
params = { board_id: board.id, id: list1.id }
diff --git a/spec/support/shared_examples/services/boards/issues_move_service.rb b/spec/support/shared_examples/services/boards/issues_move_service.rb
index 5359831f8f8..d3fa8084185 100644
--- a/spec/support/shared_examples/services/boards/issues_move_service.rb
+++ b/spec/support/shared_examples/services/boards/issues_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'issues move service' do |group|
shared_examples 'updating timestamps' do
it 'updates updated_at' do
diff --git a/spec/support/shared_examples/services/boards/lists_destroy_service.rb b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
index 62b6ffe1836..95725078f9d 100644
--- a/spec/support/shared_examples/services/boards/lists_destroy_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_destroy_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists destroy service' do
context 'when list type is label' do
it 'removes list from board' do
diff --git a/spec/support/shared_examples/services/boards/lists_list_service.rb b/spec/support/shared_examples/services/boards/lists_list_service.rb
index 0a8220111ab..29784f6da08 100644
--- a/spec/support/shared_examples/services/boards/lists_list_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_list_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists list service' do
context 'when the board has a backlog list' do
let!(:backlog_list) { create(:backlog_list, board: board) }
diff --git a/spec/support/shared_examples/services/boards/lists_move_service.rb b/spec/support/shared_examples/services/boards/lists_move_service.rb
index 2cdb968a45d..0b3bfd8e2a8 100644
--- a/spec/support/shared_examples/services/boards/lists_move_service.rb
+++ b/spec/support/shared_examples/services/boards/lists_move_service.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'lists move service' do
let!(:planning) { create(:list, board: board, position: 0) }
let!(:development) { create(:list, board: board, position: 1) }
diff --git a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
index 02de47a96dd..1e0ac8b7615 100644
--- a/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/check_ingress_ip_address_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'check ingress ip executions' do |app_name|
describe '#execute' do
let(:application) { create(app_name, :installed) }
diff --git a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
index b8db35a6ef9..1c3fa5644d3 100644
--- a/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
+++ b/spec/support/shared_examples/services/gitlab_projects_import_service_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'gitlab projects import validations' do
context 'with an invalid path' do
let(:path) { '/invalid-path/' }
diff --git a/spec/support/shared_examples/services/notification_service_shared_examples.rb b/spec/support/shared_examples/services/notification_service_shared_examples.rb
new file mode 100644
index 00000000000..dd338ea47c7
--- /dev/null
+++ b/spec/support/shared_examples/services/notification_service_shared_examples.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+# Note that we actually update the attribute on the target_project/group, rather than
+# using `allow`. This is because there are some specs where, based on how the notification
+# is done, using an `allow` doesn't change the correct object.
+shared_examples 'project emails are disabled' do
+ let(:target_project) { notification_target.is_a?(Project) ? notification_target : notification_target.project }
+
+ before do
+ reset_delivered_emails!
+ target_project.clear_memoization(:emails_disabled)
+ end
+
+ it 'sends no emails with project emails disabled' do
+ target_project.update_attribute(:emails_disabled, true)
+
+ notification_trigger
+
+ should_not_email_anyone
+ end
+
+ it 'sends emails to someone' do
+ target_project.update_attribute(:emails_disabled, false)
+
+ notification_trigger
+
+ should_email_anyone
+ end
+end
+
+shared_examples 'group emails are disabled' do
+ let(:target_group) { notification_target.is_a?(Group) ? notification_target : notification_target.project.group }
+
+ before do
+ reset_delivered_emails!
+ target_group.clear_memoization(:emails_disabled)
+ end
+
+ it 'sends no emails with group emails disabled' do
+ target_group.update_attribute(:emails_disabled, true)
+
+ notification_trigger
+
+ should_not_email_anyone
+ end
+
+ it 'sends emails to someone' do
+ target_group.update_attribute(:emails_disabled, false)
+
+ notification_trigger
+
+ should_email_anyone
+ end
+end
diff --git a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
index 36c486dbdd6..8ce94064dc3 100644
--- a/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
+++ b/spec/support/shared_examples/slack_mattermost_notifications_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f }
RSpec.shared_examples 'slack or mattermost notifications' do
@@ -452,7 +454,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'only notify for the default branch' do
context 'when enabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
@@ -470,7 +472,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do
context 'when disabled' do
let(:pipeline) do
- create(:ci_pipeline, :failed, project: project, ref: 'not-the-default-branch')
+ create(:ci_pipeline, :failed, project: project, sha: project.commit.sha, ref: 'not-the-default-branch')
end
before do
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
index 833c31a57cb..b5321c6db34 100644
--- a/spec/support/shared_examples/snippet_visibility_shared_examples.rb
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'snippet visibility' do
using RSpec::Parameterized::TableSyntax
diff --git a/spec/support/shared_examples/snippets_shared_examples.rb b/spec/support/shared_examples/snippets_shared_examples.rb
index 85f0facd5c3..5c35617bd36 100644
--- a/spec/support/shared_examples/snippets_shared_examples.rb
+++ b/spec/support/shared_examples/snippets_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# These shared examples expect a `snippets` array of snippets
RSpec.shared_examples 'paginated snippets' do |remote: false|
it "is limited to #{Snippet.default_per_page} items per page" do
diff --git a/spec/support/shared_examples/taskable_shared_examples.rb b/spec/support/shared_examples/taskable_shared_examples.rb
index 4056ff06b84..f04f509f3d2 100644
--- a/spec/support/shared_examples/taskable_shared_examples.rb
+++ b/spec/support/shared_examples/taskable_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Specs for task state functionality for issues and merge requests.
#
# Requires a context containing:
@@ -105,4 +107,25 @@ shared_examples 'a Taskable' do
expect(subject.task_status_short).to match('1 task')
end
end
+
+ describe 'with tasks in blockquotes' do
+ before do
+ subject.description = <<-EOT.strip_heredoc
+ > - [ ] Task a
+ > > - [x] Task a.1
+
+ >>>
+ 1. [ ] Task 1
+ 1. [x] Task 2
+ >>>
+ EOT
+ end
+
+ it 'returns the correct task status' do
+ expect(subject.task_status).to match('2 of')
+ expect(subject.task_status).to match('4 tasks completed')
+ expect(subject.task_status_short).to match('2/')
+ expect(subject.task_status_short).to match('4 tasks')
+ end
+ end
end
diff --git a/spec/support/shared_examples/throttled_touch.rb b/spec/support/shared_examples/throttled_touch.rb
index eba990d4037..aaaa590862d 100644
--- a/spec/support/shared_examples/throttled_touch.rb
+++ b/spec/support/shared_examples/throttled_touch.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples_for 'throttled touch' do
describe '#touch' do
it 'updates the updated_at timestamp' do
diff --git a/spec/support/shared_examples/unique_ip_check_shared_examples.rb b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
index e5c8ac6a004..65d86ddee9e 100644
--- a/spec/support/shared_examples/unique_ip_check_shared_examples.rb
+++ b/spec/support/shared_examples/unique_ip_check_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'unique ips sign in limit' do
include StubENV
before do
diff --git a/spec/support/shared_examples/update_invalid_issuable.rb b/spec/support/shared_examples/update_invalid_issuable.rb
index 4cb6d001b9b..b7ac08372f9 100644
--- a/spec/support/shared_examples/update_invalid_issuable.rb
+++ b/spec/support/shared_examples/update_invalid_issuable.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples 'update invalid issuable' do |klass|
let(:params) do
{
diff --git a/spec/support/shared_examples/updating_mentions_shared_examples.rb b/spec/support/shared_examples/updating_mentions_shared_examples.rb
index 5e3f19ba19e..ef385f94cc2 100644
--- a/spec/support/shared_examples/updating_mentions_shared_examples.rb
+++ b/spec/support/shared_examples/updating_mentions_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'updating mentions' do |service_class|
let(:mentioned_user) { create(:user) }
let(:service_class) { service_class }
diff --git a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
index 1190863d88e..9263aaff89a 100644
--- a/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/gitlab_uploader_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_examples "matches the method pattern" do |method|
let(:target) { subject }
let(:args) { nil }
diff --git a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
index 1bd176280c5..5d605dd811b 100644
--- a/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
+++ b/spec/support/shared_examples/uploaders/object_storage_shared_examples.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
shared_context 'with storage' do |store, **stub_params|
before do
subject.object_store = store
diff --git a/spec/support/shared_examples/url_validator_examples.rb b/spec/support/shared_examples/url_validator_examples.rb
index 25277ccd9aa..c5a775fefb6 100644
--- a/spec/support/shared_examples/url_validator_examples.rb
+++ b/spec/support/shared_examples/url_validator_examples.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
RSpec.shared_examples 'url validator examples' do |schemes|
- let(:validator) { described_class.new(attributes: [:link_url], **options) }
- let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
+ describe '#validate' do
+ let(:validator) { described_class.new(attributes: [:link_url], **options) }
+ let(:badge) { build(:badge, link_url: 'http://www.example.com') }
- subject { validator.validate(badge) }
+ subject { validator.validate(badge) }
- describe '#validate' do
context 'with no options' do
let(:options) { {} }
@@ -40,3 +42,52 @@ RSpec.shared_examples 'url validator examples' do |schemes|
end
end
end
+
+RSpec.shared_examples 'public url validator examples' do |setting|
+ let(:validator) { described_class.new(attributes: [:link_url]) }
+ let(:badge) { build(:badge, link_url: 'http://www.example.com') }
+
+ subject { validator.validate(badge) }
+
+ context 'by default' do
+ it 'blocks urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+
+ it 'blocks urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).to be_present
+ end
+ end
+
+ context 'when local requests are allowed' do
+ let!(:settings) { create(:application_setting) }
+
+ before do
+ stub_application_setting(setting)
+ end
+
+ it 'does not block urls pointing to localhost' do
+ badge.link_url = 'https://127.0.0.1'
+
+ subject
+
+ expect(badge.errors).not_to be_present
+ end
+
+ it 'does not block urls pointing to the local network' do
+ badge.link_url = 'https://192.168.1.1'
+
+ subject
+
+ expect(badge.errors).not_to be_present
+ end
+ end
+end
diff --git a/spec/support/sidekiq.rb b/spec/support/sidekiq.rb
index d1a765f27b9..585c458a64e 100644
--- a/spec/support/sidekiq.rb
+++ b/spec/support/sidekiq.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'sidekiq/testing/inline'
# If Sidekiq::Testing.inline! is used, SQL transactions done inside
diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb
index 55212355daa..95f0f971787 100644
--- a/spec/support/stored_repositories.rb
+++ b/spec/support/stored_repositories.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
RSpec.configure do |config|
config.before(:each, :broken_storage) do
allow(Gitlab::GitalyClient).to receive(:call) do
diff --git a/spec/support/test_reports/test_reports_helper.rb b/spec/support/test_reports/test_reports_helper.rb
index 6840fb9a860..6ba50c83b25 100644
--- a/spec/support/test_reports/test_reports_helper.rb
+++ b/spec/support/test_reports/test_reports_helper.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TestReportsHelper
def create_test_case_rspec_success(name = 'test_spec')
Gitlab::Ci::Reports::TestCase.new(
diff --git a/spec/support/trace/trace_helpers.rb b/spec/support/trace/trace_helpers.rb
index c7802bbcb94..9255715ff71 100644
--- a/spec/support/trace/trace_helpers.rb
+++ b/spec/support/trace/trace_helpers.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module TraceHelpers
def create_legacy_trace(build, content)
File.open(legacy_trace_path(build), 'wb') { |stream| stream.write(content) }
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
index 9ac7e7fc515..32b88edc2df 100644
--- a/spec/support/webmock.rb
+++ b/spec/support/webmock.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'webmock'
require 'webmock/rspec'
diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb
index 92c094f08a4..4aee6d005a8 100644
--- a/spec/tasks/gitlab/cleanup_rake_spec.rb
+++ b/spec/tasks/gitlab/cleanup_rake_spec.rb
@@ -185,4 +185,34 @@ describe 'gitlab:cleanup rake tasks' do
end
end
end
+
+ context 'sessions' do
+ describe 'gitlab:cleanup:sessions:active_sessions_lookup_keys', :clean_gitlab_redis_shared_state do
+ subject(:rake_task) { run_rake_task('gitlab:cleanup:sessions:active_sessions_lookup_keys') }
+
+ let!(:user) { create(:user) }
+ let(:existing_session_id) { '5' }
+
+ before do
+ Gitlab::Redis::SharedState.with do |redis|
+ redis.set("session:user:gitlab:#{user.id}:#{existing_session_id}",
+ Marshal.dump(true))
+ redis.sadd("session:lookup:user:gitlab:#{user.id}", (1..10).to_a)
+ end
+ end
+
+ it 'runs the task without errors' do
+ expect { rake_task }.not_to raise_error
+ end
+
+ it 'removes expired active session lookup keys' do
+ Gitlab::Redis::SharedState.with do |redis|
+ lookup_key = "session:lookup:user:gitlab:#{user.id}"
+ expect { subject }.to change { redis.scard(lookup_key) }.from(10).to(1)
+ expect(redis.smembers("session:lookup:user:gitlab:#{user.id}")).to(
+ eql([existing_session_id]))
+ end
+ end
+ end
+ end
end
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
deleted file mode 100644
index 8d1cff7a261..00000000000
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ /dev/null
@@ -1,27 +0,0 @@
-require 'spec_helper'
-require 'rake'
-
-describe 'gitlab:mail_google_schema_whitelisting rake task' do
- before :all do
- Rake.application.rake_require "tasks/gitlab/helpers"
- Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
- # empty task as env is already loaded
- Rake::Task.define_task :environment
- end
-
- describe 'call' do
- before do
- # avoid writing task output to spec progress
- allow($stdout).to receive :write
- end
-
- let :run_rake_task do
- Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
- Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
- end
-
- it 'runs the task without errors' do
- expect { run_rake_task }.not_to raise_error
- end
- end
-end
diff --git a/spec/tasks/gitlab/update_templates_rake_spec.rb b/spec/tasks/gitlab/update_templates_rake_spec.rb
new file mode 100644
index 00000000000..14b4ad5e3d8
--- /dev/null
+++ b/spec/tasks/gitlab/update_templates_rake_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'rake_helper'
+
+describe 'gitlab:update_project_templates rake task' do
+ let!(:tmpdir) { Dir.mktmpdir }
+
+ before do
+ Rake.application.rake_require 'tasks/gitlab/update_templates'
+ create(:admin)
+
+ allow(Gitlab::ProjectTemplate)
+ .to receive(:archive_directory)
+ .and_return(Pathname.new(tmpdir))
+
+ # Gitlab::HTTP resolves the domain to an IP prior to WebMock taking effect, hence the wildcard
+ stub_request(:get, %r{^https://.*/api/v4/projects/gitlab-org%2Fproject-templates%2Frails/repository/commits\?page=1&per_page=1})
+ .to_return(
+ status: 200,
+ body: [{ id: '67812735b83cb42710f22dc98d73d42c8bf4d907' }].to_json,
+ headers: { 'Content-Type' => 'application/json' }
+ )
+ end
+
+ after do
+ FileUtils.rm_rf(tmpdir)
+ end
+
+ it 'updates valid project templates' do
+ expect { run_rake_task('gitlab:update_project_templates', ['rails']) }
+ .to change { Dir.entries(tmpdir) }
+ .by(['rails.tar.gz'])
+ end
+end
diff --git a/spec/uploaders/records_uploads_spec.rb b/spec/uploaders/records_uploads_spec.rb
index 42352f9b9f8..6134137d2b7 100644
--- a/spec/uploaders/records_uploads_spec.rb
+++ b/spec/uploaders/records_uploads_spec.rb
@@ -85,6 +85,27 @@ describe RecordsUploads do
expect { existing.reload }.to raise_error(ActiveRecord::RecordNotFound)
expect(Upload.count).to eq(1)
end
+
+ it 'does not affect other uploads with different model but the same path' do
+ project = create(:project)
+ other_project = create(:project)
+
+ uploader = RecordsUploadsExampleUploader.new(other_project)
+
+ upload_for_project = Upload.create!(
+ path: File.join('uploads', 'rails_sample.jpg'),
+ size: 512.kilobytes,
+ model: project,
+ uploader: uploader.class.to_s
+ )
+
+ uploader.store!(upload_fixture('rails_sample.jpg'))
+
+ upload_for_project_fresh = Upload.find(upload_for_project.id)
+
+ expect(upload_for_project).to eq(upload_for_project_fresh)
+ expect(Upload.count).to eq(2)
+ end
end
describe '#destroy_upload callback' do
diff --git a/spec/validators/public_url_validator_spec.rb b/spec/validators/public_url_validator_spec.rb
index f6364fb1dd5..3cbf1002730 100644
--- a/spec/validators/public_url_validator_spec.rb
+++ b/spec/validators/public_url_validator_spec.rb
@@ -2,27 +2,5 @@ require 'spec_helper'
describe PublicUrlValidator do
include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
-
- context 'by default' do
- let(:validator) { described_class.new(attributes: [:link_url]) }
- let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
-
- subject { validator.validate(badge) }
-
- it 'blocks urls pointing to localhost' do
- badge.link_url = 'https://127.0.0.1'
-
- subject
-
- expect(badge.errors).to be_present
- end
-
- it 'blocks urls pointing to the local network' do
- badge.link_url = 'https://192.168.1.1'
-
- subject
-
- expect(badge.errors).to be_present
- end
- end
+ include_examples 'public url validator examples', allow_local_requests_from_web_hooks_and_services: true
end
diff --git a/spec/validators/qualified_domain_array_validator_spec.rb b/spec/validators/qualified_domain_array_validator_spec.rb
new file mode 100644
index 00000000000..6beb4c67f6f
--- /dev/null
+++ b/spec/validators/qualified_domain_array_validator_spec.rb
@@ -0,0 +1,103 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe QualifiedDomainArrayValidator do
+ class TestClass
+ include ActiveModel::Validations
+
+ attr_accessor :domain_array
+
+ def initialize(domain_array)
+ self.domain_array = domain_array
+ end
+ end
+
+ let!(:record) do
+ TestClass.new(['gitlab.com'])
+ end
+
+ subject { validator.validate(record) }
+
+ shared_examples 'can be nil' do
+ it 'allows when attribute is nil' do
+ record.domain_array = nil
+
+ subject
+
+ expect(record.errors).to be_empty
+ end
+ end
+
+ shared_examples 'can be blank' do
+ it 'allows when attribute is blank' do
+ record.domain_array = []
+
+ subject
+
+ expect(record.errors).to be_empty
+ end
+ end
+
+ describe 'validations' do
+ let(:validator) { described_class.new(attributes: [:domain_array]) }
+
+ it_behaves_like 'can be blank'
+
+ it 'returns error when attribute is nil' do
+ record.domain_array = nil
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq('entries cannot be nil')
+ end
+
+ it 'allows when domain is valid' do
+ subject
+
+ expect(record.errors).to be_empty
+ end
+
+ it 'returns error when domain contains unicode' do
+ record.domain_array = ['ğitlab.com']
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'unicode domains should use IDNA encoding'
+ end
+
+ it 'returns error when entry is larger than 255 chars' do
+ record.domain_array = ['a' * 256]
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'entries cannot be larger than 255 characters'
+ end
+
+ it 'returns error when entry contains HTML tags' do
+ record.domain_array = ['gitlab.com<h1>something</h1>']
+
+ subject
+
+ expect(record.errors).to be_present
+ expect(record.errors.first[1]).to eq 'entries cannot contain HTML tags'
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:domain_array], allow_nil: true) }
+
+ it_behaves_like 'can be nil'
+ it_behaves_like 'can be blank'
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:domain_array], allow_blank: true) }
+
+ it_behaves_like 'can be nil'
+ it_behaves_like 'can be blank'
+ end
+end
diff --git a/spec/validators/system_hook_url_validator_spec.rb b/spec/validators/system_hook_url_validator_spec.rb
new file mode 100644
index 00000000000..02384bbd1ce
--- /dev/null
+++ b/spec/validators/system_hook_url_validator_spec.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SystemHookUrlValidator do
+ include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
+ include_examples 'public url validator examples', allow_local_requests_from_system_hooks: true
+end
diff --git a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
index 2f58eec86dc..2f58eec86dc 100644
--- a/spec/views/dashboard/projects/_blank_state_admin_welcome.haml.rb
+++ b/spec/views/dashboard/projects/_blank_state_admin_welcome.haml_spec.rb
diff --git a/spec/views/dashboard/projects/_nav.html.haml.rb b/spec/views/dashboard/projects/_nav.html.haml.rb
deleted file mode 100644
index f6a8ca13040..00000000000
--- a/spec/views/dashboard/projects/_nav.html.haml.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-require 'spec_helper'
-
-describe 'dashboard/projects/_nav.html.haml' do
- it 'highlights All tab by default' do
- render
-
- expect(rendered).to have_css('li.active a', text: 'All')
- end
-
- it 'highlights Personal tab personal param is present' do
- controller.params[:personal] = true
-
- render
-
- expect(rendered).to have_css('li.active a', text: 'Personal')
- end
-end
diff --git a/spec/views/dashboard/projects/_nav.html.haml_spec.rb b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
new file mode 100644
index 00000000000..cbdd3c0acc3
--- /dev/null
+++ b/spec/views/dashboard/projects/_nav.html.haml_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe 'dashboard/projects/_nav.html.haml' do
+ it 'highlights All tab by default' do
+ render
+
+ expect(rendered).to have_css('a.active', text: 'All')
+ end
+
+ it 'highlights Personal tab personal param is present' do
+ controller.params[:personal] = true
+
+ render
+
+ expect(rendered).to have_css('a.active', text: 'Personal')
+ end
+end
diff --git a/spec/views/groups/edit.html.haml_spec.rb b/spec/views/groups/edit.html.haml_spec.rb
index 29e15960fb8..47804411b9d 100644
--- a/spec/views/groups/edit.html.haml_spec.rb
+++ b/spec/views/groups/edit.html.haml_spec.rb
@@ -35,7 +35,7 @@ describe 'groups/edit.html.haml' do
it_behaves_like '"Share with group lock" setting', { disabled: false, checked: false }
end
- context 'for a subgroup', :nested_groups do
+ context 'for a subgroup' do
let!(:subgroup) { create(:group, parent: root_group) }
let(:sub_owner) { create(:user) }
let(:test_group) { subgroup }
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index cbb4199954a..70cdc08b4b6 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -70,6 +70,23 @@ describe 'layouts/_head' do
expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />')
end
+ context 'when an asset_host is set and snowplow url is set' do
+ let(:asset_host) { 'http://test.host' }
+
+ before do
+ allow(ActionController::Base).to receive(:asset_host).and_return(asset_host)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_enabled?).and_return(true)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_collector_hostname).and_return('www.snow.plow')
+ end
+
+ it 'add a snowplow script tag with asset host' do
+ render
+ expect(rendered).to match('http://test.host/assets/snowplow/')
+ expect(rendered).to match('window.snowplow')
+ expect(rendered).to match('www.snow.plow')
+ end
+ end
+
def stub_helper_with_safe_string(method)
allow_any_instance_of(PageLayoutHelper).to receive(method)
.and_return(%q{foo" http-equiv="refresh}.html_safe)
diff --git a/spec/views/layouts/header/_new_dropdown.haml_spec.rb b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
index 2e19d0cec26..26e429ac5d0 100644
--- a/spec/views/layouts/header/_new_dropdown.haml_spec.rb
+++ b/spec/views/layouts/header/_new_dropdown.haml_spec.rb
@@ -28,7 +28,7 @@ describe 'layouts/header/_new_dropdown' do
)
end
- it 'has a "New subgroup" link', :nested_groups do
+ it 'has a "New subgroup" link' do
render
expect(rendered).to have_link(
diff --git a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
index 2befbcb3370..b627b9dba59 100644
--- a/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
+++ b/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb
@@ -21,7 +21,7 @@ describe 'layouts/nav/sidebar/_project' do
end
end
- describe 'container registry tab' do
+ describe 'packages tab' do
before do
stub_container_registry_config(enabled: true)
@@ -31,24 +31,17 @@ describe 'layouts/nav/sidebar/_project' do
.and_return('projects/registry/repositories')
end
- it 'has both Registry and Repository tabs' do
- render
-
- expect(rendered).to have_text 'Repository'
- expect(rendered).to have_text 'Registry'
- end
-
it 'highlights sidebar item and flyout' do
render
expect(rendered).to have_css('.sidebar-top-level-items > li.active', count: 1)
- expect(rendered).to have_css('.is-fly-out-only > li.active', count: 1)
+ expect(rendered).to have_css('.sidebar-sub-level-items > li.fly-out-top-item.active', count: 1)
end
it 'highlights container registry tab' do
render
- expect(rendered).to have_css('.sidebar-top-level-items > li.active', text: 'Registry')
+ expect(rendered).to have_css('.sidebar-sub-level-items > li:not(.fly-out-top-item).active', text: 'Container Registry')
end
end
diff --git a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
index 54ec4f32856..54ec4f32856 100644
--- a/spec/views/projects/deployments/_confirm_rollback_modal_spec.html.rb
+++ b/spec/views/projects/deployments/_confirm_rollback_modal_spec.html_spec.rb
diff --git a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
index 0206928a211..88c4b52b3a6 100644
--- a/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/creations/_new_submit.html.haml_spec.rb
@@ -12,6 +12,7 @@ describe 'projects/merge_requests/creations/_new_submit.html.haml' do
assign(:hidden_commit_count, 0)
assign(:total_commit_count, merge_request.commits.count)
assign(:project, merge_request.target_project)
+ assign(:target_project, merge_request.target_project)
assign(:mr_presenter, merge_request.present(current_user: merge_request.author))
allow(view).to receive(:can?).and_return(true)
diff --git a/spec/views/projects/merge_requests/edit.html.haml_spec.rb b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
index 529afa03f9c..0a3a46210ed 100644
--- a/spec/views/projects/merge_requests/edit.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/edit.html.haml_spec.rb
@@ -23,6 +23,7 @@ describe 'projects/merge_requests/edit.html.haml' do
before do
assign(:project, project)
+ assign(:target_project, project)
assign(:merge_request, closed_merge_request)
assign(:mr_presenter, closed_merge_request.present(current_user: user))
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/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb
new file mode 100644
index 00000000000..d2cd636f8c6
--- /dev/null
+++ b/spec/views/search/_filter.html.haml_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/_filter' do
+ context 'when the search page is opened' do
+ it 'displays the correct elements' do
+ render
+
+ expect(rendered).to have_selector('label[for="dashboard_search_group"]')
+ expect(rendered).to have_selector('button#dashboard_search_group')
+
+ expect(rendered).to have_selector('label[for="dashboard_search_project"]')
+ expect(rendered).to have_selector('button#dashboard_search_project')
+ end
+ end
+end
diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb
new file mode 100644
index 00000000000..69f40895d86
--- /dev/null
+++ b/spec/views/search/_form.html.haml_spec.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/_form' do
+ context 'when the search page is opened' do
+ it 'displays the correct elements' do
+ render
+
+ expect(rendered).to have_selector('.search-field-holder.form-group')
+ expect(rendered).to have_selector('label[for="dashboard_search"]')
+ end
+ end
+end
diff --git a/spec/views/search/show.html.haml_spec.rb b/spec/views/search/show.html.haml_spec.rb
new file mode 100644
index 00000000000..483b913f2cc
--- /dev/null
+++ b/spec/views/search/show.html.haml_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'search/show' do
+ let(:search_term) { nil }
+
+ before do
+ stub_template "search/_category.html.haml" => 'Category Partial'
+ stub_template "search/_results.html.haml" => 'Results Partial'
+
+ @search_term = search_term
+
+ render
+ end
+
+ context 'when the search page is opened' do
+ it 'displays the title' do
+ expect(rendered).to have_selector('h1.page-title', text: 'Search')
+ expect(rendered).not_to have_selector('h1.page-title code')
+ end
+
+ it 'does not render partials' do
+ expect(rendered).not_to render_template('search/_category')
+ expect(rendered).not_to render_template('search/_results')
+ end
+ end
+
+ context 'when search term is supplied' do
+ let(:search_term) { 'Search Foo' }
+
+ it 'renders partials' do
+ expect(rendered).to render_template('search/_category')
+ expect(rendered).to render_template('search/_results')
+ end
+ end
+end
diff --git a/spec/views/shared/_label_row.html.haml.rb b/spec/views/shared/_label_row.html.haml.rb
deleted file mode 100644
index a58d5efc1e3..00000000000
--- a/spec/views/shared/_label_row.html.haml.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe 'shared/_label_row.html.haml' do
- label_types = {
- 'project label': :label,
- 'group label': :group_label
- }
-
- label_types.each do |label_type, label_factory|
- let!(:label) { create(label_factory) }
-
- context "for a #{label_type}" do
- it 'has a non-linked label title' do
- render 'shared/label_row', label: label
-
- expect(rendered).not_to have_css('a', text: label.title)
- end
-
- it "has Issues link for #{label_type}" do
- render 'shared/label_row', label: label
-
- expect(rendered).to have_css('a', text: 'Issues')
- end
-
- it "has Merge request link for #{label_type}" do
- render 'shared/label_row', label: label
-
- expect(rendered).to have_css('a', text: 'Merge requests')
- end
- end
- end
-end
diff --git a/spec/views/shared/_label_row.html.haml_spec.rb b/spec/views/shared/_label_row.html.haml_spec.rb
new file mode 100644
index 00000000000..4cce13aa37c
--- /dev/null
+++ b/spec/views/shared/_label_row.html.haml_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe 'shared/_label_row.html.haml' do
+ label_types = {
+ 'project label': :label,
+ 'group label': :group_label
+ }
+
+ label_types.each do |label_type, label_factory|
+ let!(:label) do
+ label_record = create(label_factory)
+ label_record.present(issuable_subject: label_record.subject)
+ end
+
+ context "for a #{label_type}" do
+ before do
+ if label.project_label?
+ @project = label.project
+ else
+ @group = label.group
+ end
+ end
+
+ it 'has a non-linked label title' do
+ render 'shared/label_row', label: label
+
+ expect(rendered).not_to have_css('a', text: label.title)
+ end
+
+ it "has Issues link for #{label_type}" do
+ render 'shared/label_row', label: label
+
+ expect(rendered).to have_css('a', text: 'Issues')
+ end
+
+ it "has Merge request link for #{label_type}" do
+ render 'shared/label_row', label: label
+
+ expect(rendered).to have_css('a', text: 'Merge requests')
+ end
+ end
+ end
+end
diff --git a/spec/views/shared/milestones/_issuable.html.haml.rb b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
index 0a3f877cae0..0a3f877cae0 100644
--- a/spec/views/shared/milestones/_issuable.html.haml.rb
+++ b/spec/views/shared/milestones/_issuable.html.haml_spec.rb
diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml.rb
deleted file mode 100644
index cbbb984935f..00000000000
--- a/spec/views/shared/milestones/_issuables.html.haml.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-require 'spec_helper'
-
-describe 'shared/milestones/_issuables.html.haml' do
- let(:issuables_size) { 100 }
-
- before do
- allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil,
- show_full_project_name: nil, dom_class: '',
- issuables: double(size: issuables_size).as_null_object)
-
- stub_template 'shared/milestones/_issuable.html.haml' => ''
- end
-
- it 'shows the issuables count if show_counter is true' do
- render 'shared/milestones/issuables', show_counter: true
- expect(rendered).to have_content('100')
- end
-
- it 'does not show the issuables count if show_counter is false' do
- render 'shared/milestones/issuables', show_counter: false
- expect(rendered).not_to have_content('100')
- end
-
- describe 'a high issuables count' do
- let(:issuables_size) { 1000 }
-
- it 'shows a delimited number if show_counter is true' do
- render 'shared/milestones/issuables', show_counter: true
- expect(rendered).to have_content('1,000')
- end
- end
-end
diff --git a/spec/views/shared/milestones/_issuables.html.haml_spec.rb b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
new file mode 100644
index 00000000000..24b55338db3
--- /dev/null
+++ b/spec/views/shared/milestones/_issuables.html.haml_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'shared/milestones/_issuables.html.haml' do
+ let(:issuables_size) { 100 }
+
+ before do
+ allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil,
+ show_full_project_name: nil, dom_class: '',
+ issuables: double(length: issuables_size).as_null_object)
+
+ stub_template 'shared/milestones/_issuable.html.haml' => ''
+ end
+
+ it 'shows the issuables count if show_counter is true' do
+ render 'shared/milestones/issuables', show_counter: true
+ expect(rendered).to have_content('100')
+ end
+
+ it 'does not show the issuables count if show_counter is false' do
+ render 'shared/milestones/issuables', show_counter: false
+ expect(rendered).not_to have_content('100')
+ end
+
+ describe 'a high issuables count' do
+ let(:issuables_size) { 1000 }
+
+ it 'shows a delimited number if show_counter is true' do
+ render 'shared/milestones/issuables', show_counter: true
+ expect(rendered).to have_content('1,000')
+ end
+ end
+end
diff --git a/spec/views/shared/milestones/_top.html.haml.rb b/spec/views/shared/milestones/_top.html.haml.rb
deleted file mode 100644
index 516d81c87ac..00000000000
--- a/spec/views/shared/milestones/_top.html.haml.rb
+++ /dev/null
@@ -1,35 +0,0 @@
-require 'spec_helper'
-
-describe 'shared/milestones/_top.html.haml' do
- set(:group) { create(:group) }
- let(:project) { create(:project, group: group) }
- let(:milestone) { create(:milestone, project: project) }
-
- before do
- allow(milestone).to receive(:milestones) { [] }
- end
-
- it 'renders a deprecation message for a legacy milestone' do
- allow(milestone).to receive(:legacy_group_milestone?) { true }
-
- render 'shared/milestones/top', milestone: milestone
-
- expect(rendered).to have_css('.milestone-deprecation-message')
- end
-
- it 'renders a deprecation message for a dashboard milestone' do
- allow(milestone).to receive(:dashboard_milestone?) { true }
-
- render 'shared/milestones/top', milestone: milestone
-
- expect(rendered).to have_css('.milestone-deprecation-message')
- end
-
- it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
- assign :group, group
-
- render 'shared/milestones/top', milestone: milestone
-
- expect(rendered).not_to have_css('.milestone-deprecation-message')
- end
-end
diff --git a/spec/views/shared/milestones/_top.html.haml_spec.rb b/spec/views/shared/milestones/_top.html.haml_spec.rb
new file mode 100644
index 00000000000..f2ee8be5857
--- /dev/null
+++ b/spec/views/shared/milestones/_top.html.haml_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+describe 'shared/milestones/_top.html.haml' do
+ set(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ before do
+ allow(milestone).to receive(:milestones) { [] }
+ allow(milestone).to receive(:milestone) { milestone }
+ end
+
+ it 'renders a deprecation message for a legacy milestone' do
+ allow(milestone).to receive(:legacy_group_milestone?) { true }
+
+ render 'shared/milestones/top', milestone: milestone
+
+ expect(rendered).to have_css('.milestone-deprecation-message')
+ end
+
+ it 'renders a deprecation message for a dashboard milestone' do
+ allow(milestone).to receive(:dashboard_milestone?) { true }
+
+ render 'shared/milestones/top', milestone: milestone
+
+ expect(rendered).to have_css('.milestone-deprecation-message')
+ end
+
+ it 'does not render a deprecation message for a non-legacy and non-dashboard milestone' do
+ assign :group, group
+
+ render 'shared/milestones/top', milestone: milestone
+
+ expect(rendered).not_to have_css('.milestone-deprecation-message')
+ end
+end
diff --git a/spec/workers/archive_trace_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 368ed3f3db1..44f7be15201 100644
--- a/spec/workers/archive_trace_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -11,7 +11,7 @@ describe ArchiveTraceWorker do
it 'executes service' do
expect_any_instance_of(Ci::ArchiveTraceService)
- .to receive(:execute).with(job)
+ .to receive(:execute).with(job, anything)
subject
end
diff --git a/spec/workers/background_migration_worker_spec.rb b/spec/workers/background_migration_worker_spec.rb
index 746c858609f..e5be8ce0423 100644
--- a/spec/workers/background_migration_worker_spec.rb
+++ b/spec/workers/background_migration_worker_spec.rb
@@ -55,21 +55,13 @@ describe BackgroundMigrationWorker, :sidekiq, :clean_gitlab_redis_shared_state d
end
describe '#healthy_database?' do
- context 'using MySQL', :mysql do
- it 'returns true' do
- expect(worker.healthy_database?).to eq(true)
- end
- end
-
- context 'using PostgreSQL', :postgresql do
- context 'when replication lag is too great' do
- it 'returns false' do
- allow(Postgresql::ReplicationSlot)
- .to receive(:lag_too_great?)
- .and_return(true)
+ context 'when replication lag is too great' do
+ it 'returns false' do
+ allow(Postgresql::ReplicationSlot)
+ .to receive(:lag_too_great?)
+ .and_return(true)
- expect(worker.healthy_database?).to eq(false)
- end
+ expect(worker.healthy_database?).to eq(false)
end
context 'when replication lag is small enough' do
diff --git a/spec/workers/ci/archive_traces_cron_worker_spec.rb b/spec/workers/ci/archive_traces_cron_worker_spec.rb
index eca6cf5235f..28381fdc3be 100644
--- a/spec/workers/ci/archive_traces_cron_worker_spec.rb
+++ b/spec/workers/ci/archive_traces_cron_worker_spec.rb
@@ -34,7 +34,7 @@ describe Ci::ArchiveTracesCronWorker do
it 'executes service' do
expect_any_instance_of(Ci::ArchiveTraceService)
- .to receive(:execute).with(build)
+ .to receive(:execute).with(build, anything)
subject
end
@@ -60,7 +60,10 @@ describe Ci::ArchiveTracesCronWorker do
end
it 'puts a log' do
- expect(Rails.logger).to receive(:error).with("Failed to archive trace. id: #{build.id} message: Unexpected error")
+ expect(Sidekiq.logger).to receive(:warn).with(
+ class: described_class.name,
+ message: "Failed to archive trace. message: Unexpected error.",
+ job_id: build.id)
subject
end
diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
deleted file mode 100644
index 975088f3ee6..00000000000
--- a/spec/workers/cluster_configure_worker_spec.rb
+++ /dev/null
@@ -1,71 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ClusterConfigureWorker, '#perform' do
- let(:worker) { described_class.new }
-
- shared_examples 'configured cluster' do
- it 'creates a namespace' do
- expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_cluster).with(cluster).once
-
- worker.perform(cluster.id)
- end
- end
-
- shared_examples 'unconfigured cluster' do
- it 'does not create a namespace' do
- expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
-
- worker.perform(cluster.id)
- end
- end
-
- context 'group cluster' do
- let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
- let(:group) { cluster.group }
-
- context 'when group has a project' do
- let!(:project) { create(:project, group: group) }
-
- it_behaves_like 'unconfigured cluster'
- end
-
- context 'when group has project in a sub-group' do
- let!(:subgroup) { create(:group, parent: group) }
- let!(:project) { create(:project, group: subgroup) }
-
- it_behaves_like 'unconfigured cluster'
- end
- end
-
- context 'when provider type is gcp' do
- let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
-
- it_behaves_like 'configured cluster'
- end
-
- context 'when provider type is user' do
- let!(:cluster) { create(:cluster, :project, :provided_by_user) }
-
- it_behaves_like 'configured cluster'
- end
-
- context 'when cluster is not managed' do
- let(:cluster) { create(:cluster, :not_managed) }
-
- it 'does not configure the cluster' do
- expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
-
- described_class.new.perform(cluster.id)
- end
- end
-
- context 'when cluster does not exist' do
- it 'does not provision a cluster' do
- expect_any_instance_of(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:execute)
-
- described_class.new.perform(123)
- end
- end
-end
diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb
deleted file mode 100644
index 2ac9d0f61b4..00000000000
--- a/spec/workers/cluster_project_configure_worker_spec.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-describe ClusterProjectConfigureWorker, '#perform' do
- let(:worker) { described_class.new }
- let(:cluster) { create(:cluster, :project) }
-
- it 'configures the cluster' do
- expect(Clusters::RefreshService).to receive(:create_or_update_namespaces_for_project)
-
- described_class.new.perform(cluster.projects.first.id)
- end
-end
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index 8dd74b96d49..6bbdfe03ceb 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -74,15 +74,4 @@ describe Namespaces::RootStatisticsWorker, '#perform' do
worker.perform(group.id)
end
end
-
- context 'when update_statistics_namespace is off' do
- it 'does not create a new one' do
- stub_feature_flags(update_statistics_namespace: false, namespace: group)
-
- expect_any_instance_of(Namespaces::StatisticsRefresherService)
- .not_to receive(:execute)
-
- worker.perform(group.id)
- end
- end
end
diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
index d4a49a3f53a..be722f451e0 100644
--- a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
+++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
@@ -31,16 +31,6 @@ describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_
expect(group.aggregation_schedule).to be_present
end
end
-
- context 'when update_statistics_namespace is off' do
- it 'does not create a new one' do
- stub_feature_flags(update_statistics_namespace: false, namespace: group)
-
- expect do
- worker.perform(group.id)
- end.not_to change(Namespace::AggregationSchedule, :count)
- end
- end
end
context 'when group is not the root ancestor' do
diff --git a/spec/workers/pipeline_process_worker_spec.rb b/spec/workers/pipeline_process_worker_spec.rb
index d33cf72e51e..ac677e3b555 100644
--- a/spec/workers/pipeline_process_worker_spec.rb
+++ b/spec/workers/pipeline_process_worker_spec.rb
@@ -12,6 +12,17 @@ describe PipelineProcessWorker do
described_class.new.perform(pipeline.id)
end
+
+ context 'when build_ids are passed' do
+ let(:build) { create(:ci_build, pipeline: pipeline, name: 'my-build') }
+
+ it 'processes pipeline with a list of builds' do
+ expect_any_instance_of(Ci::Pipeline).to receive(:process!)
+ .with([build.id])
+
+ described_class.new.perform(pipeline.id, [build.id])
+ end
+ end
end
context 'when pipeline does not exist' do
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 39f1beb4efa..c8a0c22b0e8 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -14,6 +14,10 @@ describe PostReceive do
create(:project, :repository, auto_cancel_pending_pipelines: 'disabled')
end
+ def perform(changes: base64_changes)
+ described_class.new.perform(gl_repository, key_id, changes)
+ end
+
context "as a sidekiq worker" do
it "responds to #perform" do
expect(described_class.new).to respond_to(:perform)
@@ -28,18 +32,41 @@ describe PostReceive do
it "returns false and logs an error" do
expect(Gitlab::GitLogger).to receive(:error).with("POST-RECEIVE: #{error_message}")
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be(false)
+ expect(perform).to be(false)
end
end
describe "#process_project_changes" do
+ context 'with an empty project' do
+ let(:empty_project) { create(:project, :empty_repo) }
+ let(:changes) { "123456 789012 refs/heads/tést1\n" }
+
+ before do
+ allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(empty_project.owner)
+ allow(Gitlab::GlRepository).to receive(:parse).and_return([empty_project, Gitlab::GlRepository::PROJECT])
+ end
+
+ it 'expire the status cache' do
+ expect(empty_project.repository).to receive(:expire_status_cache)
+
+ perform
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(empty_project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
+ end
+
context 'empty changes' do
it "does not call any PushService but runs after project hooks" do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
expect_next_instance_of(SystemHooksService) { |service| expect(service).to receive(:execute_hooks) }
- described_class.new.perform(gl_repository, key_id, "")
+ perform(changes: "")
end
end
@@ -50,40 +77,118 @@ describe PostReceive do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be false
+ expect(perform).to be false
end
end
context 'with changes' do
before do
allow_any_instance_of(Gitlab::GitPostReceive).to receive(:identify).and_return(project.owner)
+ allow(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT])
end
context "branches" do
- let(:changes) { "123456 789012 refs/heads/tést" }
+ let(:changes) do
+ <<~EOF
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
+ EOF
+ end
- it "calls Git::BranchPushService" do
- expect_next_instance_of(Git::BranchPushService) do |service|
+ it 'expires the branches cache' do
+ expect(project.repository).to receive(:expire_branches_cache).once
+
+ perform
+ end
+
+ it 'expires the status cache' do
+ expect(project).to receive(:empty_repo?).and_return(true)
+ expect(project.repository).to receive(:expire_status_cache)
+
+ perform
+ end
+
+ it 'calls Git::BranchPushService' do
+ expect_any_instance_of(Git::BranchPushService) do |service|
expect(service).to receive(:execute).and_return(true)
end
expect(Git::TagPushService).not_to receive(:new)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
+ end
+
+ it 'schedules a cache update for repository size only' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
+ end
+
+ context 'with a default branch' do
+ let(:changes) do
+ <<~EOF
+ 123456 789012 refs/heads/tést1
+ 123456 789012 refs/heads/tést2
+ 678912 123455 refs/heads/#{project.default_branch}
+ EOF
+ end
+
+ it 'schedules a cache update for commit count and size' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size, :commit_count], true)
+
+ perform
+ end
end
end
context "tags" do
- let(:changes) { "123456 789012 refs/tags/tag" }
+ let(:changes) do
+ <<~EOF
+ 654321 210987 refs/tags/tag1
+ 654322 210986 refs/tags/tag2
+ 654323 210985 refs/tags/tag3
+ 654324 210984 refs/tags/tag4
+ 654325 210983 refs/tags/tag5
+ EOF
+ end
+
+ before do
+ expect(Gitlab::GlRepository).to receive(:parse).and_return([project, Gitlab::GlRepository::PROJECT])
+ end
+
+ it 'does not expire branches cache' do
+ expect(project.repository).not_to receive(:expire_branches_cache)
+
+ perform
+ end
+
+ it "only invalidates tags once" do
+ expect(project.repository).to receive(:repository_event).exactly(5).times.with(:push_tag).and_call_original
+ expect(project.repository).to receive(:expire_caches_for_tags).once.and_call_original
+ expect(project.repository).to receive(:expire_tags_cache).once.and_call_original
+
+ perform
+ end
it "calls Git::TagPushService" do
- expect(Git::BranchPushService).not_to receive(:execute)
+ expect(Git::BranchPushService).not_to receive(:new)
- expect_next_instance_of(Git::TagPushService) do |service|
+ expect_any_instance_of(Git::TagPushService) do |service|
expect(service).to receive(:execute).and_return(true)
end
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ expect(Git::BranchPushService).not_to receive(:new)
+
+ perform
+ end
+
+ it 'schedules a single ProjectCacheWorker update' do
+ expect(ProjectCacheWorker).to receive(:perform_async)
+ .with(project.id, [], [:repository_size], true)
+
+ perform
end
end
@@ -94,7 +199,7 @@ describe PostReceive do
expect(Git::BranchPushService).not_to receive(:new)
expect(Git::TagPushService).not_to receive(:new)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
end
@@ -111,7 +216,7 @@ describe PostReceive do
let(:changes_count) { changes.lines.count }
- subject { described_class.new.perform(gl_repository, key_id, base64_changes) }
+ subject { perform }
context "with valid .gitlab-ci.yml" do
before do
@@ -180,7 +285,13 @@ describe PostReceive do
it 'calls SystemHooksService' do
expect_any_instance_of(SystemHooksService).to receive(:execute_hooks).with(fake_hook_data, :repository_update_hooks).and_return(true)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
+ end
+
+ it 'increments the usage data counter of pushes event' do
+ counter = Gitlab::UsageDataCounters::SourceCodeCounter
+
+ expect { perform }.to change { counter.read(:pushes) }.by(1)
end
end
end
@@ -197,7 +308,7 @@ describe PostReceive do
# a second to ensure we see changes.
Timecop.freeze(1.second.from_now) do
expect do
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
project.reload
end.to change(project, :last_activity_at)
.and change(project, :last_repository_updated_at)
@@ -208,7 +319,8 @@ describe PostReceive do
context "webhook" do
it "fetches the correct project" do
expect(Project).to receive(:find_by).with(id: project.id.to_s)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+
+ perform
end
it "does not run if the author is not in the project" do
@@ -218,16 +330,18 @@ describe PostReceive do
expect(project).not_to receive(:execute_hooks)
- expect(described_class.new.perform(gl_repository, key_id, base64_changes)).to be_falsey
+ expect(perform).to be_falsey
end
it "asks the project to trigger all hooks" do
+ create(:project_hook, push_events: true, tag_push_events: true, project: project)
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
allow(Project).to receive(:find_by).and_return(project)
expect(project).to receive(:execute_hooks).twice
expect(project).to receive(:execute_services).twice
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
it "enqueues a UpdateMergeRequestsWorker job" do
@@ -235,7 +349,7 @@ describe PostReceive do
expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args)
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ perform
end
end
end
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 47bac63511e..eb1d3c364ac 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -3,8 +3,6 @@
require 'spec_helper'
describe ProcessCommitWorker do
- include ProjectForksHelper
-
let(:worker) { described_class.new }
let(:user) { create(:user) }
let(:project) { create(:project, :public, :repository) }
@@ -35,44 +33,6 @@ describe ProcessCommitWorker do
worker.perform(project.id, user.id, commit.to_hash)
end
-
- context 'when the project is forked' do
- context 'when commit already exists in the upstream project' do
- it 'does not process the commit message' do
- forked = fork_project(project, user, repository: true)
-
- expect(worker).not_to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the commit does not exist in the upstream project' do
- it 'processes the commit message' do
- empty_project = create(:project, :public)
- forked = fork_project(empty_project, user, repository: true)
-
- TestEnv.copy_repo(forked,
- bare_repo: TestEnv.factory_repo_path_bare,
- refs: TestEnv::BRANCH_SHA)
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
-
- context 'when the upstream project no longer exists' do
- it 'processes the commit message' do
- forked = fork_project(project, user, repository: true)
- project.destroy!
-
- expect(worker).to receive(:process_commit_message)
-
- worker.perform(forked.id, user.id, forked.commit.to_hash)
- end
- end
- end
end
describe '#process_commit_message' do
diff --git a/spec/workers/project_cache_worker_spec.rb b/spec/workers/project_cache_worker_spec.rb
index edc55920b8e..7f3c4881b89 100644
--- a/spec/workers/project_cache_worker_spec.rb
+++ b/spec/workers/project_cache_worker_spec.rb
@@ -49,6 +49,16 @@ describe ProjectCacheWorker do
worker.perform(project.id, %w(readme))
end
+ context 'with statistics disabled' do
+ let(:statistics) { [] }
+
+ it 'does not update the project statistics' do
+ expect(worker).not_to receive(:update_statistics)
+
+ worker.perform(project.id, [], [], false)
+ end
+ end
+
context 'with statistics' do
let(:statistics) { %w(repository_size) }
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index 4de51ecb3e9..66d517332ba 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -2,99 +2,70 @@
require 'rails_helper'
-describe RepositoryUpdateRemoteMirrorWorker do
+describe RepositoryUpdateRemoteMirrorWorker, :clean_gitlab_redis_shared_state do
subject { described_class.new }
- let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
+ let(:remote_mirror) { create(:remote_mirror) }
let(:scheduled_time) { Time.now - 5.minutes }
around do |example|
Timecop.freeze(Time.now) { example.run }
end
- describe '#perform' do
- context 'with status none' do
- before do
- remote_mirror.update(update_status: 'none')
- end
-
- it 'sets status as finished when update remote mirror service executes successfully' do
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
-
- expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished')
- end
-
- it 'resets the notification flag upon success' do
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
- remote_mirror.update_column(:error_notification_sent, true)
-
- expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false)
- end
-
- it 'sets status as failed when update remote mirror service executes with errors' do
- error_message = 'fail!'
-
- expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
- expect(service).to receive(:execute).with(remote_mirror).and_return(status: :error, message: error_message)
- end
+ def expect_mirror_service_to_return(mirror, result, tries = 0)
+ expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
+ expect(service).to receive(:execute).with(mirror, tries).and_return(result)
+ end
+ end
- # Mock the finder so that it returns an object we can set expectations on
- expect_next_instance_of(RemoteMirrorFinder) do |finder|
- expect(finder).to receive(:execute).and_return(remote_mirror)
- end
- expect(remote_mirror).to receive(:mark_as_failed).with(error_message)
+ describe '#perform' do
+ it 'calls out to the service to perform the update' do
+ expect_mirror_service_to_return(remote_mirror, status: :success)
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError, error_message)
- end
+ subject.perform(remote_mirror.id, scheduled_time)
+ end
- it 'does nothing if last_update_started_at is higher than the time the job was scheduled in' do
- remote_mirror.update(last_update_started_at: Time.now)
+ it 'does not do anything if the mirror was already updated' do
+ remote_mirror.update(last_update_started_at: Time.now, update_status: :finished)
- expect_any_instance_of(RemoteMirror).to receive(:updated_since?).with(scheduled_time).and_return(true)
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).not_to receive(:execute).with(remote_mirror)
+ expect(Projects::UpdateRemoteMirrorService).not_to receive(:new)
- expect(subject.perform(remote_mirror.id, scheduled_time)).to be_nil
- end
+ subject.perform(remote_mirror.id, scheduled_time)
end
- context 'with unexpected error' do
- it 'marks mirror as failed' do
- allow_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_raise(RuntimeError)
+ it 'schedules a retry when the mirror is marked for retrying' do
+ remote_mirror = create(:remote_mirror, update_status: :to_retry)
+ expect_mirror_service_to_return(remote_mirror, status: :error, message: 'Retry!')
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateError)
- expect(remote_mirror.reload.update_status).to eq('failed')
- end
- end
+ expect(described_class)
+ .to receive(:perform_in)
+ .with(remote_mirror.backoff_delay, remote_mirror.id, scheduled_time, 1)
- context 'with another worker already running' do
- before do
- remote_mirror.update(update_status: 'started')
- end
-
- it 'raises RemoteMirrorUpdateAlreadyInProgressError' do
- expect do
- subject.perform(remote_mirror.id, Time.now)
- end.to raise_error(RepositoryUpdateRemoteMirrorWorker::UpdateAlreadyInProgressError)
- end
+ subject.perform(remote_mirror.id, scheduled_time)
end
- context 'with status failed' do
- before do
- remote_mirror.update(update_status: 'failed')
+ it 'clears the lease if there was an unexpected exception' do
+ expect_next_instance_of(Projects::UpdateRemoteMirrorService) do |service|
+ expect(service).to receive(:execute).with(remote_mirror, 1).and_raise('Unexpected!')
end
+ expect { subject.perform(remote_mirror.id, Time.now, 1) }.to raise_error('Unexpected!')
- it 'sets status as finished if last_update_started_at is higher than the time the job was scheduled in' do
- remote_mirror.update(last_update_started_at: Time.now)
+ lease = Gitlab::ExclusiveLease.new("#{described_class.name}:#{remote_mirror.id}", timeout: 1.second)
- expect_any_instance_of(RemoteMirror).to receive(:updated_since?).with(scheduled_time).and_return(false)
- expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
+ expect(lease.try_obtain).not_to be_nil
+ end
- expect { subject.perform(remote_mirror.id, scheduled_time) }.to change { remote_mirror.reload.update_status }.to('finished')
- end
+ it 'retries 3 times for the worker to finish before rescheduling' do
+ expect(subject).to receive(:in_lock)
+ .with("#{described_class.name}:#{remote_mirror.id}",
+ retries: 3,
+ ttl: remote_mirror.max_runtime,
+ sleep_sec: described_class::LOCK_WAIT_TIME)
+ .and_raise(Gitlab::ExclusiveLeaseHelpers::FailedToObtainLockError)
+ expect(described_class).to receive(:perform_in)
+ .with(remote_mirror.backoff_delay, remote_mirror.id, scheduled_time, 0)
+
+ subject.perform(remote_mirror.id, scheduled_time)
end
end
end
diff --git a/spec/workers/stuck_ci_jobs_worker_spec.rb b/spec/workers/stuck_ci_jobs_worker_spec.rb
index 72de62f1188..c3d577e2dae 100644
--- a/spec/workers/stuck_ci_jobs_worker_spec.rb
+++ b/spec/workers/stuck_ci_jobs_worker_spec.rb
@@ -7,8 +7,6 @@ describe StuckCiJobsWorker do
let!(:runner) { create :ci_runner }
let!(:job) { create :ci_build, runner: runner }
- let(:trace_lease_key) { "trace:write:lock:#{job.id}" }
- let(:trace_lease_uuid) { SecureRandom.uuid }
let(:worker_lease_key) { StuckCiJobsWorker::EXCLUSIVE_LEASE_KEY }
let(:worker_lease_uuid) { SecureRandom.uuid }
@@ -16,7 +14,6 @@ describe StuckCiJobsWorker do
before do
stub_exclusive_lease(worker_lease_key, worker_lease_uuid)
- stub_exclusive_lease(trace_lease_key, trace_lease_uuid)
job.update!(status: status, updated_at: updated_at)
end
@@ -195,7 +192,6 @@ describe StuckCiJobsWorker do
end
it 'cancels exclusive leases after worker perform' do
- expect_to_cancel_exclusive_lease(trace_lease_key, trace_lease_uuid)
expect_to_cancel_exclusive_lease(worker_lease_key, worker_lease_uuid)
worker.perform
diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml
index 2aadd3dbe1e..852c6c3f2b0 100644
--- a/vendor/jupyter/values.yaml
+++ b/vendor/jupyter/values.yaml
@@ -57,3 +57,7 @@ ingress:
annotations:
kubernetes.io/ingress.class: "nginx"
kubernetes.io/tls-acme: "true"
+
+proxy:
+ service:
+ type: ClusterIP
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index 0c52cb5a947..0c445aeac88 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -816,8 +816,6 @@ pbkdf2,3.0.14,MIT
peek,1.0.1,MIT
peek-gc,0.0.2,MIT
peek-mysql2,1.1.0,MIT
-peek-pg,1.3.0,MIT
-peek-rblineprof,0.2.0,MIT
peek-redis,1.2.0,MIT
pg,0.18.4,"BSD,ruby,GPL"
pify,3.0.0,MIT
diff --git a/vendor/project_templates/rails.tar.gz b/vendor/project_templates/rails.tar.gz
index c1f90c3f8f7..6bb51bcd7a1 100644
--- a/vendor/project_templates/rails.tar.gz
+++ b/vendor/project_templates/rails.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index a0fbdfd4541..525c77e41d2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,41 +2,41 @@
# yarn lockfile v1
-"@babel/code-frame@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8"
- integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d"
+ integrity sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==
dependencies:
"@babel/highlight" "^7.0.0"
-"@babel/core@>=7.1.0", "@babel/core@^7.1.0", "@babel/core@^7.4.4":
- version "7.4.5"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.4.5.tgz#081f97e8ffca65a9b4b0fdc7e274e703f000c06a"
- integrity sha512-OvjIh6aqXtlsA8ujtGKfC7LYWksYSX8yQcM8Ay3LuvVeQ63lcOKgoZWVqcpFwkd29aYU9rVx7jxhfhiEDV9MZA==
+"@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==
dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.4.4"
- "@babel/helpers" "^7.4.4"
- "@babel/parser" "^7.4.5"
+ "@babel/code-frame" "^7.5.5"
+ "@babel/generator" "^7.5.5"
+ "@babel/helpers" "^7.5.5"
+ "@babel/parser" "^7.5.5"
"@babel/template" "^7.4.4"
- "@babel/traverse" "^7.4.5"
- "@babel/types" "^7.4.4"
+ "@babel/traverse" "^7.5.5"
+ "@babel/types" "^7.5.5"
convert-source-map "^1.1.0"
debug "^4.1.0"
json5 "^2.1.0"
- lodash "^4.17.11"
+ lodash "^4.17.13"
resolve "^1.3.2"
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.4.0", "@babel/generator@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.4.4.tgz#174a215eb843fc392c7edcaabeaa873de6e8f041"
- integrity sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==
+"@babel/generator@^7.1.3", "@babel/generator@^7.4.0", "@babel/generator@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.5.5.tgz#873a7f936a3c89491b43536d12245b626664e3cf"
+ integrity sha512-ETI/4vyTSxTzGnU2c49XHv2zhExkv9JHLTwDAFz85kmcwuShvYG2H08FwgIguQf4JC75CBnXAUM5PqeF4fj0nQ==
dependencies:
- "@babel/types" "^7.4.4"
+ "@babel/types" "^7.5.5"
jsesc "^2.5.1"
- lodash "^4.17.11"
+ lodash "^4.17.13"
source-map "^0.5.0"
trim-right "^1.0.1"
@@ -55,6 +55,14 @@
"@babel/helper-explode-assignable-expression" "^7.1.0"
"@babel/types" "^7.0.0"
+"@babel/helper-builder-react-jsx@^7.3.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-react-jsx/-/helper-builder-react-jsx-7.3.0.tgz#a1ac95a5d2b3e88ae5e54846bf462eeb81b318a4"
+ integrity sha512-MjA9KgwCuPEkQd9ncSXvSyJ5y+j2sICHyrI0M3L+6fnS4wMSNDc1ARXsbTfbb2cXHn17VisSnU/sHFTCxVxSMw==
+ dependencies:
+ "@babel/types" "^7.3.0"
+ esutils "^2.0.0"
+
"@babel/helper-call-delegate@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz#87c1f8ca19ad552a736a7a27b1c1fcf8b1ff1f43"
@@ -64,26 +72,26 @@
"@babel/traverse" "^7.4.4"
"@babel/types" "^7.4.4"
-"@babel/helper-create-class-features-plugin@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.4.4.tgz#fc3d690af6554cc9efc607364a82d48f58736dba"
- integrity sha512-UbBHIa2qeAGgyiNR9RszVF7bUHEdgS4JAUNT8SiqrAN6YJVxlOxeLr5pBzb5kan302dejJ9nla4RyKcR1XT6XA==
+"@babel/helper-create-class-features-plugin@^7.4.4", "@babel/helper-create-class-features-plugin@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.5.5.tgz#401f302c8ddbc0edd36f7c6b2887d8fa1122e5a4"
+ integrity sha512-ZsxkyYiRA7Bg+ZTRpPvB6AbOFKTFFK4LrvTet8lInm0V468MWCaSYJE+I7v2z2r8KNLtYiV+K5kTCnR7dvyZjg==
dependencies:
"@babel/helper-function-name" "^7.1.0"
- "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-member-expression-to-functions" "^7.5.5"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.4.4"
+ "@babel/helper-replace-supers" "^7.5.5"
"@babel/helper-split-export-declaration" "^7.4.4"
-"@babel/helper-define-map@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.4.4.tgz#6969d1f570b46bdc900d1eba8e5d59c48ba2c12a"
- integrity sha512-IX3Ln8gLhZpSuqHJSnTNBWGDE9kdkTEWl21A/K7PQ00tseBwbqCHTvNLHSBd9M0R5rER4h5Rsvj9vw0R5SieBg==
+"@babel/helper-define-map@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz#3dec32c2046f37e09b28c93eb0b103fd2a25d369"
+ integrity sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==
dependencies:
"@babel/helper-function-name" "^7.1.0"
- "@babel/types" "^7.4.4"
- lodash "^4.17.11"
+ "@babel/types" "^7.5.5"
+ lodash "^4.17.13"
"@babel/helper-explode-assignable-expression@^7.1.0":
version "7.1.0"
@@ -116,12 +124,12 @@
dependencies:
"@babel/types" "^7.4.4"
-"@babel/helper-member-expression-to-functions@^7.0.0":
- version "7.0.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f"
- integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg==
+"@babel/helper-member-expression-to-functions@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz#1fb5b8ec4453a93c439ee9fe3aeea4a84b76b590"
+ integrity sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==
dependencies:
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.5.5"
"@babel/helper-module-imports@^7.0.0":
version "7.0.0"
@@ -131,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"
@@ -155,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"
@@ -172,15 +180,15 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.4.4.tgz#aee41783ebe4f2d3ab3ae775e1cc6f1a90cefa27"
- integrity sha512-04xGEnd+s01nY1l15EuMS1rfKktNF+1CkKmHoErDppjAAZL+IUBZpzT748x262HF7fibaQPhbvWUl5HeSt1EXg==
+"@babel/helper-replace-supers@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz#f84ce43df031222d2bad068d2626cb5799c34bc2"
+ integrity sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==
dependencies:
- "@babel/helper-member-expression-to-functions" "^7.0.0"
+ "@babel/helper-member-expression-to-functions" "^7.5.5"
"@babel/helper-optimise-call-expression" "^7.0.0"
- "@babel/traverse" "^7.4.4"
- "@babel/types" "^7.4.4"
+ "@babel/traverse" "^7.5.5"
+ "@babel/types" "^7.5.5"
"@babel/helper-simple-access@^7.1.0":
version "7.1.0"
@@ -197,38 +205,43 @@
dependencies:
"@babel/types" "^7.4.4"
-"@babel/helper-wrap-function@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.1.0.tgz#8cf54e9190706067f016af8f75cb3df829cc8c66"
- integrity sha512-R6HU3dete+rwsdAfrOzTlE9Mcpk4RjU3aX3gi9grtmugQY0u79X7eogUvfXA5sI81Mfq1cn6AgxihfN33STjJA==
+"@babel/helper-wrap-function@^7.1.0", "@babel/helper-wrap-function@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa"
+ integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==
dependencies:
"@babel/helper-function-name" "^7.1.0"
"@babel/template" "^7.1.0"
"@babel/traverse" "^7.1.0"
- "@babel/types" "^7.0.0"
+ "@babel/types" "^7.2.0"
-"@babel/helpers@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.4.4.tgz#868b0ef59c1dd4e78744562d5ce1b59c89f2f2a5"
- integrity sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==
+"@babel/helpers@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.5.5.tgz#63908d2a73942229d1e6685bc2a0e730dde3b75e"
+ integrity sha512-nRq2BUhxZFnfEn/ciJuhklHvFOqjJUD5wpx+1bxUF2axL9C+v4DE/dmp5sT2dKnpOs4orZWzpAZqlCy8QqE/7g==
dependencies:
"@babel/template" "^7.4.4"
- "@babel/traverse" "^7.4.4"
- "@babel/types" "^7.4.4"
+ "@babel/traverse" "^7.5.5"
+ "@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"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.4.5":
- version "7.4.5"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.4.5.tgz#04af8d5d5a2b044a2a1bffacc1e5e6673544e872"
- integrity sha512-9mUqkL1FF5T7f0WDFfAoDdiMVPWsdD1gZYzSnaXsxUCUqzuch/8of9G3VUSNiZmMBoRxT3neyVsqeiL/ZPcjew==
+"@babel/parser@7.1.3":
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.3.tgz#2c92469bac2b7fbff810b67fca07bd138b48af77"
+ integrity sha512-gqmspPZOMW3MIRb9HlrnbZHXI1/KHTOroBwN1NcLL6pWxzqzEKGvRTq0W/PxS45OtQGbaFikSQpkS5zbnsQm2w==
+
+"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.4.3", "@babel/parser@^7.4.4", "@babel/parser@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.5.5.tgz#02f077ac8817d3df4a832ef59de67565e71cca4b"
+ integrity sha512-E5BN68cqR7dhKan1SfqgPGhQ178bkVKpXTPEXnFJBrEt8/DKRZlybmy+IgYLTeN7tp1R5Ccmbm2rBk17sHYU3g==
"@babel/plugin-proposal-async-generator-functions@^7.2.0":
version "7.2.0"
@@ -239,15 +252,73 @@
"@babel/helper-remap-async-to-generator" "^7.1.0"
"@babel/plugin-syntax-async-generators" "^7.2.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==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.5.5"
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-proposal-decorators@^7.1.2":
version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.4.4.tgz#93a6486eed86d53452ab9bab35e368e9461198ce"
- integrity sha512-WjKTI8g8d5w1Bc9zgwSz2nfrsNQsXcCf9J9cdCvrJV6RF56yztwm4TmJC0MgJ9tvwO9gUA/mcYe89bLdGfiXFg==
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.4.4.tgz#de9b2a1a8ab0196f378e2a82f10b6e2a36f21cc0"
+ integrity sha512-z7MpQz3XC/iQJWXH9y+MaWcLPNSMY9RQSthrLzak8R8hCj0fuyNk+Dzi9kfNe/JxxlWQ2g7wkABbgWjW36MTcw==
dependencies:
"@babel/helper-create-class-features-plugin" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-decorators" "^7.2.0"
+
+"@babel/plugin-proposal-do-expressions@^7.0.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-do-expressions/-/plugin-proposal-do-expressions-7.5.0.tgz#ceb594d4a618545b00aa0b5cd61cad4aaaeb7a5a"
+ integrity sha512-xe0QQrhm+DGj6H23a6XtwkJNimy1fo71O/YVBfrfvfSl0fsq9T9dfoQBIY4QceEIdUo7u9s7OPEdsWEuizfGeg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-do-expressions" "^7.2.0"
+
+"@babel/plugin-proposal-dynamic-import@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz#e532202db4838723691b10a67b8ce509e397c506"
+ integrity sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-dynamic-import" "^7.2.0"
-"@babel/plugin-proposal-json-strings@^7.2.0":
+"@babel/plugin-proposal-export-default-from@^7.0.0":
+ version "7.5.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.5.2.tgz#2c0ac2dcc36e3b2443fead2c3c5fc796fb1b5145"
+ integrity sha512-wr9Itk05L1/wyyZKVEmXWCdcsp/e185WUNl6AfYZeEKYaUPPvHXRDqO5K1VH7/UamYqGJowFRuCv30aDYZawsg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-export-default-from" "^7.2.0"
+
+"@babel/plugin-proposal-export-namespace-from@^7.0.0":
+ version "7.5.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.5.2.tgz#ccd5ed05b06d700688ff1db01a9dd27155e0d2a0"
+ integrity sha512-TKUdOL07anjZEbR1iSxb5WFh810KyObdd29XLFLGo1IDsSuGrjH3ouWSbAxHNmrVKzr9X71UYl2dQ7oGGcRp0g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-export-namespace-from" "^7.2.0"
+
+"@babel/plugin-proposal-function-bind@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-bind/-/plugin-proposal-function-bind-7.2.0.tgz#94dc2cdc505cafc4e225c0014335a01648056bf7"
+ integrity sha512-qOFJ/eX1Is78sywwTxDcsntLOdb5ZlHVVqUz5xznq8ldAfOVIyZzp1JE2rzHnaksZIhrqMrwIpQL/qcEprnVbw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-function-bind" "^7.2.0"
+
+"@babel/plugin-proposal-function-sent@^7.1.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-function-sent/-/plugin-proposal-function-sent-7.5.0.tgz#39233aa801145e7d8072077cdb2d25f781c1ffd7"
+ integrity sha512-JXdfiQpKoC6UgQliZkp3NX7K3MVec1o1nfTWiCCIORE5ag/QZXhL0aSD8/Y2K+hIHonSTxuJF9rh9zsB6hBi2A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/helper-wrap-function" "^7.2.0"
+ "@babel/plugin-syntax-function-sent" "^7.2.0"
+
+"@babel/plugin-proposal-json-strings@^7.0.0", "@babel/plugin-proposal-json-strings@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317"
integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==
@@ -255,10 +326,34 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
-"@babel/plugin-proposal-object-rest-spread@^7.4.4":
+"@babel/plugin-proposal-logical-assignment-operators@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.2.0.tgz#8a5cea6c42a7c87446959e02fff5fad012c56f57"
+ integrity sha512-0w797xwdPXKk0m3Js74hDi0mCTZplIu93MOSfb1ZLd/XFe3abWypx1QknVk0J+ohnsjYpvjH4Gwfo2i3RicB6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.2.0"
+
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.0.0":
version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.4.4.tgz#1ef173fcf24b3e2df92a678f027673b55e7e3005"
- integrity sha512-dMBG6cSPBbHeEBdFXeQ2QLc5gUpg4Vkaz8octD4aoW/ISO+jBOcsuxYL7bsb5WSu8RLP6boxrBIALEHgoHtO9g==
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.4.4.tgz#41c360d59481d88e0ce3a3f837df10121a769b39"
+ integrity sha512-Amph7Epui1Dh/xxUxS2+K22/MUi6+6JVTvy3P58tja3B6yKTSjwwx0/d83rF7551D6PVSSoplQb8GCwqec7HRw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.2.0"
+
+"@babel/plugin-proposal-numeric-separator@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.2.0.tgz#646854daf4cd22fd6733f6076013a936310443ac"
+ integrity sha512-DohMOGDrZiMKS7LthjUZNNcWl8TAf5BZDwZAH4wpm55FuJTHgfqPGdibg7rZDmont/8Yg0zA03IgT6XLeP+4sg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-numeric-separator" "^7.2.0"
+
+"@babel/plugin-proposal-object-rest-spread@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.5.5.tgz#61939744f71ba76a3ae46b5eea18a54c16d22e58"
+ integrity sha512-F2DxJJSQ7f64FyTVl5cw/9MWn6naXGdk3Q3UhDbFEEHv+EilCPoeRD3Zh/Utx1CJz4uyKlQ4uH+bJPbEhMV7Zw==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
@@ -271,6 +366,22 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
+"@babel/plugin-proposal-optional-chaining@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.2.0.tgz#ae454f4c21c6c2ce8cb2397dc332ae8b420c5441"
+ integrity sha512-ea3Q6edZC/55wEBVZAEz42v528VulyO0eir+7uky/sT4XRcdkWJcFi1aPtitTlwUzGnECWJNExWww1SStt+yWw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.2.0"
+
+"@babel/plugin-proposal-pipeline-operator@^7.0.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-pipeline-operator/-/plugin-proposal-pipeline-operator-7.5.0.tgz#4100ec55ef4f6a4c2490b5f5a4f2a22dfa272c06"
+ integrity sha512-HFYuu/yGnkn69ligXxU0ohOVvQDsMNOUJs/c4PYLUVS6ntCYOyGmRQQaSYJARJ9rvc7/ulZKIzxd4wk91hN63A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-pipeline-operator" "^7.5.0"
+
"@babel/plugin-proposal-private-methods@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.4.4.tgz#307b7db29d8ae2d259e7c0e6e665b1922d7ac856"
@@ -279,6 +390,14 @@
"@babel/helper-create-class-features-plugin" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-proposal-throw-expressions@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-throw-expressions/-/plugin-proposal-throw-expressions-7.2.0.tgz#2d9e452d370f139000e51db65d0a85dc60c64739"
+ integrity sha512-adsydM8DQF4i5DLNO4ySAU5VtHTPewOtNBV3u7F4lNMPADFF9bWQ+iDtUUe8+033cYCUz+bFlQdXQJmJOwoLpw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-throw-expressions" "^7.2.0"
+
"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.4.4.tgz#501ffd9826c0b91da22690720722ac7cb1ca9c78"
@@ -295,14 +414,63 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-dynamic-import@^7.2.0":
+"@babel/plugin-syntax-decorators@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.2.0.tgz#c50b1b957dcc69e4b1127b65e1c33eef61570c1b"
+ integrity sha512-38QdqVoXdHUQfTpZo3rQwqQdWtCn5tMv4uV6r2RMfTqNBuv4ZBhz79SfaQWKTVmxHjeFv/DnXVC/+agHCklYWA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-do-expressions@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-do-expressions/-/plugin-syntax-do-expressions-7.2.0.tgz#f3d4b01be05ecde2892086d7cfd5f1fa1ead5a2a"
+ integrity sha512-/u4rJ+XEmZkIhspVuKRS+7WLvm7Dky9j9TvGK5IgId8B3FKir9MG+nQxDZ9xLn10QMBvW58dZ6ABe2juSmARjg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-dynamic-import@^7.0.0", "@babel/plugin-syntax-dynamic-import@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz#69c159ffaf4998122161ad8ebc5e6d1f55df8612"
integrity sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-syntax-import-meta@^7.2.0":
+"@babel/plugin-syntax-export-default-from@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.2.0.tgz#edd83b7adc2e0d059e2467ca96c650ab6d2f3820"
+ integrity sha512-c7nqUnNST97BWPtoe+Ssi+fJukc9P9/JMZ71IOMNQWza2E+Psrd46N6AEvtw6pqK+gt7ChjXyrw4SPDO79f3Lw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-export-namespace-from@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.2.0.tgz#8d257838c6b3b779db52c0224443459bd27fb039"
+ integrity sha512-1zGA3UNch6A+A11nIzBVEaE3DDJbjfB+eLIcf0GGOh/BJr/8NxL3546MGhV/r0RhH4xADFIEso39TKCfEMlsGA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-flow@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.2.0.tgz#a765f061f803bc48f240c26f8747faf97c26bf7c"
+ integrity sha512-r6YMuZDWLtLlu0kqIim5o/3TNRAlWb073HwT3e2nKf9I8IIvOggPrnILYPsrrKilmn/mYEMCf/Z07w3yQJF6dg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-function-bind@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-function-bind/-/plugin-syntax-function-bind-7.2.0.tgz#68fe85b0c0da67125f87bf239c68051b06c66309"
+ integrity sha512-/WzU1lLU2l0wDfB42Wkg6tahrmtBbiD8C4H6EGSX0M4GAjzN6JiOpq/Uh8G6GSoR6lPMvhjM0MNiV6znj6y/zg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-function-sent@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-function-sent/-/plugin-syntax-function-sent-7.2.0.tgz#91474d4d400604e4c6cbd4d77cd6cb3b8565576c"
+ integrity sha512-2MOVuJ6IMAifp2cf0RFkHQaOvHpbBYyWCvgtF/WVqXhTd7Bgtov8iXVCadLXp2FN1BrI2EFl+JXuwXy0qr3KoQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-import-meta@^7.0.0", "@babel/plugin-syntax-import-meta@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.2.0.tgz#2333ef4b875553a3bcd1e93f8ebc09f5b9213a40"
integrity sha512-Hq6kFSZD7+PHkmBN8bCpHR6J8QEoCuEV/B38AIQscYjgMZkGlXB7cHNFzP5jR4RCh5545yP1ujHdmO7hAgKtBA==
@@ -316,6 +484,34 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-syntax-jsx@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.2.0.tgz#0b85a3b4bc7cdf4cc4b8bf236335b907ca22e7c7"
+ integrity sha512-VyN4QANJkRW6lDBmENzRszvZf3/4AXaj9YR7GwrWeeN9tEBPuXbmDYVU9bYBN0D70zCWVwUy0HWq2553VCb6Hw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.2.0.tgz#fcab7388530e96c6f277ce494c55caa6c141fcfb"
+ integrity sha512-l/NKSlrnvd73/EL540t9hZhcSo4TULBrIPs9Palju8Oc/A8DXDO+xQf04whfeuZLpi8AuIvCAdpKmmubLN4EfQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.2.0.tgz#f75083dfd5ade73e783db729bbd87e7b9efb7624"
+ integrity sha512-lRCEaKE+LTxDQtgbYajI04ddt6WW0WJq57xqkAZ+s11h4YgfRHhVA/Y2VhfPzzFD4qeLHWg32DMp9HooY4Kqlg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.2.0.tgz#7470fe070c2944469a756752a69a6963135018be"
+ integrity sha512-DroeVNkO/BnGpL2R7+ZNZqW+E24aR/4YWxP3Qb15d6lPU8KDzF8HlIUIRCOJRn4X77/oyW4mJY+7FHfY82NLtQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-syntax-object-rest-spread@^7.0.0", "@babel/plugin-syntax-object-rest-spread@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e"
@@ -330,6 +526,27 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-syntax-optional-chaining@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.2.0.tgz#a59d6ae8c167e7608eaa443fda9fa8fa6bf21dff"
+ integrity sha512-HtGCtvp5Uq/jH/WNUPkK6b7rufnCPLLlDAFN7cmACoIjaOOiXxUt3SswU5loHqrhtqTsa/WoLQ1OQ1AGuZqaWA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-pipeline-operator@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-pipeline-operator/-/plugin-syntax-pipeline-operator-7.5.0.tgz#8ea7c2c22847c797748bf07752722a317079dc1e"
+ integrity sha512-5FVxPiMTMXWk4R7Kq9pt272nDu8VImJdaIzvXFSTcXFbgKWWaOdbic12TvUvl6cK+AE5EgnhwvxuWik4ZYYdzg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-syntax-throw-expressions@^7.2.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-throw-expressions/-/plugin-syntax-throw-expressions-7.2.0.tgz#79001ee2afe1b174b1733cdc2fc69c9a46a0f1f8"
+ integrity sha512-ngwynuqu1Rx0JUS9zxSDuPgW1K8TyVZCi2hHehrL4vyjqE7RGoNHWlZsS7KQT2vw9Yjk4YLa0+KldBXTRdPLRg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+
"@babel/plugin-transform-arrow-functions@^7.2.0":
version "7.2.0"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550"
@@ -337,10 +554,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-async-to-generator@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.4.4.tgz#a3f1d01f2f21cadab20b33a82133116f14fb5894"
- integrity sha512-YiqW2Li8TXmzgbXw+STsSqPBPFnGviiaSp6CYOq55X8GQ2SGVLrXB6pNid8HkqkZAzOH6knbai3snhP7v0fNwA==
+"@babel/plugin-transform-async-to-generator@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz#89a3848a0166623b5bc481164b5936ab947e887e"
+ integrity sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
@@ -353,25 +570,25 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-block-scoping@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.4.4.tgz#c13279fabf6b916661531841a23c4b7dae29646d"
- integrity sha512-jkTUyWZcTrwxu5DD4rWz6rDB5Cjdmgz6z7M7RLXOJyCUkFBawssDGcGh8M/0FTSB87avyJI1HsTwUXp9nKA1PA==
+"@babel/plugin-transform-block-scoping@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.5.5.tgz#a35f395e5402822f10d2119f6f8e045e3639a2ce"
+ integrity sha512-82A3CLRRdYubkG85lKwhZB0WZoHxLGsJdux/cOVaJCJpvYFl1LVzAIFyRsa7CvXqW8rBM4Zf3Bfn8PHt5DP0Sg==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- lodash "^4.17.11"
+ lodash "^4.17.13"
-"@babel/plugin-transform-classes@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.4.4.tgz#0ce4094cdafd709721076d3b9c38ad31ca715eb6"
- integrity sha512-/e44eFLImEGIpL9qPxSRat13I5QNRgBLu2hOQJCF7VLy/otSM/sypV1+XaIw5+502RX/+6YaSAPmldk+nhHDPw==
+"@babel/plugin-transform-classes@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz#d094299d9bd680a14a2a0edae38305ad60fb4de9"
+ integrity sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.0.0"
- "@babel/helper-define-map" "^7.4.4"
+ "@babel/helper-define-map" "^7.5.5"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-optimise-call-expression" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.4.4"
+ "@babel/helper-replace-supers" "^7.5.5"
"@babel/helper-split-export-declaration" "^7.4.4"
globals "^11.1.0"
@@ -382,10 +599,10 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-destructuring@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.4.4.tgz#9d964717829cc9e4b601fc82a26a71a4d8faf20f"
- integrity sha512-/aOx+nW0w8eHiEHm+BTERB2oJn5D127iye/SUQl7NjHy0lf+j7h4MKMMSOwdazGq9OxgiNADncE+SRJkCxjZpQ==
+"@babel/plugin-transform-destructuring@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.5.0.tgz#f6c09fdfe3f94516ff074fe877db7bc9ef05855a"
+ integrity sha512-YbYgbd3TryYYLGyC7ZR+Tq8H/+bCmwoaxHfJHupom5ECstzbRLTch6gOQbhEY9Z4hiCNHEURgq06ykFv9JZ/QQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -398,10 +615,10 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.5.4"
-"@babel/plugin-transform-duplicate-keys@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3"
- integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw==
+"@babel/plugin-transform-duplicate-keys@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz#c5dbf5106bf84cdf691222c0974c12b1df931853"
+ integrity sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
@@ -413,6 +630,14 @@
"@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
+"@babel/plugin-transform-flow-strip-types@^7.0.0":
+ version "7.4.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.4.4.tgz#d267a081f49a8705fc9146de0768c6b58dccd8f7"
+ integrity sha512-WyVedfeEIILYEaWGAUWzVNyqG4sfsNooMhXWsu/YzOvVGcsnPb5PguysjJqI3t3qiaYj0BR8T2f5njdjTGe44Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-flow" "^7.2.0"
+
"@babel/plugin-transform-for-of@^7.4.4":
version "7.4.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz#0267fc735e24c808ba173866c6c4d1440fc3c556"
@@ -442,30 +667,33 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-modules-amd@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6"
- integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw==
+"@babel/plugin-transform-modules-amd@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz#ef00435d46da0a5961aa728a1d2ecff063e4fb91"
+ integrity sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==
dependencies:
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
+ babel-plugin-dynamic-import-node "^2.3.0"
-"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.4.4.tgz#0bef4713d30f1d78c2e59b3d6db40e60192cac1e"
- integrity sha512-4sfBOJt58sEo9a2BQXnZq+Q3ZTSAUXyK3E30o36BOGnJ+tvJ6YSxF0PG6kERvbeISgProodWuI9UVG3/FMY6iw==
+"@babel/plugin-transform-modules-commonjs@^7.2.0", "@babel/plugin-transform-modules-commonjs@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.5.0.tgz#425127e6045231360858eeaa47a71d75eded7a74"
+ integrity sha512-xmHq0B+ytyrWJvQTc5OWAC4ii6Dhr0s22STOoydokG51JjWhyYo5mRPXoi+ZmtHQhZZwuXNN+GG5jy5UZZJxIQ==
dependencies:
"@babel/helper-module-transforms" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/helper-simple-access" "^7.1.0"
+ babel-plugin-dynamic-import-node "^2.3.0"
-"@babel/plugin-transform-modules-systemjs@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.4.4.tgz#dc83c5665b07d6c2a7b224c00ac63659ea36a405"
- integrity sha512-MSiModfILQc3/oqnG7NrP1jHaSPryO6tA2kOMmAQApz5dayPxWiHqmq4sWH2xF5LcQK56LlbKByCd8Aah/OIkQ==
+"@babel/plugin-transform-modules-systemjs@^7.5.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz#e75266a13ef94202db2a0620977756f51d52d249"
+ integrity sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==
dependencies:
"@babel/helper-hoist-variables" "^7.4.4"
"@babel/helper-plugin-utils" "^7.0.0"
+ babel-plugin-dynamic-import-node "^2.3.0"
"@babel/plugin-transform-modules-umd@^7.2.0":
version "7.2.0"
@@ -475,12 +703,12 @@
"@babel/helper-module-transforms" "^7.1.0"
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.4.tgz#5611d96d987dfc4a3a81c4383bb173361037d68d"
- integrity sha512-Ki+Y9nXBlKfhD+LXaRS7v95TtTGYRAf9Y1rTDiE75zf8YQz4GDaWRXosMfJBXxnk88mGFjWdCRIeqDbon7spYA==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.4.5.tgz#9d269fd28a370258199b4294736813a60bbdd106"
+ integrity sha512-z7+2IsWafTBbjNsOxU/Iv5CvTJlr5w4+HGu1HovKYTtgJ362f7kBcQglkfmlspKKZ3bgrbSGvLfNx++ZJgCWsg==
dependencies:
- regexp-tree "^0.1.0"
+ regexp-tree "^0.1.6"
"@babel/plugin-transform-new-target@^7.4.4":
version "7.4.4"
@@ -489,13 +717,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-object-super@^7.2.0":
- version "7.2.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598"
- integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg==
+"@babel/plugin-transform-object-super@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz#c70021df834073c65eb613b8679cc4a381d1a9f9"
+ integrity sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
- "@babel/helper-replace-supers" "^7.1.0"
+ "@babel/helper-replace-supers" "^7.5.5"
"@babel/plugin-transform-parameters@^7.4.4":
version "7.4.4"
@@ -513,12 +741,44 @@
dependencies:
"@babel/helper-plugin-utils" "^7.0.0"
-"@babel/plugin-transform-regenerator@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.4.tgz#5b4da4df79391895fca9e28f99e87e22cfc02072"
- integrity sha512-Zz3w+pX1SI0KMIiqshFZkwnVGUhDZzpX2vtPzfJBKQQq8WsP/Xy9DNdELWivxcKOCX/Pywge4SiEaPaLtoDT4g==
+"@babel/plugin-transform-react-display-name@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.2.0.tgz#ebfaed87834ce8dc4279609a4f0c324c156e3eb0"
+ integrity sha512-Htf/tPa5haZvRMiNSQSFifK12gtr/8vwfr+A9y69uF0QcU77AVu4K7MiHEkTxF7lQoHOL0F9ErqgfNEAKgXj7A==
dependencies:
- regenerator-transform "^0.13.4"
+ "@babel/helper-plugin-utils" "^7.0.0"
+
+"@babel/plugin-transform-react-jsx-self@^7.0.0":
+ version "7.2.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.2.0.tgz#461e21ad9478f1031dd5e276108d027f1b5240ba"
+ integrity sha512-v6S5L/myicZEy+jr6ielB0OR8h+EH/1QFx/YJ7c7Ua+7lqsjj/vW6fD5FR9hB/6y7mGbfT4vAURn3xqBxsUcdg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-jsx" "^7.2.0"
+
+"@babel/plugin-transform-react-jsx-source@^7.0.0":
+ version "7.5.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.5.0.tgz#583b10c49cf057e237085bcbd8cc960bd83bd96b"
+ integrity sha512-58Q+Jsy4IDCZx7kqEZuSDdam/1oW8OdDX8f+Loo6xyxdfg1yF0GE2XNJQSTZCaMol93+FBzpWiPEwtbMloAcPg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-jsx" "^7.2.0"
+
+"@babel/plugin-transform-react-jsx@^7.0.0":
+ version "7.3.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.3.0.tgz#f2cab99026631c767e2745a5368b331cfe8f5290"
+ integrity sha512-a/+aRb7R06WcKvQLOu4/TpjKOdvVEKRLWFpKcNuHhiREPgGRB4TQJxq07+EZLS8LFVYpfq1a5lDUnuMdcCpBKg==
+ dependencies:
+ "@babel/helper-builder-react-jsx" "^7.3.0"
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-syntax-jsx" "^7.2.0"
+
+"@babel/plugin-transform-regenerator@^7.4.5":
+ version "7.4.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz#629dc82512c55cee01341fb27bdfcb210354680f"
+ integrity sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==
+ dependencies:
+ regenerator-transform "^0.14.0"
"@babel/plugin-transform-reserved-words@^7.2.0":
version "7.2.0"
@@ -573,46 +833,48 @@
"@babel/helper-regex" "^7.4.4"
regexpu-core "^4.5.4"
-"@babel/preset-env@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.4.4.tgz#b6f6825bfb27b3e1394ca3de4f926482722c1d6f"
- integrity sha512-FU1H+ACWqZZqfw1x2G1tgtSSYSfxJLkpaUQL37CenULFARDo+h4xJoVHzRoHbK+85ViLciuI7ME4WTIhFRBBlw==
+"@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==
dependencies:
"@babel/helper-module-imports" "^7.0.0"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-proposal-async-generator-functions" "^7.2.0"
+ "@babel/plugin-proposal-dynamic-import" "^7.5.0"
"@babel/plugin-proposal-json-strings" "^7.2.0"
- "@babel/plugin-proposal-object-rest-spread" "^7.4.4"
+ "@babel/plugin-proposal-object-rest-spread" "^7.5.5"
"@babel/plugin-proposal-optional-catch-binding" "^7.2.0"
"@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
"@babel/plugin-syntax-async-generators" "^7.2.0"
+ "@babel/plugin-syntax-dynamic-import" "^7.2.0"
"@babel/plugin-syntax-json-strings" "^7.2.0"
"@babel/plugin-syntax-object-rest-spread" "^7.2.0"
"@babel/plugin-syntax-optional-catch-binding" "^7.2.0"
"@babel/plugin-transform-arrow-functions" "^7.2.0"
- "@babel/plugin-transform-async-to-generator" "^7.4.4"
+ "@babel/plugin-transform-async-to-generator" "^7.5.0"
"@babel/plugin-transform-block-scoped-functions" "^7.2.0"
- "@babel/plugin-transform-block-scoping" "^7.4.4"
- "@babel/plugin-transform-classes" "^7.4.4"
+ "@babel/plugin-transform-block-scoping" "^7.5.5"
+ "@babel/plugin-transform-classes" "^7.5.5"
"@babel/plugin-transform-computed-properties" "^7.2.0"
- "@babel/plugin-transform-destructuring" "^7.4.4"
+ "@babel/plugin-transform-destructuring" "^7.5.0"
"@babel/plugin-transform-dotall-regex" "^7.4.4"
- "@babel/plugin-transform-duplicate-keys" "^7.2.0"
+ "@babel/plugin-transform-duplicate-keys" "^7.5.0"
"@babel/plugin-transform-exponentiation-operator" "^7.2.0"
"@babel/plugin-transform-for-of" "^7.4.4"
"@babel/plugin-transform-function-name" "^7.4.4"
"@babel/plugin-transform-literals" "^7.2.0"
"@babel/plugin-transform-member-expression-literals" "^7.2.0"
- "@babel/plugin-transform-modules-amd" "^7.2.0"
- "@babel/plugin-transform-modules-commonjs" "^7.4.4"
- "@babel/plugin-transform-modules-systemjs" "^7.4.4"
+ "@babel/plugin-transform-modules-amd" "^7.5.0"
+ "@babel/plugin-transform-modules-commonjs" "^7.5.0"
+ "@babel/plugin-transform-modules-systemjs" "^7.5.0"
"@babel/plugin-transform-modules-umd" "^7.2.0"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.4"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.4.5"
"@babel/plugin-transform-new-target" "^7.4.4"
- "@babel/plugin-transform-object-super" "^7.2.0"
+ "@babel/plugin-transform-object-super" "^7.5.5"
"@babel/plugin-transform-parameters" "^7.4.4"
"@babel/plugin-transform-property-literals" "^7.2.0"
- "@babel/plugin-transform-regenerator" "^7.4.4"
+ "@babel/plugin-transform-regenerator" "^7.4.5"
"@babel/plugin-transform-reserved-words" "^7.2.0"
"@babel/plugin-transform-shorthand-properties" "^7.2.0"
"@babel/plugin-transform-spread" "^7.2.0"
@@ -620,17 +882,41 @@
"@babel/plugin-transform-template-literals" "^7.4.4"
"@babel/plugin-transform-typeof-symbol" "^7.2.0"
"@babel/plugin-transform-unicode-regex" "^7.4.4"
- "@babel/types" "^7.4.4"
- browserslist "^4.5.2"
- core-js-compat "^3.0.0"
+ "@babel/types" "^7.5.5"
+ browserslist "^4.6.0"
+ core-js-compat "^3.1.1"
invariant "^2.2.2"
js-levenshtein "^1.1.3"
semver "^5.5.0"
+"@babel/preset-flow@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-flow/-/preset-flow-7.0.0.tgz#afd764835d9535ec63d8c7d4caf1c06457263da2"
+ integrity sha512-bJOHrYOPqJZCkPVbG1Lot2r5OSsB+iUOaxiHdlOeB1yPWS6evswVHwvkDLZ54WTaTRIk89ds0iHmGZSnxlPejQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-transform-flow-strip-types" "^7.0.0"
+
+"@babel/preset-react@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.0.0.tgz#e86b4b3d99433c7b3e9e91747e2653958bc6b3c0"
+ integrity sha512-oayxyPS4Zj+hF6Et11BwuBkmpgT/zMxyuZgFrMeZID6Hdh3dGlk4sHCAhdBCpuCKW2ppBfl2uCCetlrUIJRY3w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-transform-react-display-name" "^7.0.0"
+ "@babel/plugin-transform-react-jsx" "^7.0.0"
+ "@babel/plugin-transform-react-jsx-self" "^7.0.0"
+ "@babel/plugin-transform-react-jsx-source" "^7.0.0"
+
+"@babel/preset-stage-0@^7.0.0":
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/@babel/preset-stage-0/-/preset-stage-0-7.0.0.tgz#999aaec79ee8f0a763042c68c06539c97c6e0646"
+ 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"
@@ -641,30 +927,35 @@
"@babel/parser" "^7.4.4"
"@babel/types" "^7.4.4"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.4.5":
- version "7.4.5"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.4.5.tgz#4e92d1728fd2f1897dafdd321efbff92156c3216"
- integrity sha512-Vc+qjynwkjRmIFGxy0KYoPj4FdVDxLej89kMHFsWScq999uX+pwcX4v9mWRjW0KcAYTPAuVQl2LKP1wEVLsp+A==
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.4", "@babel/traverse@^7.4.3", "@babel/traverse@^7.4.4", "@babel/traverse@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.5.5.tgz#f664f8f368ed32988cd648da9f72d5ca70f165bb"
+ integrity sha512-MqB0782whsfffYfSjH4TM+LMjrJnhCNEDMDIjeTpl+ASaUvxcjoiVCo/sM1GhS1pHOXYfWVCYneLjMckuUxDaQ==
dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.4.4"
+ "@babel/code-frame" "^7.5.5"
+ "@babel/generator" "^7.5.5"
"@babel/helper-function-name" "^7.1.0"
"@babel/helper-split-export-declaration" "^7.4.4"
- "@babel/parser" "^7.4.5"
- "@babel/types" "^7.4.4"
+ "@babel/parser" "^7.5.5"
+ "@babel/types" "^7.5.5"
debug "^4.1.0"
globals "^11.1.0"
- lodash "^4.17.11"
+ lodash "^4.17.13"
-"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4":
- version "7.4.4"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.4.4.tgz#8db9e9a629bb7c29370009b4b779ed93fe57d5f0"
- integrity sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==
+"@babel/types@^7.0.0", "@babel/types@^7.1.3", "@babel/types@^7.2.0", "@babel/types@^7.3.0", "@babel/types@^7.4.0", "@babel/types@^7.4.4", "@babel/types@^7.5.5":
+ version "7.5.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.5.5.tgz#97b9f728e182785909aa4ab56264f090a028d18a"
+ integrity sha512-s63F9nJioLqOlW3UkyMd+BYhXt44YuaFm/VV0VwuteqjYwRrObkU7ra9pY4wAJR3oXi8hJrMcrcJdO/HH33vtw==
dependencies:
esutils "^2.0.2"
- lodash "^4.17.11"
+ lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@braintree/sanitize-url@^3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@braintree/sanitize-url/-/sanitize-url-3.1.0.tgz#8ff71d51053cd5ee4981e5a501d80a536244f7fd"
+ integrity sha512-GcIY79elgB+azP74j8vqkiXz8xLFfIzbQJdlwOPisgbKT00tviJQuEghOXSMVxJ00HoYJbGswr4kcllUc4xCcg==
+
"@cnakazawa/watch@^1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.3.tgz#099139eaec7ebf07a27c1786a3ff64f39464d2ef"
@@ -700,26 +991,32 @@
dependencies:
requireindex "~1.1.0"
-"@gitlab/svgs@^1.67.0":
- version "1.67.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.67.0.tgz#c7b94eca13b99fd3aaa737fb6dcc0abc41d3c579"
- integrity sha512-hJOmWEs6RkjzyKkb1vc9wwKGZIBIP0coHkxu/KgOoxhBVudpGk4CH7xJ6UuB2TKpb0SEh5CC1CzRZfBYaFhsaA==
+"@gitlab/eslint-plugin-vue-i18n@^1.2.0":
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/eslint-plugin-vue-i18n/-/eslint-plugin-vue-i18n-1.2.0.tgz#6dcd8bf6bdd7a31c1c4c2c4114762508af435836"
+ integrity sha512-lROk643/C3tBORIJeXNQlPutjAdSkjA43rpNHVxugU5OcpIOfc7oVXDZ23GwiHMZp8bE3QpP/91PEvEY9ywAVQ==
+ dependencies:
+ vue-eslint-parser "^6.0.4"
-"@gitlab/ui@^5.6.0":
- version "5.6.0"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-5.6.0.tgz#6b5408050229e2135359f3fce5d0de7718326a0d"
- integrity sha512-EohpACc5OCK8pOWgwB7/QZUcg3tA9k863ku6Ik9NxaRSKt/JIpQ8RI4wCr4UmqhejZLQMD9VZHLUmc9Sf3Mk9w==
+"@gitlab/svgs@^1.68.0":
+ version "1.68.0"
+ 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==
dependencies:
"@babel/standalone" "^7.0.0"
"@gitlab/vue-toasted" "^1.2.1"
bootstrap "4.3.1"
- bootstrap-vue "^2.0.0-rc.24"
+ bootstrap-vue "2.0.0-rc.27"
copy-to-clipboard "^3.0.8"
- core-js "^2.6.9"
- echarts "^4.2.0-rc.2"
+ echarts "^4.2.1"
highlight.js "^9.13.1"
js-beautify "^1.8.8"
- lodash "^4.17.11"
+ lodash "^4.17.14"
url-search-params-polyfill "^5.0.0"
vue "^2.6.10"
vue-loader "^15.4.2"
@@ -945,7 +1242,7 @@
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86"
integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==
-"@types/glob@5 - 7":
+"@types/glob@5 - 7", "@types/glob@^7.1.1":
version "7.1.1"
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575"
integrity sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==
@@ -1016,10 +1313,10 @@
dependencies:
source-map "^0.6.1"
-"@types/unist@*", "@types/unist@^2.0.0":
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.2.tgz#5dc0a7f76809b7518c0df58689cd16a19bd751c6"
- integrity sha512-iHI60IbyfQilNubmxsq4zqSjdynlmc2Q/QvH9kjzg9+CCYVVzq1O6tc7VBzSygIwnmOt07w80IG6HDQvjv3Liw==
+"@types/unist@*", "@types/unist@^2.0.0", "@types/unist@^2.0.2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e"
+ integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==
"@types/vfile-message@*":
version "1.0.1"
@@ -1239,6 +1536,14 @@
resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31"
integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==
+JSONStream@^1.0.3:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0"
+ integrity sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==
+ dependencies:
+ jsonparse "^1.2.0"
+ through ">=2.2.7 <3"
+
abab@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f"
@@ -1275,20 +1580,20 @@ acorn-jsx@^5.0.0:
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
-acorn-walk@^6.0.1:
- version "6.1.1"
- resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.1.1.tgz#d363b66f5fac5f018ff9c3a1e7b6f8e310cc3913"
- integrity sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==
+acorn-walk@^6.0.1, acorn-walk@^6.1.1:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
+ integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
-acorn@^5.5.3, acorn@^5.7.3:
+acorn@^5.2.1, acorn@^5.5.3:
version "5.7.3"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279"
integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==
-acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5:
- version "6.0.5"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.5.tgz#81730c0815f3f3b34d8efa95cb7430965f4d887a"
- integrity sha512-i33Zgp3XWtmZBMNvCr4azvOFeWVw1Rk6p3hfi3LUDvIFraOMywb1kAtrbi+med14m4Xfpqm3zRZMT+c0FNE7kg==
+acorn@^6.0.1, acorn@^6.0.2, acorn@^6.0.5, acorn@^6.0.7:
+ version "6.2.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.2.1.tgz#3ed8422d6dec09e6121cc7a843ca86a330a86b51"
+ integrity sha512-JD0xT5FCRDNyjDda3Lrg/IxFscp9q4tiYtxE1/nOzlKCk7hIRuYjhq1kCNkbPjMRMZuFq20HNQn1I9k8Oj0E+Q==
after@0.8.2:
version "0.8.2"
@@ -1305,10 +1610,10 @@ ajv-keywords@^3.1.0:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a"
integrity sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=
-ajv@^6.1.0, ajv@^6.5.3, ajv@^6.5.5, ajv@^6.9.1:
- version "6.9.1"
- resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1"
- integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==
+ajv@^6.1.0, ajv@^6.10.2, ajv@^6.5.3, ajv@^6.5.5:
+ version "6.10.2"
+ resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52"
+ integrity sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==
dependencies:
fast-deep-equal "^2.0.1"
fast-json-stable-stringify "^2.0.0"
@@ -1327,17 +1632,24 @@ ansi-align@^2.0.0:
dependencies:
string-width "^2.0.0"
-ansi-colors@^3.0.0:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.0.5.tgz#cb9dc64993b64fd6945485f797fc3853137d9a7b"
- integrity sha512-VVjWpkfaphxUBFarydrQ3n26zX5nIK7hcbT3/ielrvwDDyBBjuh2vuSw1P9zkPq0cfqvdw7lkYHnu+OLSfIBsg==
+ansi-colors@^3.0.0, ansi-colors@^3.2.4:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
+ integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==
ansi-escapes@^3.0.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
-ansi-html@0.0.7:
+ansi-gray@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251"
+ integrity sha1-KWLPVOyXksSFEKPetSRDaGHvclE=
+ dependencies:
+ ansi-wrap "0.1.0"
+
+ansi-html@0.0.7, ansi-html@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
@@ -1369,6 +1681,11 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1:
dependencies:
color-convert "^1.9.0"
+ansi-wrap@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf"
+ integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768=
+
anymatch@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
@@ -1377,6 +1694,14 @@ anymatch@^2.0.0:
micromatch "^3.1.4"
normalize-path "^2.1.1"
+anymatch@^3.0.1:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.0.3.tgz#2fb624fe0e84bccab00afee3d0006ed310f22f09"
+ integrity sha512-c6IvoeBECQlMVuYUjSwimnhmztImpErfxJzWZhIQinIvQWoGOnB0dLIgifbPHQt5heS6mNlaZG16f06H3C8t1g==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
apollo-cache-inmemory@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.5.1.tgz#265d1ee67b0bf0aca9c37629d410bfae44e62953"
@@ -1473,6 +1798,13 @@ apollo-utilities@1.2.1, apollo-utilities@^1.2.1:
ts-invariant "^0.2.1"
tslib "^1.9.3"
+append-buffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/append-buffer/-/append-buffer-1.0.2.tgz#d8220cf466081525efea50614f3de6514dfa58f1"
+ integrity sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=
+ dependencies:
+ buffer-equal "^1.0.0"
+
append-transform@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-1.0.0.tgz#046a52ae582a228bd72f58acfbe2967c678759ab"
@@ -1540,11 +1872,6 @@ array-flatten@^2.1.0:
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296"
integrity sha1-Qmu52oQJDBg42BLIFQryCoMx4pY=
-array-slice@^0.2.3:
- version "0.2.3"
- resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5"
- integrity sha1-3Tz7gO15c6dRF82sabC5nshhhvU=
-
array-union@^1.0.1, array-union@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39"
@@ -1557,11 +1884,6 @@ array-uniq@^1.0.1, array-uniq@^1.0.2:
resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6"
integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=
-array-unique@^0.2.1:
- version "0.2.1"
- resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53"
- integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=
-
array-unique@^0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428"
@@ -1635,12 +1957,12 @@ async@^1.5.2:
resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=
-async@^2.0.0, async@^2.6.1:
- version "2.6.2"
- resolved "https://registry.yarnpkg.com/async/-/async-2.6.2.tgz#18330ea7e6e313887f5d2f2a904bac6fe4dd5381"
- integrity sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==
+async@^2.6.2:
+ version "2.6.3"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.3.tgz#d72625e2344a3656e3a3ad4fa749fa83299d82ff"
+ integrity sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==
dependencies:
- lodash "^4.17.11"
+ lodash "^4.17.14"
asynckit@^0.4.0:
version "0.4.0"
@@ -1657,17 +1979,18 @@ atob@^2.1.1:
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
-autoprefixer@^9.0.0:
- version "9.4.7"
- resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.4.7.tgz#f997994f9a810eae47b38fa6d8a119772051c4ff"
- integrity sha512-qS5wW6aXHkm53Y4z73tFGsUhmZu4aMPV9iHXYlF0c/wxjknXNHuj/1cIQb+6YH692DbJGGWcckAXX+VxKvahMA==
+autoprefixer@^9.5.1:
+ version "9.6.1"
+ resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.6.1.tgz#51967a02d2d2300bb01866c1611ec8348d355a47"
+ integrity sha512-aVo5WxR3VyvyJxcJC3h4FKfwCQvQWb1tSI5VHNibddCVWrcD1NvlxEweg3TSgiPztMnWfjpy2FURKA2kvDE+Tw==
dependencies:
- browserslist "^4.4.1"
- caniuse-lite "^1.0.30000932"
+ browserslist "^4.6.3"
+ caniuse-lite "^1.0.30000980"
+ chalk "^2.4.2"
normalize-range "^0.1.2"
num2fraction "^1.2.2"
- postcss "^7.0.14"
- postcss-value-parser "^3.3.1"
+ postcss "^7.0.17"
+ postcss-value-parser "^4.0.0"
autosize@^4.0.0:
version "4.0.0"
@@ -1743,10 +2066,10 @@ babel-loader@^8.0.5:
mkdirp "^0.5.1"
util.promisify "^1.0.0"
-babel-plugin-dynamic-import-node@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz#c0adfb07d95f4a4495e9aaac6ec386c4d7c2524e"
- integrity sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==
+babel-plugin-dynamic-import-node@^2.2.0, babel-plugin-dynamic-import-node@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.0.tgz#f00f507bdaa3c3e3ff6e7e5e98d90a7acab96f7f"
+ integrity sha512-o6qFkpeQEBxcqt0XYlWzAVxNCSCZdUgcR8IRlhD/8DylxjjO4foPcvTW0GGKa/cVt3rvxZ7o5ippJ+/0nvLhlQ==
dependencies:
object.assign "^4.1.0"
@@ -1779,6 +2102,11 @@ babel-preset-jest@^24.6.0:
"@babel/plugin-syntax-object-rest-spread" "^7.0.0"
babel-plugin-jest-hoist "^24.6.0"
+babelify@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/babelify/-/babelify-10.0.0.tgz#fe73b1a22583f06680d8d072e25a1e0d1d1d7fb5"
+ integrity sha512-X40FaxyH7t3X+JFAKvb1H9wooWKLRCi8pg3m8poqtdZaIng+bjzp9RvKQCvRjF9isHiPkXspbbXT/zwXLtwgwg==
+
babylon@7.0.0-beta.19:
version "7.0.0-beta.19"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
@@ -1856,16 +2184,21 @@ bfj@^6.1.1:
hoopy "^0.1.2"
tryer "^1.0.0"
-big.js@^3.1.3:
- version "3.2.0"
- resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e"
- integrity sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==
+big.js@^5.2.2:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
+ integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
binary-extensions@^1.0.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205"
integrity sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=
+binary-extensions@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c"
+ integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==
+
binaryextensions@2:
version "2.1.1"
resolved "https://registry.yarnpkg.com/binaryextensions/-/binaryextensions-2.1.1.tgz#3209a51ca4a4ad541a3b8d3d6a6d5b83a2485935"
@@ -1883,10 +2216,10 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
- integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.5, bluebird@~3.5.0:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.5.tgz#a8d0afd73251effbbd5fe384a77d73003c17a71f"
+ integrity sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
@@ -1909,6 +2242,16 @@ body-parser@1.18.2, body-parser@^1.16.1:
raw-body "2.3.2"
type-is "~1.6.15"
+body@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/body/-/body-5.1.0.tgz#e4ba0ce410a46936323367609ecb4e6553125069"
+ integrity sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=
+ dependencies:
+ continuable-cache "^0.3.1"
+ error "^7.0.0"
+ raw-body "~1.1.0"
+ safe-json-parse "~1.0.1"
+
bonjour@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5"
@@ -1921,14 +2264,13 @@ bonjour@^3.5.0:
multicast-dns "^6.0.1"
multicast-dns-service-types "^1.1.0"
-bootstrap-vue@^2.0.0-rc.24:
- version "2.0.0-rc.24"
- resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.24.tgz#8ea5bbcd19e0f9b4f87ed4d9ba72abaa35231f32"
- integrity sha512-8rA/I9tOvpNVIuMKD3rdlrUqgVdPEw4vPI0X8OeFJcG2hHvCHeZDF7FmWqxSeehIrUHGDV17HlTGSuP/v1Sp5g==
+bootstrap-vue@2.0.0-rc.27:
+ version "2.0.0-rc.27"
+ resolved "https://registry.yarnpkg.com/bootstrap-vue/-/bootstrap-vue-2.0.0-rc.27.tgz#884a46a71948d13c9729134cb564467f79a7b2b9"
+ integrity sha512-gXdpt2IsKbmC3SU0bf/RgldWwgrXK7G47yslOtkk4OA1z6fOzb2orM2vU5L8NCNZQomYax9LapRucv+8PByfdA==
dependencies:
"@nuxt/opencollective" "^0.2.2"
bootstrap "^4.3.1"
- core-js ">=2.6.5 <3.0.0"
popper.js "^1.15.0"
portal-vue "^2.1.5"
vue-functional-data-merge "^3.1.0"
@@ -1959,13 +2301,6 @@ brace-expansion@^1.1.7, brace-expansion@^1.1.8:
balanced-match "^1.0.0"
concat-map "0.0.1"
-braces@^0.1.2:
- version "0.1.5"
- resolved "https://registry.yarnpkg.com/braces/-/braces-0.1.5.tgz#c085711085291d8b75fdd74eab0f8597280711e6"
- integrity sha1-wIVxEIUpHYt1/ddOqw+FlygHEeY=
- dependencies:
- expand-range "^0.1.0"
-
braces@^2.3.0, braces@^2.3.1:
version "2.3.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729"
@@ -1982,6 +2317,13 @@ braces@^2.3.0, braces@^2.3.1:
split-string "^3.0.2"
to-regex "^3.0.1"
+braces@^3.0.1, braces@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
brorand@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -1992,7 +2334,7 @@ browser-process-hrtime@^0.1.2:
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz#616f00faef1df7ec1b5bf9cfe2bdc3170f26c7b4"
integrity sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==
-browser-resolve@^1.11.3:
+browser-resolve@^1.11.3, browser-resolve@^1.7.0:
version "1.11.3"
resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==
@@ -2057,14 +2399,14 @@ browserify-zlib@^0.2.0:
dependencies:
pako "~1.0.5"
-browserslist@^4.4.1, browserslist@^4.5.2, browserslist@^4.5.4:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.0.tgz#5274028c26f4d933d5b1323307c1d1da5084c9ff"
- integrity sha512-Jk0YFwXBuMOOol8n6FhgkDzn3mY9PYLYGk29zybF05SbRTsMgPqmTNeQQhOghCxq5oFqAXE3u4sYddr4C0uRhg==
+browserslist@^4.6.0, browserslist@^4.6.2, browserslist@^4.6.3:
+ version "4.6.6"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.6.6.tgz#6e4bf467cde520bc9dbdf3747dafa03531cec453"
+ integrity sha512-D2Nk3W9JL9Fp/gIcWei8LrERCS+eXu9AM5cfXA8WEZ84lFks+ARnZ0q/R69m2SV3Wjma83QDDPxsNKXUwdIsyA==
dependencies:
- caniuse-lite "^1.0.30000967"
- electron-to-chromium "^1.3.133"
- node-releases "^1.1.19"
+ caniuse-lite "^1.0.30000984"
+ electron-to-chromium "^1.3.191"
+ node-releases "^1.1.25"
bs-logger@0.x:
version "0.2.6"
@@ -2080,6 +2422,11 @@ bser@^2.0.0:
dependencies:
node-int64 "^0.4.0"
+buffer-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-equal/-/buffer-equal-1.0.0.tgz#59616b498304d556abd466966b22eeda3eca5fbe"
+ integrity sha1-WWFrSYME1Var1GaWayLu2j7KX74=
+
buffer-from@1.x, buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
@@ -2090,6 +2437,11 @@ buffer-indexof@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.0.tgz#f54f647c4f4e25228baa656a2e57e43d5f270982"
integrity sha1-9U9kfE9OJSKLqmVqLlfkPV8nCYI=
+buffer-shims@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
+ integrity sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=
+
buffer-xor@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9"
@@ -2109,27 +2461,32 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
+bytes@1:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8"
+ integrity sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=
+
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
-cacache@^11.0.2, cacache@^11.2.0:
- version "11.3.2"
- resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa"
- integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==
+cacache@^11.0.2, cacache@^11.2.0, cacache@^11.3.3:
+ version "11.3.3"
+ resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.3.tgz#8bd29df8c6a718a6ebd2d010da4d7972ae3bbadc"
+ integrity sha512-p8WcneCytvzPxhDvYp31PD039vi77I12W+/KfR9S8AZbaiARFBCpsPJS+9uhWfeBfeAtW7o/4vt3MUqLkbY6nA==
dependencies:
- bluebird "^3.5.3"
+ bluebird "^3.5.5"
chownr "^1.1.1"
figgy-pudding "^3.5.1"
- glob "^7.1.3"
+ glob "^7.1.4"
graceful-fs "^4.1.15"
lru-cache "^5.1.1"
mississippi "^3.0.0"
mkdirp "^0.5.1"
move-concurrently "^1.0.1"
promise-inflight "^1.0.1"
- rimraf "^2.6.2"
+ rimraf "^2.6.3"
ssri "^6.0.1"
unique-filename "^1.1.1"
y18n "^4.0.0"
@@ -2173,6 +2530,11 @@ cacheable-request@^2.1.1:
normalize-url "2.0.1"
responselike "1.0.2"
+cached-path-relative@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.2.tgz#a13df4196d26776220cc3356eb147a52dba2c6db"
+ integrity sha512-5r2GqsoEb4qMTTN9J+WzXfjov+hjxT+j3u5K+kIVNIwAd99DLCJE9pBIMP1qVeybV6JiijL385Oz0DcYxfbOIg==
+
call-me-maybe@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -2264,10 +2626,10 @@ camelcase@^5.0.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
-caniuse-lite@^1.0.30000932, caniuse-lite@^1.0.30000967:
- version "1.0.30000969"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000969.tgz#7664f571f2072657bde70b00a1fc1ba41f1942a9"
- integrity sha512-Kus0yxkoAJgVc0bax7S4gLSlFifCa7MnSZL9p9VuS/HIKEL4seaqh28KIQAAO50cD/rJ5CiJkJFapkdDAlhFxQ==
+caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30000984:
+ version "1.0.30000985"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000985.tgz#0eb40f6c8a8c219155cbe43c4975c0efb4a0f77f"
+ integrity sha512-1ngiwkgqAYPG0JSSUp3PUDGPKKY59EK7NrGGX+VOxaKCNzRbNc7uXMny+c3VJfZxtoK3wSImTvG9T9sXiTw2+w==
capture-exit@^2.0.0:
version "2.0.0"
@@ -2309,7 +2671,7 @@ chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0"
supports-color "^2.0.0"
-chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -2376,7 +2738,7 @@ check-types@^7.3.0:
resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.3.0.tgz#468f571a4435c24248f5fd0cb0e8d87c3c341e7d"
integrity sha1-Ro9XGkQ1wkJI9f0MsOjYfDw0Hn0=
-chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
+chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26"
integrity sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==
@@ -2396,6 +2758,21 @@ chokidar@^2.0.0, chokidar@^2.0.2, chokidar@^2.0.3, chokidar@^2.0.4:
optionalDependencies:
fsevents "^1.2.2"
+chokidar@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.0.2.tgz#0d1cd6d04eb2df0327446188cd13736a3367d681"
+ integrity sha512-c4PR2egjNjI1um6bamCQ6bUNPDiyofNQruHvKgHQ4gDUP/ITSVSzNsiI5OWtHOsX323i5ha/kk4YmOZ1Ktg7KA==
+ dependencies:
+ anymatch "^3.0.1"
+ braces "^3.0.2"
+ glob-parent "^5.0.0"
+ is-binary-path "^2.1.0"
+ is-glob "^4.0.1"
+ normalize-path "^3.0.0"
+ readdirp "^3.1.1"
+ optionalDependencies:
+ fsevents "^2.0.6"
+
chownr@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494"
@@ -2431,11 +2808,6 @@ circular-json@^0.3.1:
resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66"
integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==
-circular-json@^0.5.5:
- version "0.5.5"
- resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.5.5.tgz#64182ef359042d37cd8e767fc9de878b1e9447d3"
- integrity sha512-13YaR6kiz0kBNmIVM87Io8Hp7bWOo4r61vkEANy8iH9R9bc6avud/1FT0SBpqR1RpIQADOh/Q+yHZDA1iL6ysA==
-
class-utils@^0.3.5:
version "0.3.6"
resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463"
@@ -2470,16 +2842,6 @@ cli-cursor@^2.1.0:
dependencies:
restore-cursor "^2.0.0"
-cli-table3@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202"
- integrity sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==
- dependencies:
- object-assign "^4.1.0"
- string-width "^2.1.1"
- optionalDependencies:
- colors "^1.1.2"
-
cli-width@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a"
@@ -2512,13 +2874,26 @@ cliui@^4.0.0:
strip-ansi "^4.0.0"
wrap-ansi "^2.0.0"
-clone-regexp@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-1.0.1.tgz#051805cd33173375d82118fc0918606da39fd60f"
- integrity sha512-Fcij9IwRW27XedRIJnSOEupS7RVcXtObJXbcUOX93UCLqqOdRpkvzKywOOSizmEK/Is3S/RHX9dLdfo6R1Q1mw==
+clone-buffer@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
+ integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg=
+
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
dependencies:
- is-regexp "^1.0.0"
- is-supported-regexp-flag "^1.0.0"
+ is-plain-object "^2.0.4"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
+
+clone-regexp@^2.1.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
+ integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==
+ dependencies:
+ is-regexp "^2.0.0"
clone-response@1.0.2:
version "1.0.2"
@@ -2527,6 +2902,25 @@ clone-response@1.0.2:
dependencies:
mimic-response "^1.0.0"
+clone-stats@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
+ integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=
+
+clone@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+ integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+
+cloneable-readable@^1.0.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.1.3.tgz#120a00cb053bfb63a222e709f9683ea2e11d8cec"
+ integrity sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==
+ dependencies:
+ inherits "^2.0.1"
+ process-nextick-args "^2.0.0"
+ readable-stream "^2.3.5"
+
co@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -2556,10 +2950,10 @@ codesandbox-import-utils@^1.2.3:
istextorbinary "^2.2.1"
lz-string "^1.4.4"
-collapse-white-space@^1.0.2:
- version "1.0.4"
- resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.4.tgz#ce05cf49e54c3277ae573036a26851ba430a0091"
- integrity sha512-YfQ1tAUZm561vpYD+5eyWN8+UsceQbSrqqlc/6zDY2gtAE+uZLSdkkovhnGpmCThsvKBFakq4EdY/FF93E8XIw==
+collapse-white-space@^1.0.0, collapse-white-space@^1.0.2:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.5.tgz#c2495b699ab1ed380d29a1091e01063e75dbbe3a"
+ integrity sha512-703bOOmytCYAX9cXYqoikYIx6twmFCXsnzRQheBcTG3nzKYBR4P/+wkYeH+Mvj7qUz8zZDtdyzbxfnEi/kYzRQ==
collection-visit@^1.0.0:
version "1.0.0"
@@ -2586,18 +2980,16 @@ color-name@1.1.3, color-name@^1.0.0:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
-colors@^1.1.0, colors@^1.1.2:
+color-support@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2"
+ integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==
+
+colors@^1.1.0:
version "1.3.3"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d"
integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==
-combine-lists@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/combine-lists/-/combine-lists-1.0.1.tgz#458c07e09e0d900fc28b70a3fec2dacd1d2cb7f6"
- integrity sha1-RYwH4J4NkA/Ci3Cj/sLazR0st/Y=
- dependencies:
- lodash "^4.5.0"
-
combined-stream@^1.0.6, combined-stream@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
@@ -2605,6 +2997,11 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
+comma-separated-tokens@^1.0.1:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59"
+ integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ==
+
commander@2, commander@^2.10.0, commander@^2.16.0, commander@^2.18.0, commander@^2.19.0, commander@~2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422"
@@ -2620,10 +3017,10 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
-compare-versions@^3.2.1:
- version "3.4.0"
- resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.4.0.tgz#e0747df5c9cb7f054d6d3dc3e1dbc444f9e92b26"
- integrity sha512-tK69D7oNXXqUW3ZNo/z7NXTEz22TCF0pTE+YF9cxvaAM9XnkLo1fV621xCLrRR6aevJlKxExkss0vWqUCUpqdg==
+compare-versions@^3.4.0:
+ version "3.5.1"
+ resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393"
+ integrity sha512-9fGPIB7C6AyM18CJJBHt5EnCZDG3oiTJYy0NjfIAGjKpzv0tkxWko7TNQHF5ymqm7IH03tqmeuBxtvD+Izh6mg==
component-bind@1.0.0:
version "1.0.0"
@@ -2677,7 +3074,7 @@ concat-map@0.0.1:
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
-concat-stream@^1.5.0:
+concat-stream@^1.5.0, concat-stream@^1.6.0:
version "1.6.2"
resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34"
integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==
@@ -2687,6 +3084,15 @@ concat-stream@^1.5.0:
readable-stream "^2.2.2"
typedarray "^0.0.6"
+concat-stream@~1.5.0:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266"
+ integrity sha1-cIl4Yk2FavQaWnQd790mHadSwmY=
+ dependencies:
+ inherits "~2.0.1"
+ readable-stream "~2.0.0"
+ typedarray "~0.0.5"
+
config-chain@^1.1.12:
version "1.1.12"
resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa"
@@ -2766,7 +3172,12 @@ content-type@~1.0.4:
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
-convert-source-map@^1.1.0, convert-source-map@^1.4.0:
+continuable-cache@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/continuable-cache/-/continuable-cache-0.3.1.tgz#bd727a7faed77e71ff3985ac93351a912733ad0f"
+ integrity sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=
+
+convert-source-map@^1.1.0, convert-source-map@^1.4.0, convert-source-map@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20"
integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==
@@ -2807,30 +3218,37 @@ copy-to-clipboard@^3.0.8:
dependencies:
toggle-selection "^1.0.3"
-core-js-compat@^3.0.0:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.0.1.tgz#bff73ba31ca8687431b9c88f78d3362646fb76f0"
- integrity sha512-2pC3e+Ht/1/gD7Sim/sqzvRplMiRnFQVlPpDVaHtY9l7zZP7knamr3VRD6NyGfHd84MrDC0tAM9ulNxYMW0T3g==
+copy-webpack-plugin@^5.0.4:
+ version "5.0.4"
+ resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-5.0.4.tgz#c78126f604e24f194c6ec2f43a64e232b5d43655"
+ integrity sha512-YBuYGpSzoCHSSDGyHy6VJ7SHojKp6WHT4D7ItcQFNAYx2hrwkMe56e97xfVR0/ovDuMTrMffXUiltvQljtAGeg==
dependencies:
- browserslist "^4.5.4"
- core-js "3.0.1"
- core-js-pure "3.0.1"
- semver "^6.0.0"
-
-core-js-pure@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.0.1.tgz#37358fb0d024e6b86d443d794f4e37e949098cbe"
- integrity sha512-mSxeQ6IghKW3MoyF4cz19GJ1cMm7761ON+WObSyLfTu/Jn3x7w4NwNFnrZxgl4MTSvYYepVLNuRtlB4loMwJ5g==
+ cacache "^11.3.3"
+ find-cache-dir "^2.1.0"
+ glob-parent "^3.1.0"
+ globby "^7.1.1"
+ is-glob "^4.0.1"
+ loader-utils "^1.2.3"
+ minimatch "^3.0.4"
+ normalize-path "^3.0.0"
+ p-limit "^2.2.0"
+ schema-utils "^1.0.0"
+ serialize-javascript "^1.7.0"
+ webpack-log "^2.0.0"
-core-js@3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.0.1.tgz#1343182634298f7f38622f95e73f54e48ddf4738"
- integrity sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==
+core-js-compat@^3.1.1:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.1.4.tgz#e4d0c40fbd01e65b1d457980fe4112d4358a7408"
+ integrity sha512-Z5zbO9f1d0YrJdoaQhphVAnKPimX92D6z8lCGphH89MNRxlL1prI9ExJPqVwP0/kgkQCv8c4GJGT8X16yUncOg==
+ dependencies:
+ browserslist "^4.6.2"
+ core-js-pure "3.1.4"
+ semver "^6.1.1"
-"core-js@>=2.6.5 <3.0.0", core-js@^2.2.0, core-js@^2.6.9:
- version "2.6.9"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
- integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
+core-js-pure@3.1.4:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.1.4.tgz#5fa17dc77002a169a3566cc48dc774d2e13e3769"
+ integrity sha512-uJ4Z7iPNwiu1foygbcZYJsJs1jiXrTTCvxfLDXNhI/I+NHbSIEyr548y4fcsCEyWY0XgfAG/qqaunJ1SThHenA==
core-js@^3.1.3:
version "3.1.3"
@@ -2847,14 +3265,14 @@ core-util-is@1.0.2, core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
-cosmiconfig@^5.0.0:
- version "5.0.7"
- resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.0.7.tgz#39826b292ee0d78eda137dfa3173bd1c21a43b04"
- integrity sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==
+cosmiconfig@^5.2.0:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
+ integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
dependencies:
import-fresh "^2.0.0"
is-directory "^0.3.1"
- js-yaml "^3.9.0"
+ js-yaml "^3.13.1"
parse-json "^4.0.0"
create-ecdh@^4.0.0:
@@ -3013,6 +3431,11 @@ cssesc@^2.0.0:
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-2.0.0.tgz#3b13bd1bb1cb36e1bcb5a4dcd27f54c5dcb35703"
integrity sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==
+cssesc@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
+ integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
+
cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0":
version "0.3.4"
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.4.tgz#8cd52e8a3acfd68d3aed38ee0a640177d2f9d797"
@@ -3391,10 +3814,10 @@ data-urls@^1.0.0:
whatwg-mimetype "^2.2.0"
whatwg-url "^7.0.0"
-date-format@^1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/date-format/-/date-format-1.2.0.tgz#615e828e233dd1ab9bb9ae0950e0ceccfa6ecad8"
- integrity sha1-YV6CjiM90aubua4JUODOzPpuytg=
+date-format@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
+ integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
date-now@^0.1.4:
version "0.1.4"
@@ -3432,7 +3855,7 @@ debug@^3.1.0, debug@^3.2.5, debug@^3.2.6:
dependencies:
ms "^2.1.1"
-debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
+debug@^4.0.1, debug@^4.1.0, debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
@@ -3535,6 +3958,11 @@ define-property@^2.0.2:
is-descriptor "^1.0.2"
isobject "^3.0.1"
+defined@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693"
+ integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=
+
del@^2.0.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8"
@@ -3598,6 +4026,13 @@ destroy@~1.0.4:
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+detab@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.2.tgz#074970d1a807b045d0258a4235df5928dd683561"
+ integrity sha512-Q57yPrxScy816TTE1P/uLRXLDKjXhvYTbfxS/e6lPD+YrqghbsMlGB9nQzj/zVtSPaF0DFPSdO916EWO4sQUyQ==
+ dependencies:
+ repeat-string "^1.5.4"
+
detect-file@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7"
@@ -3618,6 +4053,14 @@ detect-node@^2.0.4:
resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
+detective@^4.0.0:
+ version "4.7.1"
+ resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e"
+ integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==
+ dependencies:
+ acorn "^5.2.1"
+ defined "^1.0.0"
+
di@^0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
@@ -3633,6 +4076,11 @@ diff@^3.2.0, diff@^3.4.0:
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==
+diff@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff"
+ integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q==
+
diffie-hellman@^5.0.0:
version "5.0.2"
resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.2.tgz#b5835739270cfe26acf632099fded2a07f209e5e"
@@ -3642,7 +4090,7 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
-dir-glob@^2.2.1:
+dir-glob@^2.0.0, dir-glob@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4"
integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==
@@ -3674,6 +4122,13 @@ docdash@^1.0.2:
resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.0.2.tgz#0449a8f6bb247f563020b78a5485dea95ae2e094"
integrity sha512-IEM57bWPLtVXpUeCKbiGvHsHtW9O9ZiiBPfeQDAZ7JdQiAF3aNWQoJ3e/+uJ63lHO009yn0tbJjtKpXJ2AURCQ==
+doctrine-temporary-fork@2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz#36f2154f556ee4f1e60311d391cd23de5187ed57"
+ integrity sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==
+ dependencies:
+ esutils "^2.0.2"
+
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -3696,6 +4151,77 @@ document-register-element@1.13.1:
dependencies:
lightercollective "^0.1.0"
+documentation@^12.0.1:
+ version "12.0.3"
+ resolved "https://registry.yarnpkg.com/documentation/-/documentation-12.0.3.tgz#32f91da8e5cb4104f69db9fd32c87773a1ad6240"
+ integrity sha512-RoqkH+mQ4Vi/nFMxG0BaqPAnjKfsJ9lbLWB8KqoKVAZy+urSpk1K1zBzaFesdDkKeaR3aBgeR3RjtHp8Ut/1Wg==
+ dependencies:
+ "@babel/core" "^7.1.2"
+ "@babel/generator" "^7.1.3"
+ "@babel/parser" "7.1.3"
+ "@babel/plugin-proposal-class-properties" "^7.1.0"
+ "@babel/plugin-proposal-decorators" "^7.1.2"
+ "@babel/plugin-proposal-do-expressions" "^7.0.0"
+ "@babel/plugin-proposal-export-default-from" "^7.0.0"
+ "@babel/plugin-proposal-export-namespace-from" "^7.0.0"
+ "@babel/plugin-proposal-function-bind" "^7.0.0"
+ "@babel/plugin-proposal-function-sent" "^7.1.0"
+ "@babel/plugin-proposal-json-strings" "^7.0.0"
+ "@babel/plugin-proposal-logical-assignment-operators" "^7.0.0"
+ "@babel/plugin-proposal-nullish-coalescing-operator" "^7.0.0"
+ "@babel/plugin-proposal-numeric-separator" "^7.0.0"
+ "@babel/plugin-proposal-optional-chaining" "^7.0.0"
+ "@babel/plugin-proposal-pipeline-operator" "^7.0.0"
+ "@babel/plugin-proposal-throw-expressions" "^7.0.0"
+ "@babel/plugin-syntax-dynamic-import" "^7.0.0"
+ "@babel/plugin-syntax-import-meta" "^7.0.0"
+ "@babel/preset-env" "^7.1.0"
+ "@babel/preset-flow" "^7.0.0"
+ "@babel/preset-react" "^7.0.0"
+ "@babel/preset-stage-0" "^7.0.0"
+ "@babel/traverse" "^7.1.4"
+ "@babel/types" "^7.1.3"
+ ansi-html "^0.0.7"
+ babelify "^10.0.0"
+ chalk "^2.3.0"
+ chokidar "^2.0.4"
+ concat-stream "^1.6.0"
+ diff "^4.0.1"
+ doctrine-temporary-fork "2.1.0"
+ get-port "^4.0.0"
+ git-url-parse "^10.0.1"
+ github-slugger "1.2.0"
+ glob "^7.1.2"
+ globals-docs "^2.4.0"
+ highlight.js "^9.15.5"
+ js-yaml "^3.10.0"
+ lodash "^4.17.10"
+ mdast-util-inject "^1.1.0"
+ micromatch "^3.1.5"
+ mime "^2.2.0"
+ module-deps-sortable "5.0.0"
+ parse-filepath "^1.0.2"
+ pify "^4.0.0"
+ read-pkg-up "^4.0.0"
+ remark "^9.0.0"
+ remark-html "^8.0.0"
+ remark-reference-links "^4.0.1"
+ remark-toc "^5.0.0"
+ remote-origin-url "0.4.0"
+ resolve "^1.8.1"
+ stream-array "^1.1.2"
+ strip-json-comments "^2.0.1"
+ tiny-lr "^1.1.0"
+ unist-builder "^1.0.2"
+ unist-util-visit "^1.3.0"
+ vfile "^4.0.0"
+ vfile-reporter "^6.0.0"
+ vfile-sort "^2.1.0"
+ vinyl "^2.1.0"
+ vinyl-fs "^3.0.2"
+ vue-template-compiler "^2.5.16"
+ yargs "^12.0.2"
+
dom-serialize@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
@@ -3763,6 +4289,13 @@ dropzone@^4.2.0:
resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-4.2.0.tgz#fbe7acbb9918e0706489072ef663effeef8a79f3"
integrity sha1-++esu5kY4HBkiQcu9mPv/u+KefM=
+duplexer2@^0.1.2, duplexer2@~0.1.0:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
+ integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
+ dependencies:
+ readable-stream "^2.0.2"
+
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -3773,10 +4306,10 @@ duplexer@^0.1.1:
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
integrity sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=
-duplexify@^3.4.2, duplexify@^3.5.3:
- version "3.5.3"
- resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.3.tgz#8b5818800df92fd0125b27ab896491912858243e"
- integrity sha512-g8ID9OroF9hKt2POf8YLayy+9594PzmM3scI00/uBXocX3TWNgoB67hjzkFe9ITAbQOne/lLdBxHXvYUM4ZgGA==
+duplexify@^3.4.2, duplexify@^3.6.0:
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309"
+ integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==
dependencies:
end-of-stream "^1.0.0"
inherits "^2.0.1"
@@ -3791,12 +4324,12 @@ ecc-jsbn@~0.1.1:
jsbn "~0.1.0"
safer-buffer "^2.1.0"
-echarts@^4.2.0-rc.2:
- version "4.2.0-rc.2"
- resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.0-rc.2.tgz#6a98397aafa81b65cbf0bc15d9afdbfb244df91e"
- integrity sha512-5Y4Kyi4eNsRM9Cnl7Q8C6PFVjznBJv1VIiMm/VSQ9zyqeo+ce1695GqUd9v4zfVx+Ow1gnwMJX67h0FNvarScw==
+echarts@^4.2.1:
+ version "4.2.1"
+ resolved "https://registry.yarnpkg.com/echarts/-/echarts-4.2.1.tgz#9a8ea3b03354f86f824d97625c334cf16965ef03"
+ integrity sha512-pw4xScRPsLegD/cqEcoXRKeA2SD4+s+Kyo0Na166NamOWhzNl2yI5RZ2rE97tBlAopNmhyMeBVpAeD5qb+ee1A==
dependencies:
- zrender "4.0.5"
+ zrender "4.0.7"
editions@^1.3.3:
version "1.3.4"
@@ -3825,10 +4358,10 @@ ejs@^2.6.1:
resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
-electron-to-chromium@^1.3.133:
- version "1.3.135"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.135.tgz#f5799b95f2bcd8de17cde47d63392d83a4477041"
- integrity sha512-xXLNstRdVsisPF3pL3H9TVZo2XkMILfqtD6RiWIUmDK2sFX1Bjwqmd8LBp0Kuo2FgKO63JXPoEVGm8WyYdwP0Q==
+electron-to-chromium@^1.3.191:
+ version "1.3.199"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.199.tgz#f9a62a74cda77854310a2abffde8b75591ea09a1"
+ integrity sha512-gachlDdHSK47s0N2e58GH9HMC6Z4ip0SfmYUa5iEbE50AKaOUXysaJnXMfKj0xB245jWbYcyFSH+th3rqsF8hA==
elliptic@^6.0.0:
version "6.4.0"
@@ -3843,11 +4376,21 @@ elliptic@^6.0.0:
minimalistic-assert "^1.0.0"
minimalistic-crypto-utils "^1.0.0"
+"emoji-regex@>=6.0.0 <=6.1.1":
+ version "6.1.1"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e"
+ integrity sha1-xs0OwbBkLio8Z6ETfvxeeW2k+I4=
+
emoji-regex@^7.0.1, emoji-regex@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
+emoji-regex@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+ integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
emoji-unicode-version@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/emoji-unicode-version/-/emoji-unicode-version-0.2.1.tgz#0ebf3666b5414097971d34994e299fce75cdbafc"
@@ -3952,6 +4495,14 @@ error-ex@^1.2.0, error-ex@^1.3.1:
dependencies:
is-arrayish "^0.2.1"
+error@^7.0.0:
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/error/-/error-7.0.2.tgz#a5f75fff4d9926126ddac0ea5dc38e689153cb02"
+ integrity sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=
+ dependencies:
+ string-template "~0.2.1"
+ xtend "~4.0.0"
+
es-abstract@^1.5.1, es-abstract@^1.6.1:
version "1.13.0"
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9"
@@ -4201,6 +4752,15 @@ espree@^4.0.0, espree@^4.1.0:
acorn-jsx "^5.0.0"
eslint-visitor-keys "^1.0.0"
+espree@^5.0.0:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.1.tgz#5d6526fa4fc7f0788a5cf75b15f30323e2f81f7a"
+ integrity sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==
+ dependencies:
+ acorn "^6.0.7"
+ acorn-jsx "^5.0.0"
+ eslint-visitor-keys "^1.0.0"
+
esprima@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633"
@@ -4230,7 +4790,7 @@ estraverse@^4.0.0, estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
-esutils@^2.0.2:
+esutils@^2.0.0, esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
@@ -4314,27 +4874,18 @@ execa@^1.0.0:
signal-exit "^3.0.0"
strip-eof "^1.0.0"
-execall@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/execall/-/execall-1.0.0.tgz#73d0904e395b3cab0658b08d09ec25307f29bb73"
- integrity sha1-c9CQTjlbPKsGWLCNCewlMH8pu3M=
+execall@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45"
+ integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==
dependencies:
- clone-regexp "^1.0.0"
+ clone-regexp "^2.1.0"
exit@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=
-expand-braces@^0.1.1:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/expand-braces/-/expand-braces-0.1.2.tgz#488b1d1d2451cb3d3a6b192cfc030f44c5855fea"
- integrity sha1-SIsdHSRRyz06axks/AMPRMWFX+o=
- dependencies:
- array-slice "^0.2.3"
- array-unique "^0.2.1"
- braces "^0.1.2"
-
expand-brackets@^2.1.4:
version "2.1.4"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622"
@@ -4348,14 +4899,6 @@ expand-brackets@^2.1.4:
snapdragon "^0.8.1"
to-regex "^3.0.1"
-expand-range@^0.1.0:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-0.1.1.tgz#4cb8eda0993ca56fa4f41fc42f3cbb4ccadff044"
- integrity sha1-TLjtoJk8pW+k9B/ELzy7TMrf8EQ=
- dependencies:
- is-number "^0.1.1"
- repeat-string "^0.2.2"
-
expand-tilde@^2.0.0, expand-tilde@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502"
@@ -4479,6 +5022,16 @@ extsprintf@1.3.0, extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=
+fancy-log@^1.3.3:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7"
+ integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==
+ dependencies:
+ ansi-gray "^0.1.1"
+ color-support "^1.1.3"
+ parse-node-version "^1.0.0"
+ time-stamp "^1.0.0"
+
fast-deep-equal@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
@@ -4518,7 +5071,7 @@ fault@^1.0.2:
dependencies:
format "^0.2.2"
-faye-websocket@^0.10.0:
+faye-websocket@^0.10.0, faye-websocket@~0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.10.0.tgz#4e492f8d04dfb6f89003507f6edbf2d501e7c6f4"
integrity sha1-TkkvjQTftviQA1B/btvy1QHnxvQ=
@@ -4559,10 +5112,10 @@ file-entry-cache@^2.0.0:
flat-cache "^1.2.1"
object-assign "^4.0.1"
-file-entry-cache@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-4.0.0.tgz#633567d15364aefe0b299e1e217735e8f3a9f6e8"
- integrity sha512-AVSwsnbV8vH/UVbvgEhf3saVQXORNv0ZzSkvkhQIaia5Tia+JhGTaa/ePUSVoPHQyGayQNmYfkzFi3WZV5zcpA==
+file-entry-cache@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-5.0.1.tgz#ca0f6efa6dd3d561333fb14515065c2fafdf439c"
+ integrity sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==
dependencies:
flat-cache "^2.0.1"
@@ -4597,6 +5150,13 @@ fill-range@^4.0.0:
repeat-string "^1.6.1"
to-regex-range "^2.1.0"
+fill-range@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ dependencies:
+ to-regex-range "^5.0.1"
+
finalhandler@1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.0.tgz#ce0b6855b45853e791b2fcc680046d88253dd7f5"
@@ -4623,13 +5183,13 @@ finalhandler@1.1.1:
statuses "~1.4.0"
unpipe "~1.0.0"
-find-cache-dir@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d"
- integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA==
+find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
+ integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==
dependencies:
commondir "^1.0.1"
- make-dir "^1.0.0"
+ make-dir "^2.0.0"
pkg-dir "^3.0.0"
find-root@^1.0.0, find-root@^1.1.0:
@@ -4693,13 +5253,13 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.0.tgz#55122b6536ea496b4b44893ee2608141d10d9916"
integrity sha512-R+H8IZclI8AAkSBRQJLVOsxwAoHd6WC40b4QTNWIjzAa6BXOBfQcM587MXDTVPeYaopFNWHUFLx7eNmHDSxMWg==
-flush-write-stream@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.2.tgz#c81b90d8746766f1a609a46809946c45dd8ae417"
- integrity sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=
+flush-write-stream@^1.0.0, flush-write-stream@^1.0.2:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
+ integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==
dependencies:
- inherits "^2.0.1"
- readable-stream "^2.0.4"
+ inherits "^2.0.3"
+ readable-stream "^2.3.6"
follow-redirects@1.5.10:
version "1.5.10"
@@ -4762,12 +5322,14 @@ from2@^2.1.0, from2@^2.1.1:
inherits "^2.0.1"
readable-stream "^2.0.0"
-fs-access@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/fs-access/-/fs-access-1.0.1.tgz#d6a87f262271cefebec30c553407fb995da8777a"
- integrity sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=
+fs-extra@^7.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
+ integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
dependencies:
- null-check "^1.0.0"
+ graceful-fs "^4.1.2"
+ jsonfile "^4.0.0"
+ universalify "^0.1.0"
fs-minipass@^1.2.5:
version "1.2.6"
@@ -4776,6 +5338,14 @@ fs-minipass@^1.2.5:
dependencies:
minipass "^2.2.1"
+fs-mkdirp-stream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz#0b7815fc3201c6a69e14db98ce098c16935259eb"
+ integrity sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=
+ dependencies:
+ graceful-fs "^4.1.11"
+ through2 "^2.0.3"
+
fs-write-stream-atomic@^1.0.8:
version "1.0.10"
resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9"
@@ -4799,6 +5369,11 @@ fsevents@^1.2.2, fsevents@^1.2.7:
nan "^2.12.1"
node-pre-gyp "^0.12.0"
+fsevents@^2.0.6:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.0.7.tgz#382c9b443c6cbac4c57187cdda23aa3bf1ccfc2a"
+ integrity sha512-a7YT0SV3RB+DjYcppwVDLtn13UQnmg0SWZS7ezZD0UjnLwXmy8Zm21GMVGLaFGimIqcvyMQaOJBrop8MyOp1kQ==
+
fstream@^1.0.0, fstream@^1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045"
@@ -4850,6 +5425,11 @@ get-caller-file@^1.0.1:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==
+get-port@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/get-port/-/get-port-4.2.0.tgz#e37368b1e863b7629c43c5a323625f95cf24b119"
+ integrity sha512-/b3jarXkH8KJoOMQc3uVGHASwGLPq3gSFJ7tgJm2diza+bydJPTGOibin2steecKeOylE8oY2JERlVWkAJO6yw==
+
get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
@@ -4860,6 +5440,11 @@ get-stdin@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
+get-stdin@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6"
+ integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==
+
get-stream@3.0.0, get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@@ -4906,6 +5491,35 @@ gettext-extractor@^3.4.3:
pofile "^1"
typescript "2 - 3"
+git-up@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/git-up/-/git-up-2.1.0.tgz#2f14cfe78327e7c4a2b92fcac7bfc674fdfad40c"
+ integrity sha512-MJgwfcSd9qxgDyEYpRU/CDxNpUadrK80JHuEQDG4Urn0m7tpSOgCBrtiSIa9S9KH8Tbuo/TN8SSQmJBvsw1HkA==
+ dependencies:
+ is-ssh "^1.3.0"
+ parse-url "^3.0.2"
+
+git-url-parse@^10.0.1:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-10.1.0.tgz#a27813218f8777e91d15f1c121b83bf14721b67e"
+ integrity sha512-goZOORAtFjU1iG+4zZgWq+N7It09PqS3Xsy43ZwhP5unDD0tTSmXTpqULHodMdJXGejm3COwXIhIRT6Z8DYVZQ==
+ dependencies:
+ git-up "^2.0.0"
+
+github-slugger@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.0.tgz#8ada3286fd046d8951c3c952a8d7854cfd90fd9a"
+ integrity sha512-wIaa75k1vZhyPm9yWrD08A5Xnx/V+RmzGrpjQuLemGKSb77Qukiaei58Bogrl/LZSADDfPzKJX8jhLs4CRTl7Q==
+ dependencies:
+ emoji-regex ">=6.0.0 <=6.1.1"
+
+github-slugger@^1.0.0, github-slugger@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/github-slugger/-/github-slugger-1.2.1.tgz#47e904e70bf2dccd0014748142d31126cfd49508"
+ integrity sha512-SsZUjg/P03KPzQBt7OxJPasGw6NRO5uOgiZ5RGXVud5iSIZ0eNZeNp5rTwCxtavrRUa/A77j8mePVc5lEvk0KQ==
+ dependencies:
+ emoji-regex ">=6.0.0 <=6.1.1"
+
glob-parent@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae"
@@ -4914,6 +5528,29 @@ glob-parent@^3.1.0:
is-glob "^3.1.0"
path-dirname "^1.0.0"
+glob-parent@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.0.0.tgz#1dc99f0f39b006d3e92c2c284068382f0c20e954"
+ integrity sha512-Z2RwiujPRGluePM6j699ktJYxmPpJKCfpGA13jz2hmFZC7gKetzrWvg5KN3+OsIFmydGyZ1AVwERCq1w/ZZwRg==
+ dependencies:
+ is-glob "^4.0.1"
+
+glob-stream@^6.1.0:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-6.1.0.tgz#7045c99413b3eb94888d83ab46d0b404cc7bdde4"
+ integrity sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=
+ dependencies:
+ extend "^3.0.0"
+ glob "^7.1.1"
+ glob-parent "^3.1.0"
+ is-negated-glob "^1.0.0"
+ ordered-read-streams "^1.0.0"
+ pumpify "^1.3.5"
+ readable-stream "^2.1.5"
+ remove-trailing-separator "^1.0.1"
+ to-absolute-glob "^2.0.0"
+ unique-stream "^2.0.2"
+
glob-to-regexp@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab"
@@ -4924,7 +5561,7 @@ glob-to-regexp@^0.4.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
-"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1:
+"glob@5 - 7", glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@~7.1.1:
version "7.1.4"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
@@ -4984,6 +5621,11 @@ global-prefix@^3.0.0:
kind-of "^6.0.2"
which "^1.3.1"
+globals-docs@^2.4.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/globals-docs/-/globals-docs-2.4.0.tgz#f2c647544eb6161c7c38452808e16e693c2dafbb"
+ integrity sha512-B69mWcqCmT3jNYmSxRxxOXWfzu3Go8NQXPfl2o0qPd1EEFhwW0dFUg9ztTu915zPQzqwIhWAlw6hmfIcCK4kkQ==
+
globals@^11.1.0, globals@^11.7.0:
version "11.12.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
@@ -5012,13 +5654,26 @@ globby@^6.1.0:
pify "^2.0.0"
pinkie-promise "^2.0.0"
-globby@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/globby/-/globby-9.0.0.tgz#3800df736dc711266df39b4ce33fe0d481f94c23"
- integrity sha512-q0qiO/p1w/yJ0hk8V9x1UXlgsXUxlGd0AHUOXZVXBO6aznDtpx7M8D1kBrCAItoPm+4l8r6ATXV1JpjY2SBQOw==
+globby@^7.1.1:
+ version "7.1.1"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-7.1.1.tgz#fb2ccff9401f8600945dfada97440cca972b8680"
+ integrity sha1-+yzP+UAfhgCUXfral0QMypcrhoA=
dependencies:
+ array-union "^1.0.1"
+ dir-glob "^2.0.0"
+ glob "^7.1.2"
+ ignore "^3.3.5"
+ pify "^3.0.0"
+ slash "^1.0.0"
+
+globby@^9.2.0:
+ version "9.2.0"
+ resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d"
+ integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==
+ dependencies:
+ "@types/glob" "^7.1.1"
array-union "^1.0.2"
- dir-glob "^2.2.1"
+ dir-glob "^2.2.2"
fast-glob "^2.2.6"
glob "^7.1.3"
ignore "^4.0.3"
@@ -5093,10 +5748,10 @@ got@^8.0.3:
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.9:
- version "4.1.15"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
- integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
+graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.0.tgz#8d8fdc73977cb04104721cb53666c1ca64cd328b"
+ integrity sha512-jpSvDPV4Cq/bgtpndIWbI5hmYxhQGHPC4d4cqBPb4DLniCfhJokdXhwhaDuLBGLQdvvRum/UiX6ECVIPvDXqdg==
graphlibrary@^2.2.0:
version "2.2.0"
@@ -5122,6 +5777,16 @@ growly@^1.3.0:
resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
+gulp-print@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.yarnpkg.com/gulp-print/-/gulp-print-5.0.2.tgz#8f379148218d2e168461baa74352e11d1bf7aa75"
+ integrity sha512-iIpHMzC/b3gFvVXOfP9Jk94SWGIsDLVNUrxULRleQev+08ug07mh84b1AOlW6QDQdmInQiqDFqJN1UvhU2nXdg==
+ dependencies:
+ ansi-colors "^3.2.4"
+ fancy-log "^1.3.3"
+ map-stream "0.0.7"
+ vinyl "^2.2.0"
+
gzip-size@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.0.0.tgz#a55ecd99222f4c48fd8c01c625ce3b349d0a0e80"
@@ -5271,12 +5936,50 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.0"
+hast-util-is-element@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-1.0.3.tgz#423b4b26fe8bf1f25950fe052e9ce8f83fd5f6a4"
+ integrity sha512-C62CVn7jbjp89yOhhy7vrkSaB7Vk906Gtcw/Ihd+Iufnq+2pwOZjdPmpzpKLWJXPJBMDX3wXg4FqmdOayPcewA==
+
+hast-util-sanitize@^1.0.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/hast-util-sanitize/-/hast-util-sanitize-1.3.1.tgz#4e60d66336bd67e52354d581967467029a933f2e"
+ integrity sha512-AIeKHuHx0Wk45nSkGVa2/ujQYTksnDl8gmmKo/mwQi7ag7IBZ8cM3nJ2G86SajbjGP/HRpud6kMkPtcM2i0Tlw==
+ dependencies:
+ xtend "^4.0.1"
+
+hast-util-to-html@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-4.0.1.tgz#3666b05afb62bd69f8f5e6c94db04dea19438e2a"
+ integrity sha512-2emzwyf0xEsc4TBIPmDJmBttIw8R4SXAJiJZoiRR/s47ODYWgOqNoDbf2SJAbMbfNdFWMiCSOrI3OVnX6Qq2Mg==
+ dependencies:
+ ccount "^1.0.0"
+ comma-separated-tokens "^1.0.1"
+ hast-util-is-element "^1.0.0"
+ hast-util-whitespace "^1.0.0"
+ html-void-elements "^1.0.0"
+ property-information "^4.0.0"
+ space-separated-tokens "^1.0.0"
+ stringify-entities "^1.0.1"
+ unist-util-is "^2.0.0"
+ xtend "^4.0.1"
+
+hast-util-whitespace@^1.0.0:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-1.0.3.tgz#6d161b307bd0693b5ec000c7c7e8b5445109ee34"
+ integrity sha512-AlkYiLTTwPOyxZ8axq2/bCwRUPjIPBfrHkXuCR92B38b3lSdU22R5F/Z4DL6a2kxWpekWq1w6Nj48tWat6GeRA==
+
he@^1.1.0, he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
-highlight.js@^9.13.1, highlight.js@~9.13.0:
+highlight.js@^9.13.1, highlight.js@^9.15.5:
+ version "9.15.8"
+ resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.15.8.tgz#f344fda123f36f1a65490e932cf90569e4999971"
+ integrity sha512-RrapkKQWwE+wKdF73VsOa2RQdIoO3mxwJ4P8mhbI6KYJUraUHRKM5w5zQQKXNk0xNL4UVRdulV9SBJcmzJNzVA==
+
+highlight.js@~9.13.0:
version "9.13.1"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.13.1.tgz#054586d53a6863311168488a0f58d6c505ce641e"
integrity sha512-Sc28JNQNDzaH6PORtRLMvif9RSn1mYuOoX3omVjnb0+HbpPygU2ALBI0R/wsiqCb4/fcp07Gdo8g+fhtFrQl6A==
@@ -5342,10 +6045,15 @@ html-minifier@^4.0.0:
relateurl "^0.2.7"
uglify-js "^3.5.1"
-html-tags@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-2.0.0.tgz#10b30a386085f43cede353cc8fa7cb0deeea668b"
- integrity sha1-ELMKOGCF9Dzt41PMj6fLDe7qZos=
+html-tags@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.0.0.tgz#41f57708c9e6b7b46a00a22317d614c4a2bab166"
+ integrity sha512-xiXEBjihaNI+VZ2mKEoI5ZPxqUsevTKM+aeeJ/W4KAg2deGE35minmCJMn51BvwJZmiHaeAxrb2LAS0yZJxuuA==
+
+html-void-elements@^1.0.0:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-1.0.4.tgz#95e8bb5ecd6b88766569c2645f2b5f1591db9ba5"
+ integrity sha512-yMk3naGPLrfvUV9TdDbuYXngh/TpHbA6TrOw3HL9kS8yhwx7i309BReNg7CbAJXGE+UMJ6je5OqJ7lC63o6YuQ==
htmlparser2@^3.10.0, htmlparser2@^3.9.0:
version "3.10.0"
@@ -5457,15 +6165,20 @@ ignore-walk@^3.0.1:
dependencies:
minimatch "^3.0.4"
+ignore@^3.3.5:
+ version "3.3.10"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043"
+ integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==
+
ignore@^4.0.3, ignore@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc"
integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==
-ignore@^5.0.4:
- version "5.0.5"
- resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.0.5.tgz#c663c548d6ce186fb33616a8ccb5d46e56bdbbf9"
- integrity sha512-kOC8IUb8HSDMVcYrDVezCxpJkzSQWTAzf3olpKM6o9rM5zpojx23O0Fl8Wr4+qJ6ZbPEHqf1fdwev/DS7v7pmA==
+ignore@^5.0.6:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.2.tgz#e28e584d43ad7e92f96995019cc43b9e1ac49558"
+ integrity sha512-vdqWBp7MyzdmHkkRWV5nY+PfGRbYbahfuvsBCh277tq+w9zyNi7h5CYJCK0kmzti9kU+O/cB7sE8HvKv6aXAKQ==
immediate@~3.0.5:
version "3.0.6"
@@ -5490,10 +6203,10 @@ import-lazy@^2.1.0:
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
integrity sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=
-import-lazy@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-3.1.0.tgz#891279202c8a2280fdbd6674dbd8da1a1dfc67cc"
- integrity sha512-8/gvXvX2JMn0F+CDlSC4l6kOmVaLOO3XLkksI7CI3Ud95KDYJuYur2b9P/PUt/i/pDAMd/DulQsNbbbmRRsDIQ==
+import-lazy@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
+ integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
import-local@^2.0.0:
version "2.0.0"
@@ -5561,7 +6274,7 @@ inherits@2.0.1:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1"
integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=
-ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
+ini@^1.3.3, ini@^1.3.4, ini@^1.3.5, ini@~1.3.0:
version "1.3.5"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
@@ -5638,6 +6351,14 @@ ipaddr.js@1.8.0, ipaddr.js@^1.5.2:
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e"
integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4=
+is-absolute@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-absolute/-/is-absolute-1.0.0.tgz#395e1ae84b11f26ad1795e73c17378e48a301576"
+ integrity sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==
+ dependencies:
+ is-relative "^1.0.0"
+ is-windows "^1.0.1"
+
is-accessor-descriptor@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6"
@@ -5682,7 +6403,14 @@ is-binary-path@^1.0.0:
dependencies:
binary-extensions "^1.0.0"
-is-buffer@^1.1.5, is-buffer@~1.1.1:
+is-binary-path@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
+ integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
+ dependencies:
+ binary-extensions "^2.0.0"
+
+is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -5794,6 +6522,11 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
+is-fullwidth-code-point@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+ integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
is-generator-fn@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
@@ -5806,10 +6539,10 @@ is-glob@^3.1.0:
dependencies:
is-extglob "^2.1.0"
-is-glob@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0"
- integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=
+is-glob@^4.0.0, is-glob@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc"
+ integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==
dependencies:
is-extglob "^2.1.1"
@@ -5826,16 +6559,16 @@ is-installed-globally@^0.1.0:
global-dirs "^0.1.0"
is-path-inside "^1.0.0"
+is-negated-glob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-negated-glob/-/is-negated-glob-1.0.0.tgz#6910bca5da8c95e784b5751b976cf5a10fee36d2"
+ integrity sha1-aRC8pdqMleeEtXUbl2z1oQ/uNtI=
+
is-npm@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-npm/-/is-npm-1.0.0.tgz#f2fb63a65e4905b406c86072765a1a4dc793b9f4"
integrity sha1-8vtjpl5JBbQGyGBydloaTceTufQ=
-is-number@^0.1.1:
- version "0.1.1"
- resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806"
- integrity sha1-aaevEWlj1HIG7JvZtIoUIW8eOAY=
-
is-number@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
@@ -5843,6 +6576,11 @@ is-number@^3.0.0:
dependencies:
kind-of "^3.0.2"
+is-number@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+ integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
is-obj@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-1.0.1.tgz#3e4729ac1f5fde025cd7d83a896dab9f4f67db0f"
@@ -5906,6 +6644,18 @@ is-regexp@^1.0.0:
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069"
integrity sha1-/S2INUXEa6xaYz57mgnof6LLUGk=
+is-regexp@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
+ integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==
+
+is-relative@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
+ integrity sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==
+ dependencies:
+ is-unc-path "^1.0.0"
+
is-resolvable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
@@ -5916,16 +6666,18 @@ is-retry-allowed@^1.0.0, is-retry-allowed@^1.1.0:
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34"
integrity sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=
+is-ssh@^1.3.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.3.1.tgz#f349a8cadd24e65298037a522cf7520f2e81a0f3"
+ integrity sha512-0eRIASHZt1E68/ixClI8bp2YK2wmBPVWEismTs6M+M099jKgrzl/3E976zIbImSIob48N2/XGe9y7ZiYdImSlg==
+ dependencies:
+ protocols "^1.1.0"
+
is-stream@^1.0.0, is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
-is-supported-regexp-flag@^1.0.0:
- version "1.0.1"
- resolved "https://registry.yarnpkg.com/is-supported-regexp-flag/-/is-supported-regexp-flag-1.0.1.tgz#21ee16518d2c1dd3edd3e9a0d57e50207ac364ca"
- integrity sha512-3vcJecUUrpgCqc/ca0aWeNu64UGgxcvO60K/Fkr1N6RSvfGCTU60UKN68JDmKokgba0rFFJs12EnzOQa14ubKQ==
-
is-symbol@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38"
@@ -5938,11 +6690,23 @@ is-typedarray@~1.0.0:
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
-is-utf8@^0.2.0:
+is-unc-path@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-unc-path/-/is-unc-path-1.0.0.tgz#d731e8898ed090a12c352ad2eaed5095ad322c9d"
+ integrity sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==
+ dependencies:
+ unc-path-regex "^0.1.2"
+
+is-utf8@^0.2.0, is-utf8@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
integrity sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=
+is-valid-glob@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/is-valid-glob/-/is-valid-glob-1.0.0.tgz#29bf3eff701be2d4d315dbacc39bc39fe8f601aa"
+ integrity sha1-Kb8+/3Ab4tTTFdusw5vDn+j2Aao=
+
is-whitespace-character@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/is-whitespace-character/-/is-whitespace-character-1.0.2.tgz#ede53b4c6f6fb3874533751ec9280d01928d03ed"
@@ -6000,38 +6764,38 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
-istanbul-api@^2.0.5:
- version "2.1.1"
- resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.1.tgz#194b773f6d9cbc99a9258446848b0f988951c4d0"
- integrity sha512-kVmYrehiwyeBAk/wE71tW6emzLiHGjYIiDrc8sfyty4F8M02/lrgXSm+R1kXysmF20zArvmZXjlE/mg24TVPJw==
+istanbul-api@^2.1.6:
+ version "2.1.6"
+ resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-2.1.6.tgz#d61702a9d1c66ad89d92e66d401e16b0bda4a35f"
+ integrity sha512-x0Eicp6KsShG1k1rMgBAi/1GgY7kFGEBwQpw3PXGEmu+rBcBNhqU8g2DgY9mlepAsLPzrzrbqSgCGANnki4POA==
dependencies:
- async "^2.6.1"
- compare-versions "^3.2.1"
+ async "^2.6.2"
+ compare-versions "^3.4.0"
fileset "^2.0.3"
- istanbul-lib-coverage "^2.0.3"
- istanbul-lib-hook "^2.0.3"
- istanbul-lib-instrument "^3.1.0"
- istanbul-lib-report "^2.0.4"
- istanbul-lib-source-maps "^3.0.2"
- istanbul-reports "^2.1.1"
- js-yaml "^3.12.0"
- make-dir "^1.3.0"
+ istanbul-lib-coverage "^2.0.5"
+ istanbul-lib-hook "^2.0.7"
+ istanbul-lib-instrument "^3.3.0"
+ istanbul-lib-report "^2.0.8"
+ istanbul-lib-source-maps "^3.0.6"
+ istanbul-reports "^2.2.4"
+ js-yaml "^3.13.1"
+ make-dir "^2.1.0"
minimatch "^3.0.4"
once "^1.4.0"
-istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.3, istanbul-lib-coverage@^2.0.5:
+istanbul-lib-coverage@^2.0.2, istanbul-lib-coverage@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz#675f0ab69503fad4b1d849f736baaca803344f49"
integrity sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==
-istanbul-lib-hook@^2.0.3:
+istanbul-lib-hook@^2.0.7:
version "2.0.7"
resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz#c95695f383d4f8f60df1f04252a9550e15b5b133"
integrity sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==
dependencies:
append-transform "^1.0.0"
-istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.1.0:
+istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-instrument@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz#a5f63d91f0bbc0c3e479ef4c5de027335ec6d630"
integrity sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==
@@ -6044,7 +6808,7 @@ istanbul-lib-instrument@^3.0.0, istanbul-lib-instrument@^3.0.1, istanbul-lib-ins
istanbul-lib-coverage "^2.0.5"
semver "^6.0.0"
-istanbul-lib-report@^2.0.4:
+istanbul-lib-report@^2.0.4, istanbul-lib-report@^2.0.8:
version "2.0.8"
resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz#5a8113cd746d43c4889eba36ab10e7d50c9b4f33"
integrity sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==
@@ -6053,7 +6817,7 @@ istanbul-lib-report@^2.0.4:
make-dir "^2.1.0"
supports-color "^6.1.0"
-istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2:
+istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.6:
version "3.0.6"
resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz#284997c48211752ec486253da97e3879defba8c8"
integrity sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==
@@ -6064,7 +6828,7 @@ istanbul-lib-source-maps@^3.0.1, istanbul-lib-source-maps@^3.0.2:
rimraf "^2.6.3"
source-map "^0.6.1"
-istanbul-reports@^2.1.1:
+istanbul-reports@^2.1.1, istanbul-reports@^2.2.4:
version "2.2.6"
resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-2.2.6.tgz#7b4f2660d82b29303a8fe6091f8ca4bf058da1af"
integrity sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==
@@ -6540,7 +7304,7 @@ js-tokens@^3.0.2:
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls=
-js-yaml@^3.12.0, js-yaml@^3.9.0:
+js-yaml@^3.10.0, js-yaml@^3.12.0, js-yaml@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
@@ -6667,10 +7431,24 @@ json5@2.x, json5@^2.1.0:
dependencies:
minimist "^1.2.0"
-json5@^0.5.0:
- version "0.5.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821"
- integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=
+json5@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
+ integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==
+ dependencies:
+ minimist "^1.2.0"
+
+jsonfile@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
+ integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=
+ optionalDependencies:
+ graceful-fs "^4.1.6"
+
+jsonparse@^1.2.0:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280"
+ integrity sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=
jsprim@^1.2.2:
version "1.4.1"
@@ -6698,20 +7476,19 @@ jszip@^3.1.3:
pako "~1.0.2"
readable-stream "~2.0.6"
-karma-chrome-launcher@^2.2.0:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz#cf1b9d07136cc18fe239327d24654c3dbc368acf"
- integrity sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==
+karma-chrome-launcher@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/karma-chrome-launcher/-/karma-chrome-launcher-3.0.0.tgz#5c3a7f877a304e90781c28fcd9a49e334a890f42"
+ integrity sha512-u/PnVgDOP97AUe/gJeABlC6Wa6aQ83MZsm0JgsJQ5bGQ9XcXON/7b2aRhl59A62Zom+q3PFveBkczc7E1RT7TA==
dependencies:
- fs-access "^1.0.0"
which "^1.2.1"
-karma-coverage-istanbul-reporter@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.0.4.tgz#402ae4ed6eadb9d9dafbd408ffda17897c0d003a"
- integrity sha512-xJS7QSQIVU6VK9HuJ/ieE5yynxKhjCCkd96NLY/BX/HXsx0CskU9JJiMQbd4cHALiddMwI4OWh1IIzeWrsavJw==
+karma-coverage-istanbul-reporter@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz#5f1bcc13c5e14ee1d91821ee8946861674f54c75"
+ integrity sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==
dependencies:
- istanbul-api "^2.0.5"
+ istanbul-api "^2.1.6"
minimatch "^3.0.4"
karma-jasmine@^1.1.2:
@@ -6743,38 +7520,39 @@ karma-sourcemap-loader@^0.3.7:
dependencies:
graceful-fs "^4.1.2"
-karma-webpack@^4.0.0-beta.0:
- version "4.0.0-rc.2"
- resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.0-rc.2.tgz#4c194e94789842af7f0ffa0de77ee7715739c7c1"
- integrity sha512-Wuiq/xFBsbJMsHhYy5SYXxSp7Q0b8uzAG8+Siuo56ntoi5GluPE5LK3Mzl2UtD4k1leFwL6IeIE6Q+tk4F6k9Q==
+karma-webpack@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/karma-webpack/-/karma-webpack-4.0.2.tgz#23219bd95bdda853e3073d3874d34447c77bced0"
+ integrity sha512-970/okAsdUOmiMOCY8sb17A2I8neS25Ad9uhyK3GHgmRSIFJbDcNEFE8dqqUhNe9OHiCC9k3DMrSmtd/0ymP1A==
dependencies:
- async "^2.0.0"
+ clone-deep "^4.0.1"
loader-utils "^1.1.0"
- lodash "^4.17.10"
- source-map "^0.5.6"
- webpack-dev-middleware "^3.2.0"
+ neo-async "^2.6.1"
+ schema-utils "^1.0.0"
+ source-map "^0.7.3"
+ webpack-dev-middleware "^3.7.0"
-karma@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/karma/-/karma-3.0.0.tgz#6da83461a8a28d8224575c3b5b874e271b4730c3"
- integrity sha512-ZTjyuDXVXhXsvJ1E4CnZzbCjSxD6sEdzEsFYogLuZM0yqvg/mgz+O+R1jb0J7uAQeuzdY8kJgx6hSNXLwFuHIQ==
+karma@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/karma/-/karma-4.2.0.tgz#27e88b310cde090d016980ff5444e3a239196fca"
+ integrity sha512-fmCuxN1rwJxTdZfOXK5LjlmS4Ana/OvzNMpkyLL/TLE8hmgSkpVpMYQ7RTVa8TNKRVQDZNl5W1oF5cfKfgIMlA==
dependencies:
bluebird "^3.3.0"
body-parser "^1.16.1"
- chokidar "^2.0.3"
+ braces "^3.0.2"
+ chokidar "^3.0.0"
colors "^1.1.0"
- combine-lists "^1.0.0"
connect "^3.6.0"
- core-js "^2.2.0"
+ core-js "^3.1.3"
di "^0.0.1"
dom-serialize "^2.2.0"
- expand-braces "^0.1.1"
+ flatted "^2.0.0"
glob "^7.1.1"
graceful-fs "^4.1.2"
http-proxy "^1.13.0"
isbinaryfile "^3.0.0"
- lodash "^4.17.4"
- log4js "^3.0.0"
+ lodash "^4.17.11"
+ log4js "^4.0.0"
mime "^2.3.1"
minimatch "^3.0.2"
optimist "^0.6.1"
@@ -6785,7 +7563,7 @@ karma@^3.0.0:
socket.io "2.1.1"
source-map "^0.6.1"
tmp "0.0.33"
- useragent "2.2.1"
+ useragent "2.3.0"
katex@^0.10.0:
version "0.10.0"
@@ -6842,10 +7620,10 @@ kleur@^3.0.2:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
-known-css-properties@^0.11.0:
- version "0.11.0"
- resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.11.0.tgz#0da784f115ea77c76b81536d7052e90ee6c86a8a"
- integrity sha512-bEZlJzXo5V/ApNNa5z375mJC6Nrz4vG43UgcSCrg2OHC+yuB6j0iDSrY7RQ/+PRofFB03wNIIt9iXIVLr4wc7w==
+known-css-properties@^0.14.0:
+ version "0.14.0"
+ resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.14.0.tgz#d7032b4334a32dc22e6e46b081ec789daf18756c"
+ integrity sha512-P+0a/gBzLgVlCnK8I7VcD0yuYJscmWn66wH9tlKsQnmVdg689tLEmziwB9PuazZYLkcm07fvWOKCJJqI55sD5Q==
latest-version@^3.0.0:
version "3.1.0"
@@ -6854,6 +7632,13 @@ latest-version@^3.0.0:
dependencies:
package-json "^4.0.0"
+lazystream@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.0.tgz#f6995fe0f820392f61396be89462407bb77168e4"
+ integrity sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=
+ dependencies:
+ readable-stream "^2.0.5"
+
lcid@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835"
@@ -6868,6 +7653,13 @@ lcid@^2.0.0:
dependencies:
invert-kv "^2.0.0"
+lead@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/lead/-/lead-1.0.0.tgz#6f14f99a37be3a9dd784f5495690e5903466ee42"
+ integrity sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=
+ dependencies:
+ flush-write-stream "^1.0.2"
+
left-pad@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e"
@@ -6878,6 +7670,11 @@ leven@^2.1.0:
resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580"
integrity sha1-wuep93IJTe6dNCAq6KzORoeHVYA=
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
@@ -6905,6 +7702,11 @@ linkify-it@^2.0.0:
dependencies:
uc.micro "^1.0.1"
+livereload-js@^2.3.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/livereload-js/-/livereload-js-2.4.0.tgz#447c31cf1ea9ab52fc20db615c5ddf678f78009c"
+ integrity sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==
+
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -6941,14 +7743,14 @@ loader-runner@^2.3.0:
resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2"
integrity sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=
-loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
- integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
+loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3:
+ version "1.2.3"
+ resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
+ integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==
dependencies:
- big.js "^3.1.3"
+ big.js "^5.2.2"
emojis-list "^2.0.0"
- json5 "^0.5.0"
+ json5 "^1.0.1"
locate-path@^2.0.0:
version "2.0.0"
@@ -7016,28 +7818,35 @@ lodash.upperfirst@4.3.1:
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
integrity sha1-E2Xt9DFIBIHvDRxolXpe2Z1J984=
-lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.5.0, lodash@~4.17.10:
- version "4.17.14"
- resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.14.tgz#9ce487ae66c96254fe20b599f21b6816028078ba"
- integrity sha512-mmKYbW3GLuJeX+iGP+Y7Gp1AiGHGbXHCOh/jZmrawMmsE7MS4znI3RL2FsjbqOyMayHInjOeykW7PEajUk1/xw==
+lodash@^4.0.0, lodash@^4.13.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
+ version "4.17.15"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
+ integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
-log-symbols@^2.0.0, log-symbols@^2.1.0, log-symbols@^2.2.0:
+log-symbols@^2.1.0, log-symbols@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a"
integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==
dependencies:
chalk "^2.0.1"
-log4js@^3.0.0:
- version "3.0.5"
- resolved "https://registry.yarnpkg.com/log4js/-/log4js-3.0.5.tgz#b80146bfebad68b430d4f3569556d8a6edfef303"
- integrity sha512-IX5c3G/7fuTtdr0JjOT2OIR12aTESVhsH6cEsijloYwKgcPRlO6DgOU72v0UFhWcoV1HN6+M3dwT89qVPLXm0w==
+log-symbols@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4"
+ integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==
dependencies:
- circular-json "^0.5.5"
- date-format "^1.2.0"
- debug "^3.1.0"
- rfdc "^1.1.2"
- streamroller "0.7.0"
+ chalk "^2.4.2"
+
+log4js@^4.0.0:
+ version "4.5.1"
+ resolved "https://registry.yarnpkg.com/log4js/-/log4js-4.5.1.tgz#e543625e97d9e6f3e6e7c9fc196dd6ab2cae30b5"
+ integrity sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==
+ dependencies:
+ date-format "^2.0.0"
+ debug "^4.1.1"
+ flatted "^2.0.0"
+ rfdc "^1.1.4"
+ streamroller "^1.0.6"
loglevel@^1.4.1:
version "1.4.1"
@@ -7082,15 +7891,10 @@ lowlight@^1.11.0:
fault "^1.0.2"
highlight.js "~9.13.0"
-lru-cache@2.2.x:
- version "2.2.4"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d"
- integrity sha1-bGWGGb7PFAMdDQtZSxYELOTcBj0=
-
-lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
- version "4.1.3"
- resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c"
- integrity sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==
+lru-cache@4.1.x, lru-cache@^4.0.1, lru-cache@^4.1.2, lru-cache@^4.1.3:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
dependencies:
pseudomap "^1.0.2"
yallist "^2.1.2"
@@ -7107,14 +7911,14 @@ lz-string@^1.4.4:
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26"
integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY=
-make-dir@^1.0.0, make-dir@^1.3.0:
+make-dir@^1.0.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==
dependencies:
pify "^3.0.0"
-make-dir@^2.1.0:
+make-dir@^2.0.0, make-dir@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
integrity sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==
@@ -7141,7 +7945,7 @@ map-age-cleaner@^0.1.1:
dependencies:
p-defer "^1.0.0"
-map-cache@^0.2.2:
+map-cache@^0.2.0, map-cache@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=
@@ -7156,6 +7960,11 @@ map-obj@^2.0.0:
resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-2.0.0.tgz#a65cd29087a92598b8791257a523e021222ac1f9"
integrity sha1-plzSkIepJZi4eRJXpSPgISIqwfk=
+map-stream@0.0.7:
+ version "0.0.7"
+ resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8"
+ integrity sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=
+
map-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f"
@@ -7189,10 +7998,10 @@ marked@^0.3.12, marked@~0.3.6:
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
-mathml-tag-names@^2.0.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.0.tgz#490b70e062ee24636536e3d9481e333733d00f2c"
- integrity sha512-3Zs9P/0zzwTob2pdgT0CHZuMbnSUSp8MB1bddfm+HDmnFWHGT4jvEZRf+2RuPoa+cjdn/z25SEt5gFTqdhvJAg==
+mathml-tag-names@^2.1.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/mathml-tag-names/-/mathml-tag-names-2.1.1.tgz#6dff66c99d55ecf739ca53c492e626f1d12a33cc"
+ integrity sha512-pWB896KPGSGkp1XtyzRBftpTzwSOL0Gfk0wLvxt4f2mgzjY19o0LxJ3U25vNWTzsh7da+KTbuXQoQ3lOJZ8WHw==
md5.js@^1.3.4:
version "1.3.4"
@@ -7218,6 +8027,52 @@ mdast-util-compact@^1.0.0:
dependencies:
unist-util-visit "^1.1.0"
+mdast-util-definitions@^1.2.0:
+ version "1.2.4"
+ resolved "https://registry.yarnpkg.com/mdast-util-definitions/-/mdast-util-definitions-1.2.4.tgz#2b54ad4eecaff9d9fcb6bf6f9f6b68b232d77ca7"
+ integrity sha512-HfUArPog1j4Z78Xlzy9Q4aHLnrF/7fb57cooTHypyGoe2XFNbcx/kWZDoOz+ra8CkUzvg3+VHV434yqEd1DRmA==
+ dependencies:
+ unist-util-visit "^1.0.0"
+
+mdast-util-inject@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-inject/-/mdast-util-inject-1.1.0.tgz#db06b8b585be959a2dcd2f87f472ba9b756f3675"
+ integrity sha1-2wa4tYW+lZotzS+H9HK6m3VvNnU=
+ dependencies:
+ mdast-util-to-string "^1.0.0"
+
+mdast-util-to-hast@^3.0.0:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-3.0.4.tgz#132001b266031192348d3366a6b011f28e54dc40"
+ integrity sha512-/eIbly2YmyVgpJNo+bFLLMCI1XgolO/Ffowhf+pHDq3X4/V6FntC9sGQCDLM147eTS+uSXv5dRzJyFn+o0tazA==
+ dependencies:
+ collapse-white-space "^1.0.0"
+ detab "^2.0.0"
+ mdast-util-definitions "^1.2.0"
+ mdurl "^1.0.1"
+ trim "0.0.1"
+ trim-lines "^1.0.0"
+ unist-builder "^1.0.1"
+ unist-util-generated "^1.1.0"
+ unist-util-position "^3.0.0"
+ unist-util-visit "^1.1.0"
+ xtend "^4.0.1"
+
+mdast-util-to-string@^1.0.0, mdast-util-to-string@^1.0.5:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-1.0.6.tgz#7d85421021343b33de1552fc71cb8e5b4ae7536d"
+ integrity sha512-868pp48gUPmZIhfKrLbaDneuzGiw3OTDjHc5M1kAepR2CWBJ+HpEsm252K4aXdiP5coVZaJPOqGtVU6Po8xnXg==
+
+mdast-util-toc@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/mdast-util-toc/-/mdast-util-toc-3.1.0.tgz#395eeb877f067f9d2165d990d77c7eea6f740934"
+ integrity sha512-Za0hqL1PqWrvxGtA/3NH9D5nhGAUS9grMM4obEAz5+zsk1RIw/vWUchkaoDLNdrwk05A0CSC5eEXng36/1qE5w==
+ dependencies:
+ github-slugger "^1.2.1"
+ mdast-util-to-string "^1.0.5"
+ unist-util-is "^2.1.2"
+ unist-util-visit "^1.1.0"
+
mdurl@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e"
@@ -7228,13 +8083,6 @@ media-typer@0.3.0:
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
-mem@^1.1.0:
- version "1.1.0"
- resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76"
- integrity sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=
- dependencies:
- mimic-fn "^1.0.0"
-
mem@^4.0.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178"
@@ -7249,7 +8097,7 @@ memory-fs@^0.2.0:
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290"
integrity sha1-8rslNovBIeORwlIN6Slpyu4KApA=
-memory-fs@^0.4.0, memory-fs@~0.4.1:
+memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552"
integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=
@@ -7312,15 +8160,18 @@ merge2@^1.2.3:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.2.3.tgz#7ee99dbd69bb6481689253f018488a1b902b0ed5"
integrity sha512-gdUU1Fwj5ep4kplwcmftruWofEFt6lfpkkr3h860CXbAB9c3hGb55EOL2ali0Td5oebvW0E1+3Sr+Ur7XfKpRA==
-mermaid@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.1.0.tgz#f9f4c02cf98d2d9fae230d5ce28f531e605e9b72"
- integrity sha512-fsCN8bOukYHZT6FlA0eIeLs/O3H2+CWcHnxRrS86Ci1cpJes5/qvoye0xjhe8lbXJCFLM8sXWVg57aMHPtnAaw==
+mermaid@^8.2.3:
+ version "8.2.3"
+ resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-8.2.3.tgz#609bad45bedc3ee1a935161c11c3c22689cfecd9"
+ integrity sha512-G2p9BAAEeTtogPs4YXM8KyX+TsZULlgk0tGvmBPfBZ5j3YCPxgAxG9ZzleiYNItF7M1hGkE485BDLN8DbfR+/Q==
dependencies:
+ "@braintree/sanitize-url" "^3.1.0"
d3 "^5.7.0"
dagre-d3-renderer "^0.5.8"
dagre-layout "^0.8.8"
+ documentation "^12.0.1"
graphlibrary "^2.2.0"
+ gulp-print "^5.0.2"
he "^1.2.0"
lodash "^4.17.11"
minify "^4.1.1"
@@ -7332,7 +8183,7 @@ methods@~1.1.2:
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
-micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
+micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.5, micromatch@^3.1.6, micromatch@^3.1.8, micromatch@^3.1.9:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
@@ -7351,6 +8202,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.6, mic
snapdragon "^0.8.1"
to-regex "^3.0.2"
+micromatch@^4.0.0:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -7376,10 +8235,10 @@ mime@1.4.1:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
-mime@^2.0.3, mime@^2.3.1:
- version "2.3.1"
- resolved "https://registry.yarnpkg.com/mime/-/mime-2.3.1.tgz#b1621c54d63b97c47d3cfe7f7215f7d64517c369"
- integrity sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==
+mime@^2.0.3, mime@^2.2.0, mime@^2.3.1, mime@^2.4.2:
+ version "2.4.4"
+ resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.4.tgz#bd7b91135fc6b01cde3e9bae33d659b63d8857e5"
+ integrity sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==
mimic-fn@^1.0.0:
version "1.2.0"
@@ -7444,7 +8303,7 @@ minimist@1.1.x:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8"
integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=
-minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
+minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
@@ -7495,12 +8354,32 @@ mkdirp@0.5.x, mkdirp@0.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp
dependencies:
minimist "0.0.8"
+module-deps-sortable@5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/module-deps-sortable/-/module-deps-sortable-5.0.0.tgz#99db5bb08f7eab55e4c31f6b7c722c6a2144ba74"
+ integrity sha512-bnGGeghQmz/t/6771/KC4FmxpVm126iR6AAzzq4N6hVZQVl4+ZZBv+VF3PJmDyxXtVtgcgTSSP7NL+jq1QAHrg==
+ dependencies:
+ JSONStream "^1.0.3"
+ browser-resolve "^1.7.0"
+ cached-path-relative "^1.0.0"
+ concat-stream "~1.5.0"
+ defined "^1.0.0"
+ detective "^4.0.0"
+ duplexer2 "^0.1.2"
+ inherits "^2.0.1"
+ readable-stream "^2.0.2"
+ resolve "^1.1.3"
+ stream-combiner2 "^1.1.1"
+ subarg "^1.0.0"
+ through2 "^2.0.0"
+ xtend "^4.0.0"
+
moment-mini@^2.22.1:
version "2.22.1"
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.22.1.tgz#bc32d73e43a4505070be6b53494b17623183420d"
integrity sha512-OUCkHOz7ehtNMYuZjNciXUfwTuz8vmF1MTbAy59ebf+ZBYZO5/tZKuChVWCX+uDo+4idJBpGltNfV8st+HwsGw==
-moment@2.x, moment@^2.10.2:
+moment@^2.10.2:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
@@ -7603,10 +8482,10 @@ negotiator@0.6.1:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9"
integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=
-neo-async@^2.5.0, neo-async@^2.6.0:
- version "2.6.0"
- resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835"
- integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==
+neo-async@^2.5.0, neo-async@^2.6.0, neo-async@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.1.tgz#ac27ada66167fa8849a6addd837f6b189ad2081c"
+ integrity sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==
nice-try@^1.0.4:
version "1.0.5"
@@ -7719,10 +8598,10 @@ node-pre-gyp@^0.12.0:
semver "^5.3.0"
tar "^4"
-node-releases@^1.1.19:
- version "1.1.19"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.19.tgz#c492d1e381fea0350b338b646c27867e88e91b3d"
- integrity sha512-SH/B4WwovHbulIALsQllAVwqZZD1kPmKCqrhGfR29dXjLAVZMHvBjD3S6nL9D/J9QkmZ1R92/0wCMDKXUUvyyA==
+node-releases@^1.1.25:
+ version "1.1.25"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.25.tgz#0c2d7dbc7fed30fbe02a9ee3007b8c90bf0133d3"
+ integrity sha512-fI5BXuk83lKEoZDdH3gRhtsNgh05/wZacuXkgbiYkceE7+QIMXOg98n9ZV7mz27B+kFHnqHcUpscZZlGRSmTpQ==
dependencies:
semver "^5.3.0"
@@ -7828,6 +8707,23 @@ normalize-url@2.0.1:
query-string "^5.0.1"
sort-keys "^2.0.0"
+normalize-url@^1.9.1:
+ version "1.9.1"
+ resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-1.9.1.tgz#2cc0d66b31ea23036458436e3620d85954c66c3c"
+ integrity sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=
+ dependencies:
+ object-assign "^4.0.1"
+ prepend-http "^1.0.0"
+ query-string "^4.1.0"
+ sort-keys "^1.0.0"
+
+now-and-later@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/now-and-later/-/now-and-later-2.0.1.tgz#8e579c8685764a7cc02cb680380e94f43ccb1f7c"
+ integrity sha512-KGvQ0cB70AQfg107Xvs/Fbu+dGmZoTRJp2TaPwcwQm3/7PteUyN2BCgk8KBMPGBUXZdVwyWS8fDCGFygBm19UQ==
+ dependencies:
+ once "^1.3.2"
+
npm-bundled@^1.0.1:
version "1.0.6"
resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd"
@@ -7858,11 +8754,6 @@ npm-run-path@^2.0.0:
gauge "~2.7.3"
set-blocking "~2.0.0"
-null-check@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/null-check/-/null-check-1.0.0.tgz#977dffd7176012b9ec30d2a39db5cf72a0439edd"
- integrity sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=
-
num2fraction@^1.2.2:
version "1.2.2"
resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede"
@@ -7914,7 +8805,7 @@ object-visit@^1.0.0:
dependencies:
isobject "^3.0.0"
-object.assign@^4.1.0:
+object.assign@^4.0.4, object.assign@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da"
integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==
@@ -7966,7 +8857,7 @@ on-headers@~1.0.1:
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7"
integrity sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=
-once@^1.3.0, once@^1.3.1, once@^1.4.0:
+once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@@ -8019,6 +8910,13 @@ optionator@^0.8.1, optionator@^0.8.2:
type-check "~0.3.2"
wordwrap "~1.0.0"
+ordered-read-streams@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz#77c0cb37c41525d64166d990ffad7ec6a0e1363e"
+ integrity sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=
+ dependencies:
+ readable-stream "^2.0.1"
+
orderedmap@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/orderedmap/-/orderedmap-1.0.0.tgz#d90fc2ba1ed085190907d601dec6e6a53f8d41ba"
@@ -8048,15 +8946,6 @@ os-locale@^1.4.0:
dependencies:
lcid "^1.0.0"
-os-locale@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2"
- integrity sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==
- dependencies:
- execa "^0.7.0"
- lcid "^1.0.0"
- mem "^1.1.0"
-
os-locale@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a"
@@ -8118,7 +9007,7 @@ p-limit@^1.1.0:
dependencies:
p-try "^1.0.0"
-p-limit@^2.0.0:
+p-limit@^2.0.0, p-limit@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2"
integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==
@@ -8220,6 +9109,22 @@ parse-entities@^1.0.2, parse-entities@^1.1.0:
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
+parse-filepath@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891"
+ integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=
+ dependencies:
+ is-absolute "^1.0.0"
+ map-cache "^0.2.0"
+ path-root "^0.1.1"
+
+parse-git-config@^0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/parse-git-config/-/parse-git-config-0.2.0.tgz#272833fdd15fea146fb75d336d236b963b6ff706"
+ integrity sha1-Jygz/dFf6hRvt10zbSNrljtv9wY=
+ dependencies:
+ ini "^1.3.3"
+
parse-json@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9"
@@ -8235,11 +9140,34 @@ parse-json@^4.0.0:
error-ex "^1.3.1"
json-parse-better-errors "^1.0.1"
+parse-node-version@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b"
+ integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==
+
parse-passwd@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6"
integrity sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=
+parse-path@^3.0.1:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-3.0.4.tgz#a48b7b529da41f34d9d1428602a39b29fc7180e4"
+ integrity sha512-wP70vtwv2DyrM2YoA7ZHVv4zIXa4P7dGgHlj+VwyXNDduLLVJ7NMY1zsFxjUUJ3DAwJLupGb1H5gMDDiNlJaxw==
+ dependencies:
+ is-ssh "^1.3.0"
+ protocols "^1.4.0"
+
+parse-url@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-3.0.2.tgz#602787a7063a795d72b8673197505e72f60610be"
+ integrity sha1-YCeHpwY6eV1yuGcxl1BecvYGEL4=
+ dependencies:
+ is-ssh "^1.3.0"
+ normalize-url "^1.9.1"
+ parse-path "^3.0.1"
+ protocols "^1.4.0"
+
parse5@4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
@@ -8316,6 +9244,18 @@ path-parse@^1.0.6:
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
+path-root-regex@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d"
+ integrity sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=
+
+path-root@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/path-root/-/path-root-0.1.1.tgz#9a4a6814cac1c0cd73360a95f32083c8ea4745b7"
+ integrity sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=
+ dependencies:
+ path-root-regex "^0.1.0"
+
path-to-regexp@0.1.7:
version "0.1.7"
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
@@ -8356,9 +9296,9 @@ pbkdf2@^3.0.3:
sha.js "^2.4.8"
pdfjs-dist@^2.0.943:
- version "2.0.943"
- resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.0.943.tgz#32fb9a2d863df5a1d89521a0b3cd900c16e7edde"
- integrity sha512-iLhNcm4XceTHRaSU5o22ZGCm4YpuW5+rf4+BJFH/feBhMQLbCGBry+Jet8Q419QDI4qgARaIQzXuiNrsNWS8Yw==
+ version "2.1.266"
+ resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.1.266.tgz#cded02268b389559e807f410d2a729db62160026"
+ integrity sha512-Jy7o1wE3NezPxozexSbq4ltuLT0Z21ew/qrEiAEeUZzHxMHGk4DUV1D7RuCXg5vJDvHmjX1YssN+we9QfRRgXQ==
dependencies:
node-ensure "^0.0.0"
worker-loader "^2.0.0"
@@ -8368,6 +9308,11 @@ performance-now@^2.1.0:
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+picomatch@^2.0.4, picomatch@^2.0.5:
+ version "2.0.7"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.0.7.tgz#514169d8c7cd0bdbeecc8a2609e34a7163de69f6"
+ integrity sha512-oLHIdio3tZ0qH76NybpeneBhYVj0QFTfXEFTc/B3zKQspYfYYkWYgFsmzo+4kvId/bQRcNkVeguI3y+CD22BtA==
+
pify@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
@@ -8383,12 +9328,10 @@ pify@^4.0.0, pify@^4.0.1:
resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231"
integrity sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==
-pikaday@^1.6.1:
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.6.1.tgz#b91bcb9b8539cedd8d6d08e4e7465e12095671b0"
- integrity sha512-B+pxVcSGuzLblMe4dnhCF3dnI2zkyj5GAqanGX9cVcOk90fp2ULo1OZFUPRXQXUE5tmcimnk1tPOFs8tUHQetQ==
- optionalDependencies:
- moment "2.x"
+pikaday@^1.8.0:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/pikaday/-/pikaday-1.8.0.tgz#ce930e257042e852e6aadee1115e01554b2d71c5"
+ integrity sha512-SgGxMYX0NHj9oQnMaSyAipr2gOrbB4Lfs/TJTb6H6hRHs39/5c5VZi73Q8hr53+vWjdn6HzkWcj8Vtl3c9ziaA==
pinkie-promise@^2.0.0:
version "2.0.1"
@@ -8481,17 +9424,17 @@ postcss-html@^0.36.0:
dependencies:
htmlparser2 "^3.10.0"
-postcss-jsx@^0.36.0:
- version "0.36.0"
- resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.0.tgz#b7685ed3d070a175ef0aa48f83d9015bd772c82d"
- integrity sha512-/lWOSXSX5jlITCKFkuYU2WLFdrncZmjSVyNpHAunEgirZXLwI8RjU556e3Uz4mv0WVHnJA9d3JWb36lK9Yx99g==
+postcss-jsx@^0.36.1:
+ version "0.36.2"
+ resolved "https://registry.yarnpkg.com/postcss-jsx/-/postcss-jsx-0.36.2.tgz#34bcd6752426a60b8df73f069e7595383060a794"
+ integrity sha512-7B8a8a6YDOQFqMQ9UmOo+IPPb2i14Z57Fvc9cOI11B3bWMSY2O6r2XJ3tlcQhUhv93TeN0ntxehTaSWJDQavlg==
dependencies:
- "@babel/core" ">=7.1.0"
+ "@babel/core" ">=7.2.2"
-postcss-less@^3.1.0:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.2.tgz#fb67e7ba351dbdf69de3c52eebd1184c52bfaea6"
- integrity sha512-66ZBVo1JGkQ7r13M97xcHcyarWpgg21RaqIZWZXHE3XOtb5+ywK1uZWeY1DYkYRkIX/l8Hvxnx9iSKB68nFr+w==
+postcss-less@^3.1.4:
+ version "3.1.4"
+ resolved "https://registry.yarnpkg.com/postcss-less/-/postcss-less-3.1.4.tgz#369f58642b5928ef898ffbc1a6e93c958304c5ad"
+ integrity sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==
dependencies:
postcss "^7.0.14"
@@ -8539,7 +9482,7 @@ postcss-modules-values@^1.3.0:
icss-replace-symbols "^1.1.0"
postcss "^6.0.1"
-postcss-reporter@^6.0.0:
+postcss-reporter@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f"
integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==
@@ -8554,7 +9497,7 @@ postcss-resolve-nested-selector@^0.1.1:
resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e"
integrity sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=
-postcss-safe-parser@^4.0.0:
+postcss-safe-parser@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/postcss-safe-parser/-/postcss-safe-parser-4.0.1.tgz#8756d9e4c36fdce2c72b091bbc8ca176ab1fcdea"
integrity sha512-xZsFA3uX8MO3yAda03QrG3/Eg1LN3EPfjjf07vke/46HERLZyHrTsQ9E1r1w1W//fWEhtYNndo2hQplN2cVpCQ==
@@ -8594,6 +9537,15 @@ postcss-selector-parser@^5.0.0:
indexes-of "^1.0.1"
uniq "^1.0.1"
+postcss-selector-parser@^6.0.2:
+ version "6.0.2"
+ resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.2.tgz#934cf799d016c83411859e09dcecade01286ec5c"
+ integrity sha512-36P2QR59jDTOAiIkqEprfJDsoNrvwFei3eCqKd1Y0tUsBimsq39BLp7RD+JWny3WgB1zGhJX8XVePwm9k4wdBg==
+ dependencies:
+ cssesc "^3.0.0"
+ indexes-of "^1.0.1"
+ uniq "^1.0.1"
+
postcss-syntax@^0.36.2:
version "0.36.2"
resolved "https://registry.yarnpkg.com/postcss-syntax/-/postcss-syntax-0.36.2.tgz#f08578c7d95834574e5593a82dfbfa8afae3b51c"
@@ -8604,6 +9556,11 @@ postcss-value-parser@^3.3.0, postcss-value-parser@^3.3.1:
resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
+postcss-value-parser@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.0.tgz#99a983d365f7b2ad8d0f9b8c3094926eab4b936d"
+ integrity sha512-ESPktioptiSUchCKgggAkzdmkgzKfmp0EU8jXH+5kbIUB+unr0Y4CY9SRMvibuvYUBjNh1ACLbxqYNpdTQOteQ==
+
postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
version "6.0.23"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324"
@@ -8613,10 +9570,10 @@ postcss@^6.0.1, postcss@^6.0.14, postcss@^6.0.23:
source-map "^0.6.1"
supports-color "^5.4.0"
-postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.13, postcss@^7.0.14, postcss@^7.0.2, postcss@^7.0.7:
- version "7.0.14"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5"
- integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg==
+postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.7:
+ version "7.0.17"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.17.tgz#4da1bdff5322d4a0acaab4d87f3e782436bad31f"
+ integrity sha512-546ZowA+KZ3OasvQZHsbuEpysvwTZNGJv9EfyCQdsIDltPSWHAeTQ5fQy/Npi2ZDtLI3zs7Ps/p6wThErhm9fQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
@@ -8627,7 +9584,7 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
-prepend-http@^1.0.1:
+prepend-http@^1.0.0, prepend-http@^1.0.1:
version "1.0.4"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
@@ -8669,16 +9626,16 @@ private@^0.1.6:
resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff"
integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==
+process-nextick-args@^2.0.0, process-nextick-args@~2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
+ integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
+
process-nextick-args@~1.0.6:
version "1.0.7"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=
-process-nextick-args@~2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
- integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==
-
process@^0.11.10:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
@@ -8702,6 +9659,13 @@ prompts@^2.0.1:
kleur "^3.0.2"
sisteransi "^1.0.0"
+property-information@^4.0.0:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/property-information/-/property-information-4.2.0.tgz#f0e66e07cbd6fed31d96844d958d153ad3eb486e"
+ integrity sha512-TlgDPagHh+eBKOnH2VYvk8qbwsCG/TAJdmTL7f1PROUcSO8qt/KSmShEQ/OKvock8X9tFjtqjCScyOkkkvIKVQ==
+ dependencies:
+ xtend "^4.0.1"
+
prosemirror-commands@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/prosemirror-commands/-/prosemirror-commands-1.0.7.tgz#e5a2ba821e29ea7065c88277fe2c3d7f6b0b9d37"
@@ -8823,6 +9787,11 @@ proto-list@~1.2.1:
resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=
+protocols@^1.1.0, protocols@^1.4.0:
+ version "1.4.7"
+ resolved "https://registry.yarnpkg.com/protocols/-/protocols-1.4.7.tgz#95f788a4f0e979b291ffefcf5636ad113d037d32"
+ integrity sha512-Fx65lf9/YDn3hUX08XUc0J8rSux36rEsyiv21ZGUC1mOyeM3lTRpZLcrm8aAolzS4itwVfm7TAPyxC2E5zd6xg==
+
proxy-addr@~2.0.3:
version "2.0.4"
resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93"
@@ -8878,12 +9847,12 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
-pumpify@^1.3.3:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.4.0.tgz#80b7c5df7e24153d03f0e7ac8a05a5d068bd07fb"
- integrity sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==
+pumpify@^1.3.3, pumpify@^1.3.5:
+ version "1.5.1"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce"
+ integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==
dependencies:
- duplexify "^3.5.3"
+ duplexify "^3.6.0"
inherits "^2.0.3"
pump "^2.0.0"
@@ -8912,11 +9881,19 @@ qs@6.5.1:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==
-qs@~6.5.2:
+qs@^6.4.0, qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+query-string@^4.1.0:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb"
+ integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s=
+ dependencies:
+ object-assign "^4.1.0"
+ strict-uri-encode "^1.0.0"
+
query-string@^5.0.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb"
@@ -8961,10 +9938,10 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
-range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0:
- version "1.2.0"
- resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
- integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
+range-parser@^1.0.3, range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.0:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+ integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
raphael@^2.2.7:
version "2.2.7"
@@ -8988,6 +9965,14 @@ raw-body@2.3.2:
iconv-lite "0.4.19"
unpipe "1.0.0"
+raw-body@~1.1.0:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-1.1.7.tgz#1d027c2bfa116acc6623bca8f00016572a87d425"
+ integrity sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=
+ dependencies:
+ bytes "1"
+ string_decoder "0.10"
+
raw-loader@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-1.0.0.tgz#3f9889e73dadbda9a424bce79809b4133ad46405"
@@ -9070,7 +10055,7 @@ read-pkg@^3.0.0:
normalize-package-data "^2.3.2"
path-type "^3.0.0"
-"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.6:
+"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6:
version "2.3.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==
@@ -9092,7 +10077,7 @@ readable-stream@^3.0.6:
string_decoder "^1.1.1"
util-deprecate "^1.0.1"
-readable-stream@~2.0.6:
+readable-stream@~2.0.0, readable-stream@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e"
integrity sha1-j5A0HmilPMySh4jaz80Rs265t44=
@@ -9104,6 +10089,19 @@ readable-stream@~2.0.6:
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
+readable-stream@~2.1.0:
+ version "2.1.5"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.1.5.tgz#66fa8b720e1438b364681f2ad1a63c618448c9d0"
+ integrity sha1-ZvqLcg4UOLNkaB8q0aY8YYRIydA=
+ dependencies:
+ buffer-shims "^1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
readdir-enhanced@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/readdir-enhanced/-/readdir-enhanced-2.2.4.tgz#773fb8a8de5f645fb13d9403746d490d4facb3e6"
@@ -9122,6 +10120,13 @@ readdirp@^2.0.0:
readable-stream "^2.0.2"
set-immediate-shim "^1.0.1"
+readdirp@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.1.1.tgz#b158123ac343c8b0f31d65680269cc0fc1025db1"
+ integrity sha512-XXdSXZrQuvqoETj50+JAitxz1UPdt5dupjT6T5nVB+WvjMv2XKYj+s7hPeAVCXvmJrL36O4YYyWlIC3an2ePiQ==
+ dependencies:
+ picomatch "^2.0.4"
+
realpath-native@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-1.1.0.tgz#2003294fea23fb0672f2476ebe22fcf498a2d65c"
@@ -9157,10 +10162,10 @@ regenerate@^1.2.1, regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-regenerator-transform@^0.13.4:
- version "0.13.4"
- resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb"
- integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A==
+regenerator-transform@^0.14.0:
+ version "0.14.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.1.tgz#3b2fce4e1ab7732c08f665dfdb314749c7ddd2fb"
+ integrity sha512-flVuee02C3FKRISbxhXl9mGzdbWUVHubl1SMaknjxkFB1/iqpJhArQUvRxOOPEc/9tAiX0BaQ28FJH10E4isSQ==
dependencies:
private "^0.1.6"
@@ -9172,14 +10177,10 @@ regex-not@^1.0.0, regex-not@^1.0.2:
extend-shallow "^3.0.2"
safe-regex "^1.1.0"
-regexp-tree@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.0.tgz#a56ad7746097888ea16457479029ec9345b96ab0"
- integrity sha512-rHQv+tzu+0l3KS/ERabas1yK49ahNVxuH40WcPg53CzP5p8TgmmyBgHELLyJcvjhTD0e5ahSY6C76LbEVtr7cg==
- dependencies:
- cli-table3 "^0.5.0"
- colors "^1.1.2"
- yargs "^10.0.3"
+regexp-tree@^0.1.6:
+ version "0.1.11"
+ resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.11.tgz#c9c7f00fcf722e0a56c7390983a7a63dd6c272f3"
+ integrity sha512-7/l/DgapVVDzZobwMCCgMlqiqyLFJ0cduo/j+3BcDJIB+yJdsYCfKuI3l/04NV+H/rfNRdPIDbXNZHM9XvQatg==
regexpp@^2.0.1:
version "2.0.1"
@@ -9251,6 +10252,37 @@ relateurl@^0.2.7:
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=
+remark-html@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/remark-html/-/remark-html-8.0.0.tgz#9fcb859a6f3cb40f3ef15402950f1a62ec301b3a"
+ integrity sha512-3V2391GL3hxKhrkzYOyfPpxJ6taIKLCfuLVqumeWQOk3H9nTtSQ8St8kMYkBVIEAquXN1chT83qJ/2lAW+dpEg==
+ dependencies:
+ hast-util-sanitize "^1.0.0"
+ hast-util-to-html "^4.0.0"
+ mdast-util-to-hast "^3.0.0"
+ xtend "^4.0.1"
+
+remark-parse@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-5.0.0.tgz#4c077f9e499044d1d5c13f80d7a98cf7b9285d95"
+ integrity sha512-b3iXszZLH1TLoyUzrATcTQUZrwNl1rE70rVdSruJFlDaJ9z5aMkhrG43Pp68OgfHndL/ADz6V69Zow8cTQu+JA==
+ dependencies:
+ collapse-white-space "^1.0.2"
+ is-alphabetical "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ is-word-character "^1.0.0"
+ markdown-escapes "^1.0.0"
+ parse-entities "^1.1.0"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ trim "0.0.1"
+ trim-trailing-lines "^1.0.0"
+ unherit "^1.0.4"
+ unist-util-remove-position "^1.0.0"
+ vfile-location "^2.0.0"
+ xtend "^4.0.1"
+
remark-parse@^6.0.0:
version "6.0.3"
resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-6.0.3.tgz#c99131052809da482108413f87b0ee7f52180a3a"
@@ -9272,6 +10304,42 @@ remark-parse@^6.0.0:
vfile-location "^2.0.0"
xtend "^4.0.1"
+remark-reference-links@^4.0.1:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/remark-reference-links/-/remark-reference-links-4.0.4.tgz#190579a0d6b002859d6cdbdc5aeb8bbdae4e06ab"
+ integrity sha512-+2X8hwSQqxG4tvjYZNrTcEC+bXp8shQvwRGG6J/rnFTvBoU4G0BBviZoqKGZizLh/DG+0gSYhiDDWCqyxXW1iQ==
+ dependencies:
+ unist-util-visit "^1.0.0"
+
+remark-slug@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/remark-slug/-/remark-slug-5.1.2.tgz#715ecdef8df1226786204b1887d31ab16aa24609"
+ integrity sha512-DWX+Kd9iKycqyD+/B+gEFO3jjnt7Yg1O05lygYSNTe5i5PIxxxPjp5qPBDxPIzp5wreF7+1ROCwRgjEcqmzr3A==
+ dependencies:
+ github-slugger "^1.0.0"
+ mdast-util-to-string "^1.0.0"
+ unist-util-visit "^1.0.0"
+
+remark-stringify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-5.0.0.tgz#336d3a4d4a6a3390d933eeba62e8de4bd280afba"
+ integrity sha512-Ws5MdA69ftqQ/yhRF9XhVV29mhxbfGhbz0Rx5bQH+oJcNhhSM6nCu1EpLod+DjrFGrU0BMPs+czVmJZU7xiS7w==
+ dependencies:
+ ccount "^1.0.0"
+ is-alphanumeric "^1.0.0"
+ is-decimal "^1.0.0"
+ is-whitespace-character "^1.0.0"
+ longest-streak "^2.0.1"
+ markdown-escapes "^1.0.0"
+ markdown-table "^1.1.0"
+ mdast-util-compact "^1.0.0"
+ parse-entities "^1.0.2"
+ repeat-string "^1.5.4"
+ state-toggle "^1.0.0"
+ stringify-entities "^1.0.1"
+ unherit "^1.0.4"
+ xtend "^4.0.1"
+
remark-stringify@^6.0.0:
version "6.0.4"
resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-6.0.4.tgz#16ac229d4d1593249018663c7bddf28aafc4e088"
@@ -9292,6 +10360,14 @@ remark-stringify@^6.0.0:
unherit "^1.0.4"
xtend "^4.0.1"
+remark-toc@^5.0.0:
+ version "5.1.1"
+ resolved "https://registry.yarnpkg.com/remark-toc/-/remark-toc-5.1.1.tgz#8c229d6f834cdb43fde6685e2d43248d3fc82d78"
+ integrity sha512-vCPW4YOsm2CfyuScdktM9KDnJXVHJsd/ZeRtst+dnBU3B3KKvt8bc+bs5syJjyptAHfqo7H+5Uhz+2blWBfwow==
+ dependencies:
+ mdast-util-toc "^3.0.0"
+ remark-slug "^5.0.0"
+
remark@^10.0.1:
version "10.0.1"
resolved "https://registry.yarnpkg.com/remark/-/remark-10.0.1.tgz#3058076dc41781bf505d8978c291485fe47667df"
@@ -9301,6 +10377,39 @@ remark@^10.0.1:
remark-stringify "^6.0.0"
unified "^7.0.0"
+remark@^9.0.0:
+ version "9.0.0"
+ resolved "https://registry.yarnpkg.com/remark/-/remark-9.0.0.tgz#c5cfa8ec535c73a67c4b0f12bfdbd3a67d8b2f60"
+ integrity sha512-amw8rGdD5lHbMEakiEsllmkdBP+/KpjW/PRK6NSGPZKCQowh0BT4IWXDAkRMyG3SB9dKPXWMviFjNusXzXNn3A==
+ dependencies:
+ remark-parse "^5.0.0"
+ remark-stringify "^5.0.0"
+ unified "^6.0.0"
+
+remote-origin-url@0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/remote-origin-url/-/remote-origin-url-0.4.0.tgz#4d3e2902f34e2d37d1c263d87710b77eb4086a30"
+ integrity sha1-TT4pAvNOLTfRwmPYdxC3frQIajA=
+ dependencies:
+ parse-git-config "^0.2.0"
+
+remove-bom-buffer@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz#c2bf1e377520d324f623892e33c10cac2c252b53"
+ integrity sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==
+ dependencies:
+ is-buffer "^1.1.5"
+ is-utf8 "^0.2.1"
+
+remove-bom-stream@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/remove-bom-stream/-/remove-bom-stream-1.2.0.tgz#05f1a593f16e42e1fb90ebf59de8e569525f9523"
+ integrity sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=
+ dependencies:
+ remove-bom-buffer "^3.0.0"
+ safe-buffer "^5.1.0"
+ through2 "^2.0.3"
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -9311,12 +10420,7 @@ repeat-element@^1.1.2:
resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce"
integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==
-repeat-string@^0.2.2:
- version "0.2.2"
- resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-0.2.2.tgz#c7a8d3236068362059a7e4651fc6884e8b1fb4ae"
- integrity sha1-x6jTI2BoNiBZp+RlH8aITosftK4=
-
-repeat-string@^1.5.4, repeat-string@^1.6.1:
+repeat-string@^1.5.0, repeat-string@^1.5.4, repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
@@ -9328,7 +10432,7 @@ repeating@^2.0.0:
dependencies:
is-finite "^1.0.0"
-replace-ext@1.0.0:
+replace-ext@1.0.0, replace-ext@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
integrity sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=
@@ -9435,10 +10539,17 @@ resolve-from@^3.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
integrity sha1-six699nWiBvItuZTM17rywoYh0g=
-resolve-from@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
- integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
+resolve-options@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131"
+ integrity sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=
+ dependencies:
+ value-or-function "^3.0.0"
resolve-url@^0.2.1:
version "0.2.1"
@@ -9450,10 +10561,10 @@ resolve@1.1.7:
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
-resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.9.0:
- version "1.11.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.0.tgz#4014870ba296176b86343d50b60f3b50609ce232"
- integrity sha512-WL2pBDjqT6pGUNSUzMw00o4T7If+z4H2x3Gz893WoUQ5KW8Vr9txp00ykiP16VBaZF5+j/OcXJHZ9+PCvdiDKw==
+resolve@1.x, resolve@^1.1.3, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1, resolve@^1.9.0:
+ version "1.11.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.11.1.tgz#ea10d8110376982fef578df8fc30b9ac30a07a3e"
+ integrity sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==
dependencies:
path-parse "^1.0.6"
@@ -9477,12 +10588,12 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
-rfdc@^1.1.2:
- version "1.1.2"
- resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.2.tgz#e6e72d74f5dc39de8f538f65e00c36c18018e349"
- integrity sha512-92ktAgvZhBzYTIK0Mja9uen5q5J3NRVMoDkJL2VMwq6SXjVCgqvQeVP2XAaUY6HT+XpQYeLSjb3UoitBryKmdA==
+rfdc@^1.1.4:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
+ integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
-rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
+rimraf@2, rimraf@2.6.3, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.0, rimraf@^2.6.1, rimraf@^2.6.3:
version "2.6.3"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab"
integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==
@@ -9543,6 +10654,11 @@ safe-buffer@5.1.2, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, s
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+safe-json-parse@~1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/safe-json-parse/-/safe-json-parse-1.0.1.tgz#3e76723e38dfdda13c9b1d29a1e07ffee4b30b57"
+ integrity sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=
+
safe-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e"
@@ -9666,10 +10782,10 @@ semver-diff@^2.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.0.tgz#790a7cf6fea5459bac96110b29b60412dc8ff96b"
integrity sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==
-semver@^6.0.0:
- version "6.0.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65"
- integrity sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ==
+semver@^6.0.0, semver@^6.1.1:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.2.0.tgz#4d813d9590aaf8a9192693d6c85b9344de5901db"
+ integrity sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==
semver@~5.3.0:
version "5.3.0"
@@ -9695,10 +10811,10 @@ send@0.16.2:
range-parser "~1.2.0"
statuses "~1.4.0"
-serialize-javascript@^1.4.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.4.0.tgz#7c958514db6ac2443a8abc062dc9f7886a7f6005"
- integrity sha1-fJWFFNtqwkQ6irwGLcn3iGp/YAU=
+serialize-javascript@^1.4.0, serialize-javascript@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.7.0.tgz#d6e0dfb2a3832a8c94468e6eb1db97e55a192a65"
+ integrity sha512-ke8UG8ulpFOxO8f8gRYabHQe/ZntKlcig2Mp+8+URDP1D8vJZ0KUt7LYo07q25Z/+JVSgpr/cui9PIp5H6/+nA==
serve-index@^1.7.2:
version "1.9.1"
@@ -9784,6 +10900,13 @@ sha1@^1.1.1:
charenc ">= 0.0.1"
crypt ">= 0.0.1"
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
+ dependencies:
+ kind-of "^6.0.2"
+
shebang-command@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
@@ -9816,11 +10939,21 @@ sisteransi@^1.0.0:
resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.0.tgz#77d9622ff909080f1c19e5f4a1df0c1b0a27b88c"
integrity sha512-N+z4pHB4AmUv0SjveWRd6q1Nj5w62m5jodv+GD8lvmbY/83T/rpbJGZOnK5T149OldDj4Db07BSv9xY4K6NTPQ==
+slash@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55"
+ integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=
+
slash@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44"
integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==
+slash@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+ integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
slice-ansi@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636"
@@ -9940,6 +11073,13 @@ sockjs@0.3.19:
faye-websocket "^0.10.0"
uuid "^3.0.1"
+sort-keys@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-1.1.2.tgz#441b6d4d346798f1b4e49e8920adfba0e543f9ad"
+ integrity sha1-RBttTTRnmPG05J6JIK37oOVD+a0=
+ dependencies:
+ is-plain-obj "^1.0.0"
+
sort-keys@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/sort-keys/-/sort-keys-2.0.0.tgz#658535584861ec97d730d6cf41822e1f56684128"
@@ -10003,6 +11143,16 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+source-map@^0.7.3:
+ version "0.7.3"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
+ integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
+
+space-separated-tokens@^1.0.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-1.1.4.tgz#27910835ae00d0adfcdbd0ad7e611fb9544351fa"
+ integrity sha512-UyhMSmeIqZrQn2UdjYpxEkwY9JUrn8pP+7L4f91zRzOQuI8MF1FGLfYU9DKCYeLdo7LXMxwrX5zKFy7eeeVHuA==
+
spdx-correct@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40"
@@ -10140,6 +11290,13 @@ stickyfilljs@^2.0.5:
resolved "https://registry.yarnpkg.com/stickyfilljs/-/stickyfilljs-2.0.5.tgz#d229e372d2199ddf5d283bbe34ac1f7d2529c2fc"
integrity sha512-KGKdqKbv1jXit54ltFPIWw/XVeuSrJmTUS8viT1Pmdpp1Jyv3SMpFmhvPBdddX9FHDlHbm9s8cPAhPviBaBVpA==
+stream-array@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/stream-array/-/stream-array-1.1.2.tgz#9e5f7345f2137c30ee3b498b9114e80b52bb7eb5"
+ integrity sha1-nl9zRfITfDDuO0mLkRToC1K7frU=
+ dependencies:
+ readable-stream "~2.1.0"
+
stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"
@@ -10148,6 +11305,14 @@ stream-browserify@^2.0.1:
inherits "~2.0.1"
readable-stream "^2.0.2"
+stream-combiner2@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe"
+ integrity sha1-+02KFCDqNidk4hrUeAOXvry0HL4=
+ dependencies:
+ duplexer2 "~0.1.0"
+ readable-stream "^2.0.2"
+
stream-each@^1.1.0:
version "1.2.2"
resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.2.tgz#8e8c463f91da8991778765873fe4d960d8f616bd"
@@ -10172,15 +11337,16 @@ stream-shift@^1.0.0:
resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=
-streamroller@0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-0.7.0.tgz#a1d1b7cf83d39afb0d63049a5acbf93493bdf64b"
- integrity sha512-WREzfy0r0zUqp3lGO096wRuUp7ho1X6uo/7DJfTlEi0Iv/4gT7YHqXDjKC2ioVGBZtE8QzsQD9nx1nIuoZ57jQ==
+streamroller@^1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/streamroller/-/streamroller-1.0.6.tgz#8167d8496ed9f19f05ee4b158d9611321b8cacd9"
+ integrity sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==
dependencies:
- date-format "^1.2.0"
- debug "^3.1.0"
- mkdirp "^0.5.1"
- readable-stream "^2.3.0"
+ async "^2.6.2"
+ date-format "^2.0.0"
+ debug "^3.2.6"
+ fs-extra "^7.0.1"
+ lodash "^4.17.14"
strict-uri-encode@^1.0.0:
version "1.1.0"
@@ -10195,6 +11361,11 @@ string-length@^2.0.0:
astral-regex "^1.0.0"
strip-ansi "^4.0.0"
+string-template@~0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/string-template/-/string-template-0.2.1.tgz#42932e598a352d01fc22ec3367d9d84eec6c9add"
+ integrity sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=
+
string-width@^1.0.1, string-width@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
@@ -10221,6 +11392,20 @@ string-width@^3.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.0.0"
+string-width@^4.0.0, string-width@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff"
+ integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^5.2.0"
+
+string_decoder@0.10, string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+ integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
+
string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
@@ -10228,11 +11413,6 @@ string_decoder@^1.0.0, string_decoder@^1.1.1, string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-string_decoder@~0.10.x:
- version "0.10.31"
- resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
- integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=
-
stringify-entities@^1.0.1:
version "1.3.2"
resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-1.3.2.tgz#a98417e5471fd227b3e45d3db1861c11caf668f7"
@@ -10257,7 +11437,7 @@ strip-ansi@^4.0.0:
dependencies:
ansi-regex "^3.0.0"
-strip-ansi@^5.0.0:
+strip-ansi@^5.0.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==
@@ -10318,79 +11498,82 @@ style-search@^0.1.0:
resolved "https://registry.yarnpkg.com/style-search/-/style-search-0.1.0.tgz#7958c793e47e32e07d2b5cafe5c0bf8e12e77902"
integrity sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=
-stylelint-config-recommended@^2.1.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.1.0.tgz#f526d5c771c6811186d9eaedbed02195fee30858"
- integrity sha512-ajMbivOD7JxdsnlS5945KYhvt7L/HwN6YeYF2BH6kE4UCLJR0YvXMf+2j7nQpJyYLZx9uZzU5G1ZOSBiWAc6yA==
-
-stylelint-error-string-formatter@1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/stylelint-error-string-formatter/-/stylelint-error-string-formatter-1.0.2.tgz#3076c6703d3e0170daeb55fe85030d63c834745e"
- integrity sha512-xN69xRB0eTgYcGKVHWIiH2L+Xx8fRPliTiLjaS4YlbfBqkdTuZh2wjtfvNCkCzBTNeINWa5GpSa9RFYXdtwV6w==
+stylelint-config-recommended@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/stylelint-config-recommended/-/stylelint-config-recommended-2.2.0.tgz#46ab139db4a0e7151fd5f94af155512886c96d3f"
+ integrity sha512-bZ+d4RiNEfmoR74KZtCKmsABdBJr4iXRiCso+6LtMJPw5rd/KnxUWTxht7TbafrTJK1YRjNgnN0iVZaJfc3xJA==
-stylelint-scss@^3.5.4:
- version "3.5.4"
- resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.5.4.tgz#ff3ee989ac48f5c4f57313523b5ace059ffd6cc2"
- integrity sha512-hEdEOfFXVqxWcUbenBONW/cAw5cJcEDasY8tGwKNAAn1GDHoZO1ATdWpr+iIk325mPGIQqVb1sUxsRxuL70trw==
+stylelint-scss@^3.9.2:
+ version "3.9.2"
+ resolved "https://registry.yarnpkg.com/stylelint-scss/-/stylelint-scss-3.9.2.tgz#5435174a57696ee52eae40146778a4e62f7ed3a3"
+ integrity sha512-VUh173p3T1qJf016P7yeJ6nxkUpqF5qQ+VSDw3J8P6wEJbA1loaNgBHR3k3skHvUkF+9brLO1ibCHA00pjW3cw==
dependencies:
lodash "^4.17.11"
postcss-media-query-parser "^0.2.3"
postcss-resolve-nested-selector "^0.1.1"
- postcss-selector-parser "^5.0.0"
- postcss-value-parser "^3.3.1"
+ postcss-selector-parser "^6.0.2"
+ postcss-value-parser "^4.0.0"
-stylelint@^9.10.1:
- version "9.10.1"
- resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-9.10.1.tgz#5f0ee3701461dff1d68284e1386efe8f0677a75d"
- integrity sha512-9UiHxZhOAHEgeQ7oLGwrwoDR8vclBKlSX7r4fH0iuu0SfPwFaLkb1c7Q2j1cqg9P7IDXeAV2TvQML/fRQzGBBQ==
+stylelint@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-10.1.0.tgz#1bc4c4ce878107e7c396b19226d91ba28268911a"
+ integrity sha512-OmlUXrgzEMLQYj1JPTpyZPR9G4bl0StidfHnGJEMpdiQ0JyTq0MPg1xkHk1/xVJ2rTPESyJCDWjG8Kbpoo7Kuw==
dependencies:
- autoprefixer "^9.0.0"
+ autoprefixer "^9.5.1"
balanced-match "^1.0.0"
- chalk "^2.4.1"
- cosmiconfig "^5.0.0"
- debug "^4.0.0"
- execall "^1.0.0"
- file-entry-cache "^4.0.0"
- get-stdin "^6.0.0"
+ chalk "^2.4.2"
+ cosmiconfig "^5.2.0"
+ debug "^4.1.1"
+ execall "^2.0.0"
+ file-entry-cache "^5.0.1"
+ get-stdin "^7.0.0"
global-modules "^2.0.0"
- globby "^9.0.0"
+ globby "^9.2.0"
globjoin "^0.1.4"
- html-tags "^2.0.0"
- ignore "^5.0.4"
- import-lazy "^3.1.0"
+ html-tags "^3.0.0"
+ ignore "^5.0.6"
+ import-lazy "^4.0.0"
imurmurhash "^0.1.4"
- known-css-properties "^0.11.0"
- leven "^2.1.0"
- lodash "^4.17.4"
- log-symbols "^2.0.0"
- mathml-tag-names "^2.0.1"
+ known-css-properties "^0.14.0"
+ leven "^3.1.0"
+ lodash "^4.17.11"
+ log-symbols "^3.0.0"
+ mathml-tag-names "^2.1.0"
meow "^5.0.0"
- micromatch "^3.1.10"
+ micromatch "^4.0.0"
normalize-selector "^0.2.0"
- pify "^4.0.0"
- postcss "^7.0.13"
+ pify "^4.0.1"
+ postcss "^7.0.14"
postcss-html "^0.36.0"
- postcss-jsx "^0.36.0"
- postcss-less "^3.1.0"
+ postcss-jsx "^0.36.1"
+ postcss-less "^3.1.4"
postcss-markdown "^0.36.0"
postcss-media-query-parser "^0.2.3"
- postcss-reporter "^6.0.0"
+ postcss-reporter "^6.0.1"
postcss-resolve-nested-selector "^0.1.1"
- postcss-safe-parser "^4.0.0"
+ postcss-safe-parser "^4.0.1"
postcss-sass "^0.3.5"
postcss-scss "^2.0.0"
postcss-selector-parser "^3.1.0"
postcss-syntax "^0.36.2"
- postcss-value-parser "^3.3.0"
- resolve-from "^4.0.0"
+ postcss-value-parser "^3.3.1"
+ resolve-from "^5.0.0"
signal-exit "^3.0.2"
- slash "^2.0.0"
+ slash "^3.0.0"
specificity "^0.4.1"
- string-width "^3.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^5.2.0"
style-search "^0.1.0"
sugarss "^2.0.0"
svg-tags "^1.0.0"
- table "^5.0.0"
+ table "^5.2.3"
+
+subarg@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/subarg/-/subarg-1.0.0.tgz#f62cf17581e996b48fc965699f54c06ae268b8d2"
+ integrity sha1-9izxdYHplrSPyWVpn1TAauJouNI=
+ dependencies:
+ minimist "^1.1.0"
sugarss@^2.0.0:
version "2.0.0"
@@ -10411,7 +11594,7 @@ supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0, supports-co
dependencies:
has-flag "^3.0.0"
-supports-color@^6.1.0:
+supports-color@^6.0.0, supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
@@ -10438,13 +11621,13 @@ symbol-tree@^3.2.2:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
integrity sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=
-table@^5.0.0, table@^5.0.2:
- version "5.2.3"
- resolved "https://registry.yarnpkg.com/table/-/table-5.2.3.tgz#cde0cc6eb06751c009efab27e8c820ca5b67b7f2"
- integrity sha512-N2RsDAMvDLvYwFcwbPyF3VmVSSkuF+G1e+8inhBLtHpvwXGw4QRPEZhihQNeEN0i1up6/f6ObCJXNdlRG3YVyQ==
+table@^5.0.2, table@^5.2.3:
+ version "5.4.4"
+ resolved "https://registry.yarnpkg.com/table/-/table-5.4.4.tgz#6e0f88fdae3692793d1077fd172a4667afe986a6"
+ integrity sha512-IIfEAUx5QlODLblLrGTTLJA7Tk0iLSGBvgY8essPRVNGHAzThujww1YqHLs6h3HfTg55h++RzLHH5Xw/rfv+mg==
dependencies:
- ajv "^6.9.1"
- lodash "^4.17.11"
+ ajv "^6.10.2"
+ lodash "^4.17.14"
slice-ansi "^2.1.0"
string-width "^3.0.0"
@@ -10569,15 +11752,23 @@ throttle-debounce@^2.0.0:
resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.0.1.tgz#7307ddd6cd9acadb349132fbf6c18d78c88a5e62"
integrity sha512-Sr6jZBlWShsAaSXKyNXyNicOrJW/KtkDqIEwHt4wYwWA2wa/q67Luhqoujg48V8hTk60wB56tYrJJn6jc2R7VA==
-through2@^2.0.0:
- version "2.0.3"
- resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
- integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=
+through2-filter@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/through2-filter/-/through2-filter-3.0.0.tgz#700e786df2367c2c88cd8aa5be4cf9c1e7831254"
+ integrity sha512-jaRjI2WxN3W1V8/FMZ9HKIBXixtiqs3SQSX4/YGIiP3gL6djW48VoZq9tDqeCWs3MT8YY5wb/zli8VW8snY1CA==
dependencies:
- readable-stream "^2.1.5"
+ through2 "~2.0.0"
+ xtend "~4.0.0"
+
+through2@^2.0.0, through2@^2.0.3, through2@~2.0.0:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd"
+ integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==
+ dependencies:
+ readable-stream "~2.3.6"
xtend "~4.0.1"
-through@^2.3.6:
+"through@>=2.2.7 <3", through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -10587,6 +11778,11 @@ thunky@^0.1.0:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e"
integrity sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=
+time-stamp@^1.0.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3"
+ integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=
+
timeago.js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/timeago.js/-/timeago.js-3.0.2.tgz#32a67e7c0d887ea42ca588d3aae26f77de5e76cc"
@@ -10611,6 +11807,18 @@ tiny-emitter@^2.0.0:
resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c"
integrity sha512-2NM0auVBGft5tee/OxP4PI3d8WItkDM+fPnaRAVo6xTDI2knbz9eC5ArWGqtGlYqiH3RU5yMpdyTTO7MguC4ow==
+tiny-lr@^1.1.0:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"
+ integrity sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==
+ dependencies:
+ body "^5.1.0"
+ debug "^3.1.0"
+ faye-websocket "~0.10.0"
+ livereload-js "^2.3.0"
+ object-assign "^4.1.0"
+ qs "^6.4.0"
+
tiptap-commands@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/tiptap-commands/-/tiptap-commands-1.4.0.tgz#0cfb3ac138ee3099de56114cb119abd841fbcbe7"
@@ -10674,6 +11882,14 @@ tmpl@1.0.x:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+to-absolute-glob@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz#1865f43d9e74b0822db9f145b78cff7d0f7c849b"
+ integrity sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=
+ dependencies:
+ is-absolute "^1.0.0"
+ is-negated-glob "^1.0.0"
+
to-array@0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890"
@@ -10704,6 +11920,13 @@ to-regex-range@^2.1.0:
is-number "^3.0.0"
repeat-string "^1.6.1"
+to-regex-range@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+ integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+ dependencies:
+ is-number "^7.0.0"
+
to-regex@^3.0.1, to-regex@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce"
@@ -10714,6 +11937,13 @@ to-regex@^3.0.1, to-regex@^3.0.2:
regex-not "^1.0.2"
safe-regex "^1.1.0"
+to-through@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6"
+ integrity sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=
+ dependencies:
+ through2 "^2.0.3"
+
toggle-selection@^1.0.3:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
@@ -10741,6 +11971,11 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
+trim-lines@^1.0.0:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-1.1.2.tgz#c8adbdbdae21bb5c2766240a661f693afe23e59b"
+ integrity sha512-3GOuyNeTqk3FAqc3jOJtw7FTjYl94XBR5aD9QnDbK/T4CA9sW/J0l9RoaRPE9wyPP7NF331qnHnvJFBJ+IDkmQ==
+
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -10859,7 +12094,7 @@ type-is@~1.6.15, type-is@~1.6.16:
media-typer "0.3.0"
mime-types "~2.1.18"
-typedarray@^0.0.6:
+typedarray@^0.0.6, typedarray@~0.0.5:
version "0.0.6"
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
@@ -10887,6 +12122,11 @@ ultron@~1.1.0:
resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c"
integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==
+unc-path-regex@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa"
+ integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo=
+
undefsafe@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76"
@@ -10947,6 +12187,18 @@ unicode-property-aliases-ecmascript@^1.0.4:
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.4.tgz#5a533f31b4317ea76f17d807fa0d116546111dd0"
integrity sha512-2WSLa6OdYd2ng8oqiGIWnJqyFArvhn+5vgx5GTxMbUYjCYKUcuKS62YLFF0R/BDGlB1yzXjQOLtPAfHsgirEpg==
+unified@^6.0.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/unified/-/unified-6.2.0.tgz#7fbd630f719126d67d40c644b7e3f617035f6dba"
+ integrity sha512-1k+KPhlVtqmG99RaTbAv/usu85fcSRu3wY8X+vnsEhIxNP5VbVIDiXnLqyKIG+UMdyTg0ZX9EI6k2AfjJkHPtA==
+ dependencies:
+ bail "^1.0.0"
+ extend "^3.0.0"
+ is-plain-obj "^1.1.0"
+ trough "^1.0.0"
+ vfile "^2.0.0"
+ x-is-string "^0.1.0"
+
unified@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/unified/-/unified-7.1.0.tgz#5032f1c1ee3364bd09da12e27fdd4a7553c7be13"
@@ -10990,6 +12242,14 @@ unique-slug@^2.0.0:
dependencies:
imurmurhash "^0.1.4"
+unique-stream@^2.0.2:
+ version "2.3.1"
+ resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.3.1.tgz#c65d110e9a4adf9a6c5948b28053d9a8d04cbeac"
+ integrity sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==
+ dependencies:
+ json-stable-stringify-without-jsonify "^1.0.1"
+ through2-filter "^3.0.0"
+
unique-string@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unique-string/-/unique-string-1.0.0.tgz#9e1057cca851abb93398f8b33ae187b99caec11a"
@@ -10997,6 +12257,13 @@ unique-string@^1.0.0:
dependencies:
crypto-random-string "^1.0.0"
+unist-builder@^1.0.1, unist-builder@^1.0.2:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/unist-builder/-/unist-builder-1.0.4.tgz#e1808aed30bd72adc3607f25afecebef4dd59e17"
+ integrity sha512-v6xbUPP7ILrT15fHGrNyHc1Xda8H3xVhP7/HAIotHOhVPjH5dCXA097C3Rry1Q2O+HbOLCao4hfPB+EYEjHgVg==
+ dependencies:
+ object-assign "^4.1.0"
+
unist-util-find-all-after@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unist-util-find-all-after/-/unist-util-find-all-after-1.0.2.tgz#9be49cfbae5ca1566b27536670a92836bf2f8d6d"
@@ -11004,11 +12271,21 @@ unist-util-find-all-after@^1.0.2:
dependencies:
unist-util-is "^2.0.0"
+unist-util-generated@^1.1.0:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/unist-util-generated/-/unist-util-generated-1.1.4.tgz#2261c033d9fc23fae41872cdb7663746e972c1a7"
+ integrity sha512-SA7Sys3h3X4AlVnxHdvN/qYdr4R38HzihoEVY2Q2BZu8NHWDnw5OGcC/tXWjQfd4iG+M6qRFNIRGqJmp2ez4Ww==
+
unist-util-is@^2.0.0, unist-util-is@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-2.1.2.tgz#1193fa8f2bfbbb82150633f3a8d2eb9a1c1d55db"
integrity sha512-YkXBK/H9raAmG7KXck+UUpnKiNmUdB+aBGrknfQ4EreE1banuzrKABx3jP6Z5Z3fMSPMQQmeXBlKpCbMwBkxVw==
+unist-util-position@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-3.0.3.tgz#fff942b879538b242096c148153826664b1ca373"
+ integrity sha512-28EpCBYFvnMeq9y/4w6pbnFmCUfzlsc41NJui5c51hOFjBA1fejcwc+5W4z2+0ECVbScG3dURS3JTVqwenzqZw==
+
unist-util-remove-position@^1.0.0:
version "1.1.2"
resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-1.1.2.tgz#86b5dad104d0bbfbeb1db5f5c92f3570575c12cb"
@@ -11021,6 +12298,13 @@ unist-util-stringify-position@^1.0.0, unist-util-stringify-position@^1.1.1:
resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz#3f37fcf351279dcbca7480ab5889bb8a832ee1c6"
integrity sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==
+unist-util-stringify-position@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-2.0.1.tgz#de2a2bc8d3febfa606652673a91455b6a36fb9f3"
+ integrity sha512-Zqlf6+FRI39Bah8Q6ZnNGrEHUhwJOkHde2MHVk96lLyftfJJckaPslKgzhVcviXj8KcE9UJM9F+a4JEiBUTYgA==
+ dependencies:
+ "@types/unist" "^2.0.2"
+
unist-util-visit-parents@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-2.0.1.tgz#63fffc8929027bee04bfef7d2cce474f71cb6217"
@@ -11028,13 +12312,18 @@ unist-util-visit-parents@^2.0.0:
dependencies:
unist-util-is "^2.1.2"
-unist-util-visit@^1.1.0:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.0.tgz#1cb763647186dc26f5e1df5db6bd1e48b3cc2fb1"
- integrity sha512-FiGu34ziNsZA3ZUteZxSFaczIjGmksfSgdKqBfOejrrfzyUy5b7YrlzT1Bcvi+djkYDituJDy2XB7tGTeBieKw==
+unist-util-visit@^1.0.0, unist-util-visit@^1.1.0, unist-util-visit@^1.3.0:
+ version "1.4.1"
+ resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-1.4.1.tgz#4724aaa8486e6ee6e26d7ff3c8685960d560b1e3"
+ integrity sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==
dependencies:
unist-util-visit-parents "^2.0.0"
+universalify@^0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
+ integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
@@ -11145,12 +12434,12 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
-useragent@2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e"
- integrity sha1-z1k+9PLRdYdei7ZY6pLhik/QbY4=
+useragent@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.3.0.tgz#217f943ad540cb2128658ab23fc960f6a88c9972"
+ integrity sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==
dependencies:
- lru-cache "2.2.x"
+ lru-cache "4.1.x"
tmp "0.0.x"
util-deprecate@^1.0.1, util-deprecate@~1.0.1:
@@ -11196,6 +12485,11 @@ validate-npm-package-license@^3.0.1:
spdx-correct "~1.0.0"
spdx-expression-parse "~1.0.0"
+value-or-function@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-3.0.0.tgz#1c243a50b595c1be54a754bfece8563b9ff8d813"
+ integrity sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=
+
vary@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
@@ -11222,6 +12516,46 @@ vfile-message@^1.0.0:
dependencies:
unist-util-stringify-position "^1.1.1"
+vfile-message@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-2.0.1.tgz#951881861c22fc1eb39f873c0b93e336a64e8f6d"
+ integrity sha512-KtasSV+uVU7RWhUn4Lw+wW1Zl/nW8JWx7JCPps10Y9JRRIDeDXf8wfBLoOSsJLyo27DqMyAi54C6Jf/d6Kr2Bw==
+ dependencies:
+ "@types/unist" "^2.0.2"
+ unist-util-stringify-position "^2.0.0"
+
+vfile-reporter@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/vfile-reporter/-/vfile-reporter-6.0.0.tgz#753119f51dec9289b7508b457afc0cddf5e07f2e"
+ integrity sha512-8Is0XxFxWJUhPJdOg3CyZTqd3ICCWg6r304PuBl818ZG91h4FMS3Q+lrOPS+cs5/DZK3H0+AkJdH0J8JEwKtDA==
+ dependencies:
+ repeat-string "^1.5.0"
+ string-width "^4.0.0"
+ supports-color "^6.0.0"
+ unist-util-stringify-position "^2.0.0"
+ vfile-sort "^2.1.2"
+ vfile-statistics "^1.1.0"
+
+vfile-sort@^2.1.0, vfile-sort@^2.1.2:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/vfile-sort/-/vfile-sort-2.2.1.tgz#74e714f9175618cdae96bcaedf1a3dc711d87567"
+ integrity sha512-5dt7xEhC44h0uRQKhbM2JAe0z/naHphIZlMOygtMBM9Nn0pZdaX5fshhwWit9wvsuP8t/wp43nTDRRErO1WK8g==
+
+vfile-statistics@^1.1.0:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/vfile-statistics/-/vfile-statistics-1.1.3.tgz#e9c87071997fbcb4243764d2c3805e0bb0820c60"
+ integrity sha512-CstaK/ebTz1W3Qp41Bt9Lj/2DmumFsCwC2sKahDNSPh0mPh7/UyMLCoU8ZBX34CRU0d61B4W41yIFsV0NKMZeA==
+
+vfile@^2.0.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-2.3.0.tgz#e62d8e72b20e83c324bc6c67278ee272488bf84a"
+ integrity sha512-ASt4mBUHcTpMKD/l5Q+WJXNtshlWxOogYyGYYrg4lt/vuRjC1EFQtlAofL5VmtVNIZJzWYFJjzGWZ0Gw8pzW1w==
+ dependencies:
+ is-buffer "^1.1.4"
+ replace-ext "1.0.0"
+ unist-util-stringify-position "^1.0.0"
+ vfile-message "^1.0.0"
+
vfile@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/vfile/-/vfile-3.0.1.tgz#47331d2abe3282424f4a4bb6acd20a44c4121803"
@@ -11232,6 +12566,65 @@ vfile@^3.0.0:
unist-util-stringify-position "^1.0.0"
vfile-message "^1.0.0"
+vfile@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/vfile/-/vfile-4.0.1.tgz#fc3d43a1c71916034216bf65926d5ee3c64ed60c"
+ integrity sha512-lRHFCuC4SQBFr7Uq91oJDJxlnftoTLQ7eKIpMdubhYcVMho4781a8MWXLy3qZrZ0/STD1kRiKc0cQOHm4OkPeA==
+ dependencies:
+ "@types/unist" "^2.0.0"
+ is-buffer "^2.0.0"
+ replace-ext "1.0.0"
+ unist-util-stringify-position "^2.0.0"
+ vfile-message "^2.0.0"
+
+vinyl-fs@^3.0.2:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7"
+ integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng==
+ dependencies:
+ fs-mkdirp-stream "^1.0.0"
+ glob-stream "^6.1.0"
+ graceful-fs "^4.0.0"
+ is-valid-glob "^1.0.0"
+ lazystream "^1.0.0"
+ lead "^1.0.0"
+ object.assign "^4.0.4"
+ pumpify "^1.3.5"
+ readable-stream "^2.3.3"
+ remove-bom-buffer "^3.0.0"
+ remove-bom-stream "^1.2.0"
+ resolve-options "^1.1.0"
+ through2 "^2.0.0"
+ to-through "^2.0.0"
+ value-or-function "^3.0.0"
+ vinyl "^2.0.0"
+ vinyl-sourcemap "^1.1.0"
+
+vinyl-sourcemap@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/vinyl-sourcemap/-/vinyl-sourcemap-1.1.0.tgz#92a800593a38703a8cdb11d8b300ad4be63b3e16"
+ integrity sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=
+ dependencies:
+ append-buffer "^1.0.2"
+ convert-source-map "^1.5.0"
+ graceful-fs "^4.1.6"
+ normalize-path "^2.1.1"
+ now-and-later "^2.0.0"
+ remove-bom-buffer "^3.0.0"
+ vinyl "^2.0.0"
+
+vinyl@^2.0.0, vinyl@^2.1.0, vinyl@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.2.0.tgz#d85b07da96e458d25b2ffe19fece9f2caa13ed86"
+ integrity sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==
+ dependencies:
+ clone "^2.1.1"
+ clone-buffer "^1.0.0"
+ clone-stats "^1.0.0"
+ cloneable-readable "^1.0.0"
+ remove-trailing-separator "^1.0.1"
+ replace-ext "^1.0.0"
+
visibilityjs@^1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/visibilityjs/-/visibilityjs-1.2.4.tgz#bff8663da62c8c10ad4ee5ae6a1ae6fac4259d63"
@@ -11269,6 +12662,18 @@ vue-eslint-parser@^4.0.2:
esquery "^1.0.1"
lodash "^4.17.11"
+vue-eslint-parser@^6.0.4:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-6.0.4.tgz#56ff47e2c2644bff39951d5a284982c7ecd6f7fa"
+ integrity sha512-GYsDsDWwKaGtnkW4nGUxr01wqIO2FB9/QHQTW1Gl5SUr5OyQvpnR90/D+Gq2cIxURX7aJ7+VyD+37Yx9eFwTgw==
+ dependencies:
+ debug "^4.1.1"
+ eslint-scope "^4.0.0"
+ eslint-visitor-keys "^1.0.0"
+ espree "^5.0.0"
+ esquery "^1.0.1"
+ lodash "^4.17.11"
+
vue-functional-data-merge@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vue-functional-data-merge/-/vue-functional-data-merge-3.1.0.tgz#08a7797583b7f35680587f8a1d51d729aa1dc657"
@@ -11322,7 +12727,7 @@ vue-style-loader@^4.1.0:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
-vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10:
+vue-template-compiler@^2.5.16, vue-template-compiler@^2.5.20, vue-template-compiler@^2.6.10:
version "2.6.10"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.10.tgz#323b4f3495f04faa3503337a82f5d6507799c9cc"
integrity sha512-jVZkw4/I/HT5ZMvRnhv78okGusqe0+qH2A0Em0Cp8aq78+NK9TII263CDVz2QXZsIT+yyV/gZc/j/vlwa+Epyg==
@@ -11390,12 +12795,13 @@ webidl-conversions@^4.0.2:
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
-webpack-bundle-analyzer@^3.0.3:
- version "3.0.3"
- resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.0.3.tgz#dbc7fff8f52058b6714a20fddf309d0790e3e0a0"
- integrity sha512-naLWiRfmtH4UJgtUktRTLw6FdoZJ2RvCR9ePbwM9aRMsS/KjFerkPZG9epEvXRAw5d5oPdrs9+3p+afNjxW8Xw==
+webpack-bundle-analyzer@^3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.3.2.tgz#3da733a900f515914e729fcebcd4c40dde71fc6f"
+ integrity sha512-7qvJLPKB4rRWZGjVp5U1KEjwutbDHSKboAl0IfafnrdXMrgC0tOtZbQD6Rw0u4cmpgRN4O02Fc0t8eAT+FgGzA==
dependencies:
- acorn "^5.7.3"
+ acorn "^6.0.7"
+ acorn-walk "^6.1.1"
bfj "^6.1.1"
chalk "^2.4.1"
commander "^2.18.0"
@@ -11427,7 +12833,7 @@ webpack-cli@^3.2.1:
v8-compile-cache "^2.0.2"
yargs "^12.0.4"
-webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.2.0:
+webpack-dev-middleware@3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890"
integrity sha512-Q9Iyc0X9dP9bAsYskAVJ/hmIZZQwf/3Sy4xCAZgL5cUkjZmUZLt4l5HpbST/Pdgjn3u6pE7u5OdGd1apgzRujA==
@@ -11437,6 +12843,16 @@ webpack-dev-middleware@3.4.0, webpack-dev-middleware@^3.2.0:
range-parser "^1.0.3"
webpack-log "^2.0.0"
+webpack-dev-middleware@^3.7.0:
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.0.tgz#ef751d25f4e9a5c8a35da600c5fda3582b5c6cff"
+ integrity sha512-qvDesR1QZRIAZHOE3iQ4CXLZZSQ1lAUsSpnQmlB1PBfoN/xdRjmge3Dok0W4IdaVLJOGJy3sGI4sZHwjRU0PCA==
+ dependencies:
+ memory-fs "^0.4.1"
+ mime "^2.4.2"
+ range-parser "^1.2.1"
+ webpack-log "^2.0.0"
+
webpack-dev-server@^3.1.14:
version "3.1.14"
resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.14.tgz#60fb229b997fc5a0a1fc6237421030180959d469"
@@ -11727,10 +13143,10 @@ xregexp@4.0.0:
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020"
integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==
-xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1:
- version "4.0.1"
- resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
- integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68=
+xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
+ integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
xterm@^3.5.0:
version "3.5.0"
@@ -11779,13 +13195,6 @@ yargs-parser@^5.0.0:
dependencies:
camelcase "^3.0.0"
-yargs-parser@^8.1.0:
- version "8.1.0"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
- integrity sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==
- dependencies:
- camelcase "^4.1.0"
-
yargs@12.0.2:
version "12.0.2"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc"
@@ -11804,24 +13213,6 @@ yargs@12.0.2:
y18n "^3.2.1 || ^4.0.0"
yargs-parser "^10.1.0"
-yargs@^10.0.3:
- version "10.1.2"
- resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.1.2.tgz#454d074c2b16a51a43e2fb7807e4f9de69ccb5c5"
- integrity sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==
- dependencies:
- cliui "^4.0.0"
- decamelize "^1.1.1"
- find-up "^2.1.0"
- get-caller-file "^1.0.1"
- os-locale "^2.0.0"
- require-directory "^2.1.1"
- require-main-filename "^1.0.1"
- set-blocking "^2.0.0"
- string-width "^2.0.0"
- which-module "^2.0.0"
- y18n "^3.2.1"
- yargs-parser "^8.1.0"
-
yargs@^12.0.2, yargs@^12.0.4:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
@@ -11886,7 +13277,7 @@ zen-observable@^0.8.0:
resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.11.tgz#d3415885eeeb42ee5abb9821c95bb518fcd6d199"
integrity sha512-N3xXQVr4L61rZvGMpWe8XoCGX8vhU35dPyQ4fm5CY/KDlG0F75un14hjbckPXTDuKUY6V0dqR2giT6xN8Y4GEQ==
-zrender@4.0.5:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.5.tgz#6e8f738971ce2cd624aac82b2156729b1c0e5a82"
- integrity sha512-SintgipGEJPT9Sz2ABRoE4ZD7Yzy7oR7j7KP6H+C9FlbHWnLUfGVK7E8UV27pGwlxAMB0EsnrqhXx5XjAfv/KA==
+zrender@4.0.7:
+ version "4.0.7"
+ resolved "https://registry.yarnpkg.com/zrender/-/zrender-4.0.7.tgz#15ae960822f5efed410995d37e5107fe3de10e6d"
+ integrity sha512-TNloHe0ums6zxbHfnaCryM61J4IWDajZwNq6dHk9vfWhhysO/OeFvvR0drBs/nbXha2YxSzfQj2FiCd6RVBe+Q==